前幾天有這樣一個需求,在手機上把公章給摳出來,做成PNG可以貼在其他圖片上面,于是就有了今天的主題.
先放完成后的效果圖
Demo中的圖片分辨率為440*387,處理只需要一毫秒~
可以看出來,沒有一點卡頓的感覺,,所以效率上還是很不錯噠~
如果你很心急:<a href="#core">核心代碼</a>
理論基礎
了解一些基本概念還是很有必要滴
-
RGBA
R:紅色
G:綠色
B:藍色
A:透明度
紅綠藍為三原色,可以說我們看到的任何顏色都是由這三個顏色構成的.所以是圖像組成必不可少的一部分
而加入了A則多了一個透明度的描述,常見于PNG格式的圖片.
例如微信的表情包,除了主要輪廓外,其他的色域都是采用的都是當前聊天會話的背景色,這就是利用了Alpha來操作出的效果
-
像素
一條線可以看成是被無數(shù)個點構成的.同理,我們可以認為一張圖片也是由一定數(shù)量的點構成的.
以Demo中圖片為例子,分辨率440*387的圖片,一行440的像素點,有387行,那么他就包含了有約17萬個像素點
,對這些像素點的操作,將直接影響到圖片的顯示
-
灰色
在RGB的表現(xiàn)中,如何呈現(xiàn)出灰色呢?
說來慚愧,我一開始一度以為所謂的灰就是黑色的透明度不一樣,但事實當然不是這樣啦!
可以參考下表
R | G | B | 顏色 |
---|---|---|---|
0 | 0 | 0 | 黑色 |
50 | 50 | 50 | 深灰色 |
178 | 178 | 178 | 淺灰色 |
255 | 255 | 255 | 白色 |
可以看出來,灰色其實是RGB三個值相等,并且隨著數(shù)值的增大,顏色逐漸變淺,和透明度是沒有任何關系的
-
二值化
所謂的二值化,其實是將圖片的色域空間變?yōu)榛疑?在CG框架中,可以直接使用CGColorSpaceCreateDeviceGray
來進行操作,不過因為我們除了讓他變灰之外,還需要對透明度做操作,所以這里自己使用算法來進行計算.
RGB轉(zhuǎn)灰色的計算公式有很多種,我們這里使用一種較為經(jīng)典的算法
double Gray = R*0.3+G*0.59+B*0.11;
其中RGB都是以0~255取值,得到的結(jié)果即灰色的RGB色值
處理方法
因為我們想要得到主要的輪廓,所以只需要對像素進行操作即可,那么就很簡單啦,直接上代碼
UIImage *image = [UIImage imageNamed:@"1.png"];
// 分配內(nèi)存
const int imageWidth = image.size.width;
const int imageHeight = image.size.height;
size_t bytesPerRow = imageWidth * 4;
uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);
// 創(chuàng)建context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage);
// 遍歷像素
int pixelNum = imageWidth * imageHeight;
uint32_t* pCurPtr = rgbImageBuf;
for (int i = 0; i < pixelNum; i++, pCurPtr++)
{
// ABGR
uint8_t* ptr = (uint8_t*)pCurPtr;
int B = ptr[1];
int G = ptr[2];
int R = ptr[3];
double Gray = R*0.3+G*0.59+B*0.11;
if (Gray > filterValue || (Gray == filterValue && filterValue == 0)) {
ptr[0] = 0;
}else{
// ptr[3] = 0xff;
}
}
核心代碼也就是for循環(huán)那一段
因為每個像素都包含了RGBA的信息,而255在十六進制中以0xFF表示
所以假設顏色為白色不透明的情況下,RGBA的表現(xiàn)方式應該為0xFF FF FF FF,所以使用uint8_t來接收,
但是尷尬的是他的排列方式并不是RGBA,而是ABGR ??,一度讓我以為代碼寫錯了.
代碼中的Gray
就是轉(zhuǎn)換為灰度圖顯示的顏色,而filterValue
則是過濾系數(shù),取值范圍在0~255;值越大,顯示的圖像也就越多,Demo中使用UISlider來控制.
這樣色彩變化與過濾都放在了一起,減少了頻繁操作像素信息.
因為章是紅色的,所以我當時將需要顯示的像素點變?yōu)榱思t色,而被過濾掉的像素點,則直接設置為了透明.大家可根據(jù)需求自行設置
<a id="core" > </a>
完整代碼
- (void)drawImage:(double)filterValue
{
UIImage *image = [UIImage imageNamed:@"1.png"];
// 分配內(nèi)存
const int imageWidth = image.size.width;
const int imageHeight = image.size.height;
size_t bytesPerRow = imageWidth * 4;
uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);
// 創(chuàng)建context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage);
// 遍歷像素
int pixelNum = imageWidth * imageHeight;
uint32_t* pCurPtr = rgbImageBuf;
for (int i = 0; i < pixelNum; i++, pCurPtr++)
{
// ABGR
uint8_t* ptr = (uint8_t*)pCurPtr;
int B = ptr[1];
int G = ptr[2];
int R = ptr[3];
double Gray = R*0.3+G*0.59+B*0.11;
if (Gray > filterValue || (Gray == filterValue && filterValue == 0)) {
ptr[0] = 0;
}else{
// ptr[3] = 0xff;
}
}
// 將內(nèi)存轉(zhuǎn)成image
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight,NULL);
CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace,kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider,NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
UIImage* resultUIImage = [UIImage imageWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
// 釋放
CGImageRelease(imageRef);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
self.outputImg.image = resultUIImage;
}