前言:
在第一篇文章中我們提到了位圖圖形上下文,今天我們就來(lái)詳細(xì)的介紹下位圖圖形上下文。
......第二篇指南還沒寫完,兩章一起寫的,先發(fā)這章吧......
一、概述
在上一篇中我們已經(jīng)說(shuō)過(guò)通過(guò)UIGraphicsBeginImageContextWithOptions函數(shù)創(chuàng)建位圖圖形上下文的方法,并且蘋果也建議我們使用這種方法來(lái)創(chuàng)建位圖上下文,那為什么我們還要說(shuō)另一種位圖上下文的創(chuàng)建呢?因?yàn)槠洳痪窒拊?code>drawRect里才能調(diào)用,最重要的是位圖圖形上下文對(duì)圖片的處理有著巨大的優(yōu)勢(shì),還有圖像蒙版的運(yùn)用也需要這方面的知識(shí)。
這篇文章我們通過(guò)實(shí)現(xiàn)取色器來(lái)簡(jiǎn)單的了解下位圖圖形上下文。
二、創(chuàng)建位圖圖形上下文
我們通過(guò)CGBitmapContextCreate創(chuàng)建位圖圖形上下文,此方法含有7個(gè)參數(shù),分別如下:
data:創(chuàng)建上下文的大小,此內(nèi)存塊的大小應(yīng)至少為(bytesPerRow*height)個(gè)字節(jié),有趣的是在iOS中我們指定NULL就可以了,系統(tǒng)會(huì)為我們自動(dòng)分配和釋放所需的內(nèi)存。width:指定位圖上下文的寬度(以像素為單位)。height:指定位圖上下文的高度(以像素為單位)。bitsPerComponent:指定內(nèi)存中每個(gè)像素分量使用的位數(shù)。例如,對(duì)于32bpp的像素格式和RGB顏色空間,您可以指定8。bytesPerRow:指定位圖中每行像素使用的內(nèi)存字節(jié)數(shù),有意思的是,當(dāng)我們指定 0 時(shí),系統(tǒng)不僅會(huì)為我們自動(dòng)計(jì)算,而且還會(huì)進(jìn)行 cache line alignment的優(yōu)化(與data形成16字節(jié)對(duì)齊,會(huì)獲得最佳性能)。colorspace:用于位圖上下文的顏色空間。bitmapInfo:位圖布局信息。
前面三個(gè)參數(shù)我們不多說(shuō),后面幾個(gè)參數(shù)有些同學(xué)可能一臉懵逼,下面來(lái)介紹一下這幾個(gè)參數(shù):
ColorSpace:顏色空間
Quartz中的顏色由一組值表示,沒有指示如何解釋顏色信息的顏色空間,這些值是沒有意義的,說(shuō)白了就是顏色空間就是告訴Quartz怎么解析這些值的,比如以下的四種顏色空間都表示藍(lán)色,圖摘自官方文檔:

如果不知道顏色空間,那么我們根本無(wú)法知道這些值所代表的顏色,如果提供錯(cuò)誤的色彩空間,則可能會(huì)出現(xiàn)相當(dāng)大的差異,如下圖所示,雖然BGR和RGB顏色空間中的綠色被解釋為相同,但紅色和藍(lán)色值會(huì)翻轉(zhuǎn)。

在iOS中我們常用的一般有三種:
-
kCGColorSpaceGenericGray:灰度顏色空間,范圍從絕對(duì)黑色(值0.0)到絕對(duì)白色(值1.0)的單個(gè)值。 -
kCGColorSpaceGenericRGB:最常用的RGB顏色空間,一個(gè)三分量顏色空間(紅色,綠色和藍(lán)色),用于模擬在彩色顯示器上組成單個(gè)像素的方式,RGB顏色空間的每個(gè)組件的范圍從0.0(零強(qiáng)度)到1.0(全強(qiáng)度)。 -
kCGColorSpaceGenericCMYK:CMYK顏色空間,這是一個(gè)四色組件顏色空間(青色,品紅色,黃色和黑色),用于模擬在打印過(guò)程中墨水堆積的方式,CMYK顏色空間的每個(gè)組件的范圍從0.0(不吸收顏色)到1.0(完全吸收顏色)。
Pixel Format:像素格式
大家應(yīng)該都知道位圖其實(shí)就是一個(gè)像素?cái)?shù)組(不知道的面壁思過(guò)),像素從左到右從上到下有序排列,那像素又是怎么組成的呢?像素格式就是用來(lái)描述一個(gè)像素的組成,像素格式由以下信息組成:
bitsPerComponent:指定內(nèi)存中每個(gè)像素分量使用的位數(shù),就是像素中每個(gè)單獨(dú)顏色的位數(shù)。- 每像素的位數(shù),此值必須至少為每個(gè)像素分量的位數(shù)乘以每個(gè)像素的像素分量數(shù),通過(guò)顏色空間得到。
bytesPerRow:指定位圖中每行像素使用的內(nèi)存字節(jié)數(shù)。
在OS中位圖上下文支持以下17種像素格式:

我們可以看到iOS支持其中的八種,像素格式的更深含義請(qǐng)參考官方文檔,這里不在贅述。
其中bpp代表每像素的位數(shù),bpc也就是bitsPerComponent。這又引申出另一個(gè)概念,像素分量-就是像素中的每個(gè)單種顏色,像素分量的位數(shù)代表這個(gè)像素能表示多少種顏色,下圖可以很清晰的看出一個(gè)像素的組成:

聯(lián)想到我們通過(guò)
[UIColor colorWithRed:178 / 255.0 green:233 / 255.0 blue:89 / 255.0 alpha:0];這樣的函數(shù)生成顏色對(duì)象時(shí),是不是到這里恍然大悟?因?yàn)镽GB的每個(gè)像素分量都是8位,8位能表示的顏色正好是0~255共256種顏色。所以看到這里是不是對(duì)實(shí)現(xiàn)取色器已經(jīng)有了自己想法呢?我們可以通過(guò)Quartz取到位圖的data,然后取到當(dāng)前所指的像素的data,通過(guò)像素分量和像素分量位數(shù)就能知道當(dāng)前點(diǎn)的色值。
甚至通過(guò)對(duì)像素的控制,我們能針對(duì)性找到特定顏色的像素,從而找出特定顏色在圖片中的位置范圍,通過(guò)指南(一)中的裁剪上下文函數(shù)我們就能實(shí)現(xiàn)簡(jiǎn)單的摳圖功能,amazing!
當(dāng)然實(shí)現(xiàn)這些功能前,我們還得了解最后一個(gè)參數(shù),位圖布局信息。
位圖布局信息:
為了確保Quartz正確解釋每個(gè)像素的位,您還必須指定:
- 位圖是否包含Alpha通道,我們知道Quartz支持RGB,CMYK和灰色空間,同事它也支持alpha(透明度),但alpha信息并不適用于所有位圖圖像格式,當(dāng)它可用時(shí),alpha分量可以位于像素的最高有效位或最低有效位中。
- 對(duì)于具有alpha分量的位圖,顏色分量是否已經(jīng)乘以alpha值,預(yù)乘alpha表示一個(gè)源顏色已經(jīng)乘以了一個(gè)alpha值,通過(guò)消除每個(gè)顏色分量的多余乘法運(yùn)算,預(yù)乘可以加快圖像的渲染速度。
- 樣本的數(shù)據(jù)格式 - 整數(shù)或浮點(diǎn)值。
從上圖我們可以看到位圖布局信息的作用,像素格式用來(lái)描述像素的組成,位圖布局信息則用來(lái)描述像素分量的排列順序。
三、取色器的實(shí)現(xiàn)
了解了這些我們終于可以開始正式的編碼了,我們分兩步來(lái)做
1、把要取色的圖片轉(zhuǎn)換成位圖
- (CGContextRef) createRGBABitmapContext:(CGImageRef) image{
size_t imageWidth = CGImageGetWidth(image);
size_t imageHeight = CGImageGetHeight(image);
//使用設(shè)備顏色空間,和mac OS不同,iOS只能使用設(shè)備相關(guān)顏色空間
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//位圖布局信息
CGImageAlphaInfo bitmapInfo = kCGImageAlphaPremultipliedFirst;
//創(chuàng)建位圖上下文
CGContextRef context = CGBitmapContextCreate(NULL, imageWidth, imageHeight, 8, 0, colorSpace, bitmapInfo);
//繪制bitmap到上下文中
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image);
if (context == NULL){
printf("Context not created!");
}
CGColorSpaceRelease(colorSpace);
return context;
}
2、取出位圖中的像素?cái)?shù)據(jù)
#pragma mark - 獲取觸摸圖片的位置
- (void) touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch * touch = touches.anyObject;
CGPoint currentP = [touch locationInView:self.imageView];
UIColor * color = [[JMColorPicker colorPicker] pickerColorInPoint:currentP fromImage:self.imageView.image size:self.imageView.frame.size];
self.showColorView.backgroundColor = color;
const CGFloat * colorString = CGColorGetComponents(color.CGColor);
self.colorLabel.text = [NSString stringWithFormat:@"R:%.1f\n G:%.1f\n B:%.1f\n", colorString[0] * 255, colorString[1] * 255, colorString[2] * 255];
}
#pragma mark - 從一個(gè)點(diǎn)取顏色
- (UIColor *) pickerColorInPoint:(CGPoint) point fromImage:(UIImage *) image size:(CGSize) imageViewSize{
CGImageRef cgImage = image.CGImage;
if (!self.context) {
self.context = [self createRGBABitmapContext:cgImage];
}
if (self.context == NULL) {
return nil;
}
size_t w = CGImageGetWidth(cgImage);
size_t h = CGImageGetHeight(cgImage);
//傳入imageView的size主要是為了得到當(dāng)前坐標(biāo)在位圖上下文上的坐標(biāo)
CGPoint finalPoint = CGPointMake(point.x / imageViewSize.width * w, point.y / imageViewSize.height * h);
UIColor * color = nil;
unsigned char * data = CGBitmapContextGetData(self.context);
if (data != NULL) {
//我們選用的顏色空間為RGB,像素格式為32bpp,8bpc,別忘了每個(gè)像素占4個(gè)字節(jié),由此可以計(jì)算出當(dāng)前觸摸點(diǎn)在data數(shù)組中的位置
int offset = 4 * ((w * round(finalPoint.y)) + round(finalPoint.x));
int alpha = data[offset];
int red = data[offset + 1];
int green = data[offset + 2];
int blue = data[offset + 3];
NSLog(@"offset: %i colors: RGB A %i %i %i %i", offset, red, green, blue, alpha);
color = [UIColor colorWithRed:(red / 255.0f) green:(green / 255.0f) blue:(blue / 255.0f) alpha:(alpha / 255.0f)];
}
return color;
}

關(guān)于BMP圖像數(shù)據(jù)格式請(qǐng)看這篇文章 BMP圖像數(shù)據(jù)格式詳解(侵刪)
總結(jié):
清明節(jié)之前這篇文章已經(jīng)編輯了一大半,就剩最后的代碼沒上,剛才加完班回來(lái)把代碼給補(bǔ)上了,關(guān)于里面各個(gè)函數(shù)以及位圖的圖像數(shù)據(jù)格式更詳細(xì)的介紹,我會(huì)抽個(gè)時(shí)間補(bǔ)上,代碼我在整理一下也會(huì)放在GitHub上。