美文网首页
OpenGL之CGBitmapContextCreate

OpenGL之CGBitmapContextCreate

作者: starmier | 来源:发表于2019-06-18 13:43 被阅读0次

    基础知识

    图像可以分为矢量图和位图,我们通常使用的图像为位图格式,这种格式又分为几种颜色模型,如 RGB,CMYK 等,分别适用于不同的场景。

    我们常用的 RGB 则是通过颜色发光原理来设计的。其分为红(R)、绿(G)、蓝(B)三个颜色通道,每个通道的数值表示该通道的明暗程度,根据单位像素所占空间不同,其又可以分为RGB1,RGB2,RGB4,RGB8,RGB16,RGB24,RGB32等多种格式,其中RGB1、RGB2,RGB4,RGB8为调色板类型的RGB格式,即需要通过颜色索引表来描述颜色信息,RGB16为高彩色(Hi Color),RGB24为真彩色(TRUE COLOR),RGB32则带Aphal通道(RGBA)。

    对于 RGB16,其实也是通过调色板实现的,可以分为 RGB444,RGB565,RGB555三种方式,后面的三个数字分别表示三个通道的数据位数,RGB565 表示红(R)、蓝(B)各占5位,绿(G)占三位,即单像素的表示方式为:RRRRRGGGGGGBBBBB,之所以绿色使用6位是因为人眼对绿色的辨识程度比较高。而RGB555表示为 XRRRRRGGGGGBBBBB ,其中 X 表示该位不使用,由于每个颜色使用5位表示,所以每个通道最多包含32种不同亮度值,相关的索引表也就需要把亮度从明到暗均分成32份,可以通过使用屏蔽字和移位操作来得到RGB各分量的值:

    #define RGB555_MASK_RED 0x7C00
    #define RGB555_MASK_GREEN 0x03E0
    #define RGB555_MASK_BLUE 0x001F
    R = (wPixel & RGB555_MASK_RED) >> 10; // 取值范围0-31
    G = (wPixel & RGB555_MASK_GREEN) >> 5; // 取值范围0-31
    B = wPixel & RGB555_MASK_BLUE; // 取值范围0-31
    

    然后通过 RGB 的取值在索引表中得到真实的颜色。

    索引颜色是一种位图图像的编码方法。当真彩色图片转换为索引颜色的图片时,如果原图颜色不在索引颜色中,计算机会从索引颜色中选出一个相近的颜色来模拟该颜色(抖动到相近的颜色),这也是早期浏览器存在 web 安全色的原因。要了解 web 安全色 的历史,可以戳这里

    对于 RGB24 使用24位表示一个像素,RGB分量都用8位表示,取值范围为0-255。RGB32 则多了一个8位的 alpha 通道来表示透明度,可以使用RGBQUAD数据结构来操作一个像素:

    typedef struct tagRGBQUAD {
    BYTE rgbBlue; // 蓝色分量
    BYTE rgbGreen; // 绿色分量
    BYTE rgbRed; // 红色分量
    BYTE rgbReserved; // 保留字节(用作Alpha通道或忽略)
    } RGBQUAD;
    

    显然,对于 RGB24 和 RGB32 ,每个通道都使用8位表示,即每个通道有256中不同的色彩深度。那么,如果每个通道使用16位甚至32位来表示,那就用有更多的颜色深度了。包含 32 位/通道的图像也称作高动态范围(HDR)图像

    CGBitmapContextCreate 用法

    CG_EXTERN CGContextRef __nullable CGBitmapContextCreate(void * __nullable data,
        size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow,
        CGColorSpaceRef __nullable space, uint32_t bitmapInfo)
        CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
    

    创建一个位图 context,位图的像素通过 width、hight 指定。每一个像素的颜色个数(number of components 通道数)通过 space 指定,也可以通过一个颜色描述文件来指定。每像素中每个颜色占用的位空间(bit)通过 bitsPerComponent 参数指定。每个像素的字节数(bytes per pixel)通过公式 (bitsPerComponent * number of components + 7)/8 计算(这个加7应该是为了位对齐)。位图的每一行包含 bytesPerRow 字节,其最少需要 width * bytes per pixel 字节;此外,bytesPerRow 必须是 bytes per pixel 的整数倍。data 如果不为空,其指向一个至少 bytesPerRow * height 字节的内存空间。若 data 为空,context 的 data 会随着该 context 自动创建和销毁。bitmapInfo 指出该位图是否包含 alpha 通道和它是如何产生的(RGB/RGBA/RGBX…),还有每个通道应该用整数标识还是浮点数。

    示例代码:

    CGImageRef imageRef = image.CGImage;
    ...
    
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    // current
    CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
    CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
    
    bool unsupportedColorSpace = (imageColorSpaceModel == 0 || imageColorSpaceModel == -1 || imageColorSpaceModel == kCGColorSpaceModelCMYK || imageColorSpaceModel == kCGColorSpaceModelIndexed);
    if (unsupportedColorSpace)
        colorspaceRef = CGColorSpaceCreateDeviceRGB();
    
    CGContextRef context = CGBitmapContextCreate(NULL, width,
                                                 height,
                                                 CGImageGetBitsPerComponent(imageRef),
                                                 0,
                                                 colorspaceRef,
                                                 kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
    

    代码中 bitsPerComponent 参数是获取的原始图像的 bitsPerComponent 值, bytesPerRow 参数直接赋值 0。

    通过对问题图片测试发现其 bitsPerComponent 为 16,也就是说每个通道是由16位构成的。另外,apple 在 这里 有份文档,里面注明了 OS X 以及 iOS 中位图所支持的像素格式,发现在 iOS 下苹果最高只支持8位的bpc。实际上问题就出在这里了,原图的 bpc 是16位,iOS 并不支持,所以得到的 context 是 nil。

    修改后为

    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;
    
    CGContextRef context = CGBitmapContextCreate(NULL,
     width,
     height,
     bitsPerComponent,
     bytesPerRow,
     colorspaceRef,
     kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast);
    

    直接把 bpp 和 bpc 指定为4和8,既每个像素由四通道构成,每通道8位。然后后面 CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef) 的时候实际上是减少了 image 的每通道位数。同时,由于 bitmapInfo 参数设置为 kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast,意味着新的位图图像是不使用后面8位的 alpha 通道的。

    decodedImageWithImage 方法的优点

    在我们使用 UIImage 的时候,创建的图片通常不会直接加载到内存,而是在渲染的时候再进行解压并加载到内存。这就会导致 UIImage 在渲染的时候效率上不是那么高效。为了提高效率通过 decodedImageWithImage 方法把图片提前解压加载到内存,这样这张新图片就不再需要重复解压了,提高了渲染效率。这是一种空间换时间的做法。

    在UI渲染的时候,实际上是把多个图层按像素叠加计算的过程,需要对每一个像素进行 RGBA 的叠加计算。当某个 layer 的是不透明的,也就是 opaque 为 YES 时,GPU 可以直接忽略掉其下方的图层,这就减少了很多工作量。这也是调用 CGBitmapContextCreate 时 bitmapInfo 参数设置为忽略掉 alpha 通道的原因。

    转自文件:https://honglu.me/2016/09/02/%E4%B8%80%E5%BC%A0%E5%9B%BE%E7%89%87%E5%BC%95%E5%8F%91%E7%9A%84%E6%B7%B1%E6%80%9D/

    相关文章

      网友评论

          本文标题:OpenGL之CGBitmapContextCreate

          本文链接:https://www.haomeiwen.com/subject/elcfqctx.html