Quartz2D-2

1.矩阵操作

1.1.平移

- (void)drawRect:(CGRect)rect {
    //1.获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.矩阵操作
    //X轴偏移量,Y轴偏移量
    CGContextTranslateCTM(ctx, 100, 100);
    //3.绘制图形,并添加到上下文
    //绘制圆
    CGContextAddArc(ctx, rect.size.width*0.5, rect.size.height*0.5, 100, 0, M_PI*2, 1);
    //绘制直线
    CGContextMoveToPoint(ctx, 0, 0);
    CGContextAddLineToPoint(ctx, rect.size.width, rect.size.height);
    //设置属性
    CGContextSetLineWidth(ctx, 10);
    //4.渲染
    CGContextStrokePath(ctx);
}

1.2.旋转

#define angle2Arc(angle) (angle * M_PI / 180)
- (void)drawRect:(CGRect)rect {
    //1.获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.矩阵操作
    //旋转弧度
    CGContextRotateCTM(ctx, angle2Arc(10));
    //3.绘制图形,并添加到上下文
    //绘制圆
    CGContextAddArc(ctx, rect.size.width*0.5, rect.size.height*0.5, 100, 0, M_PI*2, 1);
    //绘制直线
    CGContextMoveToPoint(ctx, 0, 0);
    CGContextAddLineToPoint(ctx, rect.size.width, rect.size.height);
    //设置属性
    CGContextSetLineWidth(ctx, 10);
    //4.渲染
    CGContextStrokePath(ctx);
}

1.3.缩放

- (void)drawRect:(CGRect)rect {
    //1.获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.矩阵操作
    //X轴缩放比例,Y轴缩放比例
    CGContextScaleCTM(ctx, 0.5, 0.5);
    //3.绘制图形,并添加到上下文
    //绘制圆
    CGContextAddArc(ctx, rect.size.width*0.5, rect.size.height*0.5, 100, 0, M_PI*2, 1);
    //绘制直线
    CGContextMoveToPoint(ctx, 0, 0);
    CGContextAddLineToPoint(ctx, rect.size.width, rect.size.height);
    //设置属性
    CGContextSetLineWidth(ctx, 10);
    //4.渲染
    CGContextStrokePath(ctx);
}

1.4.注意

1.一定要先做矩阵操作,然后再绘制图形.(也就是矩阵操作在添加路径之前有效)
2.只会对矩阵操作之后并且在渲染之前绘制的图形有效.

2.图形上下文栈

2.1.通过绘图原理来理解图形上下文栈

当我们把上下文中的图形渲染到UIView上,其实就是把上下文中的图形搬到了UIView中,
上下文中没有了图形,但是上下文还在,上下文中的图形状态(颜色,线宽,样式)还在.
那我们还是可以继续画线的.

那图形上下文栈呢用来干什么呢?
其实就是我们想在操作图形上下文中之前,先把原始的图形上下文备份一下.
然后再操作,再渲染后,再把图形上下文恢复,恢复之后继续绘图就可以了.

2.2.栈的特性

先进后出

2.3.图形上下文栈操作

//入栈
void CGContextSaveGState(CGContextRef c)
  1.图形上下文栈中保存的仅仅是当前的绘图状态信息(颜色,线宽,样式,矩阵).
  2.跟上下文中的路径没有关系

//出栈
CGContextRestoreGState(ctx);
  1.将栈顶的绘图状态取出,替换掉当前的图形上下文中的绘图状态.
    (也就是说出栈操作就是在设置绘图状态而已)
  2.恢复的位置在渲染之前都是可以的.

2.4.解析代码

- (void)drawRect:(CGRect)rect {
    //1.获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //备份-入栈
    CGContextSaveGState(ctx);
    //3.绘制图形,并添加到上下文
    //绘制直线
    CGContextMoveToPoint(ctx, 0, 0);
    CGContextAddLineToPoint(ctx, rect.size.width, rect.size.height);
    //设置属性
    CGContextSetLineWidth(ctx, 10);
    [[UIColor redColor] set];
    //4.渲染
    CGContextStrokePath(ctx);
    //绘制圆
    CGContextAddArc(ctx, rect.size.width*0.5, rect.size.height*0.5, 100, 0, M_PI*2, 1);
    //恢复-出栈
    CGContextRestoreGState(ctx);
    //渲染
    CGContextStrokePath(ctx);
}
解析:
1.获得上下文就是准备了草稿纸
2.入栈:把当前上下文的状态拷贝一份保存到栈中
3.添加路径:往草稿纸中绘制路径
4.设置属性:修改当前上下文的状态信息
5.渲染:把上下文中的路径在设置状态后搬到UIView中显示.
6.再往上下文中绘制圆
7.出栈:把当前上下文中的状态替换成出栈的属性,并且删除栈顶提出的状态.
8.渲染:继续把上下文中的路径在设置状态后搬到UIView中显示.

3.Quartz2D内存管理

3.1.CGMutablePathRef路径方式问题

这段代码是存在内存泄露的问题。

- (void)drawRect:(CGRect)rect {
    //1.获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.绘制路径
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 100, 100);
    CGPathAddLineToPoint(path, NULL, 200, 200);
    //3将路径添加到上下文中
    CGContextAddPath(ctx, path);
    //4.渲染
    CGContextStrokePath(ctx);
}

3.2.C语言内存分析

3.2.1分析

Xcode --> Product --> Analyze


3.2.2查看问题


3.3.内存泄露解决方式

在代码中如果有用到C语言的方法获得一个对象。例如使用方法中带有 create,retain,copy这些词的方法获得的对象,我们一定要在不使用这个对象后进行release操作。

解决上边问题的方式

- (void)drawRect:(CGRect)rect {
    //1.获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.绘制路径
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 100, 100);
    CGPathAddLineToPoint(path, NULL, 200, 200);
    //3将路径添加到上下文中
    CGContextAddPath(ctx, path);
    //4.渲染
    CGContextStrokePath(ctx);
    //5.释放内存
    CGPathRelease(path);//第一种方式
    // CFRelease(path);//第二种方式
}

解析:
1.CGXxxCreate 对应就有 CGXxxRelease
2.通过CFRelease(任何类型),可以释放任何类型.

3.4.Analyze其它功能

可以分析出项目中类的生命周期的super调用的漏写。
例如:


4.绘制文字

4.1绘制文字

// 从某个点开始绘制,并且只绘制一行,超出区域不显示
- (void)drawRect:(CGRect)rect {
    //1.创建文字
    NSString *str = @"传智播客";
    //2.绘制文字
    //point:从哪个点开始绘制
    //attribute:文字属性,可以传nil(就是默认的属性)
    [str drawAtPoint:CGPointZero withAttributes:nil];
}
//在某个区域内绘制,会换行,超出区域不显示
- (void)drawRect:(CGRect)rect {
    //1.创建文字
    NSString *str = @"传智播客";
    //2.绘制文字
    //rect:在哪个区域内绘制
    //attribute:文字属性,可以传nil(就是默认的属性)
    [str drawInRect:CGRectMake(100, 100, 40, 60) withAttributes:nil];
}

4.2文字属性

文字属性在 UIKit 框架的 NSAttributedString.h 中

NSFontAttributeName  字体
NSParagraphStyleAttributeName  设置段落样式
NSForegroundColorAttributeName  字体颜色
NSBackgroundColorAttributeName  背景色
NSLigatureAttributeName 
NSKernAttributeName  调整字句 kerning 字句调整
NSStrikethroughStyleAttributeName  删除线
NSUnderlineStyleAttributeName  下划线
NSStrokeColorAttributeName  设置文字描边颜色,需要和NSStrokeWidthAttributeName设置描边宽度,这样就能使文字空心.
NSStrokeWidthAttributeName  设置描边宽度
NSShadowAttributeName 设置阴影,单独设置不好使,必须和其他属性搭配才好使。和这三个任一个都好使,NSVerticalGlyphFormAttributeName,NSObliquenessAttributeName,NSExpansionAttributeName
NSTextEffectAttributeName
NSAttachmentAttributeName
NSLinkAttributeName
NSBaselineOffsetAttributeName
NSUnderlineColorAttributeName  下划线颜色
NSStrikethroughColorAttributeName   删除线颜色
NSObliquenessAttributeName  设置字体倾斜
NSExpansionAttributeName  设置文本扁平化
NSWritingDirectionAttributeName
NSVerticalGlyphFormAttributeName 该属性所对应的值是一个 NSNumber 对象(整数)。0 表示横排文本。1 表示竖排文本。在 iOS 中,总是使用横排文本,0 以外的值都未定义。

5.绘制图片

5.1绘制图片-OC方式

//从某个点开始绘制-图片会按照原比例绘制
-(void)drawImage{
    //1.创建图片
    UIImage *image = [UIImage imageNamed:@"hero"];
    //2.绘制图片
    [image drawAtPoint:CGPointZero];
}
//在某个区域内进行绘制-图片会被拉伸或者压缩
-(void)drawImage:(CGRect)rect{
    //1.创建图片
    UIImage *image = [UIImage imageNamed:@"hero"];
    //2.绘制图片
    [image drawInRect:rect];
}
//会平铺在某个区域内.
-(void)drawImage:(CGRect)rect{
    //1.创建图片
    UIImage *image = [UIImage imageNamed:@"hero"];
    //2.绘制图片
    [image drawAsPatternInRect:rect];
}
-(void)drawImage:(CGRect)rect{
    //1.创建图片
    UIImage *image = [UIImage imageNamed:@"hero"];
    //注意: blendMode 是绘制图片的参数
    //2.绘制图片--从某个点开始绘制--原比例绘制
    [image drawAtPoint:CGPointZero blendMode:kCGBlendModeNormal alpha:0.3];
    //2.绘制图片--在某块区域内进行绘制--图片会被拉伸或者压缩
    // [image drawInRect:rect blendMode:kCGBlendModeNormal alpha:0.3];
}

5.2图片属性

 kCGBlendModeNormal,  正常;也是默认的模式。前景图会覆盖背景图
 kCGBlendModeMultiply,  正片叠底;混合了前景和背景的颜色,最终颜色比原先的都暗
 kCGBlendModeScreen,   滤色;把前景和背景图的颜色先反过来,然后混合
 kCGBlendModeOverlay,  覆盖;能保留灰度信息,结合kCGBlendModeSaturation能保留透明度信息,在imageWithBlendMode方法中两次执行drawInRect方法实现我们基本需求
 kCGBlendModeDarken,  变暗
 kCGBlendModeLighten,  变亮
 kCGBlendModeColorDodge, 颜色变淡
 kCGBlendModeColorBurn,  颜色加深
 kCGBlendModeSoftLight,  柔光
 kCGBlendModeHardLight,  强光
 kCGBlendModeDifference,  插值
 kCGBlendModeExclusion,  排除
 kCGBlendModeHue,  色调
 kCGBlendModeSaturation, 饱和度
 kCGBlendModeColor,  颜色
 kCGBlendModeLuminosity,  亮度
 
 //Apple额外定义的枚举
 //R: premultiplied result, 表示混合结果
 //S: Source, 表示源颜色(Sa对应透明度值: 0.0-1.0)
 //D: destination colors with alpha, 表示带透明度的目标颜色(Da对应透明度值: 0.0-1.0)
 
kCGBlendModeClear,  R = 0
kCGBlendModeCopy,  R = S
kCGBlendModeSourceIn,  R = S*Da
kCGBlendModeSourceOut,  R = S*(1 - Da)
kCGBlendModeSourceAtop,  R = S*Da + D*(1 - Sa)
kCGBlendModeDestinationOver,  R = S*(1 - Da) + D
kCGBlendModeDestinationIn,  R = D*Sa;能保留透明度信息
kCGBlendModeDestinationOut,  R = D*(1 - Sa)
kCGBlendModeDestinationAtop,  R = S*(1 - Da) + D*Sa
kCGBlendModeXOR,  R = S*(1 - Da) + D*(1 - Sa)
kCGBlendModePlusDarker,  R = MAX(0, (1 - D) + (1 - S))
kCGBlendModePlusLighter  R = MIN(1, S + D)(最后一种混合模式)

5.3绘制图片--C方式

注意:C的方式绘图会按照Quartz2D的坐标系来绘制.所以显示的图片都是反向的。

-(void)drawImageWithC:(CGRect)rect{
    //1.获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.加载图片
    UIImage *image = [UIImage imageNamed:@"dog"];
    //3.绘制图片
    // 在指定的区域绘制图片
    // 以填充方式绘制图片-图片会被拉伸或者压缩
    CGContextDrawImage(ctx, rect, image.CGImage);
}
-(void)drawImageWithC:(CGRect)rect{
    //1.获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.加载图片
    UIImage *image = [UIImage imageNamed:@"dog"];
    //3.绘制图片
    // 以平铺方式绘图
    //注意:这个rect 是设置图片的大小,我们一般给原比例就可以了
    CGContextDrawTiledImage(ctx, CGRectMake(0, 0, 100, 100), image.CGImage);
}

6.模拟ImageView

6.1.系统UIImageView的init 和 initWithImage的区别

init 方法创建的对象默认没有大小
initWithImage 方法创建的对象默认有大小

6.2.模拟ImageView

1.自定义一个UIView,起名CZImageView

2.头文件中声明image属性

@property(nonatomic,strong)UIImage *image;

3.将传入的图片绘制到设备上

- (void)drawRect:(CGRect)rect{
    //将图片绘制到屏幕上
    [self.image drawInRect:rect];
}

4.重写image的set方法,当传入图片时重绘界面

- (void)setImage:(UIImage *)image{
    _image = image;
    //重绘界面
    [self setNeedsDisplay];
}

5.自定义 initWithImage 方法

- (instancetype)initWithImage:(UIImage *)image{
    self = [super initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
    if (self) {
        self.image = image;
    }
    return self;
}

7.裁剪图片

7.1.裁剪上下文显示区域

- (void)drawRect:(CGRect)rect{
    //1.获得图片对象
    UIImage *image = [UIImage imageNamed:@"me"];
    //2.获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //3.绘制上下文显示区域
    CGContextAddArc(ctx, rect.size.width*0.5, rect.size.height*0.5, 150, 0, M_PI*2, 1);
    //4.裁剪
    CGContextClip(ctx);
    //5.绘制图片
    [image drawInRect:rect];
}

7.2注意:

裁剪的是上下文的显示区域,不是说裁剪的上下文,上下文该是多大就还是多大,就是哪一块需要显示改变了.

8.图片类型上下文

8.1基本使用

1.需求
绘制一个UIimage图片

2.使用方式

- (void)imageContext{
    //1.开启一个图片类型的上下文
    UIGraphicsBeginImageContext(CGSizeMake(300, 300));
    /*
     开启一个图片类型的上下文
     size : 上下文大小 -- 像素
     opaque : 不透明(上下文这张草稿纸是否透明) YES表示不透明 NO表示透明
     scale : 缩放 会缩放上下文 (重要参数) -- 可以根据屏幕像素比例生成想要的图片   0表示当前设置(方法内部做了判断)
     */
    // UIGraphicsBeginImageContextWithOptions(CGSizeMake(300, 300), YES, 1);
    //2.获得当前的图片类型上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    //3.绘制图形
    CGContextMoveToPoint(ctx, 0, 0);
    CGContextAddLineToPoint(ctx, 300, 300);
    
    //4.渲染
    CGContextStrokePath(ctx);
    
    //5.获得图片对象
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    //6.关闭上下文
    UIGraphicsEndImageContext();
    
    //使用我们绘制图片
    self.imageView.image = newImage;
}

8.2把图片存入沙盒

- (void)writeToFile:(UIImage *)image{
    
    //1.创建路径
    NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    filePath = [filePath stringByAppendingPathComponent:@"xx.png"];

    //2.把image保存为png图片
    NSData *pngdata = UIImagePNGRepresentation(image);
    
    /*
     把image保存为jpeg图片
     compressionQuality是质量,jpeg图片是可以压缩的
     */
    // NSData *jpegdata = UIImageJPEGRepresentation(image, 1);
    
    //3.通过NSData写入沙盒
    [pngdata writeToFile:filePath atomically:YES];

}

8.2裁剪图片

- (UIImage *)imageContext:(UIImage *)image{
    
    //1.开启一个图片类型的上下文
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    
    //2.获得当前的图片类型上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    //3.绘制裁剪图形
    CGContextAddArc(ctx, image.size.width*0.5, image.size.height*0.5, image.size.width*0.5, 0, M_PI*2, 1);
    //4.裁剪上下文
    CGContextClip(ctx);
    
    //5.渲染图片
    [image drawAtPoint:CGPointZero];
    
    //6.获得图片对象
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    //7.关闭上下文
    UIGraphicsEndImageContext();
    
    //8.返回裁剪后的图片
    return newImage;
}

8把图片保到相册

8.1使用

- (void)imageWriteToFile:(UIImage *)image{
    /*
     image : 要保存的图片
     completionTarget : 监听对象
     completionSelector : 监听回调方法
     contextInfo : 上下文
     */
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}

/*
 保存到相册的回调
 image : 保存的图片
 error : 保存信息
 contextInfo : 上下文 - 上边传什么这里就返回什么
 */
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
    if(error){
        NSLog(@"保存失败");
    }else{
        NSLog(@"保存成功");
    }
}

8.2访问相册权限问题

1弹出

2如果选择了NO,如何修改权限

8.3.报错问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容