iOS相册是支持保存GIF和APNG动图的,只是不能直接播放。
在iOS9之前,保存图片使用:
[ALAssetsLibrary writeImageDataToSavedPhotosAlbum: metadata: completionBlock:];
但是在iOS9之后,AssetsLibrary废弃了,被Photos库取代了,所以在iOS9之后保存图片使用:
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
}];
Tip:
如果直接使用[PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image];则会出现如下崩溃信息:
reason: 'This method can only be called from inside of -[PHPhotoLibrary performChanges:completionHandler:]
or -[PHPhotoLibrary performChangesAndWait:error:]'
结论:凡是图片的增删改查操作,都放在 performChanges
中进行操作 ,如下:
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
//TODO: 做相应的增删改查操作
} completionHandler:^(BOOL success, NSError * _Nullable error) {
}];
如果使用UIImageWriteToSavedPhotosAlbum()
方法直接写入相册的话,那么图像会被强制转换为png格式。
目前来说,保存UIImage有三种方式:
1、直接使用NSKeyedArchiver 把UIImage序列化保存。
2、用UIImagePNGRepresentation()先把图片转为png保存。
3、用UIImageJPEGRepresentation()把图片压缩成JPEG保存。
实际上,NSKeyedArchiver 是调用了 UIImagePNGRepresentation 进行序列化的,用它来保存图片是消耗最大的。苹果对 JPEG 有硬编码和硬解码,保存成 JPEG 会大大缩减编码解码时间,也能减小文件体积。所以如果图片不包含透明像素时,UIImageJPEGRepresentation(0.9) 是最佳的图片保存方式,其次是 UIImagePNGRepresentation()。
通过imageNamed 创建 UIImage时,系统实际上只是在Bundle内查找到文件名,然后把这个文件名放到UIImage里面返回,并没有进行实际的文件的读取和解码。当UIImage第一次显示到屏幕上的时候,其内部解码方法才会被调用,同时解码结果会保存到一个全局的缓存中,在图片解码后,App第一次退到后台和收到内存警告时,该图片的缓存才会被清空,其他情况下缓存会一直存在。
不能
,通过数据创建UIImage时,UIImage底层是调用ImageIO的CGImageSourceCreateWithData()方法,该方法有个参数叫ShouldCache,在64位的设备上,这个参数是默认开启的。这个图片也是同样在第一次显示到屏幕时才会被解码,随后解码数据被缓存到 CGImage 内部。与 imageNamed 创建的图片不同,如果这个图片被释放掉,其内部的解码数据也会被立刻释放。
- 手动调用 CGImageSourceCreateWithData() 来创建图片,并把 ShouldCache 和 ShouldCacheImmediately 关掉。这么做会导致每次图片显示到屏幕时,解码方法都会被调用,造成很大的 CPU 占用。
- 把图片用 CGContextDrawImage() 绘制到画布上,然后把画布的数据取出来当作图片。这也是常见的网络图片库的做法。
- CGImageSourceCreateWithData(data) 创建 ImageSource。
- CGImageSourceCreateImageAtIndex(source) 创建一个未解码的 CGImage。
- CGImageGetDataProvider(image) 获取这个图片的数据源。
- CGDataProviderCopyData(provider) 从数据源获取直接解码的数据。
ImageIO 解码发生在最后一步,这样获得的数据是没有经过颜色类型转换的原生数据(比如灰度图像)。
通过读取文件或数据的头几个字节然后和对应图片格式标准进行比对。具体可以参考YYImageCoder类里面的方法:
YYImageType YYImageDetectType(CFDataRef data) {
if (!data) return YYImageTypeUnknown;
uint64_t length = CFDataGetLength(data);
if (length < 16) return YYImageTypeUnknown;
const char *bytes = (char *)CFDataGetBytePtr(data);
uint32_t magic4 = *((uint32_t *)bytes);
switch (magic4) {
case YY_FOUR_CC(0x4D, 0x4D, 0x00, 0x2A): { // big endian TIFF
return YYImageTypeTIFF;
} break;
case YY_FOUR_CC(0x49, 0x49, 0x2A, 0x00): { // little endian TIFF
return YYImageTypeTIFF;
} break;
case YY_FOUR_CC(0x00, 0x00, 0x01, 0x00): { // ICO
return YYImageTypeICO;
} break;
case YY_FOUR_CC(0x00, 0x00, 0x02, 0x00): { // CUR
return YYImageTypeICO;
} break;
case YY_FOUR_CC('i', 'c', 'n', 's'): { // ICNS
return YYImageTypeICNS;
} break;
case YY_FOUR_CC('G', 'I', 'F', '8'): { // GIF
return YYImageTypeGIF;
} break;
case YY_FOUR_CC(0x89, 'P', 'N', 'G'): { // PNG
uint32_t tmp = *((uint32_t *)(bytes + 4));
if (tmp == YY_FOUR_CC('\r', '\n', 0x1A, '\n')) {
return YYImageTypePNG;
}
} break;
case YY_FOUR_CC('R', 'I', 'F', 'F'): { // WebP
uint32_t tmp = *((uint32_t *)(bytes + 8));
if (tmp == YY_FOUR_CC('W', 'E', 'B', 'P')) {
return YYImageTypeWebP;
}
} break;
/*
case YY_FOUR_CC('B', 'P', 'G', 0xFB): { // BPG
return YYImageTypeBPG;
} break;
*/
}
uint16_t magic2 = *((uint16_t *)bytes);
switch (magic2) {
case YY_TWO_CC('B', 'A'):
case YY_TWO_CC('B', 'M'):
case YY_TWO_CC('I', 'C'):
case YY_TWO_CC('P', 'I'):
case YY_TWO_CC('C', 'I'):
case YY_TWO_CC('C', 'P'): { // BMP
return YYImageTypeBMP;
}
case YY_TWO_CC(0xFF, 0x4F): { // JPEG2000
return YYImageTypeJPEG2000;
}
}
// JPG FF D8 FF
if (memcmp(bytes,"\377\330\377",3) == 0) return YYImageTypeJPEG;
// JP2
if (memcmp(bytes + 4, "\152\120\040\040\015", 5) == 0) return YYImageTypeJPEG2000;
return YYImageTypeUnknown;
}
第一种是 baseline,即逐行扫描。默认情况下,JPEG、PNG、GIF 都是这种保存方式。
第二种是 interlaced,即隔行扫描。PNG 和 GIF 在保存时可以选择这种格式。
第三种是 progressive,即渐进式。JPEG 在保存时可以选择这种方式。
在下载图片时,首先用 CGImageSourceCreateIncremental(NULL) 创建一个空的图片源,随后在获得新数据时调用
CGImageSourceUpdateData(data, false) 来更新图片源,最后在用 CGImageSourceCreateImageAtIndex() 创建图片来显示。
可以用 PINRemoteImage 或者 YYWebImage 来实现这个效果。SDWebImage 并没有用 Incremental 方式解码,所以显示效果很差。