第三篇的寫在前面
SDWebImage提供了一個用于圖片解碼的類——SDWebImageDecoder台谍。在上一篇文章中窑睁,也有提及到在diskImageForKey
方法中使用了decoder類的decodedImageWithImage:image
方法對圖片進行解壓縮后返回酪我。本篇文章則重點分析這個模塊的源碼。
圖片解碼功能的實現(xiàn)依賴于Quartz 2D的圖像處理庫钩述,如果對這些功能不熟悉的話可以參考一下Quartz 2D Programming Guide项戴。本文章也會對一些知識點進行簡單的講解。
為何需要對圖片進行解碼
Avoiding Image Decompression Sickness這篇文章中描述了一種情況:
Imagine you have a UIScrollView that displays UIImageViews for the individual pages of a catalog or magazine style app. As soon as even one pixel of the following page comes on screen you instantiate (or reuse) a UIImageView and pop it into the scroll view’s content area. That works quite well in Simulator, but when you test this on the device you find that every time you try to page to the next page, there is a noticeable delay. This delay results from the fact that images need to be decompressed from their file incarnation to be rendered on screen. Unfortunately UIImage does this decompression at the very latest possible moment, i.e. when it is to be displayed.
因此苞冯,可以假設(shè)一種最簡單為一個UIImageView獲取網(wǎng)絡(luò)圖片的流程:
- 從網(wǎng)絡(luò)上請求到壓縮過的圖片(JPEG袖牙,PNG...)
- 使用這個壓縮過的圖片對UIImage對象進行初始化
- 當UIImage要被顯示到UIImageView上面的時候,UIImage上的圖片會被解壓縮舅锄,然后顯示到UIImageView上鞭达。
所以如何將這個解壓縮的過程提前,文章中指出了幾種思路:
Then there’s the question of “How fast can I get these pixels on screen?”. The answer to this is comprised of 3 main time intervals:
- time to alloc/init the UIImage with data on disk
- time to decompress the bits into an uncompressed format
- time to transfer the uncompressed bits to a CGContext, potentially resizing, blending, anti-aliasing it
SDWebImage中使用以下策略:
- 當圖片從網(wǎng)絡(luò)中獲取到的時候就進行解壓縮皇忿。(未來會提到)
- 當圖片從磁盤緩存中獲取到的時候立即解壓縮畴蹭。(上面已經(jīng)提到了)
在這篇文章中總結(jié)了為什么我們需要解碼:
在我們使用 UIImage 的時候,創(chuàng)建的圖片通常不會直接加載到內(nèi)存鳍烁,而是在渲染的時候再進行解壓并加載到內(nèi)存叨襟。這就會導致 UIImage 在渲染的時候效率上不是那么高效。為了提高效率通過
decodedImageWithImage
方法把圖片提前解壓加載到內(nèi)存幔荒,這樣這張新圖片就不再需要重復(fù)解壓了糊闽,提高了渲染效率。這是一種空間換時間的做法爹梁。
接下來通過源碼對這個解碼過程進行分析右犹。
DecodeWithImage 方法
這個方法傳入一副圖片對該圖片進行解碼,解碼結(jié)果是另一幅圖片姚垃。
static const size_t kBytesPerPixel = 4;
static const size_t kBitsPerComponent = 8;
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
if (![UIImage shouldDecodeImage:image]) {
return image;
}
// 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];
//新建自動釋放池念链,將bitmap context和臨時變量都添加到池中在方法末尾自動釋放以防止內(nèi)存警告
@autoreleasepool{
//獲取傳入的UIImage對應(yīng)的CGImageRef(位圖)
CGImageRef imageRef = image.CGImage;
//獲取彩色空間
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
//獲取高和寬
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
//static const size_t kBytesPerPixel = 4
// 每個像素占4個字節(jié)大小 共32位 (RGBA)
size_t bytesPerRow = kBytesPerPixel * width;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
//初始化bitmap graphics context 上下文
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (context == NULL) {
return image;
}
// Draw the image into the context and retrieve the new bitmap image without alpha
//將CGImageRef對象畫到上面生成的上下文中,且將alpha通道移除
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
//使用上下文創(chuàng)建位圖
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
//從位圖創(chuàng)建UIImage對象
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
//釋放CG對象
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
簡單來說积糯,就是把UIImage繪制出來的圖像再保存起來就完成了這個解碼的過程掂墓。如果不考慮性能損耗,我們甚至可以用以下代碼完成這個任務(wù):
- (void)decompressImage:(UIImage *)image
{
UIGraphicsBeginImageContext(CGSizeMake(1, 1));
[image drawAtPoint:CGPointZero];
UIGraphicsEndImageContext();
}
關(guān)于CGImageRef
下面引用蘋果開發(fā)者文檔中的描述:
- Bitmap images and image masks are like any drawing primitive in Quartz. Both images and image masks in Quartz are represented by the CGImageRef data type.A bitmap image (or sampled image) is an array of pixels (or samples).
- Each pixel represents a single point in the image. JPEG, TIFF, and PNG graphics files are examples of bitmap images.
- Each sample in a bitmap contains one or more color components in a specified color space, plus one additional component that specifies the alpha value to indicate transparency. Each component can be from 1 to as many as 32 bits.
CGImageRef就是位圖(bitmap image)在Quartz 框架中的具體數(shù)據(jù)結(jié)構(gòu)看成。位圖(樣本)是像素的矩形陣列(Rectangular Array)君编,每個像素對應(yīng)在特定的彩色空間(color space)中的一個或多個彩色元素。關(guān)于彩色空間川慌,請參考蘋果開發(fā)者文檔中的Color Management Guide吃嘿。一般我們常用有灰度空間(Gray Spaces)和RGB空間偿荷。
關(guān)于Bitmap Graphics Context
Bitmap Graphics Context即位圖上下文。用于接收存儲了位圖數(shù)據(jù)的緩存的指針唠椭,當我們位圖上下文進行繪制時跳纳,緩存會進行更新。
A bitmap graphics context accepts a pointer to a memory buffer that contains storage space for the bitmap. When you paint into the bitmap graphics context, the buffer is updated. After you release the graphics context, you have a fully updated bitmap in the pixel format you specify.
因此贪嫂,當我們需要自己繪制一個bitmap圖片時寺庄,只需要初始化一個位圖上下文,并在上面繪制自己的圖形力崇,最后從上下文中獲取我們想要的bitmap圖形或者數(shù)據(jù)即可斗塘。
上面說了這么多,其實就是為了解釋decodedImageWithImage:image
方法對原始圖片進行了什么樣的操作——將圖片原始圖片繪制到位圖上下文亮靴,然后將位圖上下文保存為新的位圖后返回馍盟。
圖片壓縮
SDWebImageDecoder還提供了另外一個核心功能——圖片壓縮。如果圖片的體積大于特定值茧吊,則decoder會對圖片進行壓縮贞岭,防止內(nèi)存溢出。這部分源碼比較長搓侄,其中的壓縮的算法稍微有些復(fù)雜瞄桨,需要仔細閱讀。下面先給出源碼讶踪,再做具體的說明芯侥。
*
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
該參數(shù)用于設(shè)置內(nèi)存占用的最大字節(jié)數(shù)。默認為60MB乳讥,下面給出了一些舊設(shè)備的參考數(shù)值柱查。如果圖片大小大于該值,則將圖片以該數(shù)值為目標進行壓縮云石。
* Suggested value for iPad1 and iPhone 3GS: 60.
* Suggested value for iPad2 and iPhone 4: 120.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
*/
static const CGFloat kDestImageSizeMB = 60.0f;
/*
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
設(shè)置壓縮時對于源圖像使用到的*塊*的最大字節(jié)數(shù)唉工。
* Suggested value for iPad1 and iPhone 3GS: 20.
* Suggested value for iPad2 and iPhone 4: 40.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
*/
static const CGFloat kSourceImageTileSizeMB = 20.0f;
/**下面做算術(shù)題*/
//1MB中的字節(jié)數(shù)
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
//1MB大小圖像中有多少個像素
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
//壓縮的目標圖像的像素點個數(shù)
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
//源圖像*塊*中有多少個像素
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
//一個常量,具體的語義不必糾結(jié)留晚,用于后面壓縮算法
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
//1. 先對圖片解碼
if (![UIImage shouldDecodeImage:image]) {
return image;
}
//2. 判斷是否需要壓縮(以上面kDestImageSizeMB為標準)
if (![UIImage shouldScaleDownImage:image]) {
return [UIImage decodedImageWithImage:image];
}
//3. 聲明壓縮目標用的上下文
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 {
//4. 獲取源圖像位圖
CGImageRef sourceImageRef = image.CGImage;
//5. 源圖像尺寸酵紫,存儲在CGSize結(jié)構(gòu)體中
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
//6. 計算源圖像總的像素點個數(shù)
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
//7. 獲取原圖像和目標圖像的比例(以像素點個數(shù)為基準)
float imageScale = kDestTotalPixels / sourceTotalPixels;
//8. 使用scale計算目標圖像的寬高
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width*imageScale);
destResolution.height = (int)(sourceResolution.height*imageScale);
//9. 進行圖像繪制前的準備工作
// current color space
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
size_t bytesPerRow = kBytesPerPixel * destResolution.width;
// Allocate enough pixel data to hold the output image.
void* destBitmapData = malloc( bytesPerRow * destResolution.height );
if (destBitmapData == NULL) {
return image;
}
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
destContext = CGBitmapContextCreate(destBitmapData,
destResolution.width,
destResolution.height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (destContext == NULL) {
free(destBitmapData);
return image;
}
//10. 設(shè)置圖像插值的質(zhì)量為高
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
//11. 定義一個稱為*塊*的增量矩形(incremental blits告嘲,即矩形大小在每一次迭代后都不斷增長/減写砦)用于計算從源圖像到目標圖像的輸出。
//*塊*的寬度和圖片的寬度保持一致橄唬,高度動態(tài)變化
CGRect sourceTile = CGRectZero;
sourceTile.size.width = sourceResolution.width;
//11.1 *塊*的計算:根據(jù)寬度計算動態(tài)的高度
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
//11.2 *塊*的起始x值總是為0
sourceTile.origin.x = 0.0f;
// 12. 同樣的方式初始化目標圖像的塊
//寬度 = 目標圖像的寬度
//高度 = 源圖像塊的高度 * 縮放比例
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.
//13. 根據(jù)kDestSeemOverlap計算源塊的SeemOverlap常數(shù)
// 計算公式: sourceSeemOverlap = (int)kDestSeemOverlap / imageScale
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
//14. 聲明源圖像塊的位圖赋焕,在循環(huán)中繪制在destContext中
CGImageRef sourceTileImageRef;
// calculate the number of read/write operations required to assemble the
// output image.
//15. 計算循環(huán)次數(shù)
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
// 如果不能整除,有余數(shù)仰楚,則循環(huán)次數(shù)+1
// 余數(shù)記錄下來
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
iterations++;
}
//16. 將overlap常量累加到塊的高度中隆判,保存源圖像塊的高度到sourceTileHeightMinusOverlap
float sourceTileHeightMinusOverlap = sourceTile.size.height;
sourceTile.size.height += sourceSeemOverlap;
destTile.size.height += kDestSeemOverlap;
//17. 核心部分犬庇,開始循環(huán)做插值
for( int y = 0; y < iterations; ++y ) {
@autoreleasepool {
//1. 每次循環(huán)sourceTile的坐標原點y值 + sourceTileHeightMinusOverlap
//所以sourceTileHeightMinusOverlap在此作為固定增量存在
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
//2. destTile的坐標原點y值 = 目標圖像的高度 - 固定增量
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
//3. 使用sourceTile矩形內(nèi)的源圖像初始化sourceTileImageRef
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
//最后一次循環(huán)
if( y == iterations - 1 && remainder ) {
float dify = destTile.size.height;
destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
dify -= destTile.size.height;
destTile.origin.y += dify;
}
//4. 將sourceTileImageRef繪制到destTile矩形的destConext上下文
// 注意上面我們?yōu)閐estContext設(shè)置了插值質(zhì)量,此時圖像會進行縮放侨嘀,因此會進行插值操作
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
//5. 釋放臨時變量
CGImageRelease( sourceTileImageRef );
}
}
//18. 收尾工作臭挽,繪制圖片,返回UIImage對象
CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
CGContextRelease(destContext);
if (destImageRef == NULL) {
return image;
}
UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
return destImage;
}
}
壓縮算法說明
上述方法內(nèi)部描述了一個比較隱晦的圖像壓縮算法(是的咬腕,沒有用現(xiàn)成的庫調(diào)用欢峰,所以我稱之為“隱晦”,我猜想可能是為了壓縮代碼量)涨共。首先在上面提到了纽帖,位圖其實是由像素組成的矩陣,對于數(shù)字圖像處理有研究的話可以知道我們可以把圖像當做一個矩陣(或多個矩陣的組合)進行處理举反。在SDWebImage的壓縮方法中懊直,使用了一個名為塊(tile/blit)的東西,實際上是就是圖像矩陣的一個子矩陣火鼻。由于種種原因室囊,把塊的寬度固定為原圖像(original image not source image)的寬度。
那么這個塊的目的是什么魁索?
先嘗試閱讀第17步中的第3波俄,4步代碼:
//3. 使用sourceTile矩形內(nèi)的源圖像初始化sourceTileImageRef
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
//4. 將sourceTileImageRef繪制到destTile矩形的destConext上下文
// 注意上面我們?yōu)閐estContext設(shè)置了插值質(zhì)量,此時圖像會進行縮放蛾默,因此會進行插值操作
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
destContext
是最后我們要返回的上下文懦铺,上面繪制有壓縮后的圖像信息。
第三步:使用CGImageRef CGImageCreateWithImageInRect(CGImageRef image, CGRect rect)
方法將源圖像sourceImageRef
中sourceTile
塊內(nèi)的值賦值給sourceTileImageRef
支鸡。因此可以將sourceTile
看做源圖像的一小塊冬念。
第四步:將上面的塊圖像繪制到destContext
上下文的destTile
塊中。需要注意到的是牧挣,由于sourceTile
的大小不等于destTile
的大小急前,因此這里CGContextDrawImage
方法會對圖像使用CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh)
設(shè)置的插值質(zhì)量進行插值處理。對此在NSHipster上有相關(guān)說明:
Next, CGContextSetInterpolationQuality allows for the context to interpolate pixels at various levels of fidelity. In this case, kCGInterpolationHigh is passed for best results. CGContextDrawImage allows for the image to be drawn at a given size and position, allowing for the image to be cropped on a particular edge or to fit a set of image features, such as faces. Finally, CGBitmapContextCreateImage creates a CGImage from the context.
如果對于插值精確度有疑問瀑构,可以參考這個問題裆针。
上面兩部是壓縮過程的主要內(nèi)容。如果理解了這部分對整個算法的理解很重要寺晌。接下來說明在循環(huán)中這個塊(嚴格的說應(yīng)該是兩個塊——sourceTile和destTile)在程序中如何進行操作世吨。
源碼中定義了幾個與tile
有關(guān)的常量在下面會使用到:
static const CGFloat kSourceImageTileSizeMB = 20.0f;
//destSeemOverlap
static const CGFloat kDestSeemOverlap = 2.0f;
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
然后初始化sourceTile
和destTile
的大小
CGRect sourceTile = CGRectZero;
sourceTile.size.width = sourceResolution.width;
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
sourceTile.origin.x = 0.0f;
CGRect destTile;
destTile.size.width = destResolution.width;
destTile.size.height = sourceTile.size.height * imageScale;
destTile.origin.x = 0.0f;
所以tile
的寬度是固定的,無論是source
還是dest
都與其對應(yīng)的原圖片的寬度相等呻征。接著初始化第二個用于計算的overlap
變量耘婚。與上面的destOverlap
一樣,不必在意其語義陆赋。
// 計算公式: sourceSeemOverlap = (int)kDestSeemOverlap / imageScale
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
接著作進入循環(huán)前的準備工作:計算循環(huán)次數(shù)沐祷;使用overlap
更新tile
塊的高度嚷闭。
//15. 計算循環(huán)次數(shù)
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
// 如果不能整除,有余數(shù)赖临,則循環(huán)次數(shù)+1
// 余數(shù)記錄下來
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
iterations++;
}
//16. 將overlap常量累加到塊的高度中胞锰,保存源圖像塊的高度到sourceTileHeightMinusOverlap
float sourceTileHeightMinusOverlap = sourceTile.size.height;
sourceTile.size.height += sourceSeemOverlap;
destTile.size.height += kDestSeemOverlap;
進入循環(huán),每次循環(huán)都更新塊的縱坐標兢榨。更新法則如下:
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
經(jīng)過了上面這么多的鋪墊胜蛉,現(xiàn)在可以看到tile
是以一個什么方式來進行移動。顯而易見色乾,每經(jīng)過一次循環(huán):
-
sourceTile
的y
值都以sourceTileHeightMinusOverlap
為增量增加(假設(shè)在UIKit的坐標系上就是每次向下移動增量大刑懿帷) -
destTile
的y
值會逐漸從大變小。增量為sourceTileHeightMinusOverlap * imageScale
暖璧。 - 每次循環(huán)中
tile
的size
保持固定(最后一次循環(huán)除外)
為了更直觀的理解案怯,將這部分代碼提取了一下作為測試,加入了用于調(diào)試的Log澎办。
輸入為一副3992 * 2442的圖片嘲碱,在運行過程中控制臺輸出如下:
2017-05-24 10:53:16.430 SDWebDecoderTest[1007:65513] 循環(huán)次數(shù):0
2017-05-24 10:53:16.430 SDWebDecoderTest[1007:65513] 在{{0, 6}, {3992, 71}} 內(nèi)繪制sourceTileImageRef
2017-05-24 10:53:16.646 SDWebDecoderTest[1007:65513] 將sourceTileImageRef 繪制到 destTile: {{0, 634.96174430847168}, {1169, 21.038255661725998}}中
2017-05-24 10:53:16.646 SDWebDecoderTest[1007:65513] 循環(huán)次數(shù):1
2017-05-24 10:53:16.647 SDWebDecoderTest[1007:65513] 在{{0, 71}, {3992, 71}} 內(nèi)繪制sourceTileImageRef
2017-05-24 10:53:16.659 SDWebDecoderTest[1007:65513] 將sourceTileImageRef 繪制到 destTile: {{0, 615.92348861694336}, {1169, 21.038255661725998}}中
2017-05-24 10:53:16.659 SDWebDecoderTest[1007:65513] 循環(huán)次數(shù):2
2017-05-24 10:53:16.659 SDWebDecoderTest[1007:65513] 在{{0, 136}, {3992, 71}} 內(nèi)繪制sourceTileImageRef
2017-05-24 10:53:16.671 SDWebDecoderTest[1007:65513] 將sourceTileImageRef 繪制到 destTile: {{0, 596.88523483276367}, {1169, 21.038255661725998}}中
2017-05-24 10:53:16.671 SDWebDecoderTest[1007:65513] 循環(huán)次數(shù):3
2017-05-24 10:53:16.671 SDWebDecoderTest[1007:65513] 在{{0, 201}, {3992, 71}} 內(nèi)繪制sourceTileImageRef
2017-05-24 10:53:16.683 SDWebDecoderTest[1007:65513] 將sourceTileImageRef 繪制到 destTile: {{0, 577.84697723388672}, {1169, 21.038255661725998}}中
·····中間的省略······
2017-05-24 10:53:17.029 SDWebDecoderTest[1007:65513] 循環(huán)次數(shù):31
2017-05-24 10:53:17.029 SDWebDecoderTest[1007:65513] 在{{0, 2021}, {3992, 71}} 內(nèi)繪制sourceTileImageRef
2017-05-24 10:53:17.041 SDWebDecoderTest[1007:65513] 將sourceTileImageRef 繪制到 destTile: {{0, 44.77581787109375}, {1169, 21.038255661725998}}中
2017-05-24 10:53:17.041 SDWebDecoderTest[1007:65513] 循環(huán)次數(shù):32
2017-05-24 10:53:17.041 SDWebDecoderTest[1007:65513] 在{{0, 2086}, {3992, 71}} 內(nèi)繪制sourceTileImageRef
2017-05-24 10:53:17.053 SDWebDecoderTest[1007:65513] 將sourceTileImageRef 繪制到 destTile: {{0, 25.737548828125}, {1169, 21.038255661725998}}中
2017-05-24 10:53:17.053 SDWebDecoderTest[1007:65513] 循環(huán)次數(shù):33
2017-05-24 10:53:17.054 SDWebDecoderTest[1007:65513] 在{{0, 2151}, {3992, 71}} 內(nèi)繪制sourceTileImageRef
2017-05-24 10:53:17.065 SDWebDecoderTest[1007:65513] 將sourceTileImageRef 繪制到 destTile: {{0, 6.69927978515625}, {1169, 21.038255661725998}}中
2017-05-24 10:53:17.066 SDWebDecoderTest[1007:65513] 循環(huán)次數(shù):34
2017-05-24 10:53:17.066 SDWebDecoderTest[1007:65513] 在{{0, 2216}, {3992, 71}} 內(nèi)繪制sourceTileImageRef
2017-05-24 10:53:17.071 SDWebDecoderTest[1007:65513] 將sourceTileImageRef 繪制到 destTile: {{0, 1.0840253829956055}, {1169, 7.6153020858764648}}中
補充一個獲取圖片類型的代碼
在SDWebImage中的NSData+ImageContentType分類中使用以下方法獲取圖片類型:
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
uint8_t c;
//Copies a number of bytes from the start of the receiver's data into a given buffer.
[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:
// R as RIFF for WEBP
if (data.length < 12) {
return SDImageFormatUndefined;
}
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
return SDImageFormatUndefined;
}
圖片數(shù)據(jù)的第一個字節(jié)是固定的,一種類型的圖片第一個字節(jié)就是它的標識。
總結(jié)
SDWebImageDecoder提供了圖片解碼功能局蚀,同時還允許對圖片進行壓縮操作麦锯,防止解壓后內(nèi)存暴漲。