馬賽克
馬賽克在圖片效果中應(yīng)該是一種最常見(jiàn)的處理方式,日常生活中也幾乎處處可見(jiàn)。前段時(shí)間項(xiàng)目中要實(shí)現(xiàn)圖片馬賽克處理,就研究了一下。其實(shí),用蘋果濾鏡CIFilter就能非常便捷的實(shí)現(xiàn)加碼,但使用的過(guò)程中我發(fā)現(xiàn)濾鏡只能處理.png格式的圖片,如果遇到.jpeg格式的圖片就沒(méi)有效果了,于是決定研究一下馬賽克算法,發(fā)現(xiàn)可以通過(guò)操作圖片的像素點(diǎn)來(lái)實(shí)現(xiàn)同樣的效果。當(dāng)然后者的實(shí)用性更加廣泛,隨便你什么類型的圖片都可以。文章最后還有涂抹馬賽克效果實(shí)現(xiàn)以及“復(fù)原”的Demo,希望多多star支持~
操作像素點(diǎn)實(shí)現(xiàn)馬賽克
我們都知道圖片是一個(gè)一個(gè)像素點(diǎn)構(gòu)成的,其實(shí)很早之前就有想過(guò)為什么所有的圖片都是矩形的?有沒(méi)有那種不規(guī)則的圖片?計(jì)算機(jī)中的圖片為什么都是矩形的?顯示圓形也只能周圍透明?,估計(jì)只有非科班出身的我才會(huì)問(wèn)這種問(wèn)題吧~簡(jiǎn)單來(lái)說(shuō),其實(shí)就是為了統(tǒng)一、更加方便的來(lái)處理圖片,所以圖片就是由像素矩陣構(gòu)成的,平時(shí)我們看到的不規(guī)則的圖片沒(méi)有顏色的地方只是透明了而已。然后我就會(huì)想能不能局部的改變圖片的顏色呢,比如把指定的不規(guī)則區(qū)域顏色改為別的顏色,以上都是自己以前胡亂想的,研究了之后發(fā)現(xiàn)其實(shí)都可以實(shí)現(xiàn)(不過(guò)沒(méi)那么精確),我們可以通過(guò)操作圖片的像素點(diǎn)來(lái)實(shí)現(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;//一個(gè)像素四個(gè)分量,即ARGB
NSUInteger bytesPerRow = bytesPerPixel * imageW;
unsigned char *rawData = (unsigned char *)calloc(imageH*imageW*bytesPerPixel, sizeof(unsigned char));
NSUInteger bitsPerComponent = 8;//每個(gè)分量8個(gè)字節(jié)
/*
參數(shù)1:數(shù)據(jù)源
參數(shù)2:圖片寬
參數(shù)3:圖片高
參數(shù)4:表示每一個(gè)像素點(diǎn),每一個(gè)分量大小
在我們圖像學(xué)中,像素點(diǎn):ARGB組成 每一個(gè)表示一個(gè)分量(例如,A,R,G,B)
在我們計(jì)算機(jī)圖像學(xué)中每一個(gè)分量的大小是8個(gè)字節(jié)
參數(shù)5:每一行大?。ㄆ鋵?shí)圖片是由像素?cái)?shù)組組成的)
如何計(jì)算每一行的大小,所占用的內(nèi)存
首先計(jì)算每一個(gè)像素點(diǎn)大小(我們?nèi)∽畲笾担?ARGB是4個(gè)分量 = 每個(gè)分量8個(gè)字節(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ù)組存儲(chǔ)方式RGBA(第一個(gè)像素)RGBA(第二個(gè)像素)
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)){//計(jì)算黑色部分所占比例
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)建一個(gè)Bitmap的上下文,其中rawData中存放的就是我們所需要的像素點(diǎn)的集合。每個(gè)像素點(diǎn)由ARGB四個(gè)分量組成,我們看到的不規(guī)則圖片沒(méi)有顏色的地方也就是A為0的像素點(diǎn),像素在數(shù)組中存放的是方式也是一個(gè)分量一個(gè)分量的存進(jìn)去的,這樣我們就可以通過(guò)修改數(shù)組中的數(shù)據(jù)來(lái)實(shí)現(xiàn)修改像素點(diǎn)。通過(guò)上述方法我們就可以把一個(gè)圖片的局部不規(guī)則區(qū)域修改顏色,效果如下圖:

馬賽克算法
馬賽克核心算法的大概原理就是把某一個(gè)點(diǎn)的顏色賦值給它周圍的指定區(qū)域,這個(gè)區(qū)域大小可以我們自己來(lái)定義。
unsigned char *pixels[4] = {0};
if (i % sizeLevel == 0) {
if (j % sizeLevel == 0) {
memcpy(pixels, bitMapData+4*currentIndex, 4);
}else{
//將上一個(gè)像素點(diǎn)的值賦給第二個(gè)
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)存地址的起始位置開(kāi)始拷貝n個(gè)字節(jié)到目標(biāo)dest所指的內(nèi)存地址的起始位置中。其實(shí)就是先把某個(gè)像素點(diǎn)的ARGB存到一個(gè)空數(shù)組pixels中,然后遍歷像素點(diǎn),如果是sizeLevel的整數(shù)倍就獲取新的像素信息,不是的話就更換當(dāng)前像素點(diǎn)的信息,這樣就能實(shí)現(xiàn)sizeLevel大小區(qū)域的顏色是統(tǒng)一的了,也就是我們看到的馬賽克中一個(gè)方塊區(qū)域。下邊這張圖片描述的就很貼切

其實(shí)這樣做的話還會(huì)有問(wèn)題,因?yàn)橥该鲄^(qū)域的像素點(diǎn)RGB信息也為0,0,0,跟黑色一樣,這么一來(lái)后邊再跟根據(jù)bitmap去繪制圖片的時(shí)候,會(huì)把透明區(qū)域當(dāng)成黑色來(lái)處理,所以我在中間進(jìn)行了一下過(guò)濾
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;
}
這種做法也不太嚴(yán)謹(jǐn),但暫時(shí)想不到什么好的辦法。
涂抹實(shí)現(xiàn)馬賽克
馬賽克平時(shí)的運(yùn)用更多的是跟用戶交互息息相關(guān)的,比如手指涂抹區(qū)域打上馬賽克,其實(shí)這種實(shí)現(xiàn)也挺簡(jiǎn)單。剛開(kāi)始做的時(shí)候覺(jué)得還要去計(jì)算,但這樣顯然不易于實(shí)現(xiàn)。其實(shí)用兩張圖片就可以搞定了,一張是原圖,用imageView來(lái)顯示;一張是用馬賽克處理過(guò)的圖片,用CALayer來(lái)顯示;馬賽克處理過(guò)的圖片覆蓋在原圖上邊,然后利用layer的mask屬性來(lái)控制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的時(shí)候會(huì)自動(dòng)填充
self.imageLayer.mask = self.shapeLayer;
self.path = CGPathCreateMutable();
然后我們?cè)趖ouchMove方法中根據(jù)手指移動(dòng)軌跡設(shè)置self.shapeLayer的path屬性就可以實(shí)現(xiàn)想要的效果了。我把這些都封裝在XScratchView類中了,使用的時(shí)候只需要初始化并給圖片屬性賦值,
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ù)原時(shí)只需要調(diào)用recover方法,
[_scratchView recover];
需要保存的時(shí)候只需要截取圖片區(qū)域就可以獲取加碼后的圖片了。效果如下

具體實(shí)現(xiàn)代碼都在我的RGBTool這個(gè)Demo中,有什么問(wèn)題還請(qǐng)大家多多指教,共同進(jìn)步!