馬賽克
馬賽克在圖片效果中應(yīng)該是一種最常見的處理方式匹涮,日常生活中也幾乎處處可見贴硫。前段時間項目中要實現(xiàn)圖片馬賽克處理,就研究了一下目派。其實坤候,用蘋果濾鏡CIFilter就能非常便捷的實現(xiàn)加碼,但使用的過程中我發(fā)現(xiàn)濾鏡只能處理.png格式的圖片企蹭,如果遇到.jpeg格式的圖片就沒有效果了白筹,于是決定研究一下馬賽克算法,發(fā)現(xiàn)可以通過操作圖片的像素點來實現(xiàn)同樣的效果谅摄。當(dāng)然后者的實用性更加廣泛徒河,隨便你什么類型的圖片都可以。文章最后還有涂抹馬賽克效果實現(xiàn)以及“復(fù)原”的Demo螟凭,希望多多star支持~
操作像素點實現(xiàn)馬賽克
我們都知道圖片是一個一個像素點構(gòu)成的虚青,其實很早之前就有想過為什么所有的圖片都是矩形的?有沒有那種不規(guī)則的圖片螺男?計算機中的圖片為什么都是矩形的棒厘?顯示圓形也只能周圍透明?,估計只有非科班出身的我才會問這種問題吧~簡單來說下隧,其實就是為了統(tǒng)一奢人、更加方便的來處理圖片,所以圖片就是由像素矩陣構(gòu)成的淆院,平時我們看到的不規(guī)則的圖片沒有顏色的地方只是透明了而已何乎。然后我就會想能不能局部的改變圖片的顏色呢,比如把指定的不規(guī)則區(qū)域顏色改為別的顏色,以上都是自己以前胡亂想的支救,研究了之后發(fā)現(xiàn)其實都可以實現(xiàn)(不過沒那么精確)抢野,我們可以通過操作圖片的像素點來實現(xiàn),直接上代碼吧……
+ (NSArray *)getRGBsArrFromImage:(UIImage *)image{
//1.get the image into your data buffer
CGImageRef imageRef = [image CGImage];
NSUInteger imageW = CGImageGetWidth(imageRef);
NSUInteger imageH = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;//一個像素四個分量各墨,即ARGB
NSUInteger bytesPerRow = bytesPerPixel * imageW;
unsigned char *rawData = (unsigned char *)calloc(imageH*imageW*bytesPerPixel, sizeof(unsigned char));
NSUInteger bitsPerComponent = 8;//每個分量8個字節(jié)
/*
參數(shù)1:數(shù)據(jù)源
參數(shù)2:圖片寬
參數(shù)3:圖片高
參數(shù)4:表示每一個像素點指孤,每一個分量大小
在我們圖像學(xué)中,像素點:ARGB組成 每一個表示一個分量(例如贬堵,A恃轩,R,G黎做,B)
在我們計算機圖像學(xué)中每一個分量的大小是8個字節(jié)
參數(shù)5:每一行大胁骢恕(其實圖片是由像素數(shù)組組成的)
如何計算每一行的大小,所占用的內(nèi)存
首先計算每一個像素點大姓舻睢(我們?nèi)∽畲笾担?ARGB是4個分量 = 每個分量8個字節(jié) * 4
參數(shù)6:顏色空間
參數(shù)7:是否需要透明度
*/
CGContextRef context = CGBitmapContextCreate(rawData, imageW, imageH, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(context, CGRectMake(0, 0, imageW, imageH), imageRef);
//2.Now your rawData contains the image data int the RGBA8888 pixel format
NSUInteger blackPixel = 0;
NSMutableArray *pixelsArr = [NSMutableArray array];
for (int y = 0; y < imageH; y++) {
for (int x = 0; x < imageW; x++) {
NSUInteger byteIndex = bytesPerRow*y + bytesPerPixel*x;
//rawData一維數(shù)組存儲方式RGBA(第一個像素)RGBA(第二個像素)
NSUInteger red = rawData[byteIndex];
NSUInteger green = rawData[byteIndex+1];
NSUInteger blue = rawData[byteIndex+2];
NSUInteger alpha = rawData[byteIndex+3];
XPixelItem *pixelItem = [[XPixelItem alloc] init];
pixelItem.color = [UIColor colorWithRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:alpha/255.0];
pixelItem.location = CGPointMake(x, y);
[pixelsArr addObject:pixelItem];
if (red+green+blue == 0 && (alpha/255.0 >= 0.5)){//計算黑色部分所占比例
blackPixel++;
}
}
}
NSLog(@"黑色所占的面積--%f,%lu",blackPixel*1.0/(imageW*imageH),(unsigned long)pixelsArr.count);
imageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
free(rawData);
return pixelsArr;
}
其中最主要的方法就是CGBitmapContextCreate筷厘,根據(jù)分配好的內(nèi)存創(chuàng)建一個Bitmap的上下文,其中rawData中存放的就是我們所需要的像素點的集合伟桅。每個像素點由ARGB四個分量組成敞掘,我們看到的不規(guī)則圖片沒有顏色的地方也就是A為0的像素點,像素在數(shù)組中存放的是方式也是一個分量一個分量的存進去的楣铁,這樣我們就可以通過修改數(shù)組中的數(shù)據(jù)來實現(xiàn)修改像素點玖雁。通過上述方法我們就可以把一個圖片的局部不規(guī)則區(qū)域修改顏色,效果如下圖:
馬賽克算法
馬賽克核心算法的大概原理就是把某一個點的顏色賦值給它周圍的指定區(qū)域盖腕,這個區(qū)域大小可以我們自己來定義。
unsigned char *pixels[4] = {0};
if (i % sizeLevel == 0) {
if (j % sizeLevel == 0) {
memcpy(pixels, bitMapData+4*currentIndex, 4);
}else{
//將上一個像素點的值賦給第二個
memcpy(bitMapData+4*currentIndex, pixels, 4);
}
}else{
preCurrentIndex = (i-1)*imageW+j;
memcpy(bitMapData+4*currentIndex, bitMapData+4*preCurrentIndex, 4);
}
memcpy指的是c和c++使用的內(nèi)存拷貝函數(shù)溃列,memcpy函數(shù)的功能是從源src所指的內(nèi)存地址的起始位置開始拷貝n個字節(jié)到目標(biāo)dest所指的內(nèi)存地址的起始位置中。其實就是先把某個像素點的ARGB存到一個空數(shù)組pixels中听隐,然后遍歷像素點补鼻,如果是sizeLevel的整數(shù)倍就獲取新的像素信息雅任,不是的話就更換當(dāng)前像素點的信息,這樣就能實現(xiàn)sizeLevel大小區(qū)域的顏色是統(tǒng)一的了沪么,也就是我們看到的馬賽克中一個方塊區(qū)域。下邊這張圖片描述的就很貼切
其實這樣做的話還會有問題禽车,因為透明區(qū)域的像素點RGB信息也為0寇漫,0,0州胳,跟黑色一樣,這么一來后邊再跟根據(jù)bitmap去繪制圖片的時候陋葡,會把透明區(qū)域當(dāng)成黑色來處理亚亲,所以我在中間進行了一下過濾
if (red+green+blue == 0 && (alpha/255.0 <= 0.5)) {
rawData[currentIndex*4] = 255;
rawData[currentIndex*4+1] = 255;
rawData[currentIndex*4+2] = 255;
rawData[currentIndex*4+3] = 0;
continue;
}
這種做法也不太嚴謹,但暫時想不到什么好的辦法腐缤。
涂抹實現(xiàn)馬賽克
馬賽克平時的運用更多的是跟用戶交互息息相關(guān)的,比如手指涂抹區(qū)域打上馬賽克肛响,其實這種實現(xiàn)也挺簡單岭粤。剛開始做的時候覺得還要去計算,但這樣顯然不易于實現(xiàn)特笋。其實用兩張圖片就可以搞定了剃浇,一張是原圖,用imageView來顯示猎物;一張是用馬賽克處理過的圖片虎囚,用CALayer來顯示;馬賽克處理過的圖片覆蓋在原圖上邊蔫磨,然后利用layer的mask屬性來控制CALayer指定區(qū)域的顯示與否淘讥。
self.imageLayer = [CALayer layer];
self.imageLayer.frame = self.bounds;
[self.layer addSublayer:self.imageLayer];
self.shapeLayer = [CAShapeLayer layer];
self.shapeLayer.frame = self.bounds;
self.shapeLayer.lineCap = kCALineCapRound;
self.shapeLayer.lineJoin = kCALineJoinRound;
self.shapeLayer.lineWidth = 20;
self.shapeLayer.strokeColor = [UIColor blueColor].CGColor;
self.shapeLayer.fillColor = nil;//此處必須設(shè)為nil,否則后邊添加addLine的時候會自動填充
self.imageLayer.mask = self.shapeLayer;
self.path = CGPathCreateMutable();
然后我們在touchMove方法中根據(jù)手指移動軌跡設(shè)置self.shapeLayer的path屬性就可以實現(xiàn)想要的效果了堤如。我把這些都封裝在XScratchView類中了蒲列,使用的時候只需要初始化并給圖片屬性賦值,
XScratchView *scratchView = [[XScratchView alloc] initWithFrame:CGRectMake(0, 100, kScreenWidth, 300)];
scratchView.surfaceImage = [UIImage imageNamed:@"smoke.jpeg"];
scratchView.mosaicImage = [XRGBTool getMosaicImageWith:[UIImage imageNamed:@"smoke.jpeg"] level:0];
復(fù)原時只需要調(diào)用recover方法搀罢,
[_scratchView recover];
需要保存的時候只需要截取圖片區(qū)域就可以獲取加碼后的圖片了蝗岖。效果如下
具體實現(xiàn)代碼都在我的RGBTool這個Demo中,有什么問題還請大家多多指教榔至,共同進步抵赢!