iOS中ImageIO框架详解与应用分析

一、引言

ImageIO框架提供了读取与写入图片数据的基本方法,使用它可以直接获取到图片文件的内容数据,ImageIO框架中包含6个头文件,其中完成主要功能的是前两个头文件中定义的方法:

1.CGImageSource.h:负责读取图片数据。

2.CGImageDestination.h:负责写入图片数据。

3.CGImageMetadata.h:图片文件元数据类。

4.CGImageProperties:定义了框架中使用的字符串常量和宏。

5.ImageIOBase.h:预处理逻辑,无需关心。

二、CGImageSource详解

CGImageSource类的主要作用是用来读取图片数据,在平时开发中,关于图片我们使用的最多的可能是UIImage类,UIImage是iOS系统UI系统中用于构建图像对象的类,但是其中只有图像数据,实际上一个图片文件中存储的除了图片数据外,还有一些地理位置、设备类型、时间等信息,除此之外,一个图片文件中可能存储的也不只一张图像(例如gif文件)。CGImageSource就是这样的一个抽象图片数据示例,从其中可以获取到我们所关心的所有数据。

读取图片文件数据,并将其展示在视图的简单代码示例如下:

//获取图片文件路径
NSString * path = [[NSBundle mainBundle]pathForResource:@"timg" ofType:@"jpeg"];
NSURL * url = [NSURL fileURLWithPath:path];
CGImageRef myImage = NULL;
CGImageSourceRef myImageSource;
//通过文件路径创建CGImageSource对象
myImageSource = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
//获取第一张图片
myImage = CGImageSourceCreateImageAtIndex(myImageSource,
0,
NULL);
CFRelease(myImageSource);
UIImageView * image = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
image.image = [UIImage imageWithCGImage:myImage];
[self.view addSubview:image];

上面的示例代码采用的是本地的一个素材文件,当然通过网络图片链接也是可以创建CGImageSource独享的。除了通过URL链接的方式创建对象,ImageIO框架中还提供了两种方法,解析如下:

//通过数据提供器创建CGImageSource对象
/*
CGDataProviderRef是CoreGraphics框架中的一个数据读取类,其也可以通过Data数据,URL和文件名来创建
*/
CGImageSourceRef __nullable CGImageSourceCreateWithDataProvider(CGDataProviderRef __nonnull provider, CFDictionaryRef __nullable options);
//通过Data数据创建CGImageSource对象
CGImageSourceRef __nullable CGImageSourceCreateWithData(CFDataRef __nonnull data, CFDictionaryRef __nullable options);

需要注意,上面所提到的所有创建CGImageSource的方法中都可以传入一个CFDictionaryRef类型的字典,可以配置的键值意义如下:

/*
设置一个预期的图片文件格式,需要设置为字符串类型的值
*/
const CFStringRef kCGImageSourceTypeIdentifierHint;
/*
设置是否以解码的方式读取图片数据 默认为kCFBooleanTrue
如果设置为true,在读取数据时就进行解码 如果为false 则在渲染时才进行解码
*/
const CFStringRef kCGImageSourceShouldCache;
/*
返回CGImage对象时是否允许使用浮点值 默认为kCFBooleanFalse
*/
const CFStringRef kCGImageSourceShouldAllowFloa;
/*
设置如果不存在缩略图则创建一个缩略图,缩略图的尺寸受开发者设置影响,如果不设置尺寸极限,则为图片本身大小
默认为kCFBooleanFalse
*/
const CFStringRef kCGImageSourceCreateThumbnailFromImageIfAbsent;
/*
设置是否创建缩略图,无论原图像有没有包含缩略图kCFBooleanFalse
*/
const CFStringRef kCGImageSourceCreateThumbnailFromImageAlways;
/*
设置缩略图的宽高尺寸 需要设置为CFNumber值
*/
const CFStringRef kCGImageSourceThumbnailMaxPixelSize;
/*
设置缩略图是否进行Transfrom变换
*/
const CFStringRef kCGImageSourceCreateThumbnailWithTransform;

CGImageSource类中其他方法解析如下:

//获取CGImageSource类在CoreFundation框架中的id
CFTypeID CGImageSourceGetTypeID (void);
//获取所支持的图片格式数组
CFArrayRef __nonnull CGImageSourceCopyTypeIdentifiers(void);
//获取CGImageSource对象的图片格式
CFStringRef __nullable CGImageSourceGetType(CGImageSourceRef __nonnull isrc);
//获取CGImageSource中的图片张数 不包括缩略图
size_t CGImageSourceGetCount(CGImageSourceRef __nonnull isrc);
//获取CGImageSource的文件信息
/*
字典参数可配置的键值对与创建CGImageSource所传参数意义一致
返回的字典中的键值意义后面介绍
*/
CFDictionaryRef __nullable CGImageSourceCopyProperties(CGImageSourceRef __nonnull isrc, CFDictionaryRef __nullable options);
//获取CGImageSource中某个图像的附加数据
/*
index参数设置获取第几张图像 options参数可配置的键值对与创建CGImageSource所传参数意义一致
返回的字典中的键值意义后面介绍
*/
CFDictionaryRef __nullable CGImageSourceCopyPropertiesAtIndex(CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
//获取图片的元数据信息 CGImageMetadataRef类是图像原数据的抽象
CGImageMetadataRef __nullable CGImageSourceCopyMetadataAtIndex (CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
//获取CGImageSource中的图片数据
CGImageRef __nullable CGImageSourceCreateImageAtIndex(CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
//删除一个指定索引图像的缓存
void CGImageSourceRemoveCacheAtIndex(CGImageSourceRef __nonnull isrc, size_t index);
//获取某一帧图片的缩略图
CGImageRef __nullable CGImageSourceCreateThumbnailAtIndex(CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
//创建一个空的CGImageSource容器,逐步加载大图片
CGImageSourceRef __nonnull CGImageSourceCreateIncremental(CFDictionaryRef __nullable options);
//使用新的数据更新CGImageSource容器
void CGImageSourceUpdateData(CGImageSourceRef __nonnull isrc, CFDataRef __nonnull data, bool final);
//更新数据提供器来填充CGImageSource容器
void CGImageSourceUpdateDataProvider(CGImageSourceRef __nonnull isrc, CGDataProviderRef __nonnull provider, bool final);
//获取当前CGImageSource的状态
/*
CGImageSourceStatus枚举意义:
typedef CF_ENUM(int32_t, CGImageSourceStatus) {
kCGImageStatusUnexpectedEOF = -5, //文件结尾出错
kCGImageStatusInvalidData = -4, //数据无效
kCGImageStatusUnknownType = -3, //未知的图片类型
kCGImageStatusReadingHeader = -2, //读标题过程中
kCGImageStatusIncomplete = -1, //操作不完整
kCGImageStatusComplete = 0 //操作完整
};
*/
CGImageSourceStatus CGImageSourceGetStatus(CGImageSourceRef __nonnull isrc);
//同上,获取某一个图片的状态
CGImageSourceStatus CGImageSourceGetStatusAtIndex(CGImageSourceRef __nonnull isrc, size_t index);

三、CGImageDestination详解

CGImageSource是图片文件数据的抽象对象,而CGImageDestination的作用则是将抽象的图片数据写入指定的目标中。将图片写成文件示例如下:

//创建存储路径
NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *newPath = [paths.firstObject stringByAppendingPathComponent:[NSString stringWithFormat:@"image.png"]];
CFURLRef URL = CFURLCreateWithFileSystemPath (
kCFAllocatorDefault,
(CFStringRef)newPath,
kCFURLPOSIXPathStyle,
false);
//创建CGImageDestination对象
CGImageDestinationRef myImageDest = CGImageDestinationCreateWithURL(URL,CFSTR("public.png"), 1, NULL);
UIImage * image = [UIImage imageNamed:@"timg.jpeg"];
//写入图片
CGImageDestinationAddImage(myImageDest, image.CGImage, NULL);
CGImageDestinationFinalize(myImageDest);
CFRelease(myImageDest);

同样,除了可以直接将图片数据写入url外,也可以Data数据或数据消费器,方法如下:

//将图片数据写入数据消费者
CGImageDestinationRef __nullable CGImageDestinationCreateWithDataConsumer(CGDataConsumerRef __nonnull consumer, CFStringRef __nonnull type, size_t count, CFDictionaryRef __nullable options);
//将图片数据写入Data
CGImageDestinationRef __nullable CGImageDestinationCreateWithData(CFMutableDataRef __nonnull data, CFStringRef __nonnull type, size_t count, CFDictionaryRef __nullable options);

需要注意,上面方法的type参数设置写入数据的文件格式,必须为ImageIO框架所支持的格式,前面有方法可以获取所有支持的格式,还有一点,这3个写入方法的中options参数目前并没有什么作用,其是留给未来使用的,目前传入NULL即可。

CGImageDestination类中的其他方法解析如下:

//获取CGImageDestination的CFTypeID
CFTypeID CGImageDestinationGetTypeID(void);
//获取CGImageDestination所支持的图片文件类型
/*
目前支持如下:iOS10.1
(
"public.jpeg",
"public.png",
"com.compuserve.gif",
"public.tiff",
"public.jpeg-2000",
"com.microsoft.ico",
"com.microsoft.bmp",
"com.adobe.photoshop-image",
"com.adobe.pdf",
"com.truevision.tga-image",
"com.ilm.openexr-image",
"public.pbm",
"public.pvr",
"org.khronos.astc",
"org.khronos.ktx",
"com.microsoft.dds",
"com.apple.rjpeg"
)
*/
CFArrayRef __nonnull CGImageDestinationCopyTypeIdentifiers(void);
//设置图片文件属性
/*
可以设置的键值对意义如下:
const CFStringRef kCGImageDestinationLossyCompressionQuality; //设置压缩质量 0-1之间的cfnumberref值
const CFStringRef kCGImageDestinationBackgroundColor; //将图片数据写为无alpha通道时的默认背景色 cgcolor值
*/
void CGImageDestinationSetProperties(CGImageDestinationRef __nonnull idst, CFDictionaryRef __nullable properties);
//向CGImageDestination中添加一张图片 其中的option参数意义和上面一致,设置此图片的质量与无alpha默认背景色
void CGImageDestinationAddImage(CGImageDestinationRef __nonnull idst, CGImageRef __nonnull image, CFDictionaryRef __nullable properties);
//通过CGImageSource对象来向CGImageDestination中添加图片
void CGImageDestinationAddImageFromSource(CGImageDestinationRef __nonnull idst, CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable properties);
//进行写入操作 执行此方法后 不可以在写入其他信息
bool CGImageDestinationFinalize(CGImageDestinationRef __nonnull idst);
//添加图片元信息
void CGImageDestinationAddImageAndMetadata(CGImageDestinationRef __nonnull idst, CGImageRef __nonnull image, CGImageMetadataRef __nullable metadata, CFDictionaryRef __nullable options);
//将CGImageSource信息拷贝进CGImageDestination
/*
options参数可以用来添加元信息
*/
bool CGImageDestinationCopyImageSource(CGImageDestinationRef __nonnull idst, CGImageSourceRef __nonnull isrc, CFDictionaryRef __nullable options, __nullable CFErrorRef * __nullable err);

上面列举的方法中,CGImageDestinationCopyImageSource()方法中的options参数可以添加一些图片的元信息,可以设置的键值对意义如下:

//设置元信息 需要设置为CGImageMetadataRef对象
const CFStringRef kCGImageDestinationMetadata;
//是否将CGImageSource的元信息信息合并操作 默认为kCFBooleanFalse
const CFStringRef kCGImageDestinationMergeMetadata;
//XMP数据是否不被写入 默认为kCFBooleanFalse
const CFStringRef kCGImageMetadataShouldExcludeXMP;
//GPS信息是否不被写入 默认为kCFBooleanFalse
const CFStringRef kCGImageMetadataShouldExcludeGPS;
//更新元数据的时间值 需要设置为CFStringRef或者CFDateRef
const CFStringRef kCGImageDestinationDateTime;
//更新元数据的方向值 需要设置为NSNumber1-8
const CFStringRef kCGImageDestinationOrientation;

四、关于CGImageMetadata

前面我们很多次提到元数据,CGImageMetadata类就是元数据的抽象,其中封装了一些方法供开发者读取或写入元数据信息。奇怪的是Apple的官方文档与API文档中并没有CGImageMetadata的介绍与解释,博客中本部分的内容,多出自我的理解,有疏漏和不对的地方,清楚的朋友可以指点与建议。

前边介绍,CGImageSource中有获取图片元数据的方法,CGImageDestination中也有写入图片元数据的方法,元数据中抽象出的CGImageMetadataTag是对具体数据内容的封装。CGImageMetadata解析如下:

//获取CGImageMetadata类的CFTypeID
CFTypeID CGImageMetadataGetTypeID(void);
//创建一个空的可变的CGImageMetadata对象
CGMutableImageMetadataRef __nonnull CGImageMetadataCreateMutable(void);
//拷贝一个可变的CGImageMetadata对象
CGMutableImageMetadataRef __nullable CGImageMetadataCreateMutableCopy(CGImageMetadataRef __nonnull metadata);
//获取CGImageMetadataTag类的CFTypeID
CFTypeID CGImageMetadataTagGetTypeID(void);
//创建一个CGImageMetadataTag对象
/*
这个方法比较复杂
xmlns参数设置命名空间
prefix参数设置命名空间的缩写或前缀
name参数设置CGImageMetadataTag的名称
type参数设置CGImageMetadataTag对应值的类型
value参数设置CGImageMetadataTag的对应值
*/
CGImageMetadataTagRef __nullable CGImageMetadataTagCreate (CFStringRef __nonnull xmlns, CFStringRef __nullable prefix, CFStringRef __nonnull name, CGImageMetadataType type, CFTypeRef __nonnull value);

上面创建CGImageMetadataTag的方法中,xmlns设置命名空间,必须使用一个预定义的命名空间或者自定义的命名空间,对于自定义的命名空间,必须遵守Adobe的XMP规范。一些共用的命名空间定义如下:

//Exif命名空间
const CFStringRef kCGImageMetadataNamespaceExif;
//ExifAux命名空间
const CFStringRef kCGImageMetadataNamespaceExifAux;
//ExifEX命名空间
const CFStringRef kCGImageMetadataNamespaceExifEX;
//DublineCore命名空间
const CFStringRef kCGImageMetadataNamespaceDublinCore;
//IPTCCore命名空间
const CFStringRef kCGImageMetadataNamespaceIPTCCore;
//Photoshop命名空间
const CFStringRef kCGImageMetadataNamespacePhotoshop;
//TIFF命名空间
const CFStringRef kCGImageMetadataNamespaceTIFF;
//XMPBasic命名空间
const CFStringRef kCGImageMetadataNamespaceXMPBasic;
//XMPRights命名空间
const CFStringRef kCGImageMetadataNamespaceXMPRights;

上面创建CGImageMetadataTag的方法中prefix设置命名空间缩写或前缀,同样一些公用的前缀定义如下:

//Exif命名空间前缀
const CFStringRef kCGImageMetadataPrefixExif;
//ExifAux命名空间前缀
const CFStringRef kCGImageMetadataPrefixExifAux;
//ExifEX命名空间前缀
const CFStringRef kCGImageMetadataPrefixExifEX;
//DublinCore命名空间前缀
const CFStringRef kCGImageMetadataPrefixDublinCore;
//IPCCore命名空间前缀
const CFStringRef kCGImageMetadataPrefixIPTCCore;
//Photoshop命名空间前缀
const CFStringRef kCGImageMetadataPrefixPhotoshop;
//TIFF命名空间前缀
const CFStringRef kCGImageMetadataPrefixTIFF;
//XMPBasic命名空间前缀
const CFStringRef kCGImageMetadataPrefixXMPBasic;
//XMPRights命名空间前缀
const CFStringRef kCGImageMetadataPrefixXMPRights;

上面创建CGImageMetadataTag的方法中type设置对应值的类型,其是一个CGImageMetadataType类型的枚举,意义如下:

typedef CF_ENUM(int32_t, CGImageMetadataType) {
//无效的数据类型
kCGImageMetadataTypeInvalid = -1,
//基本的CFType类型
kCGImageMetadataTypeDefault = 0,
//字符串类型
kCGImageMetadataTypeString = 1,
//无需集合类型
kCGImageMetadataTypeArrayUnordered = 2,
//有序集合类型
kCGImageMetadataTypeArrayOrdered = 3,
//有序阵列
kCGImageMetadataTypeAlternateArray = 4,
//特殊的数组 其中元素进行不同的本地化
kCGImageMetadataTypeAlternateText = 5,
//结构类型 如字典
kCGImageMetadataTypeStructure = 6
};

获取到CGImageMetadataTag后,可以通过如下方法来获取其中封装的信息:

//获取标签的命名空间
CFStringRef __nullable CGImageMetadataTagCopyNamespace(CGImageMetadataTagRef __nonnull tag);
//获取标签的命名空间前缀
CFStringRef __nullable CGImageMetadataTagCopyPrefix(CGImageMetadataTagRef __nonnull tag);
//获取标签名称
CFStringRef __nullable CGImageMetadataTagCopyName(CGImageMetadataTagRef __nonnull tag);
//获取标签的值
CFTypeRef __nullable CGImageMetadataTagCopyValue(CGImageMetadataTagRef __nonnull tag);
//获取标签值的类型
CGImageMetadataType CGImageMetadataTagGetType(CGImageMetadataTagRef __nonnull tag);
//获取标签的Qualifier数组
CFArrayRef __nullable CGImageMetadataTagCopyQualifiers(CGImageMetadataTagRef __nonnull tag);

下面这些方法用于向CGImageMetadata中添加标签或者获取标签:

//获取CGImageMetadata中的所有标签
CFArrayRef __nullable CGImageMetadataCopyTags(CGImageMetadataRef __nonnull metadata);
//通过路径查找特殊的标签
CGImageMetadataTagRef __nullable CGImageMetadataCopyTagWithPath(CGImageMetadataRef __nonnull metadata, CGImageMetadataTagRef __nullable parent, CFStringRef __nonnull path);
//通过路径查找特殊标签的值
CFStringRef __nullable CGImageMetadataCopyStringValueWithPath(CGImageMetadataRef __nonnull metadata, CGImageMetadataTagRef __nullable parent, CFStringRef __nonnull path);
//为一个前缀注册一个命名空间
bool CGImageMetadataRegisterNamespaceForPrefix(CGMutableImageMetadataRef __nonnull metadata, CFStringRef __nonnull xmlns, CFStringRef __nonnull prefix, __nullable CFErrorRef * __nullable err);
//通过路径为CGImageMetadata设置标签
bool CGImageMetadataSetTagWithPath(CGMutableImageMetadataRef __nonnull metadata, CGImageMetadataTagRef __nullable parent, CFStringRef __nonnull path, CGImageMetadataTagRef __nonnull tag);
//通过路径为CGImageMetadata设置标签的值
bool CGImageMetadataSetValueWithPath(CGMutableImageMetadataRef __nonnull metadata, CGImageMetadataTagRef __nullable parent, CFStringRef __nonnull path, CFTypeRef __nonnull value);
//通过路径移除一个标签
bool CGImageMetadataRemoveTagWithPath(CGMutableImageMetadataRef __nonnull metadata, CGImageMetadataTagRef __nullable parent, CFStringRef __nonnull path);
//对标签进行枚举
void CGImageMetadataEnumerateTagsUsingBlock(CGImageMetadataRef __nonnull metadata, CFStringRef __nullable rootPath, CFDictionaryRef __nullable options, CGImageMetadataTagBlock __nonnull block);

六、ImageIO框架在实际开发中的几个应用

1.显示特殊格式的图片

在平时开发中,我们通常使用UIImage来读取图片,UIImage支持的图片包括png与jpg等,但是类似windows系统的ico图标,UIImage默认是无法显示的,可以通过ImageIO框架来在iOS系统中使用ico图标,示例如下:

    NSString * path = [[NSBundle mainBundle]pathForResource:@"image" ofType:@"ico"];
NSURL * url = [NSURL fileURLWithPath:path];
CGImageRef myImage = NULL;
CGImageSourceRef myImageSource;
CFDictionaryRef myOptions = NULL;
myImageSource = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
myImage = CGImageSourceCreateImageAtIndex(myImageSource,
0,
NULL);
CFRelease(myImageSource);
UIImageView * image = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
image.image = [UIImage imageWithCGImage:myImage];

2.读取数码相机拍摄图片的地理位置、时间等信息

3.对相册中图片的地理位置,时间等信息进行自定义修改。

4.将自定义格式的图片数据写入本地文件。

5.展示GIF动图

6.渐进渲染大图

渐进渲染技术在对加载大图片时特别重要,你应该使用过地图软件,地图视图在加载时是局部进行加载,当移动或者放大时,地图会一部分一部分的渐进进行加载,使用ImageIO框架可以实现大图渐进渲染的效果,一般在对大图片进行网络请求时,可以获取一部分数据就加载一部分数据,为了便于演示,博客中使用定时器来默认网络返回数据,代码示例如下:

@interface ViewController ()
{
NSMutableData * _data;
NSData * _allData;
NSUInteger length;
UIImageView * _imageView;
NSTimer * timer;
NSInteger le;
}
@end
@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
_data = [[NSMutableData alloc]init];
NSString * path = [[NSBundle mainBundle]pathForResource:@"Default-Portrait-ns@2x" ofType:@"png"];
_allData = [NSData dataWithContentsOfFile:path];
length = _allData.length;
le = length/10;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateImage) userInfo:nil repeats:YES];
_imageView = [[UIImageView alloc]initWithFrame:self.view.frame];
[self.view addSubview:_imageView];
}


-(void)updateImage{
static int index = 0;
if (index==10) {
return;
}
NSUInteger l;
if (index==9) {
l=length-le*9;
}else{
l= le;
}

Byte by[l];
[_allData getBytes:by range:NSMakeRange(index*le, l)];
[_data appendBytes:by length:l];
CGImageSourceRef myImageSource = CGImageSourceCreateWithData((CFDataRef)_data, NULL);
CGImageRef myImage = CGImageSourceCreateImageAtIndex(myImageSource,
0,
NULL);
CFRelease(myImageSource);

_imageView.image = [UIImage imageWithCGImage:myImage];
// image.image = [UIImage imageNamed:@"image.ico"];
index++;
}
@end

效果如下:

iOS中ImageIO框架详解与应用分析_iOS获取图片中的信息