SDWebImage源碼閱讀(四)SDWebImageDecoder

  通常咱們都是使用:html

1 + (nullable UIImage *)imageNamed:(NSString *)name;      // load from main bundle

  和:算法

1 + (nullable UIImage *)imageWithContentsOfFile:(NSString *)path;

  兩種方式加載圖片,它們兩個的區別在SDWebImage源碼閱讀前的準備(三)UIImage.h 裏面的 「(六):加載和建立UIImage 的類方法和實例方法:」部分有詳細的介紹。chrome

 

  爲何要對圖片進行解碼?難道不能直接使用上面的兩種加載方式直接進行加載顯示嗎,答案是能夠,並且大概咱們編碼都是使用上面的兩種方式直接在主線程加載圖片而後顯示在UIImageView上,而且並無發現什麼問題。那爲何SDWebImage 還要費勁去進行解碼圖片呢,其實咱們本身不解碼圖片咱們也是能夠直接使用的(實際上是系統爲咱們進行了解碼的操做),通常下載的圖片或者咱們手動拖進主bundle 的圖片都是PNG 或者JPG 其餘格式的圖片,這些圖片都是通過編碼壓縮後的圖片數據,並非控件能夠直接顯示的位圖,若是咱們直接使用 "+ (nullable UIImage *)imageNamed:(NSString *)name" 來加載圖片,系統默認會在主線程當即進行圖片的解碼工做,這個過程就是把圖片數據解碼成可供控件直接顯示的位圖數據,因爲這個解碼操做比較耗時,而且默認是在主線程進行,因此當在主線程調用了大量的 "+ (nullable UIImage *)imageNamed:(NSString *)name" 方法後就會產生卡頓。(同時因爲位圖體積較大,因此在磁盤緩存中不會直接緩存位圖數據,而是編碼壓縮過的PNG 活着JPG 數據)緩存

  

  爲了解決這個問題,咱們有兩種功能比較簡單的處理方法:app

  1.使用 "+ (nullable UIImage *)imageWithContentsOfFile:(NSString *)path" 獲取到圖片框架

  2.本身解碼圖片,而且把解碼過程放在子線程ide

 

  下面首先對圖片的知識作一些拓展:函數

 

參考連接:https://www.objccn.io/issue-21-2/post

http://honglu.me/2016/09/02/一張圖片引起的深思/動畫

 

  下面首先看 SDWebImageDecoder.h 文件:

1 @interface UIImage (ForceDecode)
2 
3 + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image;
4 
5 + (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image;
6 
7 @end

  實際上是一個 UIImage 的 ForceDecode 分類,並定義了兩個類方法,一個是解碼圖像,另外一個是壓縮圖像。

  接下來看 SDWebImageDecoder.m 的文件實現:

  首先是條件編譯:

 1 #if SD_UIKIT || SD_WATCH
 2 
 3     xxx...
 4 
 5 #elif SD_MAC
 6 + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
 7     return image;
 8 }
 9 
10 + (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
11     return image;
12 }
13 #endif

  若是是 MAC 開發平臺,兩個方法都是直接返回 image 參數。

  若是是 iOS/TV/WATCH 開發平臺: + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image 

 1 static const size_t kBytesPerPixel = 4;
 2 static const size_t kBitsPerComponent = 8;
 3 
 4 + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
 5     if (![UIImage shouldDecodeImage:image]) {
 6         return image;
 7     }
 8     
 9     // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
10     // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
11     @autoreleasepool{
12         
13         CGImageRef imageRef = image.CGImage;
14         CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
15         
16         size_t width = CGImageGetWidth(imageRef);
17         size_t height = CGImageGetHeight(imageRef);
18         size_t bytesPerRow = kBytesPerPixel * width;
19 
20         // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
21         // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
22         // to create bitmap graphics contexts without alpha info.
23         CGContextRef context = CGBitmapContextCreate(NULL,
24                                                      width,
25                                                      height,
26                                                      kBitsPerComponent,
27                                                      bytesPerRow,
28                                                      colorspaceRef,
29                                                      kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
30         if (context == NULL) {
31             return image;
32         }
33         
34         // Draw the image into the context and retrieve the new bitmap image without alpha
35         CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
36         CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
37         UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
38                                                          scale:image.scale
39                                                    orientation:image.imageOrientation];
40         
41         CGContextRelease(context);
42         CGImageRelease(imageRefWithoutAlpha);
43         
44         return imageWithoutAlpha;
45     }
46 }

   兩個靜態不可變的類型是 size_t 的變量:

1 static const size_t kBytesPerPixel = 4;

  kBytesPerPixel 按命名英語直譯過來是每一個像素佔內存多少字節(Byte),賦值爲4,表示每一個像素佔4個字節。(圖像在iOS 設備上是以像素爲單位顯示的)

1 static const size_t kBitsPerComponent = 8;

  kBitsPerComponent 按命名英語直譯過來是每一個組件佔多少位(Bit),這個很差理解,舉個例子,好比RGBA,其中 R (紅色)G(綠色)B(藍色)A(透明度)總共4個組件,每一個像素由這4個組件組成,且該變量被賦值爲8,因此一個 RGBA 像素就是8 * 4 = 32 Bits。

  知道了 kBitsPerComponent 和每一個像素有多少組件組成就能計算 kBytesPerPixel 了。計算公式是: (bitsPerComponent * number of components + 7)/ 8。

  [UIImage shouldDecodeImage:image]

  判斷要不要解碼,並非全部的image 都要解碼,看 + (BOOL)shouldDecodeImage:(nullable UIImage *)image 函數實現:

 1 + (BOOL)shouldDecodeImage:(nullable UIImage *)image {
 2     // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
 3     if (image == nil) {
 4         return NO;
 5     }
 6 
 7     // do not decode animated images
 8     if (image.images != nil) {
 9         return NO;
10     }
11     
12     CGImageRef imageRef = image.CGImage;
13     
14     CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
15     BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
16                      alpha == kCGImageAlphaLast ||
17                      alpha == kCGImageAlphaPremultipliedFirst ||
18                      alpha == kCGImageAlphaPremultipliedLast);
19     // do not decode images with alpha
20     if (anyAlpha) {
21         return NO;
22     }
23     
24     return YES;
25 }

  1.若是 image 等於 nil 返回 NO。 // 防止 "CGBitmapContextCreateImage: invalid context 0X0" 的錯誤

  2.若是 image 是動效圖片,即 image.images 不等於 nil,返回 NO。// 不要解碼動畫圖像

  3.經過 CGImageGetAlphaInfo(image.CGImage) 獲取 CGImageAlphaInfo alpha,若是 alpha 等於

1     kCGImageAlphaPremultipliedLast,  /* For example, premultiplied RGBA */
2     kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */
3     kCGImageAlphaLast,               /* For example, non-premultiplied RGBA */
4     kCGImageAlphaFirst,              /* For example, non-premultiplied ARGB */

  任一個枚舉值,即帶有 A(透明度因素),返回 NO。// 不要用alpha 解碼圖像

 

  註釋翻譯:  

  // 當有內存警告的時候,自動釋放 bitmap context 和全部的 vars 釋放系統內存

  // 在iOS 7 ,別忘了調用 [[SDImageCache sharedImageCache] clearMemory];

  CGImageRef imageref

1 CGImageRef imageRef = image.CGImage;

  CGColorSpaceRef colorspaceRef

1 CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];

  經過 imageRef 得到顏色空間(CGColorSpaceRef)colorspaceRef,得到沒有 alpha 通道的colorspaceRef,下面看 + (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef 函數實現:

 1 + (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
 2     // current
 3     CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
 4     CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
 5     
 6     BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
 7                                   imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
 8                                   imageColorSpaceModel == kCGColorSpaceModelCMYK ||
 9                                   imageColorSpaceModel == kCGColorSpaceModelIndexed);
10     if (unsupportedColorSpace) {
11         colorspaceRef = CGColorSpaceCreateDeviceRGB();
12         CFAutorelease(colorspaceRef);
13     }
14     return colorspaceRef;
15 }

  CGImageGetColorSpace(imageRef)

1 /* Return the color space of `image', or NULL if `image' is an image
2    mask. */
3 
4 CG_EXTERN CGColorSpaceRef __nullable CGImageGetColorSpace(CGImageRef cg_nullable image)
5     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

  CGColorSpaceGetModel(CGImageGetColorSpace(imageRef))

1 /* Return the color space model of `space'. */
2 
3 CG_EXTERN CGColorSpaceModel CGColorSpaceGetModel(CGColorSpaceRef cg_nullable space)
4   CG_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

  CGColorSpaceCreateDeviceRGB()

  這裏很重要,判斷若是有 alpha 則建立一個 DeviceRGB 顏色的空間。

1     BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
2                                   imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
3                                   imageColorSpaceModel == kCGColorSpaceModelCMYK ||
4                                   imageColorSpaceModel == kCGColorSpaceModelIndexed);
5     if (unsupportedColorSpace) {
6         colorspaceRef = CGColorSpaceCreateDeviceRGB();
7         CFAutorelease(colorspaceRef);
8     }

  這三個函數都是 CoreGraphics 框架裏面的。

  CGImageGetWidth(imageRef)

1 size_t width = CGImageGetWidth(imageRef);
1 /* Return the width of `image'. */
2 
3 CG_EXTERN size_t CGImageGetWidth(CGImageRef cg_nullable image)
4     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

  CGImageGetHeight(imageRef)

1 size_t height = CGImageGetHeight(imageRef);
1 /* Return the height of `image'. */
2 
3 CG_EXTERN size_t CGImageGetHeight(CGImageRef cg_nullable image)
4     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

  bytesPerRow 

1 size_t bytesPerRow = kBytesPerPixel * width;

  表示每行佔內存多少個字節,計算方法是行寬乘以每一個像素在內存佔幾個字節。

 

  // kCGImageAlphaNone 表示不支持 CGBitmapContextCreate

  // 由於這裏的原始圖像沒有 alpha 信息,使用 kCGImageAlphaNoneSkipLast

  // 建立bitmap graphics contexts 沒有 alpha 信息

1         CGContextRef context = CGBitmapContextCreate(NULL,
2                                                      width,
3                                                      height,
4                                                      kBitsPerComponent,
5                                                      bytesPerRow,
6                                                      colorspaceRef,
7                                                      kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
 1 /* Create a bitmap context. The context draws into a bitmap which is `width'
 2    pixels wide and `height' pixels high. The number of components for each
 3    pixel is specified by `space', which may also specify a destination color
 4    profile. The number of bits for each component of a pixel is specified by
 5    `bitsPerComponent'. The number of bytes per pixel is equal to
 6    `(bitsPerComponent * number of components + 7)/8'. Each row of the bitmap
 7    consists of `bytesPerRow' bytes, which must be at least `width * bytes
 8    per pixel' bytes; in addition, `bytesPerRow' must be an integer multiple
 9    of the number of bytes per pixel. `data', if non-NULL, points to a block
10    of memory at least `bytesPerRow * height' bytes. If `data' is NULL, the
11    data for context is allocated automatically and freed when the context is
12    deallocated. `bitmapInfo' specifies whether the bitmap should contain an
13    alpha channel and how it's to be generated, along with whether the
14    components are floating-point or integer. */
15 
16 CG_EXTERN CGContextRef __nullable CGBitmapContextCreate(void * __nullable data,
17     size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow,
18     CGColorSpaceRef cg_nullable space, uint32_t bitmapInfo)
19     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

  建立沒有透明因素的 bitmap graphics contexts (主要在參數 colorspaceRef,不包含 alpha 通道,和

kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast )

  注意:

  這裏建立的 contexts 是沒有透明因素的。在 UI 渲染的時候,其實是把多個圖層按像素疊加計算的過程,須要對每個像素進行 RGBA 的疊加計算(R、G、B  分別都要乘以 A)。當某個 layer 的是不透明的,也就是 opaque 爲 YES 時,GPU 能夠直接忽略掉其下方的圖層。這也是調用 CGBitmapContextCreate 時 bitmapInfo 參數設置爲忽略掉 alpha 通道的緣由。

 

  // 將圖像繪製到上下文中,並在沒有alpha 信息的狀況下檢索新位圖圖像

  繪製圖像

1         // Draw the image into the context and retrieve the new bitmap image without alpha
2         CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
3         CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
4         UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
5                                                          scale:image.scale
6                                                    orientation:image.imageOrientation];
7         
8         CGContextRelease(context);
9         CGImageRelease(imageRefWithoutAlpha);

   1.CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef)

1 /** Image functions. **/
2 
3 /* Draw `image' in the rectangular area specified by `rect' in the context
4    `c'. The image is scaled, if necessary, to fit into `rect'. */
5 
6 CG_EXTERN void CGContextDrawImage(CGContextRef cg_nullable c, CGRect rect,
7     CGImageRef cg_nullable image)
8     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

  2.CGBitmapContextCreateImage(context)

 1 /* Return an image containing a snapshot of the bitmap context `context'. If
 2    context is not a bitmap context, or if the image cannot be created for
 3    any reason, this function returns NULL. This is a "copy" operation ---
 4    subsequent changes to context will not affect the contents of the
 5    returned image.
 6 
 7    Note that in some cases the copy will actually follow "copy-on-write"
 8    semantics, so that the actual physical copy of the bits will only occur
 9    if the underlying data in the bitmap context is modified. As a
10    consequence, you may wish to use the resulting image and release it
11    before performing more drawing into the bitmap context; in this way, the
12    actual physical copy of the data may be avoided. */
13 
14 CG_EXTERN CGImageRef __nullable CGBitmapContextCreateImage(
15     CGContextRef cg_nullable context)
16     CG_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0);

  3.根據返回的 CGImageRef 初始化一個 UIImage。

  4.CGContextRelease(context)

1 /* Equivalent to `CFRelease(c)'. */
2 
3 CG_EXTERN void CGContextRelease(CGContextRef cg_nullable c)
4     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

  5.CGImageRelease(imageRefWithoutAlpha)

1 /* Equivalent to `CFRelease(image)'. */
2 
3 CG_EXTERN void CGImageRelease(CGImageRef cg_nullable image)
4     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

  6.return imageWithoutAlpha;

 

  下面看壓縮圖像的方法:+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image, 看方法實現以前先看下幾個靜態不可變 CGFloat 變量:

 1 /*
 2  * Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
 3  * Suggested value for iPad1 and iPhone 3GS: 60.
 4  * Suggested value for iPad2 and iPhone 4: 120.
 5  * Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
 6  */
 7 static const CGFloat kDestImageSizeMB = 60.0f;
 8 
 9 /*
10  * Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
11  * Suggested value for iPad1 and iPhone 3GS: 20.
12  * Suggested value for iPad2 and iPhone 4: 40.
13  * Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
14  */
15 static const CGFloat kSourceImageTileSizeMB = 20.0f;
16 
17 static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
18 static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
19 static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
20 static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
21 
22 static const CGFloat kDestSeemOverlap = 2.0f;   // the numbers of pixels to overlap the seems where tiles meet.

   kDestImageSizeMB 表示須要壓縮圖像源的大小界限,而且賦值是60 默認的單位是MB。

  固然要壓縮一張圖像的時候,首先要把圖像源文件的大小和 kDestImageSizeMB 進行比較,若是源文件大小超過了 kDestImageSizeMB,那麼該圖像須要被壓縮。  

  SDWebImage 的建議:

   * 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.

  kSourceImageTileSizeMb 表示用於分割源圖像方塊的大小,這個方塊將會被用來分割原圖,而且賦值爲20 默認單位是MB。

  SDWebImage 的建議:

   * 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.

  kBytesPerMB 表示1 MB 有多少字節,相信對此都特別熟悉(1 *1024 * 1024)。

1 static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;

  kPixelsPerMB 表示1 MB 有多少像素,算法是:1 MB 有多少字節除以 1 個像素佔多少字節(4 字節),即:kBytesPerMB / kBytesPerPixel == (1024 * 1024 /4 = 262144 個像素)。

1 static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;

  kDestTotalPixels 表示圖像須要壓縮時的圖像像素的界限,算法是:最大支持的壓縮圖像源有多少 MB 乘以1 MB 有多少像素。即: kDestImageSizeMB * kPixelsPerMB == (60 * 262144 = 15728640 個像素)。

1 static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;

  kTileTotalPixels 表示用於分割圖像源的方塊的總像素,算法是:原圖方塊有多少 MB 乘以1 MB 有多少像素。即:kSourceImageTileSizeMB * kPixelsPerMB == (20 * 262144 =5242880 個像素)。

1 static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;

  kDestSeemOverlap 表示重疊像素大小,並賦值爲2。

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

  好了,下面開始看 + (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image 函數實現:

  1 + (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
  2     if (![UIImage shouldDecodeImage:image]) {
  3         return image;
  4     }
  5     
  6     if (![UIImage shouldScaleDownImage:image]) {
  7         return [UIImage decodedImageWithImage:image];
  8     }
  9     
 10     CGContextRef destContext;
 11     
 12     // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
 13     // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
 14     @autoreleasepool {
 15         CGImageRef sourceImageRef = image.CGImage;
 16         
 17         CGSize sourceResolution = CGSizeZero;
 18         sourceResolution.width = CGImageGetWidth(sourceImageRef);
 19         sourceResolution.height = CGImageGetHeight(sourceImageRef);
 20         float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
 21         // Determine the scale ratio to apply to the input image
 22         // that results in an output image of the defined size.
 23         // see kDestImageSizeMB, and how it relates to destTotalPixels.
 24         float imageScale = kDestTotalPixels / sourceTotalPixels;
 25         CGSize destResolution = CGSizeZero;
 26         destResolution.width = (int)(sourceResolution.width*imageScale);
 27         destResolution.height = (int)(sourceResolution.height*imageScale);
 28         
 29         // current color space
 30         CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
 31         
 32         size_t bytesPerRow = kBytesPerPixel * destResolution.width;
 33         
 34         // Allocate enough pixel data to hold the output image.
 35         void* destBitmapData = malloc( bytesPerRow * destResolution.height );
 36         if (destBitmapData == NULL) {
 37             return image;
 38         }
 39         
 40         // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
 41         // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
 42         // to create bitmap graphics contexts without alpha info.
 43         destContext = CGBitmapContextCreate(destBitmapData,
 44                                             destResolution.width,
 45                                             destResolution.height,
 46                                             kBitsPerComponent,
 47                                             bytesPerRow,
 48                                             colorspaceRef,
 49                                             kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
 50         
 51         if (destContext == NULL) {
 52             free(destBitmapData);
 53             return image;
 54         }
 55         CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
 56         
 57         // Now define the size of the rectangle to be used for the
 58         // incremental blits from the input image to the output image.
 59         // we use a source tile width equal to the width of the source
 60         // image due to the way that iOS retrieves image data from disk.
 61         // iOS must decode an image from disk in full width 'bands', even
 62         // if current graphics context is clipped to a subrect within that
 63         // band. Therefore we fully utilize all of the pixel data that results
 64         // from a decoding opertion by achnoring our tile size to the full
 65         // width of the input image.
 66         CGRect sourceTile = CGRectZero;
 67         sourceTile.size.width = sourceResolution.width;
 68         // The source tile height is dynamic. Since we specified the size
 69         // of the source tile in MB, see how many rows of pixels high it
 70         // can be given the input image width.
 71         sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
 72         sourceTile.origin.x = 0.0f;
 73         // The output tile is the same proportions as the input tile, but
 74         // scaled to image scale.
 75         CGRect destTile;
 76         destTile.size.width = destResolution.width;
 77         destTile.size.height = sourceTile.size.height * imageScale;
 78         destTile.origin.x = 0.0f;
 79         // The source seem overlap is proportionate to the destination seem overlap.
 80         // this is the amount of pixels to overlap each tile as we assemble the ouput image.
 81         float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
 82         CGImageRef sourceTileImageRef;
 83         // calculate the number of read/write operations required to assemble the
 84         // output image.
 85         int iterations = (int)( sourceResolution.height / sourceTile.size.height );
 86         // If tile height doesn't divide the image height evenly, add another iteration
 87         // to account for the remaining pixels.
 88         int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
 89         if(remainder) {
 90             iterations++;
 91         }
 92         // Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
 93         float sourceTileHeightMinusOverlap = sourceTile.size.height;
 94         sourceTile.size.height += sourceSeemOverlap;
 95         destTile.size.height += kDestSeemOverlap;
 96         for( int y = 0; y < iterations; ++y ) {
 97             @autoreleasepool {
 98                 sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
 99                 destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
100                 sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
101                 if( y == iterations - 1 && remainder ) {
102                     float dify = destTile.size.height;
103                     destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
104                     dify -= destTile.size.height;
105                     destTile.origin.y += dify;
106                 }
107                 CGContextDrawImage( destContext, destTile, sourceTileImageRef );
108                 CGImageRelease( sourceTileImageRef );
109             }
110         }
111         
112         CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
113         CGContextRelease(destContext);
114         if (destImageRef == NULL) {
115             return image;
116         }
117         UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
118         CGImageRelease(destImageRef);
119         if (destImage == nil) {
120             return image;
121         }
122         return destImage;
123     }
124 }

  如何把一個很大的原圖壓縮成指定的大小?

   首先定義了一個靜態不可變的 CGFloat 變量 kSourceImageTileSizeMB 並賦值爲20,表示一個固定大小爲20 MB的寬度和傳進來的 UIImage 圖像的寬度相等的圖像方塊(方塊的高度能夠根據大小爲20 MB 乘以1 MB 多少像素得出總的像素數再除以寬度得出),而後把傳進來的 UIImage 圖像按照方塊的高度進行分割,最後把分割的每一個方塊的數據畫到一個目標畫布上,最終獲得一個目標圖像。

  1.[UIImage shouldDecodeImage:image]

 1     if (![UIImage shouldDecodeImage:image]) {
 2         return image;
 3     }
 4 
 5 
 6 + (BOOL)shouldDecodeImage:(nullable UIImage *)image {
 7     // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
 8     if (image == nil) {
 9         return NO;
10     }
11 
12     // do not decode animated images
13     if (image.images != nil) {
14         return NO;
15     }
16     
17     CGImageRef imageRef = image.CGImage;
18     
19     CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
20     BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
21                      alpha == kCGImageAlphaLast ||
22                      alpha == kCGImageAlphaPremultipliedFirst ||
23                      alpha == kCGImageAlphaPremultipliedLast);
24     // do not decode images with alpha
25     if (anyAlpha) {
26         return NO;
27     }
28     
29     return YES;
30 }

  檢測圖像能不能解碼。

  2.[UIImage shouldScaleDownImage:image]

 1     if (![UIImage shouldScaleDownImage:image]) {
 2         return [UIImage decodedImageWithImage:image];
 3     }
 4 
 5 + (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
 6     BOOL shouldScaleDown = YES;
 7         
 8     CGImageRef sourceImageRef = image.CGImage;
 9     CGSize sourceResolution = CGSizeZero;
10     sourceResolution.width = CGImageGetWidth(sourceImageRef);
11     sourceResolution.height = CGImageGetHeight(sourceImageRef);
12     float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
13     float imageScale = kDestTotalPixels / sourceTotalPixels;
14     if (imageScale < 1) {
15         shouldScaleDown = YES;
16     } else {
17         shouldScaleDown = NO;
18     }
19     
20     return shouldScaleDown;
21 }

  檢測圖像應不該該去壓縮,判斷標準是傳入的UIImage 圖像的像素數是否大於 kDestTotalPixels (圖像須要壓縮時的圖像像素的界限)。

  3.destContext

1 CGContextRef destContext;
2 
3 typedef struct CGContext *CGContextRef;

  4.@autoreleasepool { }

1     // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
2     // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
3 
4     // 當有內存警告的時候,自動釋放 bitmap context 和全部的 vars 釋放系統內存
5     // 在iOS 7 ,別忘了調用 [[SDImageCache sharedImageCache] clearMemory];

  5.sourceImageRef

1 CGImageRef sourceImageRef = image.CGImage;

  6.sourceResolution/sourceTotalPixels/imageScale

1         CGSize sourceResolution = CGSizeZero;
2         sourceResolution.width = CGImageGetWidth(sourceImageRef);
3         sourceResolution.height = CGImageGetHeight(sourceImageRef);
4         float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
5         // Determine the scale ratio to apply to the input image
6         // that results in an output image of the defined size.
7         // see kDestImageSizeMB, and how it relates to destTotalPixels.
8         float imageScale = kDestTotalPixels / sourceTotalPixels;

  根據 sourceImageRef 得到源圖像的寬和高,計算出源圖像的總像素數 sourceTotalPixels,根據 kDestTotalPixels / sourceTotalPixels 計算出源圖像的壓縮比例 imageScale。

  7.destResolution

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

  使用 imageScale 分別乘以 sourceResolution.width  和 sourceResolution.height 得到源圖像壓縮後得到的目標圖像的寬和高。

  8.colorspaceRef

 1         // current color space
 2         CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
 3 
 4 + (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
 5     // current
 6     CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
 7     CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
 8     
 9     BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
10                                   imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
11                                   imageColorSpaceModel == kCGColorSpaceModelCMYK ||
12                                   imageColorSpaceModel == kCGColorSpaceModelIndexed);
13     if (unsupportedColorSpace) {
14         colorspaceRef = CGColorSpaceCreateDeviceRGB();
15         CFAutorelease(colorspaceRef);
16     }
17     return colorspaceRef;
18 }

  得到當前的顏色空間 colorspaceRef。

  9.destBitmapData

1         size_t bytesPerRow = kBytesPerPixel * destResolution.width;
2         
3         // Allocate enough pixel data to hold the output image.
4         void* destBitmapData = malloc( bytesPerRow * destResolution.height );
5         if (destBitmapData == NULL) {
6             return image;
7         }

  先根據壓縮後獲得的目標圖像的寬度乘以每一個像素佔多少內存字節獲得目標圖像的每一行的佔用的內存字節 bytesPerRow,而後建立申請目標圖像的內存空間 destBitmapData(目標圖像的每一行佔的內存大小乘以目標圖像的高度)。

  10.CGBitmapContextCreate

 1         // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
 2         // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
 3         // to create bitmap graphics contexts without alpha info.
 4         destContext = CGBitmapContextCreate(destBitmapData,
 5                                             destResolution.width,
 6                                             destResolution.height,
 7                                             kBitsPerComponent,
 8                                             bytesPerRow,
 9                                             colorspaceRef,
10                                             kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
11         
12         if (destContext == NULL) {
13             free(destBitmapData);
14             return image;
15         }
 1 /* Create a bitmap context. The context draws into a bitmap which is `width'
 2    pixels wide and `height' pixels high. The number of components for each
 3    pixel is specified by `space', which may also specify a destination color
 4    profile. The number of bits for each component of a pixel is specified by
 5    `bitsPerComponent'. The number of bytes per pixel is equal to
 6    `(bitsPerComponent * number of components + 7)/8'. Each row of the bitmap
 7    consists of `bytesPerRow' bytes, which must be at least `width * bytes
 8    per pixel' bytes; in addition, `bytesPerRow' must be an integer multiple
 9    of the number of bytes per pixel. `data', if non-NULL, points to a block
10    of memory at least `bytesPerRow * height' bytes. If `data' is NULL, the
11    data for context is allocated automatically and freed when the context is
12    deallocated. `bitmapInfo' specifies whether the bitmap should contain an
13    alpha channel and how it's to be generated, along with whether the
14    components are floating-point or integer. */
15 
16 CG_EXTERN CGContextRef __nullable CGBitmapContextCreate(void * __nullable data,
17     size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow,
18     CGColorSpaceRef cg_nullable space, uint32_t bitmapInfo)
19     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

  建立目標圖像上下文 destContext,CGBitmapContextCreate(...) 方法傳入的參數都是上面定義的變量。

  11.CGContextSetInterpolationQuality

1 CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
1 /* Set the interpolation quality of `context' to `quality'. */
2 
3 CG_EXTERN void CGContextSetInterpolationQuality(CGContextRef cg_nullable c,
4     CGInterpolationQuality quality)
5     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
1 /* Interpolation quality. */
2 
3 typedef CF_ENUM (int32_t, CGInterpolationQuality) {
4   kCGInterpolationDefault = 0,  /* Let the context decide. */
5   kCGInterpolationNone = 1,     /* Never interpolate. */
6   kCGInterpolationLow = 2,      /* Low quality, fast interpolation. */
7   kCGInterpolationMedium = 4,   /* Medium quality, slower than kCGInterpolationLow. */
8   kCGInterpolationHigh = 3      /* Highest quality, slower than kCGInterpolationMedium. */
9 };

  設置壓縮後的圖像的質量。

  12.sourceTile

 1         // Now define the size of the rectangle to be used for the
 2         // incremental blits from the input image to the output image.
 3         // we use a source tile width equal to the width of the source
 4         // image due to the way that iOS retrieves image data from disk.
 5         // iOS must decode an image from disk in full width 'bands', even
 6         // if current graphics context is clipped to a subrect within that
 7         // band. Therefore we fully utilize all of the pixel data that results
 8         // from a decoding opertion by achnoring our tile size to the full
 9         // width of the input image.
10         CGRect sourceTile = CGRectZero;
11         sourceTile.size.width = sourceResolution.width;
12         // The source tile height is dynamic. Since we specified the size
13         // of the source tile in MB, see how many rows of pixels high it
14         // can be given the input image width.
15         sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
16         sourceTile.origin.x = 0.0f;

  定義一個 CGRect 變量 sourceTile 表示用來分割源圖像的方塊的 rect,寬度和源圖像的寬度同樣,高度根據固定的方塊的佔內存的大小(20 MB)算出方塊的總的像素數再除以寬度得出,x 賦值爲 0。

  13.destTile

1         // The output tile is the same proportions as the input tile, but
2         // scaled to image scale.
3         CGRect destTile;
4         destTile.size.width = destResolution.width;
5         destTile.size.height = sourceTile.size.height * imageScale;
6         destTile.origin.x = 0.0f;

  定義一個 CGRect 變量 destTile 表示壓縮後的分割目標圖像的方塊的 rect,寬度和源圖像同樣,高度是 sourceTile 的高度乘以imageScale (壓縮比例),x 賦值爲 0。

  14.sourceSeemOverlap

1         // The source seem overlap is proportionate to the destination seem overlap.
2         // this is the amount of pixels to overlap each tile as we assemble the ouput image.
3         float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);

  計算源圖像與壓縮後目標圖像重疊的像素大小。

  15.sourceTileImageRef

1 CGImageRef sourceTileImageRef;

  16.iterations/remainder

1         // calculate the number of read/write operations required to assemble the
2         // output image.
3         int iterations = (int)( sourceResolution.height / sourceTile.size.height );
4         // If tile height doesn't divide the image height evenly, add another iteration
5         // to account for the remaining pixels.
6         int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
7         if(remainder) {
8             iterations++;
9         }

  源圖像的高度除以分割源圖像的方塊的高度得出源圖像被分割成多少個方塊並賦值給 iterations,再作取餘運算取得分割的最大的整數。

  17.分割圖片並寫入新圖

 1         // Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
 2         float sourceTileHeightMinusOverlap = sourceTile.size.height;
 3         sourceTile.size.height += sourceSeemOverlap;
 4         destTile.size.height += kDestSeemOverlap;
 5         for( int y = 0; y < iterations; ++y ) {
 6             @autoreleasepool {
 7                 sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
 8                 destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
 9                 sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
10                 if( y == iterations - 1 && remainder ) {
11                     float dify = destTile.size.height;
12                     destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
13                     dify -= destTile.size.height;
14                     destTile.origin.y += dify;
15                 }
16                 CGContextDrawImage( destContext, destTile, sourceTileImageRef );
17                 CGImageRelease( sourceTileImageRef );
18             }
19         }

  17.1 定義一個 float 變量 sourceTitleHeightMinusOverlap 存放那個用來分割源圖像,大小爲 20 MB 的方塊的高度。

  17.2 用於切割源圖像大小爲 20 MB 的方塊的高度加上源圖像與源圖像分割方塊的像素重疊數。

  17.3 目標圖像的分割方塊的高度加上 kDestSeemOverlap(像素重疊數賦值爲 2)。

  17.4 進行for 循環,y 從0開始,到小於源圖像被分割的塊數。

  17.5 sourceTile 和 destTile 都是寬度和高度固定的,x 值爲 0,只有 y  值隨着循環的 y  值在變化,sourceTile 的 y 值在遞增,destTile 的 y 值在遞減。

  17.6 而後循環的從源圖像的 sourceImageRef 根據大小爲 20 MB 的分割塊的不一樣 CGRect 的矩形區域內獲取 sourceTileImageRef。

  17.7 而後把  sourceTileImageRef  寫入和源圖像分割塊相對應的目標圖像的分割塊的矩形區域的 CGContextRef。

  17.8 另外每一次都要 CGImageRelease(sourceTileImageRef)。

  18.destImage 

 1         CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
 2         CGContextRelease(destContext);
 3         if (destImageRef == NULL) {
 4             return image;
 5         }
 6         UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
 7         CGImageRelease(destImageRef);
 8         if (destImage == nil) {
 9             return image;
10         }
11         return destImage;

  CGBitmapContextCreateImage(destContext) 得到目標圖像的 CGImageRef 並作參數建立 destImage,返回destImage。

 

 參考連接:http://www.jianshu.com/p/9322acb7a7b1

END 

相關文章
相關標籤/搜索