第四篇
前言
首先,我們要弄明白一個(gè)問題? 為什么要對(duì)UIImage進(jìn)行解碼呢?難道不能直接使用嗎?
其實(shí)不解碼也是可以使用的,假如說(shuō)我們通過(guò)imageNamed:
來(lái)加載image,系統(tǒng)默認(rèn)會(huì)在主線程立即進(jìn)行圖片的解碼工作。這一過(guò)程就是把image解碼成可供控件直接使用的位圖。
當(dāng)在主線程調(diào)用了大量的imageNamed:
方法后,就會(huì)產(chǎn)生卡頓了。為了解決這個(gè)問題我們有兩種比較簡(jiǎn)單的處理方法:
- 我們不使用
imageNamed:
加載圖片,使用其他的方法,比如imageWithContentsOfFile:
- 我們自己解碼圖片,可以把這個(gè)解碼過(guò)程放到子線程
通過(guò)上邊這兩點(diǎn)小小的建議,我們知道了處理圖片的一些小技巧。我們還需要知道圖片的一些基礎(chǔ)知識(shí)和如何解碼圖片。
圖像存儲(chǔ)
首先圖像的存儲(chǔ)是二維的,所以我們需要考慮如何表示圖像中某個(gè)特定位置的值。然后,我們需要考慮具體的值應(yīng)該如何量化。另外,根據(jù)我們捕捉圖像的途徑,也會(huì)有不同的方式來(lái)編碼圖形數(shù)據(jù)。一般來(lái)說(shuō),最直觀的方式是將其存為位圖數(shù)據(jù),可如果你想處理一組幾何圖形,效率就會(huì)偏低。一個(gè)圓形可以只由三個(gè)值 (兩個(gè)坐標(biāo)值和半徑) 來(lái)表示,使用位圖會(huì)使文件更大,卻只能做粗略的近似。
不同于位圖把值存在陣列中,矢量格式存儲(chǔ)的是繪圖圖像的指令。在處理一些可以被歸納為幾何形狀的簡(jiǎn)單圖像時(shí),這樣做顯然更有效率;但面對(duì)照片數(shù)據(jù)時(shí)矢量?jī)?chǔ)存就會(huì)顯得乏力了。建筑師設(shè)計(jì)房屋更傾向于使用矢量的方式,因?yàn)槭噶扛袷讲⒉粌H僅局限于線條的繪制,也可以用漸變或圖案的填充作為展示,所以利用矢量方式完全可以生成房屋的擬真渲染圖。
用于填充的圖案單元?jiǎng)t更適合被儲(chǔ)存為一個(gè)位圖,在這種情況下,我們可能需要一個(gè)混合格式。一個(gè)非常普遍的混合格式的一個(gè)例子是 PostScript,(或者時(shí)下比較流行的衍生格式,PDF),它基本上是一個(gè)用于繪制圖像的描述語(yǔ)言。上述格式主要針對(duì)印刷業(yè),而 NeXT 和 Adobe 開發(fā)的 Display Postscript 則是進(jìn)行屏幕繪制的指令集。PostScript 能夠排布字母,甚至位圖,這使得它成為了一個(gè)非常靈活的格式。
矢量圖像
矢量格式的一大優(yōu)點(diǎn)是縮放。矢量格式的圖像其實(shí)是一組繪圖指令,這些指令通常是獨(dú)立于尺寸的。如果你想擴(kuò)大一個(gè)圓形,只需在繪制前擴(kuò)大它的半徑就可以了。位圖則沒這么容易。最起碼,如果擴(kuò)大的比例不是二的倍數(shù),就會(huì)涉及到重繪圖像,并且各個(gè)元素都只是簡(jiǎn)單地增加尺寸,成為一個(gè)色塊。由于我們不知道這圖像是一個(gè)圓形,所以無(wú)法確保弧線的準(zhǔn)確描繪,效果看起來(lái)肯定不如按比例繪制的線條那樣好。也因此,在像素密度不同的設(shè)備中,矢量圖像作為圖形資源會(huì)非常有用。位圖的話,同樣的圖標(biāo),在視網(wǎng)膜屏幕之前的 iPhone 上看起來(lái)并沒有問題,在拉伸兩倍后的視網(wǎng)膜屏幕上看起來(lái)就會(huì)發(fā)虛。就好像僅適配了 iPhone 的 App 運(yùn)行在 iPad 的 2x 模式下就不再那么清晰了。
雖然 Xcode 6 已經(jīng)支持了 PDF 格式,但迄今仍不完善,只是在編譯時(shí)將其創(chuàng)建成了位圖圖像。最常見的矢量圖像格式為 SVG,在 iOS 中也有一個(gè)渲染 SVG 文件的庫(kù),SVGKit。
位圖
大部分圖像都是以位圖方式處理的,從這里開始,我們就將重點(diǎn)放在如何處理它們上。第一個(gè)問題,是如何表示兩個(gè)維度。所有的格式都以一系列連續(xù)的行作為單元,而每一行則水平地按順序存儲(chǔ)了每個(gè)像素。大多數(shù)格式會(huì)按照行的順序進(jìn)行存儲(chǔ),但是這并不絕對(duì),比如常見的交叉格式,就不嚴(yán)格按照行順序。其優(yōu)點(diǎn)是當(dāng)圖像被部分加載時(shí),可以更好的顯示預(yù)覽圖像。在互聯(lián)網(wǎng)初期,這是一個(gè)問題,隨著數(shù)據(jù)的傳輸速度提升,現(xiàn)在已經(jīng)不再被當(dāng)做重點(diǎn)。
表示位圖最簡(jiǎn)單的方法是將二進(jìn)制作為每個(gè)像素的值:一個(gè)像素只有開、關(guān)兩種狀態(tài),我們可以在一個(gè)字節(jié)中存儲(chǔ)八個(gè)像素,效率非常高。不過(guò),由于每一位只有最多兩個(gè)值,我們只能儲(chǔ)存兩種顏色??紤]到現(xiàn)實(shí)中的顏色數(shù)以百萬(wàn)計(jì),上述方法聽起來(lái)并不是很有用。不過(guò)有一種情況還是需要用到這樣的方法:遮罩。比如,圖像的遮罩可以被用于透明性,在 iOS 中,遮罩被應(yīng)用在 tab bar 的圖標(biāo)上 (即便實(shí)際圖標(biāo)不是單像素位圖)。
如果要添加更多的顏色,有兩個(gè)基本的選擇:使用一個(gè)查找表,或直接用真實(shí)的顏色值。GIF 圖像有一個(gè)顏色表 (或色彩面板),可以存儲(chǔ)最多 256 種顏色。存儲(chǔ)在位圖中的值是該查詢列表中的索引值,對(duì)應(yīng)著其相應(yīng)的顏色。所以,GIF 文件僅限于 256 色。對(duì)于簡(jiǎn)單的線條圖或純色圖,這是一種不錯(cuò)的解決方法。但對(duì)于照片來(lái)說(shuō),就會(huì)顯示的不夠真實(shí),照片需要更精細(xì)的顏色深度。進(jìn)一步的改進(jìn)是 PNG 文件,這種格式可以使用一個(gè)預(yù)置的色板或者獨(dú)立的通道,它們都支持可變的顏色深度。在一個(gè)通道中,每個(gè)像素的顏色分量 (紅,綠,藍(lán),即 RGB,有時(shí)添加透明度值,即RGBA) 是直接指定的。
GIF 和 PNG 對(duì)于具有大面積相同顏色的圖像是最好的選擇,因?yàn)樗鼈兪褂玫?(主要是基于游程長(zhǎng)度編碼的) 壓縮算法可以減少存儲(chǔ)需求。這種壓縮是無(wú)損的,這意味著圖像質(zhì)量不會(huì)被壓縮過(guò)程影響。
一個(gè)有損壓縮圖像格式的例子是 JPEG。創(chuàng)建 JPEG 圖像時(shí),通常會(huì)指定一個(gè)與圖像質(zhì)量相關(guān)的壓縮比值參數(shù),壓縮程度過(guò)高會(huì)導(dǎo)致圖像質(zhì)量惡化。JPEG 不適用于對(duì)比鮮明的圖像 (如線條圖),其壓縮方式對(duì)類似區(qū)域的圖像質(zhì)量損害會(huì)相對(duì)嚴(yán)重。如果某張截圖中包含了文本,且保存為 JPEG 格式,就可以清楚地看到:生成的圖像中字符周圍會(huì)出現(xiàn)雜散的像素點(diǎn)。在大部分照片中不存在這個(gè)問題,所以照片主要使用 JPEG 格式。
總結(jié):就放大縮小而言,矢量格式 (如 SVG) 是最好的。對(duì)比鮮明且顏色數(shù)量有限的線條圖最適合 GIF 或 PNG (其中 PNG 更為強(qiáng)大),而照片,則應(yīng)該使用 JPEG。當(dāng)然,這些都不是不可逾越的規(guī)則,不過(guò)通常而言,對(duì)一定的圖像質(zhì)量與圖像尺寸而言,遵守規(guī)則會(huì)得到最好的結(jié)果。
做一些好玩的事
連接了上邊的知識(shí)呢,就可以做一些好玩的事了。比如說(shuō)給圖像打馬賽克,合并圖像等等。再次就不介紹怎么實(shí)現(xiàn)了。有興趣的同學(xué)可以自己網(wǎng)上去搜,例子很多。
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image
好了,言歸正傳,讀完上邊的內(nèi)容,我們明白了為什么要解碼圖片,那么這個(gè)方法就是解碼圖片的實(shí)現(xiàn)過(guò)程。這給我們提供了一種思路:我們有時(shí)在優(yōu)化代碼的時(shí)候,可以考慮用這個(gè)方法來(lái)處理圖像數(shù)據(jù)。
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];
@autoreleasepool{
CGImageRef imageRef = image.CGImage;
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
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.
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
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
我們一行一行的看:
static const size_t kBytesPerPixel = 4;
kBytesPerPixel
用來(lái)說(shuō)明每個(gè)像素占用內(nèi)存多少個(gè)字節(jié),在這里是占用4個(gè)字節(jié)。(圖像在iOS設(shè)備上是以像素為單位顯示的)。
static const size_t kBitsPerComponent = 8;
kBitsPerComponent
表示每一個(gè)組件占多少位。這個(gè)不太好理解,我們先舉個(gè)例子,比方說(shuō)RGBA,其中R(紅色)G(綠色)B(藍(lán)色)A(透明度)是4個(gè)組件,每個(gè)像素由這4個(gè)組件組成,那么我們就用8位來(lái)表示著每一個(gè)組件,所以這個(gè)RGBA就是8*4 = 32位。
知道了kBitsPerComponent
和每個(gè)像素有多少組件組成就能計(jì)算kBytesPerPixel
了。計(jì)算公式是:(bitsPerComponent * number of components + 7)/8
.
判斷要不要解碼
if (![UIImage shouldDecodeImage:image]) {
return image;
}
并不是所有的image都要解碼的。我們來(lái)看看shouldDecodeImage:
這個(gè)函數(shù):
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
// do not decode animated images
if (image.images != nil) {
return NO;
}
CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
// do not decode images with alpha
if (anyAlpha) {
return NO;
}
return YES;
}
不適合解碼的條件為:
- image為nil
- animated images 動(dòng)圖不適合
- 帶有透明因素的圖像不適合
獲取核心數(shù)據(jù)
通過(guò)CGImageRef imageRef = image.CGImage
可以拿到和圖像有關(guān)的各種參數(shù)。
- 顏色空間
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
- 寬
size_t width = CGImageGetWidth(imageRef);
- 高
size_t height = CGImageGetHeight(imageRef);
- 計(jì)算出每行的像素?cái)?shù)
size_t bytesPerRow = kBytesPerPixel * width;
創(chuàng)建沒有透明因素的bitmap graphics contexts
// 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.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (context == NULL) {
return image;
}
注意:這里創(chuàng)建的contexts是沒有透明因素的。在UI渲染的時(shí)候,實(shí)際上是把多個(gè)圖層按像素疊加計(jì)算的過(guò)程,需要對(duì)每一個(gè)像素進(jìn)行 RGBA 的疊加計(jì)算。當(dāng)某個(gè) layer 的是不透明的,也就是 opaque 為 YES 時(shí),GPU 可以直接忽略掉其下方的圖層,這就減少了很多工作量。這也是調(diào)用 CGBitmapContextCreate 時(shí) bitmapInfo 參數(shù)設(shè)置為忽略掉 alpha 通道的原因。
繪制圖像
// Draw the image into the context and retrieve the new bitmap image without alpha
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image
/*
* 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.
+ (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;
// 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;
}
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;
}
}
......... 這個(gè)方法也真夠長(zhǎng)的,看了就頭疼啊。不過(guò)我們還是會(huì)一點(diǎn)點(diǎn)分析。我們能夠?qū)W會(huì)如何壓縮一個(gè)圖像。
最大支持壓縮圖像源的大小
static const CGFloat kDestImageSizeMB = 60.0f;
默認(rèn)的單位是MB,這里設(shè)置了60MB。當(dāng)我們要壓縮一張圖像的時(shí)候,首先就是要定義最大支持的源文件的大小,不能沒有任何限制。下邊是SDWebImage
的建議:
/*
* 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 kSourceImageTileSizeMB = 20.0f;
這個(gè)方塊將會(huì)被用來(lái)分割原圖,默認(rèn)設(shè)置為20M。
1M有多少字節(jié)
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
1M有多少像素
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
目標(biāo)總像素
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.
重點(diǎn)來(lái)了,如何把一個(gè)很大的原圖壓縮成指定的大???
原理: 首先定義一個(gè)大小固定的方塊,然后把原圖按照方塊的大小進(jìn)行分割,最后把每個(gè)方塊中的數(shù)據(jù)畫到目標(biāo)畫布上,這樣就能得到目標(biāo)圖像了。接下來(lái)我們做出相信的解釋。
-
檢測(cè)圖像能否解碼
if (![UIImage shouldDecodeImage:image]) { return image; }
-
檢查圖像應(yīng)不應(yīng)該壓縮,原則是:如果圖像大于目標(biāo)尺寸才需要壓縮
if (![UIImage shouldScaleDownImage:image]) { return [UIImage decodedImageWithImage:image]; } + (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ù)信息 sourceImageRef
CGImageRef sourceImageRef = image.CGImage;
-
計(jì)算原圖的像素 sourceResolution
CGSize sourceResolution = CGSizeZero; sourceResolution.width = CGImageGetWidth(sourceImageRef); sourceResolution.height = CGImageGetHeight(sourceImageRef);
-
計(jì)算原圖總像素 sourceTotalPixels
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
-
計(jì)算壓縮比例 imageScale
// 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;
-
計(jì)算目標(biāo)像素 destResolution
CGSize destResolution = CGSizeZero; destResolution.width = (int)(sourceResolution.width*imageScale); destResolution.height = (int)(sourceResolution.height*imageScale);
-
獲取當(dāng)前的顏色空間 colorspaceRef
// current color space CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef]; + (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef { // current CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef)); CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef); BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown || imageColorSpaceModel == kCGColorSpaceModelMonochrome || imageColorSpaceModel == kCGColorSpaceModelCMYK || imageColorSpaceModel == kCGColorSpaceModelIndexed); if (unsupportedColorSpace) { colorspaceRef = CGColorSpaceCreateDeviceRGB(); CFAutorelease(colorspaceRef); } return colorspaceRef; }
-
計(jì)算并創(chuàng)建目標(biāo)圖像的內(nèi)存 destBitmapData
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; }
-
創(chuàng)建目標(biāo)上下文 destContext
// 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; }
-
設(shè)置壓縮質(zhì)量
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
-
計(jì)算第一個(gè)原圖方塊 sourceTile,這個(gè)方塊的寬度同原圖一樣,高度根據(jù)方塊容量計(jì)算
// 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;
-
計(jì)算目標(biāo)圖像方塊 destTile
// 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;
-
計(jì)算原圖像方塊與方塊重疊的像素大小 sourceSeemOverlap
// 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);
-
計(jì)算原圖像需要被分割成多少個(gè)方塊 iterations
// 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++; }
-
根據(jù)重疊像素計(jì)算原圖方塊的大小后,獲取原圖中該方塊內(nèi)的數(shù)據(jù),把該數(shù)據(jù)寫入到相對(duì)應(yīng)的目標(biāo)方塊中
// 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 ); } }
-
返回目標(biāo)圖像
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; }
總結(jié)
好了,這篇文章已經(jīng)很長(zhǎng)了 ,但是令人高興的是,我們學(xué)到了很多關(guān)于圖像的知識(shí)。其中比較重要的是圖片的基礎(chǔ)知識(shí),還有就是把圖片按照方塊進(jìn)行切割的思想了,目前我能想的使用場(chǎng)景就是當(dāng)我們加載一個(gè)比較大的數(shù)據(jù)時(shí),可以把數(shù)據(jù)切成一個(gè)一個(gè)的方塊,然后顯示。
由于個(gè)人知識(shí)有限,如有錯(cuò)誤之處,還望各路大俠給予指出啊
發(fā)現(xiàn)一片文章講解的也很有意思,一張圖片引發(fā)的深思。