基础知识
图像可以分为矢量图和位图,我们通常使用的图像为位图格式,这种格式又分为几种颜色模型,如 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 通道的原因。
网友评论