iOS二值化扣圖

前幾天有這樣一個(gè)需求,在手機(jī)上把公章給摳出來,做成PNG可以貼在其他圖片上面,于是就有了今天的主題.

先放完成后的效果圖

filter.gif

Demo中的圖片分辨率為440*387,處理只需要一毫秒~
可以看出來,沒有一點(diǎn)卡頓的感覺,,所以效率上還是很不錯(cuò)噠~

如果你很心急:<a href="#core">核心代碼</a>

理論基礎(chǔ)

了解一些基本概念還是很有必要滴

  • RGBA

R:紅色
G:綠色
B:藍(lán)色
A:透明度
紅綠藍(lán)為三原色,可以說我們看到的任何顏色都是由這三個(gè)顏色構(gòu)成的.所以是圖像組成必不可少的一部分
而加入了A則多了一個(gè)透明度的描述,常見于PNG格式的圖片.

例如微信的表情包,除了主要輪廓外,其他的色域都是采用的都是當(dāng)前聊天會(huì)話的背景色,這就是利用了Alpha來操作出的效果

  • 像素

一條線可以看成是被無數(shù)個(gè)點(diǎn)構(gòu)成的.同理,我們可以認(rèn)為一張圖片也是由一定數(shù)量的點(diǎn)構(gòu)成的.
以Demo中圖片為例子,分辨率440*387的圖片,一行440的像素點(diǎn),有387行,那么他就包含了有約17萬個(gè)像素點(diǎn)
,對(duì)這些像素點(diǎn)的操作,將直接影響到圖片的顯示

  • 灰色

在RGB的表現(xiàn)中,如何呈現(xiàn)出灰色呢?
說來慚愧,我一開始一度以為所謂的灰就是黑色的透明度不一樣,但事實(shí)當(dāng)然不是這樣啦!
可以參考下表

R G B 顏色
0 0 0 黑色
50 50 50 深灰色
178 178 178 淺灰色
255 255 255 白色

可以看出來,灰色其實(shí)是RGB三個(gè)值相等,并且隨著數(shù)值的增大,顏色逐漸變淺,和透明度是沒有任何關(guān)系的


  • 二值化

所謂的二值化,其實(shí)是將圖片的色域空間變?yōu)榛疑?在CG框架中,可以直接使用CGColorSpaceCreateDeviceGray來進(jìn)行操作,不過因?yàn)槲覀兂俗屗兓抑?還需要對(duì)透明度做操作,所以這里自己使用算法來進(jìn)行計(jì)算.

RGB轉(zhuǎn)灰色的計(jì)算公式有很多種,我們這里使用一種較為經(jīng)典的算法
double Gray = R*0.3+G*0.59+B*0.11;
其中RGB都是以0~255取值,得到的結(jié)果即灰色的RGB色值

處理方法

因?yàn)槲覀兿胍玫街饕妮喞?所以只需要對(duì)像素進(jìn)行操作即可,那么就很簡(jiǎn)單啦,直接上代碼

    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)那一段
因?yàn)槊總€(gè)像素都包含了RGBA的信息,而255在十六進(jìn)制中以0xFF表示
所以假設(shè)顏色為白色不透明的情況下,RGBA的表現(xiàn)方式應(yīng)該為0xFF FF FF FF,所以使用uint8_t來接收,
但是尷尬的是他的排列方式并不是RGBA,而是ABGR ??,一度讓我以為代碼寫錯(cuò)了.

代碼中的Gray就是轉(zhuǎn)換為灰度圖顯示的顏色,而filterValue則是過濾系數(shù),取值范圍在0~255;值越大,顯示的圖像也就越多,Demo中使用UISlider來控制.
這樣色彩變化與過濾都放在了一起,減少了頻繁操作像素信息.
因?yàn)檎率羌t色的,所以我當(dāng)時(shí)將需要顯示的像素點(diǎn)變?yōu)榱思t色,而被過濾掉的像素點(diǎn),則直接設(shè)置為了透明.大家可根據(jù)需求自行設(shè)置

<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;
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前幾天有這樣一個(gè)需求,在手機(jī)上把公章給摳出來,做成PNG可以貼在其他圖片上面,于是就有了今天的主題.先放完成后的效...
    念念不忘一個(gè)丫頭的容閱讀 789評(píng)論 1 2
  • 卷首語 歡迎來到 objc.io 的第三期! 這一期都是關(guān)于視圖層的。當(dāng)然視圖層有很多方面,我們需要把它們縮小到幾...
    評(píng)評(píng)分分閱讀 1,926評(píng)論 0 18
  • 繪制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一個(gè)像素是如何繪制到屏幕上去的?有很多...
    阿貍旅途T恤閱讀 1,756評(píng)論 0 7
  • 接下來我們看Base文件夾下的UIKIt文件夾的內(nèi)容。 1.UIColor+YYAdd 這里看了這個(gè)類,里面有許多...
    充滿活力的早晨閱讀 2,470評(píng)論 0 1
  • 古奇,不是名貴包包,沒錯(cuò),這是一只狗的名字。普通的不能再普通的一只狗。多年前的我甚至沒有想到這個(gè)名字究竟意味著什么...
    風(fēng)聽月閱讀 625評(píng)論 0 11

友情鏈接更多精彩內(nèi)容