iOS對圖片進行畫素讀取
圖片解碼
常見的圖片壓縮格式主要是PNG和JPEG,在iOS的程式開發中,一般不需要獲取一張圖片解碼後的資料,但如果需要對畫素進行操作,可能就需要了解怎麼獲取相關的畫素值。
點陣圖影象(bitmap),是由多個畫素點排列組成的。當我們對PNG和JPG進行解碼後,應該獲取到一組畫素點資料,這樣一組資料就組成了解碼後的點陣圖。圖片解碼可以看做一次解壓縮,所以點陣圖佔用的空間會更大,點陣圖所佔的空間很好計算,圖片的面積*每個畫素佔用的位元組。
+ (nullable UIImage *)imageWithContentsOfFile:(NSString *)path;
通常會使用 imageWithContentsOfFile 來載入一張圖片,這樣建立 UIImage 時並不會在這裡進行解碼, 當 UIImage 被繪製時才會解碼。
CGImage
CGImage 的解釋是點陣圖或者圖片蒙版,也就是說可以通過 CGImage 來讀取畫素值。當直接對 CGImage 的畫素讀取時可以使用下面的方式。
NSData *imageData = [NSData dataWithContentsOfFile:imagePath]; CFDataRef dataRef = (__bridge CFDataRef)imageData; CGImageSourceRef source = CGImageSourceCreateWithData(dataRef, nil); CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil); width = (int)CGImageGetWidth(cgImage); height = (int)CGImageGetHeight(cgImage); size_t pixelCount = width * height; CGDataProviderRef provider = CGImageGetDataProvider(cgImage); CFDataRef data = CGDataProviderCopyData(provider); CFRelease(data); CGImageRelease(cgImage); CFRelease(source);
這種讀取畫素的方式無法指定顏色格式,而是讀取圖片原有的顏色空間所組成畫素的組合。需要注意的是,所有創建出來的資料都需要呼叫相應的release來進行釋放。這裡建立 CGImage 的方法也可以對 CGImage 的快取方式進行設定。而且當我們從 CGImage 的 dataProvider 中獲取資料時,目前只能拷貝一份到 CFDataRef 中,而無法直接讀取。
CGBitmapContext
以 RGB 顏色空間為例, 顏色相關的資料可能有以上這麼多,CGImage 有相關的方法可以讀取相應的資料,但有些方法iOS12之後才支援。
typedef CF_ENUM(uint32_t, CGImageAlphaInfo) { kCGImageAlphaNone, /* For example, RGB. */ kCGImageAlphaPremultipliedLast, /* For example, premultiplied RGBA */ kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */ kCGImageAlphaLast, /* For example, non-premultiplied RGBA */ kCGImageAlphaFirst, /* For example, non-premultiplied ARGB */ kCGImageAlphaNoneSkipLast, /* For example, RBGX. */ kCGImageAlphaNoneSkipFirst, /* For example, XRGB. */ kCGImageAlphaOnly /* No color data, alpha data only */ }; typedef CF_ENUM(uint32_t, CGImageByteOrderInfo) { kCGImageByteOrderMask = 0x7000, kCGImageByteOrderDefault = (0 << 12), kCGImageByteOrder16Little = (1 << 12), kCGImageByteOrder32Little = (2 << 12), kCGImageByteOrder16Big = (3 << 12), kCGImageByteOrder32Big = (4 << 12) } CG_AVAILABLE_STARTING(10.0, 2.0); typedef CF_ENUM(uint32_t, CGImagePixelFormatInfo) { kCGImagePixelFormatMask = 0xF0000, kCGImagePixelFormatPacked = (0 << 16), kCGImagePixelFormatRGB555 = (1 << 16), /* Only for RGB 16 bits per pixel */ kCGImagePixelFormatRGB565 = (2 << 16), /* Only for RGB 16 bits per pixel */ kCGImagePixelFormatRGB101010 = (3 << 16), /* Only for RGB 32 bits per pixel */ kCGImagePixelFormatRGBCIF10 = (4 << 16), /* Only for RGB 32 bits per pixel */ } CG_AVAILABLE_STARTING(10.14, 12.0); typedef CF_OPTIONS(uint32_t, CGBitmapInfo) { kCGBitmapAlphaInfoMask = 0x1F, kCGBitmapFloatInfoMask = 0xF00, kCGBitmapFloatComponents = (1 << 8), kCGBitmapByteOrderMask = kCGImageByteOrderMask, kCGBitmapByteOrderDefault = kCGImageByteOrderDefault, kCGBitmapByteOrder16Little = kCGImageByteOrder16Little, kCGBitmapByteOrder32Little = kCGImageByteOrder32Little, kCGBitmapByteOrder16Big = kCGImageByteOrder16Big, kCGBitmapByteOrder32Big = kCGImageByteOrder32Big } CG_AVAILABLE_STARTING(10.0, 2.0); #ifdef __BIG_ENDIAN__ # define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big # define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big #else /* Little endian. */ # define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little # define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little #endif
圖片中單個畫素的組成根據顏色空間,排列,以及佔位有很多排列組合的方式,所以為了避免處理過多的情況,可能使用 CGBitmapContext 是一種對使用者來說比較友好的方式。
CGBitmapContext 可以用來把點陣圖按位畫進記憶體的畫布。畫布上每個畫素點都是按照 CGBitmapContext 中指定的顏色空間來排布的。
在 CGBitmapContext 建立時,需要傳入一些引數來指定我們需要的畫布是什麼樣的。
CGContextRef CGBitmapContextCreate(
//畫布中渲染的記憶體地址,這個尺寸至少需要bytesPerRow * height,
//如果傳入NULL,這個方法會為點陣圖開闢一段空間。
void *data,
//點陣圖的寬度
size_t width,
//點陣圖的高度
size_t height,
//畫素中每個元素所佔用的位數,如一個32位的RGB格式,一個元素所佔的位數為8
size_t bitsPerComponent,
//點陣圖每一行所佔的位元組數,如果data傳了NULL,這裡傳入0自動計算
size_t bytesPerRow,
//顏色空間
CGColorSpaceRef space,
//一些其他相關資訊,比如是否包含alpha通道,alpha通道在畫素中的位置,畫素中元素是整形還是浮點型等。
uint32_t bitmapInfo);
在建立 CGBitmapContext 時,有些配置是不支援的,相應支援的格式可以檢視文件 Graphics Contexts 。這裡舉個例子,對於沒有alpha通道的圖片,想直接用 kCGImageAlphaNone
來生成不含alpha通道的畫布,是不允許的。但使用 kCGImageAlphaNoneSkipLast
來忽略alpha通道是可以的。當想直接獲取非預乘的畫素資料如 kCGImageAlphaLast
也是不允許的,只能使用預乘後的。
這裡我們將顏色空間控制成 BGRA,使用瞭如下的引數。
uint8_t* bitmapData = (uint8_t *)calloc(pixelCount * 4, sizeof(uint8_t));
int bytesPerRow = 4 * width;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(bitmapData, width, height, 8, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
if (!context) {
return nullptr;
}
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
之後對RGB元素進行操作獲取非預乘的畫素資料。
auto iter = bitmapData;
int temp;
for (int i = 0; i < pixelCount; i++) {
uint8_t alpha = *(iter + 3);
if (alpha != 0) {
for (int j = 0; j < 3; j++) {
temp = *iter * 255;
*iter++ = temp /alpha;
}
iter++;
}else {
iter += 4;
}
}
應該還有很多空間可以優化,比如某些操作對於不帶透明度的圖片可以直接跳過。