SDWebImage探究(二十) —— 圖像的解碼及縮放 (一)

版本記錄

版本號 時(shí)間
V1.0 2018.02.26

前言

我們做APP,文字和圖片是絕對不可缺少的元素,特別是圖片一般存儲在圖床里面皱蹦,一般公司可以委托第三方保存衫冻,NB的公司也可以自己存儲圖片诀紊,ios有很多圖片加載的第三方框架,其中最優(yōu)秀的莫過于SDWebImage隅俘,它幾乎可以滿足你所有的需求邻奠,用了好幾年這個(gè)框架笤喳,今天想總結(jié)一下。感興趣的可以看其他幾篇碌宴。
1. SDWebImage探究(一)
2. SDWebImage探究(二)
3. SDWebImage探究(三)
4. SDWebImage探究(四)
5. SDWebImage探究(五)
6. SDWebImage探究(六) —— 圖片類型判斷深入研究
7. SDWebImage探究(七) —— 深入研究圖片下載流程(一)之有關(guān)option的位移枚舉的說明
8. SDWebImage探究(八) —— 深入研究圖片下載流程(二)之開始下載并返回下載結(jié)果的總的方法
9. SDWebImage探究(九) —— 深入研究圖片下載流程(三)之下載之前的緩存查詢操作
10. SDWebImage探究(十) —— 深入研究圖片下載流程(四)之查詢緩存后的block回調(diào)處理
11. SDWebImage探究(十一) —— 深入研究圖片下載流程(五)之SDWebImageDownloadToken和操作對象的生成和返回
12. SDWebImage探究(十二) —— 深入研究圖片下載流程(六)之下載器到具體下載操作的代理分發(fā)實(shí)現(xiàn)
13. SDWebImage探究(十三) —— 深入研究圖片下載流程(七)之NSURLSession中幾個(gè)代理的基本用法和關(guān)系
14. SDWebImage探究(十四) —— 深入研究圖片下載流程(八)之下載完成代理方法的調(diào)用
15. SDWebImage探究(十五) —— 深入研究圖片下載流程(九)之身份驗(yàn)證質(zhì)詢代理方法調(diào)用
16. SDWebImage探究(十六) —— 深入研究圖片下載流程(十)之緩存相關(guān)代理方法調(diào)用
17. SDWebImage探究(十七) —— 深入研究圖片下載流程(十一)之收到響應(yīng)代理方法調(diào)用
18. SDWebImage探究(十八) —— 深入研究圖片下載流程(十二)之收到圖像數(shù)據(jù)代理方法調(diào)用
19. SDWebImage探究(十九) —— 圖像的解碼 (一)

圖像是否需要解碼的判斷

關(guān)于是否可以解碼的判斷杀狡,上一篇已經(jīng)寫很多了,這里就不多說了贰镣,直接看上一篇 19. SDWebImage探究(十九) —— 圖像的解碼 (一)


圖像是否需要縮放的判斷

下面看一下是否需要縮放的判斷呜象,主要對應(yīng)下面這段代碼。

+ (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
    BOOL shouldScaleDown = YES;
        
    CGImageRef sourceImageRef = image.CGImage;
    CGSize sourceResolution = CGSizeZero;
    sourceResolution.width = CGImageGetWidth(sourceImageRef);
    sourceResolution.height = CGImageGetHeight(sourceImageRef);
    float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
    float imageScale = kDestTotalPixels / sourceTotalPixels;
    if (imageScale < 1) {
        shouldScaleDown = YES;
    } else {
        shouldScaleDown = NO;
    }
    
    return shouldScaleDown;
}

這里實(shí)際涉及到的是像素的計(jì)算碑隆。

float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
float imageScale = kDestTotalPixels / sourceTotalPixels;

原像素的總數(shù)就是sourceTotalPixels恭陡,這里規(guī)定了一個(gè)目標(biāo)像素?cái)?shù)目,計(jì)算方式如下:

static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
static const size_t kBytesPerPixel = 4;

/*
 * Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
 * 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.
 */
定義設(shè)置標(biāo)記“SDWebImageScaleDownLargeImages”時(shí)解碼圖像的最大大猩厦骸(以MB為單位)
iPad1和iPhone 3GS的建議值:60
iPad2和iPhone 4的建議值:120
iPhone 3G和iPod 2及更早版本設(shè)備的建議值:30
static const CGFloat kDestImageSizeMB = 60.0f;

這里可以看到休玩,kDestTotalPixels = 目標(biāo)圖像大小 * 每MB的像素?cái)?shù)目 = 60 * 每MB的字節(jié)數(shù)/每像素的字節(jié)數(shù) = 60 * 1024.0f * 1024.0f / 4,具體多少就不算了劫狠,只要下載下來的圖像的像素?cái)?shù)比這個(gè)要大拴疤,那么就需要縮放kDestTotalPixels / sourceTotalPixels < 1,返回YES独泞;否則返回NO呐矾,就不需要縮放。


圖像的解碼和縮放的實(shí)現(xiàn)

下面看一下圖像的解碼和縮放的實(shí)現(xiàn)阐肤。

+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
    if (![UIImage shouldDecodeImage:image]) {
        return image;
    }
    
    if (![UIImage shouldScaleDownImage:image]) {
        return [UIImage decodedImageWithImage:image];
    }
    
    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);
        float 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.
        float imageScale = kDestTotalPixels / sourceTotalPixels;
        CGSize destResolution = CGSizeZero;
        destResolution.width = (int)(sourceResolution.width*imageScale);
        destResolution.height = (int)(sourceResolution.height*imageScale);
        
        // current color space
        CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
        
        size_t bytesPerRow = kBytesPerPixel * destResolution.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.
        destContext = CGBitmapContextCreate(NULL,
                                            destResolution.width,
                                            destResolution.height,
                                            kBitsPerComponent,
                                            bytesPerRow,
                                            colorspaceRef,
                                            kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        
        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)(kTileTotalPixels / 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;
                    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 imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
        CGImageRelease(destImageRef);
        if (destImage == nil) {
            return image;
        }
        return destImage;
    }
}

下面是代碼中用到的一些常量

/*
 * Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
 * 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
 * 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;

static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;

static const CGFloat kDestSeemOverlap = 2.0f;   // the numbers of pixels to overlap the seems where tiles meet.

這里凫佛,其實(shí)就是做下面幾個(gè)工作:

  • 獲取圖像的縮放比
  • 實(shí)例化位圖上下文
  • 獲取原色塊矩形和目標(biāo)色塊矩形的大小
  • 生成圖像

下面我們就詳細(xì)的看一下解碼和縮放的實(shí)現(xiàn)。

1. 獲取圖像的縮放比

CGImageRef sourceImageRef = image.CGImage;

CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
float 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.
float imageScale = kDestTotalPixels / sourceTotalPixels;

這個(gè)獲取圖像的縮放比孕惜,其實(shí)和是否需要進(jìn)行縮放里面的判斷是類似的愧薛,都是計(jì)算圖像的像素?cái)?shù)目和指定的目標(biāo)像素?cái)?shù)目進(jìn)行比較imageScale = kDestTotalPixels / sourceTotalPixels,這個(gè)是一個(gè)大于0小于1的數(shù)字衫画。

接下里就是獲取目標(biāo)圖像尺寸毫炉,其實(shí)就是原圖像的寬高乘以縮放比。

CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width*imageScale);
destResolution.height = (int)(sourceResolution.height*imageScale);

2. 實(shí)例化位圖上下文

這里實(shí)例化位圖上下文的方式和上一篇中示例的方式是一樣的削罩,就不多說了瞄勾。

(a) 容錯(cuò)處理

首先進(jìn)行了容錯(cuò)處理。

if (destContext == NULL) {
    return image;
}

如果實(shí)例化失敗弥激,那么直接返回傳入的原圖像进陡。

(b) 設(shè)置上下文的插值質(zhì)量

CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);

我們首先看一下這個(gè)函數(shù)

/* Set the interpolation quality of `context' to `quality'. */

CG_EXTERN void CGContextSetInterpolationQuality(CGContextRef cg_nullable c,
    CGInterpolationQuality quality)
    CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

這里插值質(zhì)量的參數(shù)是一個(gè)枚舉,如下所示:

/* Interpolation quality. */

typedef CF_ENUM (int32_t, CGInterpolationQuality) {
  kCGInterpolationDefault = 0,  /* Let the context decide. */
  kCGInterpolationNone = 1,     /* Never interpolate. */
  kCGInterpolationLow = 2,      /* Low quality, fast interpolation. */
  kCGInterpolationMedium = 4,   /* Medium quality, slower than kCGInterpolationLow. */
  kCGInterpolationHigh = 3      /* Highest quality, slower than kCGInterpolationMedium. */
};

CGContextSetInterpolationQuality允許上下文在各個(gè)保真度等級插入像素微服。

3. 獲取原色塊矩形和目標(biāo)色塊矩形的大小

現(xiàn)在定義用于從輸入圖像到輸出圖像的增量色塊的矩形的大小趾疚。 由于iOS從磁盤檢索圖像數(shù)據(jù)的方式,我們使用與源圖像寬度相等的源圖像寬度。 即使當(dāng)前圖形上下文在該范圍內(nèi)被裁剪為子矩形糙麦,iOS也必須從全寬度“范圍”解碼磁盤上的圖像辛孵。 因此,我們通過將我們的圖塊大小修改為輸入圖像的整個(gè)寬度赡磅,充分利用解碼操作產(chǎn)生的所有像素?cái)?shù)據(jù)魄缚。

(a) 計(jì)算原色塊矩形和目標(biāo)色塊矩形大小

主要就是對應(yīng)下邊這段代碼

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)(kTileTotalPixels / 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;

(b) 計(jì)算組成輸出圖像所需要的讀寫操作

// 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;
            dify -= destTile.size.height;
            destTile.origin.y += dify;
        }
        CGContextDrawImage( destContext, destTile, sourceTileImageRef );
        CGImageRelease( sourceTileImageRef );
    }
}

4. 生成圖像

有了前面的計(jì)算狭归,生成圖像就很簡單了匙赞,主要就是對應(yīng)下邊這幾句代碼。

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;
}

這里仍有兩處容錯(cuò)處理虽风,采用的生成方法就是

+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(4_0);

后記

本篇文章主要是對圖像進(jìn)行解碼和縮放咆瘟。首先獲取圖像的縮放比徙硅,實(shí)例化位圖上下文,然后獲取原色塊矩形和目標(biāo)色塊矩形的大小搞疗,最后生成圖像。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末须肆,一起剝皮案震驚了整個(gè)濱河市匿乃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌豌汇,老刑警劉巖幢炸,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拒贱,居然都是意外死亡宛徊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門逻澳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闸天,“玉大人,你說我怎么就攤上這事斜做“” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵瓤逼,是天一觀的道長笼吟。 經(jīng)常有香客問我,道長霸旗,這世上最難降的妖魔是什么贷帮? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮诱告,結(jié)果婚禮上撵枢,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好诲侮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布镀虐。 她就那樣靜靜地躺著,像睡著了一般沟绪。 火紅的嫁衣襯著肌膚如雪刮便。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天绽慈,我揣著相機(jī)與錄音恨旱,去河邊找鬼。 笑死坝疼,一個(gè)胖子當(dāng)著我的面吹牛搜贤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钝凶,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼仪芒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了耕陷?” 一聲冷哼從身側(cè)響起掂名,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哟沫,沒想到半個(gè)月后饺蔑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗜诀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年猾警,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隆敢。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡发皿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筑公,到底是詐尸還是另有隱情雳窟,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布匣屡,位于F島的核電站封救,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捣作。R本人自食惡果不足惜誉结,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望券躁。 院中可真熱鬧惩坑,春花似錦掉盅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蔓钟,卻和暖如春永票,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滥沫。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工侣集, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兰绣。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓世分,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缀辩。 傳聞我的和親對象是個(gè)殘疾皇子臭埋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

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