Image I/O

2.jpeg

Image I/O基础

Image I/O框架提供了从源图像读取数据的不透明数据类型(CGImageSourceRef)和图像数据写入到目的地(CGImageDestinationRef),它支持多种图片格式,包括标准的web格式,高动态范围图片和相机原始数据,Image I/O还有许多特性,如:

  1. 最快的图片编解码(MAC平台)

  2. 增量加载图片的能力

  3. 支持图像元数据

  4. 有效的缓存
    创建图片源和写入目的地的方法有:

  5. URLS:可以将指定位置的图片(URL)作为图片数据的提供者或接受者,在Image I/O中,URL在Core Foundation框架中的数据类型是CFURLRef,可以通过(__bridge CFURLRef)NSURL桥接.

  6. Core Foundation对象 CFDataRef和CFMutableDataRef

  7. Quartz data consumer(消费者) (CGDataConsumerRef) 和 data provider(提供者) (CGDataProviderRef)

使用

导入头文件#import <ImageIO/ImageIO.h>

支持的图片格式

Image I/O框架支持大多数常见的图像文件格式,如JPEG、JPEG2000、RAW、TIFF、BMP和PNG。并不是每个平台都支持所有格式。可以调用函数CGImageSourceCopyTypeIdentifiers(创建图片源),CGImageDestinationCopyTypeIdentifiers(写入)来查看支持的格式列表

// 获取、打印所支持的统一类型标识符
- (void)getUniformyTypeIdentifiers{
    //数组
    CFArrayRef mySourceTypes = CGImageSourceCopyTypeIdentifiers();
    //在控制台打印数组
    CFShow(mySourceTypes);
    
    CFArrayRef myDestinationTypes = CGImageDestinationCopyTypeIdentifiers();
    CFShow(myDestinationTypes);
}

创建和使用图片源

图片源提取了数据访问任务并且节省了通过原始缓存数据中管理数据的需要。数据源可以包含多个图片,缩略图,每张图片的属性和图片文件。当你需要用到图片数据时,图片源是最好的方式。在创建CGImageSource对象后,你可以通过引用这个对象获取图片,缩略图,图片属性和其他图片信息.

创建一张图片通过图片源

Image I/O最经常完成的一个任务就是从图片源中创建图片。如下面的例子所示。该例子展示了怎样从路径名中创建一个图片源并且抽取图片。当你创建图片源对象后,你可以提供一个图片格式的提示

从图片源中创建图片时,必须指定一个索引并且提供属性的字典来指定是否创建缩略图、是否允许缓存等。详见CGImageSource Reference 和 CGImageProperties Reference

提供索引的原因是因为一些图片文件格式允许同个图片源中有多个图片(比如gif)。如果只有一个的话可以传0。可以调用CGImageSourceGetCount来获取数量

- (void)getImageWithImageSourceRef{
    
    NSString *string = [[NSBundle mainBundle]pathForResource:@"videoPlay.png" ofType:nil];
    NSURL *url = [NSURL fileURLWithPath:string];
    
    
    CFStringRef keys[2],values[2];
    //将图片缓存成解码格式
    keys[0] = kCGImageSourceShouldCache;
    values[0] = (CFTypeRef)kCFBooleanTrue;
    //在图片格式支持浮点值时使用浮点值
    keys[1] = kCGImageSourceShouldAllowFloat;
    values[1] = (CFTypeRef)kCFBooleanTrue;
    /**
     创建一个不可变字典
     @param allocator#> 为字典分配内存,传NULL或kCFAllocatorDefault使用当前默认的分配器 description#>
     @param keys#> key的数组。如果numValues参数为0,则这个值可能是NULL。这个函数没有改变或释放这个数组。该值必须是有效的C数组 description#>
     @param values#> value的数组 description#>
     @param numValues#> 键值对数目。>=0 && >=实际数目。 description#>
     @param keyCallBacks#> 键的回调。 description#>
     @param valueCallBacks#> 值的回调 description#>
     @return CF字典
     */
    CFDictionaryRef options = CFDictionaryCreate(NULL, (const void**)keys, (const void **)values, 2,  &kCFTypeDictionaryKeyCallBacks,& kCFTypeDictionaryValueCallBacks);
    
    CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)url, options);
    
    
    if (imageSource == NULL) {
        fprintf(stderr, "Image source is NULL.");
    }
    CFRelease(options);
    
    size_t count = CGImageSourceGetCount(imageSource);
    NSLog(@"图片数量:%zu",count);
    
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
    
    if (imageRef == NULL) {
       fprintf(stderr, "Image not created from image source.");
    }
    self.imageView.image = [UIImage imageWithCGImage:imageRef];
}

通过图片源创建一张缩略图

一些图片源文件包含可以检索的缩略图。如果缩略图还没有出现,Image I/O将提供创建它们的选项。可以指定最大缩略图大小以及是否对缩略图进行转换。

使用kCGImageSourceCreateThumbnailWithTransform,指定缩略图是否应该旋转和缩放,以匹配整张图片的方向和像素宽高比。

CGImageRef MyCreateThumbnailImageFromData (NSData * data, int imageSize)
{
    CGImageRef        myThumbnailImage = NULL;
    CGImageSourceRef  myImageSource;
    CFDictionaryRef   myOptions = NULL;
    CFStringRef       myKeys[3];
    CFTypeRef         myValues[3];
    CFNumberRef       thumbnailSize;
    
    //通过NSData创建图片源
    myImageSource = CGImageSourceCreateWithData((CFDataRef)data,
                                                NULL);
    //确认图片源存在
    if (myImageSource == NULL){
        fprintf(stderr, "Image source is NULL.");
        return  NULL;
    }
    
    // 创建CFNumber对象.  kCFNumerType
    thumbnailSize = CFNumberCreate(NULL, kCFNumberIntType, &imageSize);
    
    //配置缩略图选项
    myKeys[0] = kCGImageSourceCreateThumbnailWithTransform;
    myValues[0] = (CFTypeRef)kCFBooleanTrue;
    
    myKeys[1] = kCGImageSourceCreateThumbnailFromImageIfAbsent;
    myValues[1] = (CFTypeRef)kCFBooleanTrue;
    
    myKeys[2] = kCGImageSourceThumbnailMaxPixelSize;
    myValues[2] = (CFTypeRef)thumbnailSize;
    
    myOptions = CFDictionaryCreate(NULL, (const void **) myKeys,
                                   (const void **) myValues, 2,
                                   &kCFTypeDictionaryKeyCallBacks,
                                   & kCFTypeDictionaryValueCallBacks);
    
    //创建缩略图
    myThumbnailImage = CGImageSourceCreateThumbnailAtIndex(myImageSource,
                                                           0,
                                                           myOptions);
    //ref类型的需要释放
    CFRelease(thumbnailSize);
    CFRelease(myOptions);
    CFRelease(myImageSource);
    
    if (myThumbnailImage == NULL){
        fprintf(stderr, "Thumbnail image not created from image source.");
        return NULL;
    }
    
    return myThumbnailImage;
}

增量显示一张图片

如果有非常大的图片,或者正在Web上加载图片,可以创建一个逐步加载的图片源,动态的绘制图片。
步骤:

  1. 创建CFData对象来累加图片数据
  2. 通过调用函数CGImageSourceCreateIncremental创建增量图片源
  3. 添加图片数据到CFData对象。
  4. 调用函数CGImageSourceUpdateData,将CFData对象和指定数据参数是否包含整张图片亦或者是部分的图片数据的布尔值传递过去。事实上,数据参数必须包含完整的图片文件数据,以便能加载到那个点。
  5. 如果已经积累了足够的图像数据,通过调用CGImageSourceCreateImageAtIndex创建一个图像,绘制部分图像,然后释放它。
  6. 调用函数CGImageSourceGetStatusAtIndex检查所有的图像数据。如果图像是完整的,这个函数返回kCGImageStatusComplete。如果图像不完整,请重复步骤3和步骤4,直到它结束为止。
  7. 释放增量图像源。
@interface ViewController ()<NSURLSessionDataDelegate>{
    CGImageSourceRef _incrementallyImgSource;
    NSMutableData *_recieveData;
    bool _isLoadFinished;
    long long _expectedLeght;
}
@property (weak, nonatomic) IBOutlet UIImageView *imageView;


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //创建一个空的CGImageSource
    _incrementallyImgSource = CGImageSourceCreateIncremental(NULL);
    _recieveData = [[NSMutableData alloc]init];
    _isLoadFinished = false;
    
    // 使用代理方法需要设置代理,但是session的delegate属性是只读的,要想设置代理只能通过这种方式创建session
    NSURLSession *session = [NSURLSession sessionWithConfiguration
                             :[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self
                                                     delegateQueue:[NSOperationQueue mainQueue]];
    
    // 创建任务(因为要使用代理方法,就不需要block方式的初始化了)
    NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://cdn.eso.org/images/large/eso0934a.jpg"]]];
    
    // 启动任务
    [task resume];
}
//对应的代理方法如下:
// 1.接收到服务器的响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    
    _expectedLeght = response.expectedContentLength;
    NSLog(@"expected Length: %lld", _expectedLeght);
    
    NSString *mimeType = response.MIMEType;
    NSLog(@"MIME TYPE %@", mimeType);
    
    NSArray *arr = [mimeType componentsSeparatedByString:@"/"];
    if (arr.count < 1 || ![[arr objectAtIndex:0] isEqual:@"image"]) {
        NSLog(@"not a image url");
    }
    // 允许处理服务器的响应,才会继续接收服务器返回的数据
    completionHandler(NSURLSessionResponseAllow);
}

// 2.接收到服务器的数据(可能调用多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    // 处理每次接收的数据
    
    [_recieveData appendData:data];
    
    _isLoadFinished = false;
    if (_expectedLeght == _recieveData.length) {
        _isLoadFinished = true;
    }
//    每次收到数据的时候调用CGImageSourceUpdateData更新imageSource的数据,接着调用CGImageSourceCreateImageAtIndex获取最新的图片
        CGImageSourceUpdateData(_incrementallyImgSource, (CFDataRef)_recieveData, _isLoadFinished);
        CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_incrementallyImgSource, 0, NULL);
        self.imageView.image = [UIImage imageWithCGImage:imageRef];
        CGImageRelease(imageRef);
}

// 3.请求成功或者失败(如果失败,error有值)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // 请求完成,成功或者失败的处理
    if (!error) {
        NSLog(@"下载完毕!");
        CFRelease(_incrementallyImgSource);
    }
}
@end

显示图片属性

数码照片包含了丰富的信息:图像尺寸(dimensions),分辨率(resolution),方向9orientation0,颜色配置(color profile),光圈(aperture),测光模式(metering mode),焦距(focal length),创建日期,关键字,标题(caption),以及更多的信息。通过调用CGImageSourceCopyPropertiesAtIndex,我们可以拿到这些信息,这将对于图像处理和图像编辑非常有用.

- (void)getImageSourceProperty{
    
    NSString *path = [[NSBundle mainBundle]pathForResource:@"gif.gif" ofType:nil];
    NSURL *imageFileURL = [NSURL fileURLWithPath:path];
    
    CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)imageFileURL, NULL);
    if (imageSource) {
        NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache:@NO};
        CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
        CFShow(imageProperties);
        
        if (imageProperties) {
            
            CFStringRef width = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
            NSLog(@"width:%@",width);
            
            CFStringRef height = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
            NSLog(@"height:%@",height);
            
            CFDictionaryRef exif = CFDictionaryGetValue(imageProperties, kCGImagePropertyExifDictionary);
            if (exif) {
                NSString *dateTakenString = (NSString *)CFDictionaryGetValue(exif, kCGImagePropertyExifDateTimeOriginal);
                NSLog(@"Date Taken: %@", dateTakenString);
            }
            
            CFDictionaryRef tiff = CFDictionaryGetValue(imageProperties, kCGImagePropertyTIFFDictionary);
            if (tiff) {
                NSString *cameraModel = (NSString *)CFDictionaryGetValue(tiff, kCGImagePropertyTIFFModel);
                NSLog(@"Camera Model: %@", cameraModel);
            }
            
            CFDictionaryRef gps = CFDictionaryGetValue(imageProperties, kCGImagePropertyGPSDictionary);
            if (gps) {
                NSString *latitudeString = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLatitude);
                NSString *latitudeRef = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLatitudeRef);
                NSString *longitudeString = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLongitude);
                NSString *longitudeRef = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLongitudeRef);
                NSLog(@"GPS Coordinates: %@ %@ / %@ %@", longitudeString, longitudeRef, latitudeString, latitudeRef);
            }
            
            CFRelease(imageProperties);
        }
        
        CFRelease(imageSource);
    } else {
        NSLog(@"Error loading image");
    }
}

CGImageDestination

CGImageDestination抽象化了数据写入任务并且省去了从缓存器管理数据的步骤。CGImageDestination可以呈现一张或多张图片。它包含缩略图和每张图片的属性。创建CGImageDestination对象可以通过(URL, CFData或者Quartz数据)参数,然后添加图片数据,设置图片属性,完成操作后,调用CGImageDestinationFinalize.

设置属性

CGImageDestinationSetProperties添加了属性字典(CFDictionaryRef)。虽然属性的设置是可选的,但是通常都会设置属性来完成自己的需求。举个例子,如果你的程序允许用户添加图片关键字或者改变饱和度,曝光值等,就可将这些值保存在字典中,调用这个函数。

Image I/O定义了一系列可扩展的键来指定压缩质量,背景颜色合成的EXIF字典键,颜色模型值,GIF字典键,尼康和佳能相机键,等等。
在设置属性时,你有两个选择。可以创建CFDictionary对象或者创建NSDictionary对象。然后在传递可选字典到CGImageDestinationSetProperties时将它当做CFDictionaryRef.

float compression = 1.0; //设置压缩比
int orientation = 4; // 设置朝向bottom, left.
CFStringRef myKeys[3];
CFTypeRef   myValues[3];
CFDictionaryRef myOptions = NULL;
myKeys[0] = kCGImagePropertyOrientation;
myValues[0] = CFNumberCreate(NULL, kCFNumberIntType, &orientation);
myKeys[1] = kCGImagePropertyHasAlpha;
myValues[1] = kCFBooleanTrue;
myKeys[2] = kCGImageDestinationLossyCompressionQuality;
myValues[2] = CFNumberCreate(NULL, kCFNumberFloatType, &compression);
myOptions = CFDictionaryCreate( NULL, (const void **)myKeys, (const void **)myValues, 3,
                      &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
// 记得Release不需要的变量

写入一张图片通过CGImageDestinationRef

你可以创建一个CGImageDestinationRef对象通过以下函数:
CGImageDestinationCreateWithURL,
CGImageDestinationCreateWithData, CGImageDestinationCreateWithDataConsumer
你需要提供所生成图像文件的UTI

创建完成CGImageDestinationRef,你可以调用CGImageDestinationAddImage 或者 CGImageDestinationAddImageFromSource方法添加一张图片,如果目标文件的格式支持多张图片,你可以重复调用,添加完成调用CGImageDestinationFinalize方法,来表明结束添加.此时,不可再次进行添加图片

- (void) writeCGImage: (CGImageRef) image toURL: (NSURL*) url withType: (CFStringRef) imageType andOptions: (CFDictionaryRef) options
{
    CGImageDestinationRef myImageDest = CGImageDestinationCreateWithURL((CFURLRef)url, imageType, 1, nil);
    //添加数据和图片
    CGImageDestinationAddImage(myImageDest, image, options);
    //最后调用,完成数据写入
    CGImageDestinationFinalize(myImageDest);
    //释放
    CFRelease(myImageDest);
}

创建一张动图

Image I/O框架可以用来创建动画图像。创建一个动画形象的时候,调用CGImageDestinationAddImage添加每一帧动画。还必须指定控制动画执行方式的一些属性。

- (void)gifSynthesize{

    //1.获取数据
    NSMutableArray *tmpArray = [[NSMutableArray alloc]init];
    for (int i = 0; i < 4; i ++) {
        UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"Documents%d",i]];
        [tmpArray addObject:image];
    }
    //2.创建gif文件
    NSArray *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentStr = [document objectAtIndex:0];
    NSFileManager *manager = [NSFileManager defaultManager];
    NSString *textDict = [documentStr stringByAppendingString:@"/gif"];
    [manager createDirectoryAtPath:textDict withIntermediateDirectories:YES attributes:nil error:nil];
    
    NSString *path = [textDict stringByAppendingString:@"test1.gif"];
    NSLog(@"path:%@",path);
    //3.配置gif的属性
    CGImageDestinationRef destination;
    CFURLRef url  = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)path, kCFURLPOSIXPathStyle, false);
    destination = CGImageDestinationCreateWithURL(url, kUTTypeGIF, tmpArray.count, NULL);
    NSDictionary *frameDic = [NSDictionary dictionaryWithObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:@(0.3),(NSString *)kCGImagePropertyGIFDelayTime,nil] forKey:(NSString *)kCGImagePropertyGIFDelayTime];
    NSMutableDictionary *gifParmdict = [NSMutableDictionary dictionaryWithCapacity:2];
    [gifParmdict setObject:@(YES) forKey:(NSString *)kCGImagePropertyGIFHasGlobalColorMap];
    [gifParmdict setObject:(NSString *)kCGImagePropertyColorModelRGB forKey:(NSString *)kCGImagePropertyColorModel];
    [gifParmdict setObject:@(8) forKey:(NSString *)kCGImagePropertyDepth];
    [gifParmdict setObject:@(0) forKey:(NSString *)kCGImagePropertyGIFLoopCount];
    
    NSDictionary *gifProperty = [NSDictionary dictionaryWithObject:gifParmdict forKey:(NSString *)kCGImagePropertyGIFDictionary];
    
    //4.单帧添加到图片
    for (UIImage *image in tmpArray) {
        CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef)frameDic);
    }
    CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)gifProperty);
    //释放
    CGImageDestinationFinalize(destination);
    CFRelease(destination);
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容