SDWebImage源碼解析(三)——SDWebImage圖片解碼/壓縮模塊

第三篇的寫在前面

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ò)圖片的流程:

  1. 從網(wǎng)絡(luò)上請求到壓縮過的圖片(JPEG袖牙,PNG...)
  2. 使用這個壓縮過的圖片對UIImage對象進行初始化
  3. 當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中使用以下策略:

  1. 當圖片從網(wǎng)絡(luò)中獲取到的時候就進行解壓縮皇忿。(未來會提到)
  2. 當圖片從磁盤緩存中獲取到的時候立即解壓縮畴蹭。(上面已經(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)方法將源圖像sourceImageRefsourceTile塊內(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; 

然后初始化sourceTiledestTile的大小

        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):

  1. sourceTiley值都以sourceTileHeightMinusOverlap為增量增加(假設(shè)在UIKit的坐標系上就是每次向下移動增量大刑懿帷)
  2. destTiley值會逐漸從大變小。增量為sourceTileHeightMinusOverlap * imageScale暖璧。
  3. 每次循環(huán)中tilesize保持固定(最后一次循環(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)存暴漲。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琅绅,一起剝皮案震驚了整個濱河市扶欣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌千扶,老刑警劉巖料祠,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異澎羞,居然都是意外死亡髓绽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門妆绞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顺呕,“玉大人,你說我怎么就攤上這事括饶≈瓴瑁” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵巷帝,是天一觀的道長忌卤。 經(jīng)常有香客問我扫夜,道長楞泼,這世上最難降的妖魔是什么驰徊? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮堕阔,結(jié)果婚禮上棍厂,老公的妹妹穿的比我還像新娘。我一直安慰自己超陆,他們只是感情好牺弹,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著时呀,像睡著了一般张漂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谨娜,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天航攒,我揣著相機與錄音,去河邊找鬼趴梢。 笑死漠畜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的坞靶。 我是一名探鬼主播憔狞,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彰阴!你這毒婦竟也來了瘾敢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤尿这,失蹤者是張志新(化名)和其女友劉穎廉丽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妻味,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡正压,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了责球。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焦履。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雏逾,靈堂內(nèi)的尸體忽然破棺而出嘉裤,到底是詐尸還是另有隱情,我是刑警寧澤栖博,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布屑宠,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凸丸。R本人自食惡果不足惜最爬,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一帚稠、第九天 我趴在偏房一處隱蔽的房頂上張望余寥。 院中可真熱鬧子姜,春花似錦慌植、人聲如沸驯嘱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脱茉,卻和暖如春剪芥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背琴许。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工粗俱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人虚吟。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓寸认,卻偏偏與公主長得像,于是被迫代替她去往敵國和親串慰。 傳聞我的和親對象是個殘疾皇子偏塞,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 第四篇 前言 首先,我們要弄明白一個問題邦鲫? 為什么要對UIImage進行解碼呢灸叼?難道不能直接使用嗎? 其實不解碼也...
    老馬的春天閱讀 5,386評論 4 30
  • 5.SDWebImageDownloader 下面分析這個類看這個類的結(jié)構(gòu) 這個類的屬性比較多庆捺。 先看這個類的pu...
    充滿活力的早晨閱讀 1,096評論 0 0
  • 我曾經(jīng)無意間讀到過一句話古今,發(fā)人深省,“幸福感是衡量人生的唯一標準滔以,是所有目標的最終目標捉腥。”幸福在我們的生活中如此重...
    作家格格閱讀 188評論 0 1
  • 忘記我們的曾經(jīng) 失去了才知道要珍惜 總要有人來喚醒 一顆顆悲哀冷漠的心 我們還在徘徊不知所措 而你已經(jīng)看見了終點的...
    愛Now閱讀 233評論 0 0
  • 有種說法叫做“混圈”你画,即抵碟,你想要做的事情或者要想成為什么樣的人首先得進入他的那個圈圈。最近坏匪,對這個詞有了深刻的體...
    夢在雨巷閱讀 395評論 0 0