ios 繪畫之涂鴉,貼圖,馬賽克,高斯筆涂鴉
https://github.com/wangjinshan/IJSPhotoSDK
項(xiàng)目展示
前言
Quartz 2D是一個(gè)二維圖形繪制引擎,支持iOS環(huán)境和Mac OS X環(huán)境压恒。我們可以使用Quartz 2D API來(lái)實(shí)現(xiàn)許多功能暇检,如基本路徑的繪制拇泣、透明度阳似、描影、繪制陰影昌抠、透明層锄贷、顏色管理、反鋸齒牢酵、PDF文檔生成和PDF元數(shù)據(jù)訪問(wèn).
當(dāng)你的程序進(jìn)行位圖繪制時(shí)悬包,不管使用哪種方式,都是基于 Quartz 2D 的,也就是說(shuō)馍乙,CPU 部分實(shí)現(xiàn)的繪制是通過(guò) Quartz 2D 實(shí)現(xiàn)的布近。盡管 Quartz 可以做其它的事情,但是我們這里還是集中于位圖繪制丝格,在緩沖區(qū)(一塊內(nèi)存)繪制位圖會(huì)包括 RGBA 數(shù)據(jù)
簡(jiǎn)單例子:
UIKit實(shí)現(xiàn)
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(16.72, 7.22)];
[path addLineToPoint:CGPointMake(3.29, 20.83)];
[path addLineToPoint:CGPointMake(0.4, 18.05)];
[path addLineToPoint:CGPointMake(18.8, -0.47)];
[path addLineToPoint:CGPointMake(37.21, 18.05)];
[path addLineToPoint:CGPointMake(34.31, 20.83)];
[path addLineToPoint:CGPointMake(20.88, 7.22)];
[path addLineToPoint:CGPointMake(20.88, 42.18)];
[path addLineToPoint:CGPointMake(16.72, 42.18)];
[path addLineToPoint:CGPointMake(16.72, 7.22)];
[path closePath];
path.lineWidth = 1;
[[UIColor redColor] setStroke];
[path stroke];
相對(duì)應(yīng)的 Core Graphics 代碼:
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 16.72, 7.22);
CGContextAddLineToPoint(ctx, 3.29, 20.83);
CGContextAddLineToPoint(ctx, 0.4, 18.05);
CGContextAddLineToPoint(ctx, 18.8, -0.47);
CGContextAddLineToPoint(ctx, 37.21, 18.05);
CGContextAddLineToPoint(ctx, 34.31, 20.83);
CGContextAddLineToPoint(ctx, 20.88, 7.22);
CGContextAddLineToPoint(ctx, 20.88, 42.18);
CGContextAddLineToPoint(ctx, 16.72, 42.18);
CGContextAddLineToPoint(ctx, 16.72, 7.22);
CGContextClosePath(ctx);
CGContextSetLineWidth(ctx, 1);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextStrokePath(ctx);
ctx是什么?
正好引出所謂的 CGContext 登場(chǎng)撑瞧。我們傳過(guò)去的ctx參數(shù)正是在那個(gè)上下文中。而這個(gè)上下文定義了我們需要繪制的地方显蝌。如果我們實(shí)現(xiàn)了 CALayer 的 -drawInContext: 這時(shí)已經(jīng)傳過(guò)來(lái)一個(gè)上下文预伺。繪制到這個(gè)上下文中的內(nèi)容將會(huì)被繪制到圖層的備份區(qū)(圖層的緩沖區(qū)).但是我們也可以創(chuàng)建我們自己的上下文,叫做基于位圖的上下文曼尊,比如 CGBitmapContextCreate().這個(gè)方法返回一個(gè)我們可以傳給 CGContext 方法來(lái)繪制的上下文酬诀。
注意 UIKit 版本的代碼為何不傳入一個(gè)上下文參數(shù)到方法中?這是因?yàn)楫?dāng)使用 UIKit 或者 AppKit 時(shí)骆撇,上下文是唯一的瞒御。UIkit 維護(hù)著一個(gè)上下文堆棧,UIKit 方法總是繪制到最頂層的上下文中神郊。你可以使用 UIGraphicsGetCurrentContext() 來(lái)得到最頂層的上下文肴裙。你可以使用 UIGraphicsPushContext() 和 UIGraphicsPopContext() 在 UIKit 的堆棧中推進(jìn)或取出上下文。
最為突出的是涌乳,UIKit 使用 UIGraphicsBeginImageContextWithOptions() 和 UIGraphicsEndImageContext() 方便的創(chuàng)建類似于 CGBitmapContextCreate() 的位圖上下文践宴。混合調(diào)用 UIKit 和 Core Graphics 非常簡(jiǎn)單
UIGraphicsBeginImageContextWithOptions(CGSizeMake(45, 45), YES, 2);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 16.72, 7.22);
CGContextAddLineToPoint(ctx, 3.29, 20.83);
...
CGContextStrokePath(ctx);
UIGraphicsEndImageContext();
解釋一下UIGraphicsBeginImageContextWithOptions函數(shù)參數(shù)的含義:
第一個(gè)參數(shù)表示所要?jiǎng)?chuàng)建的圖片的尺寸爷怀;
第二個(gè)參數(shù)用來(lái)指定所生成圖片的背景是否為不透明阻肩,如上我們使用YES而不是NO,則我們得到的圖片背景將會(huì)是黑色,顯然這不是我想要的烤惊;
第三個(gè)參數(shù)指定生成圖片的縮放因子乔煞,這個(gè)縮放因子與UIImage的scale屬性所指的含義是一致的。傳入0則表示讓圖片的縮放因子根據(jù)屏幕的分辨率而變化柒室,所以我們得到的圖片不管是在單分辨率還是視網(wǎng)膜屏上看起來(lái)都會(huì)很好
也可以寫成:
CGContextRef ctx = CGBitmapContextCreate(NULL, 90, 90, 8, 90 * 4, space, bitmapInfo);
CGContextScaleCTM(ctx, 0.5, 0.5);
UIGraphicsPushContext(ctx);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(16.72, 7.22)];
[path addLineToPoint:CGPointMake(3.29, 20.83)];
...
[path stroke];
UIGraphicsPopContext(ctx);
CGContextRelease(ctx);
注意: 必須要有UIGraphicsEndImageContext(); CGContextRelease(ctx); 否則會(huì)造成內(nèi)存泄露,程序卡死
其他相關(guān)的操作
繪制
#pragma mark 繪制矩形
-(void)_drawRectWithContext:(CGContextRef)context
{
//添加矩形對(duì)象
CGRect rect=CGRectMake(20, 50, 280, 50);
CGContextAddRect(context,rect);
//設(shè)置屬性
[[UIColor blueColor]set];
//繪制
CGContextDrawPath(context, kCGPathFillStroke);
}
#pragma mark 繪制矩形(利用UIKit的封裝方法)
-(void)_drawRectByUIKitWithContext:(CGContextRef)context
{
CGRect rect= CGRectMake(20, 150, 280.0, 50.0);
CGRect rect2=CGRectMake(20, 250, 280.0, 50.0);
//設(shè)置屬性
[[UIColor yellowColor]set];
//繪制矩形,相當(dāng)于創(chuàng)建對(duì)象渡贾、添加對(duì)象到上下文、繪制三個(gè)步驟
UIRectFill(rect);//繪制矩形(只有填充)
[[UIColor redColor]setStroke];
UIRectFrame(rect2);//繪制矩形(只有邊框)
}
#pragma mark 繪制橢圓
-(void)_drawEllipse:(CGContextRef)context
{
//添加對(duì)象,繪制橢圓(圓形)的過(guò)程也是先創(chuàng)建一個(gè)矩形
CGRect rect=CGRectMake(50, 50, 220.0, 180.0);
CGContextAddEllipseInRect(context, rect);
//設(shè)置屬性
[[UIColor purpleColor]set];
//繪制
CGContextDrawPath(context, kCGPathFillStroke);
}
#pragma mark - 繪制圓弧
-(void)_drawArc:(CGContextRef)context
{
/*添加弧形對(duì)象
x:中心點(diǎn)x坐標(biāo)
y:中心點(diǎn)y坐標(biāo)
radius:半徑
startAngle:起始弧度
endAngle:終止弧度
closewise:是否逆時(shí)針繪制雄右,0則順時(shí)針繪制
*/
CGContextAddArc(context, 160, 160, 100.0, 0.0, M_PI_2, 1);
//設(shè)置屬性
[[UIColor yellowColor]set];
//繪制
CGContextDrawPath(context, kCGPathStroke); // kCGPathStroke 描邊 kCGPathFillStroke填充
}
#pragma mark - 繪制文字
-(void)_drawText:(CGContextRef)context
{
//繪制到指定的區(qū)域內(nèi)容
NSString *str=@"這是一個(gè)非常牛逼的操作啊";
CGRect rect= CGRectMake(20, 50, 280, 300);
UIFont *font=[UIFont systemFontOfSize:18];//設(shè)置字體
UIColor *color=[UIColor redColor];//字體顏色
NSMutableParagraphStyle *style=[[NSMutableParagraphStyle alloc]init];//段落樣式
NSTextAlignment align=NSTextAlignmentLeft;//對(duì)齊方式
style.alignment=align;
NSDictionary<NSAttributedStringKey, id> *attributeDic = @{NSFontAttributeName:font,NSForegroundColorAttributeName:color,NSParagraphStyleAttributeName:style};
[str drawInRect:rect withAttributes:attributeDic];
}
#pragma mark - 繪制圖片
-(void)_drawImage:(CGContextRef)context
{
UIImage *image=[UIImage imageNamed:@"8.png"];
//從某一點(diǎn)開始繪制
[image drawAtPoint:CGPointMake(10, 50)];
//繪制到指定的矩形中空骚,注意如果大小不合適會(huì)會(huì)進(jìn)行拉伸
// [image drawInRect:CGRectMake(10, 50, 300, 450)];
//平鋪繪制
// [image drawAsPatternInRect:CGRectMake(0, 0, 320, 568)];
}
#pragma mark 圖形上下文形變
-(void)_drawImageCTM:(CGContextRef)context
{
//保存初始狀態(tài)
CGContextSaveGState(context);
//形變第一步:圖形上下文向右平移 X = 100
CGContextTranslateCTM(context, 100, 20);
//形變第二步:縮放0.8
CGContextScaleCTM(context, 0.5, 0.5);
//形變第三步:旋轉(zhuǎn)
CGContextRotateCTM(context, M_PI_4/4);
UIImage *image=[UIImage imageNamed:@"8"];
[image drawInRect:CGRectMake(0, 50, 240, 300)];
//恢復(fù)到初始狀態(tài)
CGContextRestoreGState(context);
}
點(diǎn)擊屏幕時(shí),把控件的View截屏
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
把View的內(nèi)容截屏生成一張新的圖片.
開啟一個(gè)跟控制器view相同大小的上下文.
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
把View的內(nèi)容繪制到上下文當(dāng)中.
注意:View是不能夠直接繪制到上下文當(dāng)中的.View之所以能夠顯示是因?yàn)樗鼉?nèi)部有一個(gè)layer(層),
層是通過(guò)渲染的方式繪制到上下文當(dāng)中的.
獲取當(dāng)前的上下文.
CGContextRef ctx = UIGraphicsGetCurrentContext();
把當(dāng)前View的內(nèi)容渲染到View上面.
[self.view.layer renderInContext:ctx];
從上下文當(dāng)中生成一張圖片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
關(guān)閉上下文.
UIGraphicsEndImageContext();
把生成的圖片寫到桌面上.
桌面都是以流的形式傳遞數(shù)據(jù),所以我們要把圖片轉(zhuǎn)成二進(jìn)流.
image:要轉(zhuǎn)的圖片
compressionQuality:壓縮質(zhì)量,1代表質(zhì)量最高
NSData *data = UIImageJPEGRepresentation(newImage, 1);
原始質(zhì)量的png圖片.
NSData *data = UIImagePNGRepresentation(newImage);
把二進(jìn)流寫到桌面.
[data writeToFile:@"路徑" atomically:YES];
}
本文主要以具體的實(shí)例講解如何繪制
1, 隨筆涂鴉
有兩方案處理:
方案1: 直接在 - (void)drawRect:(CGRect)rect{} 中繪制需要的圖形,因?yàn)槔锩嬉呀?jīng)有了上下文
方案2: 使用自己創(chuàng)建上下文的方式
大體的思路如下:
創(chuàng)建繼承自UIView的 IJSIPanDrawingView, 添加手勢(shì)并實(shí)現(xiàn)手勢(shì)方法
創(chuàng)建 IJSIPath對(duì)象 主要存儲(chǔ) 路徑的基本信息
手勢(shì)方法:
- (void)drawingViewDidPan:(UIPanGestureRecognizer*)sender
{
CGPoint currentDraggingPosition = [sender locationInView:self.drawingView]; //獲取到的是手指點(diǎn)擊屏幕實(shí)時(shí)的坐標(biāo)點(diǎn)
if(sender.state == UIGestureRecognizerStateBegan) //一個(gè)手勢(shì)已經(jīng)開始但尚未改變或者完成時(shí)
{
// 初始化一個(gè)UIBezierPath對(duì)象, 把起始點(diǎn)存儲(chǔ)到UIBezierPath對(duì)象中, 用來(lái)存儲(chǔ)所有的軌跡點(diǎn)
IJSIPath *path = [IJSIPath pathToPoint:currentDraggingPosition pathWidth: self.editorController.panWidth != 0 ? self.editorController.panWidth: MAX(1, 4)];
path.pathColor = self.editorController.panColor != nil ? self.editorController.panColor : [UIColor redColor];
path.shape.strokeColor =[UIColor greenColor].CGColor; //代表設(shè)置它的邊框色
[self.allLineArr addObject:path]; //添加路線
}
if(sender.state == UIGestureRecognizerStateChanged) //手勢(shì)狀態(tài)改變
{
// 獲得數(shù)組中的最后一個(gè)UIBezierPath對(duì)象(因?yàn)槲覀兠看味及裊IBezierPath存入到數(shù)組最后一個(gè),因此獲取時(shí)也取最后一個(gè))
IJSIPath *path = [self.allLineArr lastObject];
[path pathLineToPoint:currentDraggingPosition];//添加點(diǎn)
[self drawLine];
if (self.panDrawingViewdrawingCallBack) self.panDrawingViewdrawingCallBack(YES);
}
}
滑動(dòng)開始 創(chuàng)建 allLineArr 數(shù)組 IJSIPath對(duì)象把所有的路徑信息保存到路徑數(shù)組中
滑動(dòng)進(jìn)行中去除數(shù)組最后一個(gè) IJSIPath對(duì)象把正在進(jìn)行的所有的點(diǎn)添加IJSIPath對(duì)象里面
核心繪制方法:
- (void)drawLine
{
CGSize size = self.drawingView.frame.size; //獲取繪制的大小
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); //創(chuàng)建一個(gè)基于位圖的上下文/NO 設(shè)置透明
CGContextRef context = UIGraphicsGetCurrentContext(); // 獲取當(dāng)前上下文
CGContextSetAllowsAntialiasing(context, true); //去掉鋸齒
CGContextSetShouldAntialias(context, true);
for (IJSIPath *path in self.allLineArr)
{
[path drawPath];
}
self.drawingView.image = UIGraphicsGetImageFromCurrentImageContext(); //生成一個(gè)image對(duì)象
UIGraphicsEndImageContext();
}
- (void)drawPath
{
[self.pathColor set]; //填充顏色
[self.bezierPath stroke]; // 根據(jù)坐標(biāo)點(diǎn)連線
}
2, 貼圖,或者文字
繪制文字和圖片比較簡(jiǎn)單,我們采用 UIKit為我們封裝的方法
比如: 首選選取圖片添加到 self.view子視圖,接下來(lái)我們要做的就是遍歷所有的子視圖并繪制
文字繪制:
- (void)drawRect:(CGRect)rect
{
NSString *name = @"繪制文字繪制文字繪制文字繪制文字繪制文字";
NSMutableDictionary *dic =[NSMutableDictionary dictionary];
dic[NSFontAttributeName] = [UIFont systemFontOfSize:40]; // 字號(hào)
NSShadow *shadow = [[NSShadow alloc]init];
shadow.shadowOffset = CGSizeMake(10, 20); // 偏移量
shadow.shadowBlurRadius = 2; // 模糊度
shadow.shadowColor = [UIColor blueColor];
dic[NSShadowAttributeName] =shadow;
// 繪制方法1: 自動(dòng)換行
// [name drawInRect:CGRectMake(0, 0, rect.size.width, rect.size.height) withAttributes:dic];
// 繪制方法2: 繪制不會(huì)換行
[name drawAtPoint:CGPointZero withAttributes:dic];
}
drawAtPoint:要畫到哪個(gè)位置
withAttributes:文本的樣式.
[str drawAtPoint:CGPointZero withAttributes:nil];
富文本中文字的注釋:
#import <Foundation/NSAttributedString.h>
字符屬性
字符屬性可以應(yīng)用于 attributed string 的文本中。
NSString *const NSFontAttributeName;(字體)
NSString *const NSParagraphStyleAttributeName;(段落)
NSString *const NSForegroundColorAttributeName;(字體顏色)
NSString *const NSBackgroundColorAttributeName;(字體背景色)
NSString *const NSLigatureAttributeName;(連字符)
NSString *const NSKernAttributeName;(字間距)
NSString *const NSStrikethroughStyleAttributeName;(刪除線)
NSString *const NSUnderlineStyleAttributeName;(下劃線)
NSString *const NSStrokeColorAttributeName;(邊線顏色)
NSString *const NSStrokeWidthAttributeName;(邊線寬度)
NSString *const NSShadowAttributeName;(陰影)(橫豎排版)
NSString *const NSVerticalGlyphFormAttributeName;
常量
1> NSFontAttributeName(字體)
該屬性所對(duì)應(yīng)的值是一個(gè) UIFont 對(duì)象擂仍。該屬性用于改變一段文本的字體囤屹。如果不指定該屬性,則默認(rèn)為12-point Helvetica(Neue)逢渔。
2> NSParagraphStyleAttributeName(段落)
該屬性所對(duì)應(yīng)的值是一個(gè) NSParagraphStyle 對(duì)象肋坚。該屬性在一段文本上應(yīng)用多個(gè)屬性。如果不指定該屬性肃廓,則默認(rèn)為 NSParagraphStyle 的defaultParagraphStyle 方法返回的默認(rèn)段落屬性智厌。
3> NSForegroundColorAttributeName(字體顏色)
該屬性所對(duì)應(yīng)的值是一個(gè) UIColor 對(duì)象。該屬性用于指定一段文本的字體顏色盲赊。如果不指定該屬性铣鹏,則默認(rèn)為黑色。
4> NSBackgroundColorAttributeName(字體背景色)
該屬性所對(duì)應(yīng)的值是一個(gè) UIColor 對(duì)象哀蘑。該屬性用于指定一段文本的背景顏色诚卸。如果不指定該屬性,則默認(rèn)無(wú)背景色递礼。
5> NSLigatureAttributeName(連字符)
該屬性所對(duì)應(yīng)的值是一個(gè) NSNumber 對(duì)象(整數(shù))惨险。連體字符是指某些連在一起的字符羹幸,它們采用單個(gè)的圖元符號(hào)脊髓。0 表示沒(méi)有連體字符。1 表示使用默認(rèn)的連體字符栅受。2表示使用所有連體符號(hào)将硝。默認(rèn)值為 1(注意,iOS 不支持值為 2)屏镊。
6> NSKernAttributeName(字間距)
該屬性所對(duì)應(yīng)的值是一個(gè) NSNumber 對(duì)象(整數(shù))依疼。字母緊排指定了用于調(diào)整字距的像素點(diǎn)數(shù)。字母緊排的效果依賴于字體而芥。值為 0 表示不使用字母緊排律罢。默認(rèn)值為0。
7> NSStrikethroughStyleAttributeName(刪除線)
該屬性所對(duì)應(yīng)的值是一個(gè) NSNumber 對(duì)象(整數(shù))。該值指定是否在文字上加上刪除線误辑,該值參考“Underline Style Attributes”沧踏。默認(rèn)值是NSUnderlineStyleNone。
8> NSUnderlineStyleAttributeName(下劃線)
該屬性所對(duì)應(yīng)的值是一個(gè) NSNumber 對(duì)象(整數(shù))巾钉。該值指定是否在文字上加上下劃線翘狱,該值參考“Underline Style Attributes”。默認(rèn)值是NSUnderlineStyleNone砰苍。
9> NSStrokeColorAttributeName(邊線顏色)
該屬性所對(duì)應(yīng)的值是一個(gè) UIColor 對(duì)象潦匈。如果該屬性不指定(默認(rèn)),則等同于 NSForegroundColorAttributeName赚导。否則茬缩,指定為刪除線或下劃線顏色。更多細(xì)節(jié)見(jiàn)“Drawing attributedstrings that are both filled and stroked”辟癌。
10> NSStrokeWidthAttributeName(邊線寬度)
該屬性所對(duì)應(yīng)的值是一個(gè) NSNumber 對(duì)象(小數(shù))寒屯。該值改變描邊寬度(相對(duì)于字體size 的百分比)。默認(rèn)為 0黍少,即不改變寡夹。正數(shù)只改變描邊寬度。負(fù)數(shù)同時(shí)改變文字的描邊和填充寬度厂置。例如菩掏,對(duì)于常見(jiàn)的空心字,這個(gè)值通常為3.0昵济。
11> NSShadowAttributeName(陰影)
該屬性所對(duì)應(yīng)的值是一個(gè) NSShadow 對(duì)象智绸。默認(rèn)為 nil。
12> NSVerticalGlyphFormAttributeName(橫豎排版)
該屬性所對(duì)應(yīng)的值是一個(gè) NSNumber 對(duì)象(整數(shù))访忿。0 表示橫排文本瞧栗。1 表示豎排文本。在 iOS 中海铆,總是使用橫排文本迹恐,0 以外的值都未定義。
2,圖片繪制
圖片繪制和繪制文字一樣
- (void)drawRect:(CGRect)rect
{
UIImage *image =[UIImage imageNamed:@"001.png"];
UIRectClip(CGRectMake(10, 10, 50, 50)); // 裁剪,超過(guò)的區(qū)域?qū)⒉眉? [image drawAtPoint:CGPointZero]; //繪制出來(lái)的圖圖片跟圖片的實(shí)際尺寸一樣大
[image drawInRect:rect]; // 拉伸圖片,拉伸到指定的大小使用這個(gè)方法繪制出來(lái)的圖片尺寸會(huì)和傳入的rect區(qū)域一樣大.
[image drawAsPatternInRect:rect]; // 平鋪
}
3, 高斯筆繪制
大體的思路就是: 獲取一張高斯圖,用戶獲取高斯圖的顏色作為涂鴉筆的顏色
首先獲取一張高斯圖
// 根據(jù)全圖獲取一張高斯模糊圖
- (UIImage *)getImageFilterForGaussianBlur:(int)blurNumber
{
CGFloat blur = blurNumber * self.size.width / [UIScreen mainScreen].bounds.size.width;
CIContext *context = [CIContext contextWithOptions:nil];
CIImage *inputImage = [CIImage imageWithCGImage:self.CGImage];
CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"
keysAndValues:kCIInputImageKey, inputImage,
@"inputRadius", @(blur),
nil];
CIImage *outputImage = filter.outputImage;
return [UIImage imageWithCGImage:[context createCGImage:outputImage fromRect:CGRectMake(0, 0, self.size.width, self.size.height)]];
}
繪制過(guò)程和隨筆涂鴉相同區(qū)別只要在下面一句
CGContextSetStrokeColorWithColor(context, [UIColor colorWithPatternImage:self.gaussanViewGaussanImage].CGColor);
此處采用底層的實(shí)現(xiàn)方法
- (void)drawSmearView
{
UIGraphicsBeginImageContext(self.originImage.size); // 開啟上下文
CGContextRef context = UIGraphicsGetCurrentContext(); // 獲取當(dāng)前的上下
CGContextSetLineCap(context, kCGLineCapRound); // 設(shè)置線尾的樣式
[self.originImage drawInRect:CGRectMake(0, 0,self.originImage.size.width,self.originImage.size.height)]; // 繪制原圖用于地圖顯示
CGContextSetStrokeColorWithColor(context, [UIColor colorWithPatternImage:self.gaussanViewGaussanImage].CGColor); //獲取高斯圖的顏色
CGContextSetLineWidth(context, 10 * self.originImage.size.width / self.bounds.size.width); //線寬
for (int i = 0 ; i < self.allLineArr.count ; i ++ ) {
NSMutableArray *array = [self.allLineArr objectAtIndex:i];
for (int i = 0 ; i < array.count ; i ++ ) {
NSValue *value = [array objectAtIndex:i];
CGPoint p = [value CGPointValue];
p.x = p.x * self.originImage.size.width / self.bounds.size.width;
p.y = p.y * self.originImage.size.height / self.bounds.size.height;
if (i == 0) {
CGContextMoveToPoint(context, p.x, p.y); // 設(shè)置起點(diǎn)
CGContextAddLineToPoint(context, p.x, p.y); //添加移動(dòng)的點(diǎn)
}else{
CGContextAddLineToPoint(context, p.x, p.y);
}
}
}
CGContextDrawPath(context, kCGPathStroke); //將路徑繪制到上下問(wèn)
// 將繪制的結(jié)果存儲(chǔ)在內(nèi)存中
self.nowImage = UIGraphicsGetImageFromCurrentImageContext();
// 結(jié)束繪制
UIGraphicsEndImageContext();
[self setNeedsDisplay]; //重繪制
}
3, 馬賽克繪制
思路: 刮刮卡
單獨(dú)的view,沒(méi)有底圖的情況下需要?jiǎng)?chuàng)建一個(gè) UIImageView 用于顯示原圖
單獨(dú)的view UI結(jié)構(gòu)如下:
- self.view 子視圖是 UIImageView 用于顯示沒(méi)有馬賽克的部分
- 創(chuàng)建 CALayer 添加到 self.layer,主要用于顯示馬賽克圖, 在這一層的 contents 中添加馬賽克的圖片
- 創(chuàng)建 CAShapeLayer 主要用與在手指滑動(dòng)時(shí)候顯示馬賽克路徑, CAShapeLayer是一個(gè)通過(guò)矢量圖形來(lái)繪制的圖層子類,可以實(shí)現(xiàn)不規(guī)則路徑的視圖顯示, 這里創(chuàng)建的 CAShapeLayer 只有大小沒(méi)有具體內(nèi)容,所以 CALayer 上的馬賽克視圖不會(huì)顯示
非常重要的屬性:
self.imageLayer.mask = self.shapeLayer; // 子視圖完全遮蓋馬賽克視圖
mask將 self.shapeLayer; 設(shè)置成 self.imageLayer.mask 相當(dāng)于 給 self.imageLayer 添加了一層面罩,罩住的部分是顯示的, 沒(méi)有罩住的部分不顯示
代碼如下:
創(chuàng)建 CALayer CAShapeLayer
設(shè)置self.imageLayer.mask = self.shapeLayer; // 子視圖完全遮蓋馬賽克視圖
移動(dòng)中中間的過(guò)程和之前的路徑移動(dòng)一樣 唯一的區(qū)別在于
#pragma mark 繪制
- (void)drawSmearView
{
for (int i = 0 ; i < self.allLineArr.count ; i ++ )
{
NSMutableArray *array = [self.allLineArr objectAtIndex:i];
for (int i = 0 ; i < array.count ; i ++ )
{
CGMutablePathRef path = (__bridge CGMutablePathRef)([array objectAtIndex:i]);
self.shapeLayer.path = path; // 將移動(dòng)中 CGMutablePathRef 路徑設(shè)置給 CAShapeLayer 可以產(chǎn)生一個(gè)不規(guī)則的路徑,并且是透明空的
}
}
}
到此大功告成
關(guān)于馬賽克原理的介紹:
其實(shí)給圖片打碼并不是在原有的圖片上添加一層“蒙版”卧斟,而是使用各個(gè)平臺(tái)提供的API去操作像素點(diǎn)殴边,認(rèn)為的干擾了像素點(diǎn),就實(shí)現(xiàn)了馬賽克的效果珍语,以下面兩幅圖為例子锤岸,介紹一下如何的去“干擾像素”。
在圖像學(xué)中板乙,如果你想去對(duì)圖片進(jìn)行處理是偷,就必須得知道一個(gè)概念什么是“位圖圖像”。位圖圖像(bitmap), 亦稱為點(diǎn)陣圖像或繪制圖像,是由稱作像素(圖片元素)的單個(gè)點(diǎn)組成的蛋铆。這些點(diǎn)可以進(jìn)行不同的排列和染色以構(gòu)成圖樣饿幅。當(dāng)放大位圖時(shí),可以看見(jiàn)賴以構(gòu)成整個(gè)圖像的無(wú)數(shù)單個(gè)方塊戒职。擴(kuò)大位圖尺寸的效果是增大單個(gè)像素栗恩,從而使線條和形狀顯得參差不齊。然而洪燥,如果從稍遠(yuǎn)的位置觀看它磕秤,位圖圖像的顏色和形狀又顯得是連續(xù)的
圖解:
1, 假如一個(gè)圖像是由6 * 9 = 72個(gè)像素組成,現(xiàn)將一個(gè)像素點(diǎn)放大到圖1方塊單位大小捧韵。
2, 坐標(biāo)系
現(xiàn)在以左下角第一個(gè)方塊為原點(diǎn)將圖像納入坐標(biāo)系中市咆,如下圖所示。馬賽克效果實(shí)際上是在原始圖片的起始位置(0再来,8)到(2蒙兰,6)其中包含了9個(gè)像素(馬賽克矩形3 * 3)。
馬賽克矩形:
這里所說(shuō)的馬賽克矩形芒篷,指的是N個(gè)三位像素所組成的矩形(這里使用3*3)搜变,使每一個(gè)矩形的ARGB都和第一個(gè)矩形的ARGB相同,就達(dá)到了破壞原有圖像的效果
代碼
- (UIImage *)getMosaicImageFromOrginImageBlockLevel:(NSUInteger)level
{
// self == OrginImage
//1,獲取BitmapData
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // 創(chuàng)建顏色空間
CGImageRef imgRef = self.CGImage; // 圖片轉(zhuǎn)換
CGFloat width = CGImageGetWidth(imgRef); //圖片寬
CGFloat height = CGImageGetHeight(imgRef); //高
// 2, 創(chuàng)建圖片上下文(解析圖片信息针炉,繪制圖片 開辟內(nèi)存空間挠他,這塊空間用于處理馬賽克圖片
CGContextRef context = CGBitmapContextCreate (nil, //數(shù)據(jù)源
width,
height,
kBitsPerComponent, //每個(gè)顏色值8bit,圖像學(xué)中,像素點(diǎn):ARGB組成 每一個(gè)表示一個(gè)分量(例如A,R,G,B),
width*kPixelChannelCount, //每一行的像素點(diǎn)占用的字節(jié)數(shù),每個(gè)像素點(diǎn)的ARGB四個(gè)通道各占8個(gè)bit
colorSpace, // 顏色空間
kCGImageAlphaPremultipliedLast); //是否需要透明度
// 3, 根據(jù)圖片上下文繪制圖片
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imgRef);
// 4, 獲取圖片的像素?cái)?shù)組
unsigned char *bitmapData = CGBitmapContextGetData (context);
//5, 核心算法 圖片打碼,加入馬賽克,這里把BitmapData進(jìn)行馬賽克轉(zhuǎn)換,讓一個(gè)像素點(diǎn)替換為和它相同的矩形區(qū)域(正方形殖侵,圓形都可以)
unsigned char pixel[kPixelChannelCount] = {0}; // 像素點(diǎn)默認(rèn)是4個(gè)通道,默認(rèn)值是0
NSUInteger index,preIndex;
for (NSUInteger i = 0; i < height - 1 ; i++)
{
for (NSUInteger j = 0; j < width - 1; j++)
{
index = i * width + j; // 獲取當(dāng)前像素點(diǎn)坐標(biāo)
if (i % level == 0)
{
if (j % level == 0)
{
memcpy(pixel, bitmapData + kPixelChannelCount*index, kPixelChannelCount); //給我們的像素點(diǎn)賦值
}else{
memcpy(bitmapData + kPixelChannelCount*index, pixel, kPixelChannelCount);
}
} else {
preIndex = (i-1)*width +j;
memcpy(bitmapData + kPixelChannelCount*index, bitmapData + kPixelChannelCount*preIndex, kPixelChannelCount);
}
}
}
// 6, 獲取圖片數(shù)據(jù)集合
NSInteger dataLength = width*height* kPixelChannelCount;
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bitmapData, dataLength, NULL);
//7, 創(chuàng)建要輸出的圖像
CGImageRef mosaicImageRef = CGImageCreate(width, height,
kBitsPerComponent, //表示每一個(gè)像素點(diǎn),每一個(gè)分量的大小
kBitsPerPixel, //每一個(gè)像素點(diǎn)的大小
width*kPixelChannelCount , //每一行內(nèi)存大小
colorSpace, //顏色空間
kCGBitmapByteOrderDefault, //位圖信息
provider, //數(shù)據(jù)源(數(shù)據(jù)集合)
NULL, //數(shù)據(jù)解碼器
NO, // 是否抗鋸齒
kCGRenderingIntentDefault); //渲染器
// 8 創(chuàng)建輸出馬賽克圖片(填充顏色)
CGContextRef outputContext = CGBitmapContextCreate(nil,
width,
height,
kBitsPerComponent,
width*kPixelChannelCount,
colorSpace,
kCGImageAlphaPremultipliedLast);
CGContextDrawImage(outputContext, CGRectMake(0.0f, 0.0f, width, height), mosaicImageRef); // //繪制圖片
CGImageRef resultImageRef = CGBitmapContextCreateImage(outputContext); // //創(chuàng)建圖片
UIImage *resultImage = nil;
if([UIImage respondsToSelector:@selector(imageWithCGImage:scale:orientation:)])
{
float scale = [[UIScreen mainScreen] scale];
resultImage = [UIImage imageWithCGImage:resultImageRef scale:scale orientation:UIImageOrientationUp];
} else {
resultImage = [UIImage imageWithCGImage:resultImageRef];
}
//釋放
if(resultImageRef)
{
CFRelease(resultImageRef);
}
if(mosaicImageRef)
{
CFRelease(mosaicImageRef);
}
if(colorSpace)
{
CGColorSpaceRelease(colorSpace);
}
if(provider)
{
CGDataProviderRelease(provider);
}
if(context)
{
CGContextRelease(context);
}
if(outputContext)
{
CGContextRelease(outputContext);
}
return resultImage;
}
ios圖像知識(shí)
1,什么是圖形圖像
一張圖像就是像素點(diǎn)的集合镰烧,每一個(gè)像素都是一個(gè)單獨(dú)拢军,明了的顏色。圖像一般情況下都存儲(chǔ)成數(shù)組怔鳖,你可以把他們相像成2維數(shù)組茉唉。
這一張是縮放版本的幽靈,被放大后:
圖像中這些小的“方塊”就是像素败砂,每一像素只表示一種顏色赌渣。當(dāng)成百上千萬(wàn)的像素集體到一起后魏铅,就構(gòu)成了圖形圖像昌犹。
如何用字節(jié)來(lái)表示顏色
表示圖形的方式有許多種。在本教程中使用的是最簡(jiǎn)單的:32位RGBA模式览芳。
如同它的名字一樣斜姥,32位 RGBA 模式會(huì)將一個(gè)顏色值存儲(chǔ)在32位,或者4個(gè)字節(jié)中。每一個(gè)字節(jié)存儲(chǔ)一個(gè)部分或者一個(gè)顏色通道铸敏。這4個(gè)部分分別是:
~ R代表紅色
~ G代表綠色
~ B代表藍(lán)色
~ A代表透明度
正如你所知道的缚忧,紅,綠和藍(lán)是所有顏色的基本顏色集杈笔。你幾乎可以使用他們創(chuàng)建搭配出任何想要的顏色闪水。
由于使用8位表示每一種顏色值,那么使用32位RGBA模式實(shí)際上可以創(chuàng)建出不透明的顏色的總數(shù)是256256256種蒙具,已經(jīng)接近17億種球榆。驚嘆,那是好多好多好多的顏色禁筏!
alpha通道與其它的不同持钉。你可以把它當(dāng)成透明的東西,就像UIView的alpah屬性篱昔。
透明顏色意味著沒(méi)有任何的顏色每强,除非在它的后面有另外一種顏色;它的主要功能就是要告訴圖像處理這個(gè)像素的透明度是多少州刽,于是空执,就會(huì)有多少顏色值穿透過(guò)它而顯示出來(lái)。
你將會(huì)通過(guò)本節(jié)后面的內(nèi)容更新深入的了解穗椅。
總結(jié)一下脆烟,一個(gè)圖形就是像素的集體,并且每一個(gè)像素只能表示一種顏色房待。本節(jié)邢羔,你已經(jīng)了解了32位RGBA模式。
提示:你有沒(méi)有想過(guò)桑孩,位圖的結(jié)構(gòu)組成拜鹤?一張位圖就是一張2D的地圖,每一塊就是一個(gè)像素流椒!像素就是地圖的每一塊敏簿。
現(xiàn)在你已經(jīng)了解了用字節(jié)表示顏色的基礎(chǔ)了。不過(guò)在你開始著手寫代碼前宣虾,還有三個(gè)以上的概念需要你了解惯裕。
2,顏色空間
使用RGB模式表示顏色是顏色空間的一個(gè)例子。它只是眾多存儲(chǔ)顏色方法中的一種绣硝。另外一種顏色空間是灰階空間蜻势。像它的名字一樣,所有的圖形都只有黑和白鹉胖,只需要保存一個(gè)值來(lái)表示這種顏色握玛。
下面這種使用RGB模式表示的顏色够傍,人類的肉眼是很難識(shí)別的。
Red: 0 Green:104 Blue:55
你認(rèn)為RGB值為[0,104,55]會(huì)產(chǎn)生一種什么顏色挠铲?
認(rèn)真的思考一下冕屯,你也許會(huì)說(shuō)是一種藍(lán)綠色或者綠色,但那是錯(cuò)的拂苹。原來(lái)安聘,你所看到的是深綠色。
另外兩種比較常見(jiàn)的顏色空間是HSV和YUV瓢棒。
HSV搞挣,使用色調(diào),飽和度和亮度來(lái)直觀的存儲(chǔ)顏色值音羞。你可以把這三個(gè)部分這樣來(lái)看:
·色調(diào)就是顏色
·飽和度就是這個(gè)顏色有多么的飽滿
·值就是顏色的亮度有多亮
在這種顏色空間中囱桨,如果你發(fā)現(xiàn)自己并不知道HSV的值,那么通過(guò)它的三個(gè)值嗅绰,可以很容易的相像出大概是什么顏色舍肠。
RGB和HSV顏色空間的區(qū)別是很容易理解的,請(qǐng)看下面的圖像:
YUV是另外一種常見(jiàn)的顏色空間窘面,電視機(jī)使用的就是這種方式翠语。
最開始的時(shí)候,電視機(jī)只有灰階空間一種顏色通道财边。后來(lái)肌括,當(dāng)彩色電影出現(xiàn)后,就有了2種通道酣难。當(dāng)然谍夭,如果你想在本教程中使用YUV,那么你需要去研究更多關(guān)于YUV和其它顏色空間的相關(guān)知識(shí)憨募。
NOTE:同樣的顏色空間紧索,你也可以使用不同的方法表示顏色。比如16位RGB模式,可以使用5個(gè)字節(jié)存儲(chǔ)R,6個(gè)字節(jié)存儲(chǔ)G父款,5個(gè)字節(jié)存儲(chǔ)B。
為什么用6個(gè)字節(jié)存儲(chǔ)綠色媳危,5個(gè)字節(jié)存儲(chǔ)藍(lán)色?這是一個(gè)有意思的問(wèn)題冈敛,答案就是因?yàn)檠矍虼ΑH祟惖难矍驅(qū)G色比較敏感,所以人類的眼球更空間分辨出綠色的顏色值變化莺债。
3,坐標(biāo)系統(tǒng)
既然一個(gè)圖形是由像素構(gòu)成的平面地圖滋觉,那么圖像的原點(diǎn)需要說(shuō)明一下。通常原點(diǎn)在圖像的左上角齐邦,Y軸向下椎侠;或者原點(diǎn)在圖像的左下,Y軸向上措拇。
沒(méi)有固定的坐標(biāo)系統(tǒng)我纪,蘋果在不同的地方可能會(huì)使用不同的坐標(biāo)系。
目前丐吓,UIImage和UIView使用的是左上原點(diǎn)坐標(biāo)浅悉,Core Image和Core Graphics使用的是左下原點(diǎn)坐標(biāo)。這個(gè)概念很重要券犁,當(dāng)你遇到圖像繪制倒立問(wèn)題的時(shí)候你就知道了术健。
4,圖形壓縮
這是在你開始編寫代碼前的最后一個(gè)需要了解的概念了!原圖的每一個(gè)像素都被存儲(chǔ)在各自的內(nèi)存中粘衬。
如果你使用一張8像素的圖形做運(yùn)算荞估,它將會(huì)消耗810^6像素4比特/像素=32兆字節(jié)內(nèi)存。關(guān)注一下數(shù)據(jù)稚新!
這就是為什么會(huì)出現(xiàn)jpeg,png和其它圖形格式的原因勘伺。這些都是圖形壓縮格式。
當(dāng)GPU在繪制圖像的時(shí)候褂删,會(huì)使用大量?jī)?nèi)存把圖像的原始尺寸進(jìn)行解壓縮飞醉。如果你的程序占用了過(guò)多的內(nèi)存,那么操作系統(tǒng)會(huì)將進(jìn)程殺死(程序崩潰)屯阀。所以請(qǐng)確定你的程序使用較大的圖像進(jìn)行過(guò)測(cè)試缅帘。
關(guān)注一下像素
圖片加載到你的手機(jī)上會(huì)看到如下的圖像:
在控制臺(tái),你會(huì)看到如下的輸出:
當(dāng)前的程序可以加載這張幽靈的圖像难衰,并得到圖像的所有像素值股毫,打印出每個(gè)像素的亮度值到日志中。
亮度值是神馬召衔?它就是紅色铃诬,綠色和藍(lán)色通過(guò)的平均值。