參考iOS圖片解碼實踐 / iOS圖像解碼和最佳實踐 / SDWebImage / YYImage
imageNamed 和 imageWithContentsOfFile的區(qū)別
1、+ (UIImage *)imageNamed:(NSString *)name;
name:圖片資源或文件的名稱。對于assets中的資源,是指定的圖片資源名稱,圖片資源中可以包含1x、2x、3x圖片,系統(tǒng)會自動選擇。對于png圖像可以省略擴展名,對于其他格式圖片需要始終包含文件擴展名。
當在assets目錄中查找時,查找symbol image優(yōu)先于同名當bitmap image。由于symbol image僅支持iOS13及之后版本,所以可能需要在assets中包含兩套圖片格式,系統(tǒng)會在早期iOS版本自動選擇bitmap image,不過我覺得一般沒人這么干吧~。不能使用這個方法加載系統(tǒng)的symbol image,需要使用systemImageNamed:方法。
這個方法會根據(jù)指定的名稱在系統(tǒng)緩存中查找,選擇最適合屏幕的image返回。如果系統(tǒng)緩存中沒有找到,則從asstes目錄或磁盤中找到正確的圖片創(chuàng)建一個image返回。系統(tǒng)會隨時清空圖片緩存,當然僅僅是清空未使用的圖片緩存。
這個方法在iOS9之后是線程安全的。
Tip:
如果僅加載一次圖片或者不想要添加到系統(tǒng)緩存中,可以使用
imageWithContentsOfFile方法創(chuàng)建image。保證使用次數(shù)少的圖片不添加到系統(tǒng)緩存中,可以有效提高App內(nèi)存使用效率。
2、+ (UIImage *)imageWithContentsOfFile:(NSString *)path;
這個方法不會緩存image對象。而且只會根據(jù)文件名獲取,并不會根據(jù)2x、3x自動創(chuàng)建。
圖片解壓縮
由于png、jpeg等圖片格式均是壓縮的位圖圖片,在顯示時需要解碼成位圖。png是無損壓縮且支持alpha通道,jpeg是有損壓縮。
UIImageView *imgV = [[UIImageView alloc] initWithFrame:self.view.bounds];
UIImage *image = [UIImage imageNamed:@"test.jpeg"];
[self.view addSubview:imgV];
上述代碼第一次加載時做了如下操作:
- 從磁盤加載test.jpeg圖片到內(nèi)存中為
Data Buffer - 當圖片真正要被顯示在屏幕上時,會觸發(fā)解碼,
Data Buffer--->Image Buffer - 然后
Image Buffer傳遞給GPU,GPU經(jīng)過頂點坐標轉(zhuǎn)換、頂點著色器、光柵化、片元著色器等步驟轉(zhuǎn)化為Frame Buffer存在幀緩沖區(qū),等待屏幕刷新顯示
下面代碼通過CGBitmapContextCreateImage從圖形上下文中獲取到解碼到位圖。
+ (void)decodeSourceImage:(UIImage *)sourceImage completion:(nonnull void (^)(UIImage * _Nonnull))completion {
if (!sourceImage) return;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//獲取圖片寬高
CGImageRef cgimage = sourceImage.CGImage;
size_t width = CGImageGetWidth(cgimage);
size_t height = CGImageGetHeight(cgimage);
if (width == 0 || height == 0) return;
//判斷是否有alpha通道
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgimage) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//創(chuàng)建圖形上下文
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace, bitmapInfo);
if (!context) return;
//繪制到上下文中解壓
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgimage); // decode
//從上下文中獲取
CGImageRef newCgimage = CGBitmapContextCreateImage(context);
UIImage *desImage = [UIImage imageWithCGImage:newCgimage];
CGContextRelease(context);
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(desImage);
}
});
});
}
YYKit中提供了另一種解碼方式如下,在注釋中說使用原始數(shù)據(jù)解碼會造成丟失一些精度。
+ (void)decodeSourceImage1:(UIImage *)sourceImage completion:(nonnull void (^)(UIImage * _Nonnull))completion {
if (!sourceImage) return;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
CGImageRef cgimage = sourceImage.CGImage;
size_t width = CGImageGetWidth(cgimage);
size_t height = CGImageGetHeight(cgimage);
if (width == 0 || height == 0) return;
CGColorSpaceRef colorSpace = CGImageGetColorSpace(cgimage);
size_t bitsPerComponent = CGImageGetBitsPerComponent(cgimage);
size_t bitsPerPixel = CGImageGetBitsPerPixel(cgimage);
size_t bytesPerRow = CGImageGetBytesPerRow(cgimage);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgimage);
if (bytesPerRow == 0 || width == 0 || height == 0) return;
CGDataProviderRef provider = CGImageGetDataProvider(cgimage);
if (!provider) return;
//獲取原始數(shù)據(jù)
CFDataRef data = CGDataProviderCopyData(provider);
if (!data) return;
CGDataProviderRef newProvider = CGDataProviderCreateWithCFData(data);
CFRelease(data);
if (!newProvider) return;
CGImageRef newImageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, newProvider, NULL, false, kCGRenderingIntentDefault);
UIImage *desImage = [UIImage imageWithCGImage:newImageRef];
CFRelease(newImageRef);
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(desImage);
}
});
});
}