一 图片参数的正确获取
先拿一张图片作为测试使用
图片参数如下:
图片的尺寸为:
-宽1236个像素点
-高748个像素点
-色彩空间为RGB
-描述文件为彩色LCD
-带有Alpha通道
请记住这几个参数,待会儿我们演示如何正确获取。
将这张图片分别放在三个位置
位置1: 2x图位置
位置2: 3x图位置
位置3: bundle中
然后我们通过代码分别获取这三个不同位置的图片,然后输出他的宽高以及CGImageRef对象获取的宽高,代码如下:
输出结果如图所示:
会发现,如果单纯的使用[UIImage imageNamed:@""];方法来获取图片之后,直接输出图片的宽高和图片的原尺寸是不相符的:
如果是2X位置的图片,输出的高度为图片真实高度/2的结果
如果是3X位置的图片,输出的高度为图片真实高度/3的结果
如果是从bundle中直接获取图片,输出的宽高为真实的结果。
原因是因为IOS会根据我们存放图片的位置不同,在加载的时候对比例进行处理,如果只是单纯的展示的话并不会有问题,但是如果是做图片区域裁剪,那么需要注意使用[UIImage imageNamed:@""]获取到的图片对象的size并不是图片的真实尺寸,而是要使用CGImageRef来获取到图像的结构体指针,再去获取到真正的图片尺寸。
官方文档里对[UIImage imageNamed:@""]是这样描述的:
系统会从缓存中寻找这张图片,并对图片进行适当调整以返回适合屏幕显示的图片,所以如果这张图片在项目中会多次用到,那么可以用这个方法来加载图片以提高内存资源利用效率和加载速度,如果只是使用一次的话,更推荐使用imageWithContentsOfFile:方法来加载图片,这样图片不会被加载到缓存中,较少内存压力。
二 使用CGImageRef获得图片的详细参数
CGImageRef中还存储了很多图片的信息,打印一下
可以看到输出的参数有:
CGColorSpace
kCGColorSpaceICCBased : ICCBased颜色空间 ICCBased颜色空间基于 ICC( 国际色彩组织 ) 制定的跨平台颜色配置文件
kCGColorSpaceModelRGB:使用RGB颜色空间
LCD : 使用LCD色域
width = 1236 :图片的宽度为1236个像素点
height = 748 :图片的高度为748个像素点
bpc = 8:每个通道有8bit 也就是支持256个色值
bpp = 32: 每个像素有32位,R通道8位 + G通道8位 + B通道8位 + A通道8位 = 32位
row bytes = 4944:每行占用4944个字节,计算公式为(每个像素32位*每行1236个像素点/8bit = 4944字节
kCGImageAlphaNoneSkipLast : 有alpha通道,且在最末尾,但是不存储alhpa值
0 (default byte order):这个其实存储的是CGBitmapInfo,使用默认的字节序
kCGImagePixelFormatPacked:像素格式信息,根据像素格式压缩
is mask? No:是否为Mask图层,Mask图层是设置其显示部分与不显示部分达到特殊的效果的方法
has masking color? No:是否有mask color,如果设置有的话对应的颜色会变成透明
has soft mask:是否有渐变遮罩
has matte? No:是否有蒙版,mask和matte两个的作用都是控制图像的透明区域的。通常mask都是在图层(节点)上临时绘制的。而matte很多时候都是使用现成的黑白图作为控制透明区域的图来使用。
should interpolate? Yes:是否抗锯齿
使用CGImageRef 我们几乎可以获得我们想要的任何参数。
三 获取CGImageRef属性值的常用方法
先获取到CGImageRef的引用,本章节介绍的方法全部基于该引用演示:
UIImage *img = [UIImage imageNamed:@"8BitImg2x"];
CGImageRef imgRef = [img CGImage];
1.获取图片的宽度
size_t CGImageGetWidth(CGImageRef cg_nullable image)
该方法返回一个sizt_t类型的数值,size_t 的全称是size type,是无符号整型,size_t的真实类型与操作系统有关。
在32位的系统中定义为 typedef unsigned int size_t;
在64位系统中定义为 typedef unsigned long size_t;
使用的时候把它作为无符号整数使用就可以了
CGImageGetWidth方法返回图片的像素宽度,使用及输出示例如下:
size_t imgWidth = CGImageGetWidth(imgRef);
printf("图片的像素宽度为:%zu",imgWidth);
输出结果:图片的像素宽度为:1236
2.获取图片的高度
size_t CGImageGetHeight(CGImageRef cg_nullable image)
跟获取图片宽度的方法类似,该方法返回图片的高度,使用示例及结果输出如下
size_t imgHeight = CGImageGetHeight(imgRef);
printf("图片的像素高度为:%zu",imgHeight);
输出结果:图片的像素高度为:748
3.获取图片每个颜色通道占用的位数(bpp)
CGImageGetBitsPerComponent(CGImageRef cg_nullable image)
使用及输出示例如下:
size_t bitsPerComponent = CGImageGetBitsPerComponent(imgRef);
printf("每个通道占用的位数:%zu",bitsPerComponent);
输出结果:每个通道占用的位数:8
4.获取每个像素点占用的位数
size_t CGImageGetBitsPerPixel(CGImageRef cg_nullable image)
使用及输出示例如下:
size_t bitsPerPixel = CGImageGetBitsPerPixel(imgRef);
printf("每个像素占用的位数:%zu",bitsPerPixel);
输出结果:每个像素占用的位数:32
其实通过CGImageGetBitsPerComponent方法及CGImageGetBitsPerPixel,我们已经可以得知该图片格式为32位,每个通道8位,所以该图片也就是4通道,RGBA格式,带有alpha通道的图片,这在我们需要判断图片是否带有alpha通道的场景下还是很有用的。
针对这个方法,文尾还有更进一步的深究,一定要看。(划重点)
5.获取每行像素占用的字节数
size_t CGImageGetBytesPerRow(CGImageRef cg_nullable image)
使用及输出示例如下:
size_t bitsPerRow = CGImageGetBytesPerRow(imgRef);
printf("每行像素占用的位数:%zu",bitsPerRow);
输出结果:每行像素占用的位数:4944
计算方法为:图片的宽度1236(宽有1236个像素点) * 每个像素占用的位数32 ÷ 每个字节的位数8(1byte = 8bit) = 每行像素占用的字节数4944
6.获取图片的颜色空间
CGColorSpaceRef __nullable CGImageGetColorSpace(CGImageRef cg_nullable image)
使用及输出示例如下:
CGColorSpaceRef colorSpaceRef = CGImageGetColorSpace(imgRef);
NSLog(@"颜色空间为%@",colorSpaceRef);
如果是8位三通道的图片,那么CGImageGetBitsPerPixel方法返回的结果就是24,拿一张不带alpha通道的图片验证一下:
使用及输出示例如下:
输出结果:颜色空间为<CGColorSpace 0x60000151fea0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1)
代表使用的是RGB颜色空间
7.获取图片的alpha信息
使用及输出示例如下:
CGImageAlphaInfo imageAlphaInfo = CGImageGetAlphaInfo(imgRef);
NSLog(@"alpha通道信息为 %u",imageAlphaInfo);
输出结果:alpha通道信息为 5
通过CGImageAlphaInfo的枚举值来查阅
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 */
};
可知该图片的alpha信息为kCGImageAlphaNoneSkipLast,代表该图片有alpha通道且在末尾,但是忽略该值,并不参与图片实际显示的计算。
CGImageAlphaInfo的作用:
(1)位图是否包含alpha通道
(2)alpha位在图像数据中的位置,在bimap首位还是末尾
(3)alpha值是否被预乘,在显示的时候是否需要将每个通道的值乘上alpa值作为最终展示的值
Alpha混合是通过使用线性插值公式将源图像的颜色分量与目标图像的颜色成分相结合来实现的。
对于每个枚举值的说明如下
kCGImageAlphaFirst 阿尔法分量存储在每个像素的最高有效位中。例如,非预乘ARGB。
kCGImageAlphaLast 阿尔法分量被存储在每个像素的最低有效位中。例如,非预乘RGBA。
kCGImageAlphaNone 没有alpha通道。
kCGImageAlphaNoneSkipFirst 没有alpha通道。如果像素的总大小大于颜色空间中颜色分量数量所需的空间,则忽略最高有效位。
kCGImageAlphaOnly 没有颜色数据,只有一个alpha通道。
kCGImageAlphaNoneSkipLast 没有alpha通道。
kCGImageAlphaPremultipliedFirst 阿尔法分量存储在每个像素的最高有效位中,并且颜色分量已经乘以该阿尔法值。例如,预乘ARGB。
kCGImageAlphaPremultipliedLast 阿尔法分量存储在每个像素的最低有效位中,并且颜色分量已经乘以该阿尔法值。例如,预乘RGBA。
8.获取像素点里的字节排序
CGImageByteOrderInfo CGImageGetByteOrderInfo(CGImageRef cg_nullable image)
使用及输出示例如下:
CGImageByteOrderInfo imageByteOrderInfo = CGImageGetByteOrderInfo(imgRef);
NSLog(@"imageByteOrderInfo信息为 %d",imageByteOrderInfo);
输出结果:imageByteOrderInfo信息为 0
CGImageByteOrderInfoCGImageAlphaInfo的枚举值来查阅
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);
可知该图片使用的字节排序位默认排序kCGImageByteOrderDefault
CGImageByteOrderInfo定义了一个bitmap中像素读取的顺序,主要分为大端小端读取模式
大端模式:高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中
小端模式:高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
苹果手机使用的是小端读取模式,如果在做到需要配置像素字节读取顺序这种需求的时候,需要留意一下像素字节的读取顺序,如果读取错误的话会导致颜色和图片的显示错误
常用枚举值的定义如下
kCGImageByteOrder16Little 16位小端读取
kCGImageByteOrder16Big 16位大端读取
kCGImageByteOrder32Little 32位小端读取
kCGImageByteOrder32Big 23位大端读取
需要注意,quartz 2D引擎在iOS端只支持 kCGImageByteOrderDefault,kCGImageByteOrder16Little,kCGImageByteOrder16Big这三种读取模式
8.获取图片的CGBitmapInfo信息
CGBitmapInfo CGImageGetBitmapInfo(CGImageRef cg_nullable image)
使用及输出示例如下:
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imgRef);
NSLog(@"bitmapInfo信息为 %d",bitmapInfo);
输出结果:bitmapInfo信息为 5
枚举值定义如下:
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);
需要注意,CGBitmapInfo 在使用的时候是需要跟适当的常量使用 | 操作符配合链接使用的 ,官方文档解释如下:
所以我们在定义CGBitmapInfo作为参数传值使用的时候,一般会使用CGImageAlphaInfo及CGImageByteOrderInfo搭配使用,来定义图片是否含有alpha通道及读取顺序,举例如下:
CGBitmapInfo alphaInfo = = kCGImageAlphaLast | kCGImageByteOrder16Little; //alpha通道在末尾,且使用16位小端读取的方式来读取
所以使用CGImageGetBitmapInfo获取该图片的信息输出5,应该搭配文初获取的图片信息来看
kCGImageAlphaNoneSkipLast | kCGImageByteOrderDefault = 5
所以当我们直接获取CGBitmapInfo的值的时候,图片输出结果为5
其余的几种方法使用频率较低,可以翻阅api文档查阅
附: CGImageGetBitsPerPixel深究
在实际的项目开发中,使用CGImageGetBitsPerPixel获取的图片位数也并不一定准确
接下来使用五张不同的图片来演示CGImageGetBitsPerPixel获取像素位数的问题:
第一张图片 格式为 8bpc 24bpp 的Png格式的图片,也就是每通道有8位,不带alpha通道,bitmap为24位,但是使用CGImageGetBitsPerPixel输出的结果却是32位,结果错误
第二张图片 格式为 8bpc 24bpp 的Jpg格式的图片,也就是每通道有8位,不带alpha通道,bitmap为24位,但是使用CGImageGetBitsPerPixel输出的结果却是32位,结果错误
第三张图片 格式为 8bpc 24bpp 的Tiff格式的图片,也就是每通道有8位,不带alpha通道,bitmap为24位,但是使用CGImageGetBitsPerPixel输出的结果是24位,结果正确
第四张图片 格式为 16bpc 48bpp 的Png格式的图片,也就是每通道有16位,不带alpha通道,bitmap为48位,但是使用CGImageGetBitsPerPixel输出的结果是48位,结果正确
第五张图片 格式为 16bpc 48bpp 的Tiff格式的图片,也就是每通道有16位,不带alpha通道,bitmap为48位,但是使用CGImageGetBitsPerPixel输出的结果是48位,结果正确
使用五张图片信息如下 【命名规则(以img3CPng8Bit为例) img + 3C(3channel 3通道) + Png(图片格式) + 8Bit(通道位深) = img3CPng8Bit,统一命名规则方便代码演示区分】
读取代码及输出结果如下:
NSString *path8BitPng = [[NSBundle mainBundle] pathForResource:@"img3CPng8Bit" ofType:@".png"];
NSData *bundleImgData8BitPng = [NSData dataWithContentsOfFile:path8BitPng];
UIImage *bundleImage8BitPng = [UIImage imageWithData:bundleImgData8BitPng];
CGImageRef imgRef8BitPng = [bundleImage8BitPng CGImage];
size_t bitsPerComponent8BitPng = CGImageGetBitsPerComponent(imgRef8BitPng );
printf("img8Bit3CPng 每个通道占用的位数:%zu \n",bitsPerComponent8BitPng );
size_t bitsPerPixel8BitPng = CGImageGetBitsPerPixel(imgRef8BitPng);
printf("img8Bit3CPng 每个像素占用的位数:%zu \n\n",bitsPerPixel8BitPng );
NSString *path8BitJpg = [[NSBundle mainBundle] pathForResource:@"img3Cjpg8Bit" ofType:@".jpg"];
NSData *bundleImgData8BitJpg = [NSData dataWithContentsOfFile:path8BitJpg];
UIImage *bundleImage8BitJpg = [UIImage imageWithData:bundleImgData8BitJpg];
CGImageRef imgRef8BitJpg = [bundleImage8BitJpg CGImage];
size_t bitsPerComponent8BitJpg = CGImageGetBitsPerComponent(imgRef8BitJpg );
printf("img8Bit3CJpg 每个通道占用的位数:%zu \n",bitsPerComponent8BitJpg );
size_t bitsPerPixe8BitJpg = CGImageGetBitsPerPixel(imgRef8BitJpg);
printf("img8Bit3CJpg 每个像素占用的位数:%zu \n\n",bitsPerPixe8BitJpg );
NSString *path8Bit3CTiff = [[NSBundle mainBundle] pathForResource:@"img3CTiff8Bit" ofType:@".tif"];
NSData *bundleImgData8Bit3CTiff = [NSData dataWithContentsOfFile:path8Bit3CTiff];
UIImage *bundleImage8Bit3CTiff = [UIImage imageWithData:bundleImgData8Bit3CTiff];
CGImageRef imgRef8Bit3CTiff = [bundleImage8Bit3CTiff CGImage];
size_t bitsPerComponent8Bit3CTiff = CGImageGetBitsPerComponent(imgRef8Bit3CTiff );
printf("img8Bit3CTiff 每个通道占用的位数:%zu \n",bitsPerComponent8Bit3CTiff );
size_t bitsPerPixel8Bit3CTiff = CGImageGetBitsPerPixel(imgRef8Bit3CTiff);
printf("img8Bit3CTiff 每个像素占用的位数:%zu \n\n",bitsPerPixel8Bit3CTiff );
NSString *path16Bit3CPng = [[NSBundle mainBundle] pathForResource:@"img3CPng16Bit" ofType:@".png"];
NSData *bundleImgData16Bit3CPng = [NSData dataWithContentsOfFile:path16Bit3CPng];
UIImage *bundleImage16Bit3CPng = [UIImage imageWithData:bundleImgData16Bit3CPng];
CGImageRef imgRef16Bit3CPng = [bundleImage16Bit3CPng CGImage];
size_t bitsPerComponent16Bit3CPng = CGImageGetBitsPerComponent(imgRef16Bit3CPng );
printf("img16Bit3CPng 每个通道占用的位数:%zu \n",bitsPerComponent16Bit3CPng );
size_t bitsPerPixe16Bit3CPng = CGImageGetBitsPerPixel(imgRef16Bit3CPng);
printf("img16Bit3CPng 每个像素占用的位数:%zu \n\n",bitsPerPixe16Bit3CPng );
NSString *path16Bit3CTiff = [[NSBundle mainBundle] pathForResource:@"img3CTiff16Bit" ofType:@".tif"];
NSData *bundleImgData16Bit3CTiff = [NSData dataWithContentsOfFile:path16Bit3CTiff];
UIImage *bundleImage16Bit3CTiff = [UIImage imageWithData:bundleImgData16Bit3CTiff];
CGImageRef imgRef16Bit3CTiff = [bundleImage16Bit3CTiff CGImage];
size_t bitsPerComponent16Bit3CTiff = CGImageGetBitsPerComponent(imgRef16Bit3CTiff );
printf("img16Bit3CTiff 每个通道占用的位数:%zu \n",bitsPerComponent16Bit3CTiff );
size_t bitsPerPixel16Bit3CTiff = CGImageGetBitsPerPixel(imgRef16Bit3CTiff);
printf("img16Bit3CTiff 每个像素占用的位数:%zu \n\n",bitsPerPixel16Bit3CTiff );
输出结果为:
img8Bit3CPng 每个通道占用的位数:8
img8Bit3CPng 每个像素占用的位数:32
img8Bit3CJpg 每个通道占用的位数:8
img8Bit3CJpg 每个像素占用的位数:32
img8Bit3CTiff 每个通道占用的位数:8
img8Bit3CTiff 每个像素占用的位数:24
img16Bit3CPng 每个通道占用的位数:16
img16Bit3CPng 每个像素占用的位数:48
img16Bit3CTiff 每个通道占用的位数:16
img16Bit3CTiff 每个像素占用的位数:48
可以看到
8位3通道的Png及jpeg格式图片使用CGImageGetBitsPerPixel方法获取到的结果并不是24,而是32;
8位3通道tiff格式、16位3通道png格式、16位3通道tiff格式的图片输出结果都正确
猜测可能是苹果在加载最常用的8bpc格式的png及jpeg图片的时候,如果图片本身没有alpha通道,从本地读取图片数据的过程中,会默认给图片增加一个不透明的值位255的alpha通道,论证该猜想如下:
集成opencv框架,使用该框架创建一个宽度为3000,高度为4000的矩阵,再将该矩阵转化为8bpc 3通道的纯红色的图片,然后使用读取该图片信息,代码及输出如下
cv::Mat originImgMat1 = Mat(3000, 4000, CV_8UC3);
for(int row = 0; row < 3000; row ++){
for(int col = 0; col < 4000; col ++){
originImgMat1.at<Vec3b>(row,col)[0] = 255;
originImgMat1.at<Vec3b>(row,col)[1] = 0;
originImgMat1.at<Vec3b>(row,col)[2] = 0;
}
}
UIImage *image = [CVTools2 UIImageFromCVMat:originImgMat1];
CGImageRef imageRef = [image CGImage];
size_t imgbitsPerComponent = CGImageGetBitsPerComponent(imageRef );
printf("imgbitsPerComponent 每个通道占用的位数:%zu \n",imgbitsPerComponent );
size_t imgBitsPerPixe = CGImageGetBitsPerPixel(imageRef);
printf("imgBitsPerPixe 每个像素占用的位数:%zu \n\n",imgBitsPerPixe );
UIImageFromCVMat 方法:
+(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
CGColorSpaceRef colorSpace;
if (cvMat.elemSize() == 1) {
colorSpace = CGColorSpaceCreateDeviceGray();
} else {
colorSpace = CGColorSpaceCreateDeviceRGB();
}
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
// Creating CGImage from cv::Mat
CGBitmapInfo alphaInfo;
if(cvMat.channels() == 4){
alphaInfo = kCGImageAlphaLast | kCGImageByteOrder16Little;
}else{
alphaInfo = kCGImageAlphaNone | kCGImageByteOrderDefault;
}
CGImageRef imageRef = CGImageCreate(cvMat.cols, //width
cvMat.rows, //height
8, //bits per component
8 * cvMat.elemSize(), //bits per pixel
cvMat.step[0], //bytesPerRow
colorSpace, //colorspace
alphaInfo,// bitmap info
provider, //CGDataProviderRef
NULL, //decode
true, //should interpolate
kCGRenderingIntentDefault //intent
);
// Getting UIImage from CGImage
UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return finalImage;
}
输出结果为:
imgbitsPerComponent 每个通道占用的位数:8
imgBitsPerPixe 每个像素占用的位数:24
可以看到输出的结果是正确的。
将该图片写入沙盒,然后读取出来集成到项目中再次读取。代码及输出如下
NSString *path = [[NSBundle mainBundle] pathForResource:@"red" ofType:@".png"];
NSData *bundleImgData = [NSData dataWithContentsOfFile:path];
UIImage *bundleImage = [UIImage imageWithData:bundleImgData];
CGImageRef imgRef = [bundleImage CGImage];
size_t bitsPerComponent = CGImageGetBitsPerComponent(imgRef );
printf("bitsPerComponent 每个通道占用的位数:%zu \n",bitsPerComponent );
size_t bitsPerPixel = CGImageGetBitsPerPixel(imgRef);
printf("bitsPerPixel 每个像素占用的位数:%zu \n\n",bitsPerPixel );
输出结果:
bitsPerComponent 每个通道占用的位数:8
bitsPerPixel 每个像素占用的位数:32
可见从本地读取图片的时候,又增加上了alpha通道。所以可以论证【可能是苹果在加载最常用的8bpc格式的png及jpeg图片的时候,如果图片本身没有alpha通道,从本地读取图片数据的过程中,会默认给图片增加一个不透明的值位255的alpha通道】这个观点。
有其他想法欢迎留言一起交流学习,后续会增加使用quartz 2D来完成图片裁剪需求的多种实现方式。