SDWebImage主線設計的解碼大致有兩種:普通解碼和漸進式解碼。本文只對普通解碼進行解析。
普通解碼又分為正常解碼和大圖解碼。
普通解碼
普通解碼從 -[SDWebImageDownloaderOperation URLSession:task:didCompleteWithError:]
發起
更多解析請參考SDWebImage主線梳理(二)
同步鎖加持,發送兩個通知:
SDWebImageDownloadStopNotification
,SDWebImageDownloadFinishNotification
如果 self.callbackBlocks 有 LoaderCompletedBlock(key=kCompletedCallbackKey), 繼續
self.imageData(NSMutableData) 在
didReceiveData:
方法中一點一點拼接的可變data異步串行隊列(self.coderQueue),調用 SDImageLoaderDecodeImageData() 解碼imageData,輸出UIImage
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
解碼函數 SDImageLoaderDecodeImageData()
SDImageLoaderDecodeImageData()
函數在 SDImageLoader.m
中
-
SDImageScaleFactorForKey(NSString * key),返回一個屏幕倍數因子。
- 如果key是普通名字,判斷key包不包含"@2x."或者"@3x.",包含就返回這個倍數因子。
- 如果key是一個URL,百分號編碼的下的@ = %40,判斷key包不包含"%402x."或者"%403x."。
如果不是“僅解碼第一幀”并且是動圖,則裝載動圖的所有幀
如果不是動圖,
-[SDImageCodersManager(sharedManager) decodedImageWithData:imageData options:]
,參考詳解一動圖不解碼
如果應該解碼,判斷是否應該按比例縮小圖片
按比例縮小圖片:
-[SDImageCoderHelper decodedAndScaledDownImageWithImage:limitBytes:]
,參考詳解二不縮小圖片:
-[SDImageCoderHelper decodedImageWithImage:]
,參考詳解三
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
詳解一
+[SDImageCodersManager sharedManager] 方法
調用 -[SDImageCodersManager new]
即 -[SDImageCodersManager init]
+ (nonnull instancetype)sharedManager {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
-[SDImageCodersManager init] 方法
- 創建可變數組,初始化賦值 SDImageIOCoder, SDImageGIFCoder, SDImageAPNGCoder; 都是調用 sharedCoder 方法獲取, 待展開
- 創建信號量線程鎖,保存在 SDImageCodersManager 的 _codersLock 屬性中
- (instancetype)init {
if (self = [super init]) {
// initialize with default coders
_imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]];
_codersLock = dispatch_semaphore_create(1);
}
return self;
}
-[SDImageCodersManager decodedImageWithData:options:] 方法
取出(加鎖) _imageCoders, SDImageIOCoder, SDImageGIFCoder, SDImageAPNGCoder;
反轉遍歷 _imageCoders ,順序為 SDImageAPNGCoder -> SDImageGIFCoder -> SDImageIOCoder
-
每個 coder 都調用
canDecodeFromData:
方法,判斷是否是可以解碼的格式-
-[SDImageIOAnimatedCoder canDecodeFromData:]
,取兩個值(圖片格式)進行對比-
+[NSData(ImageContentType) sd_imageFormatForImageData:]
獲取data中包含的圖片格式-
-[NSData getBytes:ength:]
獲取的一個字節(第一個字節)可以區分圖片格式
-
- (self.class.imageFormat 也就是
+[SDImageAPNGCoder imageFormat]
) == SDImageFormatPNG
-
-
-
如果返回值為YES則 coder 調用
decodedImageWithData:options:
方法,輸出 image。-
-[SDImageIOAnimatedCoder decodedImageWithData:options:]
::: 此處并不是解碼的地方,只是將壓縮的圖片二進制流讀取到UIImage中。
-
打斷for循環,返回image
// -[SDImageCodersManager decodedImageWithData:options:]
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
if (!data) {
return nil;
}
UIImage *image;
NSArray<id<SDImageCoder>> *coders = self.coders;
for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
if ([coder canDecodeFromData:data]) {
image = [coder decodedImageWithData:data options:options];
break;
}
}
return image;
}
// -[SDImageIOAnimatedCoder canDecodeFromData:]
- (BOOL)canDecodeFromData:(nullable NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
}
@implementation NSData (ImageContentType)
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
// File signatures table: http://www.garykessler.net/library/file_sigs.html
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return SDImageFormatJPEG;
case 0x89:
return SDImageFormatPNG;
case 0x47:
return SDImageFormatGIF;
case 0x49:
case 0x4D:
return SDImageFormatTIFF;
case 0x52: {
if (data.length >= 12) {
//RIFF....WEBP
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
break;
}
case 0x00: {
if (data.length >= 12) {
//....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"ftypheic"]
|| [testString isEqualToString:@"ftypheix"]
|| [testString isEqualToString:@"ftyphevc"]
|| [testString isEqualToString:@"ftyphevx"]) {
return SDImageFormatHEIC;
}
//....ftypmif1 ....ftypmsf1
if ([testString isEqualToString:@"ftypmif1"] || [testString isEqualToString:@"ftypmsf1"]) {
return SDImageFormatHEIF;
}
}
break;
}
}
return SDImageFormatUndefined;
}
// -[SDImageIOAnimatedCoder decodedImageWithData:options:]
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
if (!data) {
return nil;
}
CGFloat scale = 1;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
#if SD_MAC
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
imageRep.size = size;
NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
[animatedImage addRepresentation:imageRep];
return animatedImage;
#else
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) {
return nil;
}
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
if (decodeFirstFrame || count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data scale:scale];
} else {
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
for (size_t i = 0; i < count; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!imageRef) {
continue;
}
NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
[frames addObject:frame];
}
NSUInteger loopCount = [self.class imageLoopCountWithSource:source];
animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = loopCount;
}
animatedImage.sd_imageFormat = self.class.imageFormat;
CFRelease(source);
return animatedImage;
#endif
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
詳解二-解碼大圖
+[SDImageCoderHelper decodedAndScaledDownImageWithImage:limitBytes:] 方法
-
+[SDImageCoderHelper shouldDecodeImage:]
- 已解碼過(判斷關聯屬性sd_isDecoded)的不再解碼;
- 圖片為nil不解碼;
- 動圖不解碼;
-
+[SDImageCoderHelper shouldScaleDownImage:limitBytes:]
,判斷是否是要比例縮小,如果不需要則直接正常解碼圖片即可- 限制圖片當超過多少字節時需要縮小,可以手動設置也可以走SD的默認值
- SD默認當圖片總像素數量超過 262144(60M所擁有的像素數量)需要縮小
- 只要限制的像素數量比源像素數量小即需要縮小。PS:太小了也不行,至少要超過(大于) 1M 才需要。
初始化目標總像素量(destTotalPixels,60M的總像素) 以及目標圖片的單片(tile)總像素量(tileTotalPixels,20M的總像素)
更多的解析看源碼中添加的注釋吧
+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes {
#if SD_MAC
return image;
#else
if (![self shouldDecodeImage:image]) {
return image;
}
if (![self shouldScaleDownImage:image limitBytes:bytes]) {
return [self decodedImageWithImage:image];
}
CGFloat destTotalPixels;
CGFloat tileTotalPixels;
if (bytes > 0) {
destTotalPixels = bytes / kBytesPerPixel;
tileTotalPixels = destTotalPixels / 3;
} else {
destTotalPixels = kDestTotalPixels;
tileTotalPixels = kTileTotalPixels;
}
CGContextRef destContext;
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
@autoreleasepool {
CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height;
// Determine the scale ratio to apply to the input image
// that results in an output image of the defined size.
// see kDestImageSizeMB, and how it relates to destTotalPixels.
// 此處開方,假設destTotalPixels = 60M(擁有的像素量), sourceTotalPixels = 240M, imageScale = 1/2。
// 因為dest的總像素量需要寬高相乘才能得到,所以需要比例值需要開方。
CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels);
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width * imageScale);
destResolution.height = (int)(sourceResolution.height * imageScale);
// device color space
CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB];
BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef];
// iOS display alpha info (BGRA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipFirst
// to create bitmap graphics contexts without alpha info.
destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,
kBitsPerComponent,
0,
colorspaceRef,
bitmapInfo);
if (destContext == NULL) {
return image;
}
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
// Now define the size of the rectangle to be used for the
// incremental blits from the input image to the output image.
// we use a source tile width equal to the width of the source
// image due to the way that iOS retrieves image data from disk.
// iOS must decode an image from disk in full width 'bands', even
// if current graphics context is clipped to a subrect within that
// band. Therefore we fully utilize all of the pixel data that results
// from a decoding opertion by achnoring our tile size to the full
// width of the input image.
CGRect sourceTile = CGRectZero;
sourceTile.size.width = sourceResolution.width;
// The source tile height is dynamic. Since we specified the size
// of the source tile in MB, see how many rows of pixels high it
// can be given the input image width.
sourceTile.size.height = (int)(tileTotalPixels / sourceTile.size.width );
sourceTile.origin.x = 0.0f;
// The output tile is the same proportions as the input tile, but
// scaled to image scale.
CGRect destTile;
destTile.size.width = destResolution.width;
destTile.size.height = sourceTile.size.height * imageScale;
destTile.origin.x = 0.0f;
// The source seem overlap is proportionate to the destination seem overlap.
// this is the amount of pixels to overlap each tile as we assemble the ouput image.
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
CGImageRef sourceTileImageRef;
// calculate the number of read/write operations required to assemble the
// output image.
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
// If tile height doesn't divide the image height evenly, add another iteration
// to account for the remaining pixels.
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
iterations++;
}
// Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
float sourceTileHeightMinusOverlap = sourceTile.size.height;
sourceTile.size.height += sourceSeemOverlap;
destTile.size.height += kDestSeemOverlap;
for( int y = 0; y < iterations; ++y ) {
@autoreleasepool {
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);// 用戶空間和設備空間的坐標系轉換
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
if( y == iterations - 1 && remainder ) {// 最后一次并且高不能被整除
float dify = destTile.size.height;
// 獲取不能被整除的最后一小塊真實高度
destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
// 修正y值
dify -= destTile.size.height;
destTile.origin.y += dify;
}
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
CGImageRelease( sourceTileImageRef );
}
}
CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
CGContextRelease(destContext);
if (destImageRef == NULL) {
return image;
}
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
destImage.sd_isDecoded = YES;
destImage.sd_imageFormat = image.sd_imageFormat;
return destImage;
}
#endif
}
這個解碼大圖不是SD的原創,是蘋果官方給出的解決方案,提供demo可下載。想要了解更多,就去《Large Image Downsizing》探索偉大航路吧!
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
詳解三-正常解碼
+[SDImageCoderHelper decodedImageWithImage:] 方法
+[SDImageCoderHelper shouldDecodeImage:]:已解碼過(判斷關聯屬性sd_isDecoded)的不再解碼;圖片為nil不解碼;動圖不解碼;
+[SDImageCoderHelper CGImageCreateDecoded:]
-
+[SDImageCoderHelper CGImageCreateDecoded:orientation:]:輸入image.CGImage,輸出解碼的CGImageRef; 核心解碼函數:CGContextDrawImage()
獲取圖片的寬高,如果方向是左、右旋轉則交換寬高數據
判斷是否含有alpha信息
獲取32位字節順序(kCGBitmapByteOrder32Host這個宏避免考慮大小端問題),保存到位圖信息bitmapInfo
bitmapInfo 按位或添加像素格式(alpha信息)。有alpha選擇kCGImageAlphaPremultipliedFirst(BGRA8888), 無alpha選擇kCGImageAlphaNoneSkipFirst
調用 +[SDImageCoderHelper colorSpaceGetDeviceRGB] 獲取顏色空間,一個單例
調用 CGBitmapContextCreate() 函數創建位圖上下文
調用 SDCGContextTransformFromOrientation() 獲取方向旋轉的 CGAffineTransform,調用 CGContextConcatCTM 關聯到位圖上下文,坐標系轉換
-
??解碼:CGContextDrawImage()??
- 傳入的 bytesPerRow 參數是 0,正常情況下應傳入 width * bytesPerPixel,但是這里傳入 0 系統會幫助計算,而且系統還加了點小優化。
- 可以明顯的看到Xcode檢測到的內存情況和CPU使用情況的變化
獲取位圖:CGBitmapContextCreateImage(),輸入context,輸出解碼后的CGImageRef。
釋放上下文context
-[UIImage initWithCGImage:scale:orientation:],CGImage 轉為 UIImage
UIImage 關聯屬性賦值:decodedImage.sd_isDecoded = YES; decodedImage.sd_imageFormat = image.sd_imageFormat;
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
另外再提一句
函數 SDImageCacheDecodeImageData() 也是普通的正常解碼,與我們前面說的解碼函數 SDImageLoaderDecodeImageData() 邏輯基本一致。
函數實現在 SDImageCacheDefine.m
文件中,此文件僅僅只寫了這一個函數,其它什么都沒有。
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
UIImage *image;
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
if (context) {
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
coderOptions = [mutableCoderOptions copy];
}
if (!decodeFirstFrame) {
Class animatedImageClass = context[SDWebImageContextAnimatedImageClass];
// check whether we should use `SDAnimatedImage`
if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)]) {
image = [[animatedImageClass alloc] initWithData:imageData scale:scale options:coderOptions];
if (image) {
// Preload frames if supported
if (options & SDWebImagePreloadAllFrames && [image respondsToSelector:@selector(preloadAllFrames)]) {
[((id<SDAnimatedImage>)image) preloadAllFrames];
}
} else {
// Check image class matching
if (options & SDWebImageMatchAnimatedImageClass) {
return nil;
}
}
}
}
if (!image) {
image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions];
}
if (image) {
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
// `SDAnimatedImage` do not decode
shouldDecode = NO;
} else if (image.sd_isAnimated) {
// animated image do not decode
shouldDecode = NO;
}
if (shouldDecode) {
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
if (shouldScaleDown) {
image = [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
} else {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
}
}
return image;
}
FAQ
-[SDWebImageDownloaderDecryptor decryptedDataWithData:response:]
內部調用私有block處理imageData,并返回NSData賦值給imageData。
-
Q1:
尋找 decryptor 初始化的位置,以及 block 的實現在哪 -
A1:
沒有實現block的地方,默認情況下 decryptor 是nil,此為開發者來指定的屬性
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
Q2:
SDImageLoader.h 和 SDImageLoader.m 文件的構成 -
A2:
SDImageLoader.h 只有兩個函數的EXPORT聲明,以及一個協議的聲明;SDImageLoader.m 僅僅是兩個函數的實現。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
Q3:
SDImageCacheDefine 文件和 SDImageLoader 文件 -
A3:
解碼代碼基本一致,聲明的協議不同。
解碼:
SDImageLoader 的解碼實現是SDImageLoaderDecodeImageData()
SDImageCacheDefine 的解碼實現是SDImageCacheDecodeImageData()
協議:
SDImageLoader 聲明的協議是@protocol SDImageLoader
;
SDImageCacheDefine 聲明的協議是@protocol SDImageCache
;
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
Q4:
有多少種Coder? -
A4:
Coder大致分兩類:蘋果內建的和SD自己實現的;一共5種;
-
SDImageIOCoder
蘋果內建,支持PNG, JPEG, TIFF解碼,以及 GIF 第一幀的靜態圖。- 使用
UIImage *image = [[UIImage alloc] initWithData:data scale:scale];
進行解碼
- 使用
SDImageIOAnimatedCoder
下面三種的基類SDImageGIFCoder
繼承自SDImageIOAnimatedCoder
SDImageAPNGCoder
繼承自SDImageIOAnimatedCoder
SDImageHEICCoder
繼承自SDImageIOAnimatedCoder
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
Q5:
CGContextSetInterpolationQuality()
是在設置什么? -
A5:
在設置“插值質量”。對圖片的尺寸進行縮放時,由于尺寸不同,所以在生成新圖的過程中像素不可能是一一對應,因此會有插值操作。所謂插值即根據原圖和目標圖大小比例,結合原圖像素信息生成的新的像素的過程。常見的插值算法有線性插值,雙線性插值,立方卷積插值等。
一點點圖片相關小知識
以下內容均來自蘋果官方文檔和官方示例,地址:Quartz 2D Programming Guide.
首先普及一下圖片的關鍵小知識:
顏色空間(ColorSpace, CS)
顏色空間最直白的說就是平時所見到的例如 RGB、CMYK 這些個東西
顏色空間文檔地址
像素格式(Pixel format)
由三個數值組成: Bits per pixel (bpp)、Bits per component (bpc)、Bytes per row
像素格式文檔地址
顏色分量(Color Component);
顏色分量就是顏色空間的各個小組件。比如 RGB 顏色空間,R 是一個顏色分量,同理G和B都是顏色分量。
顏色空間對應的像素格式
每個顏色空間對應的像素格式是規定好的(定死的),即bits per pixel (bpp) 和 bits per component (bpc);下表就是各種顏色空間對應的像素格式:
ColorSpace | Pixel format | bitmap information constant | Availability |
---|---|---|---|
Null | 8 bpp, 8 bpc | kCGImageAlphaOnly | Mac OS X, iOS |
Gray | 8 bpp, 8 bpc | kCGImageAlphaNone | Mac OS X, iOS |
Gray | 8 bpp, 8 bpc | kCGImageAlphaOnly | Mac OS X, iOS |
RGB | 16 bpp, 5 bpc | kCGImageAlphaNoneSkipFirst | Mac OS X, iOS |
RGB | 32 bpp, 8 bpc | kCGImageAlphaNoneSkipFirst | Mac OS X, iOS |
RGB | 32 bpp, 8 bpc | kCGImageAlphaNoneSkipLast | Mac OS X, iOS |
RGB | 32 bpp, 8 bpc | kCGImageAlphaPremultipliedFirst | Mac OS X, iOS |
RGB | 32 bpp, 8 bpc | kCGImageAlphaPremultipliedLast | Mac OS X, iOS |
解釋一下為什么上表中像素格式為啥缺了 Bytes per row 這個指標,是因為 Bytes per row 其實是通過計算確定的,Bytes per row = width * bytes per pixel
。 所以有了 bpp 也就同時有了 Bytes per row
參考解碼大圖的官方示例
解碼大圖的官方示例中定義了許多宏,可以讓我們很快的熟悉圖片的相關知識。哪些是可以定死的,哪些是可以通過計算獲得的。
#define bytesPerMB 1048576.0f
#define bytesPerPixel 4.0f
#define pixelsPerMB ( bytesPerMB / bytesPerPixel ) // 262144 pixels, for 4 bytes per pixel.
#define destTotalPixels kDestImageSizeMB * pixelsPerMB
#define tileTotalPixels kSourceImageTileSizeMB * pixelsPerMB
#define destSeemOverlap 2.0f // the numbers of pixels to overlap the seems where tiles meet.
#define kDestImageSizeMB 60.0f // The resulting image will be (x)MB of uncompressed image data.
#define kSourceImageTileSizeMB 20.0f // The tile size will be (x)MB of uncompressed image data.
解碼的核心函數 CGBitmapContextCreate()
翻譯自 CGBitmapContextCreate() 函數的頭文件注釋,俺自己翻的,水平有限,請見諒
原文:The number of components for each pixel is specified by space
, which may also specify a destination color profile.
譯文:每個像素的顏色分量數量由“space”指定,它也可以指定目標顏色配置文件。
原文:Note that the only legal case when space
can be NULL is when alpha is specified as kCGImageAlphaOnly.
譯文:注意,space為NULL只有一種情況是合法的,就是當alpha被指定為kCGImageAlphaOnly。
原文:The number of bits for each component of a pixel is specified by bitsPerComponent
.
譯文:一個顏色分量的比特數由“bitsPerComponent”指定。
原文:The number of bytes per pixel is equal to (bitsPerComponent * number of components + 7)/8
.
譯文:一像素擁有的字節數 = (bitsPerComponent * number of components + 7)/8
原文:Each row of the bitmap consists of bytesPerRow
bytes, which must be at least width * bytes per pixel
bytes; in addition, bytesPerRow
must be an integer multiple of the number of bytes per pixel.
譯文:位圖每一行由bytesPerRow
字節構成,bytesPerRow
必須至少是width * bytes per pixel
字節。另外,bytesPerRow
必須是整型數字乘以一像素擁有的字節數。
原文:data
, if non-NULL, points to a block of memory at least bytesPerRow * height
bytes.
譯文:data
如果非空,要指向一個至少是bytesPerRow * height
字節的內存塊。
原文:If data
is NULL, the data for context is allocated automatically and freed when the context is deallocated.
譯文:如果data
為空,context的data會被自動分配內存并且在context被銷毀的時候釋放。
原文:bitmapInfo
specifies whether the bitmap should contain an alpha channel and how it's to be generated, along with whether the components are floating-point or integer
譯文:bitmapInfo
指定位圖是否應該包含一個alpha通道,以及如何生成它,以及顏色分量是浮點數還是整數。