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

应该还有很多空间可以优化,比如某些操作对于不带透明度的图片可以直接跳过。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容