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];則會出現(xiàn)如下崩潰信息:
reason: 'This method can only be called from inside of -[PHPhotoLibrary performChanges:completionHandler:]
or -[PHPhotoLibrary performChangesAndWait:error:]'
結(jié)論:凡是圖片的增刪改查操作,都放在 performChanges
中進行操作 柜裸,如下:
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
//TODO: 做相應(yīng)的增刪改查操作
} completionHandler:^(BOOL success, NSError * _Nullable error) {
}];
如果使用UIImageWriteToSavedPhotosAlbum()
方法直接寫入相冊的話缕陕,那么圖像會被強制轉(zhuǎn)換為png格式。
目前來說,保存UIImage有三種方式:
1铐然、直接使用NSKeyedArchiver 把UIImage序列化保存蔬崩。
2恶座、用UIImagePNGRepresentation()先把圖片轉(zhuǎn)為png保存。
3沥阳、用UIImageJPEGRepresentation()把圖片壓縮成JPEG保存跨琳。
實際上,NSKeyedArchiver 是調(diào)用了 UIImagePNGRepresentation 進行序列化的桐罕,用它來保存圖片是消耗最大的脉让。蘋果對 JPEG 有硬編碼和硬解碼,保存成 JPEG 會大大縮減編碼解碼時間功炮,也能減小文件體積溅潜。所以如果圖片不包含透明像素時,UIImageJPEGRepresentation(0.9) 是最佳的圖片保存方式死宣,其次是 UIImagePNGRepresentation()伟恶。
通過imageNamed 創(chuàng)建 UIImage時毅该,系統(tǒng)實際上只是在Bundle內(nèi)查找到文件名,然后把這個文件名放到UIImage里面返回潦牛,并沒有進行實際的文件的讀取和解碼眶掌。當UIImage第一次顯示到屏幕上的時候,其內(nèi)部解碼方法才會被調(diào)用巴碗,同時解碼結(jié)果會保存到一個全局的緩存中朴爬,在圖片解碼后,App第一次退到后臺和收到內(nèi)存警告時橡淆,該圖片的緩存才會被清空召噩,其他情況下緩存會一直存在。
不能
具滴,通過數(shù)據(jù)創(chuàng)建UIImage時,UIImage底層是調(diào)用ImageIO的CGImageSourceCreateWithData()方法师倔,該方法有個參數(shù)叫ShouldCache构韵,在64位的設(shè)備上,這個參數(shù)是默認開啟的趋艘。這個圖片也是同樣在第一次顯示到屏幕時才會被解碼疲恢,隨后解碼數(shù)據(jù)被緩存到 CGImage 內(nèi)部。與 imageNamed 創(chuàng)建的圖片不同瓷胧,如果這個圖片被釋放掉显拳,其內(nèi)部的解碼數(shù)據(jù)也會被立刻釋放。
- 手動調(diào)用 CGImageSourceCreateWithData() 來創(chuàng)建圖片杂数,并把 ShouldCache 和 ShouldCacheImmediately 關(guān)掉遇八。這么做會導致每次圖片顯示到屏幕時,解碼方法都會被調(diào)用耍休,造成很大的 CPU 占用刃永。
- 把圖片用 CGContextDrawImage() 繪制到畫布上,然后把畫布的數(shù)據(jù)取出來當作圖片羊精。這也是常見的網(wǎng)絡(luò)圖片庫的做法斯够。
- CGImageSourceCreateWithData(data) 創(chuàng)建 ImageSource读规。
- CGImageSourceCreateImageAtIndex(source) 創(chuàng)建一個未解碼的 CGImage。
- CGImageGetDataProvider(image) 獲取這個圖片的數(shù)據(jù)源燃少。
- CGDataProviderCopyData(provider) 從數(shù)據(jù)源獲取直接解碼的數(shù)據(jù)束亏。
ImageIO 解碼發(fā)生在最后一步,這樣獲得的數(shù)據(jù)是沒有經(jīng)過顏色類型轉(zhuǎn)換的原生數(shù)據(jù)(比如灰度圖像)阵具。
通過讀取文件或數(shù)據(jù)的頭幾個字節(jié)然后和對應(yīng)圖片格式標準進行比對。具體可以參考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) 創(chuàng)建一個空的圖片源,隨后在獲得新數(shù)據(jù)時調(diào)用
CGImageSourceUpdateData(data, false) 來更新圖片源跛蛋,最后在用 CGImageSourceCreateImageAtIndex() 創(chuàng)建圖片來顯示熬的。
可以用 PINRemoteImage 或者 YYWebImage 來實現(xiàn)這個效果。SDWebImage 并沒有用 Incremental 方式解碼赊级,所以顯示效果很差押框。