1. 程式人生 > 其它 >iOS對圖片進行畫素讀取

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

應該還有很多空間可以優化,比如某些操作對於不帶透明度的圖片可以直接跳過。