Quartz2D的使用

本文的主要内容:

1.drawRect方法的使用

2.常见的图形的绘制:线条、多边形、圆

3.绘图状态的设置;文字的颜色、线宽等

4.图形上下文状态的保存与恢复(图形上下文栈)

5.图片的裁剪、截图

一、Quartz2D的简单介绍:

Quartz2D是一个二维绘图引擎,同时支持iOSMac OS X系统(跨平台, C语言的)。包含在Core Graphics框架中。

Quartz2D能完成的工作

绘制图形 :线条\三角形\矩形\\弧等

绘制文字

绘制\生成图片(图像)

读取\生成PDF

截图\裁剪图片(具体裁剪操作步骤详见下面流程及代码)

自定义UI控件(自定义View,通过Quartz 2D绘制自己的控件,通过继承UIView,重写drawRect方法实现控件上绘制各种内容

数据类型和函数基本都是以CG作为前缀的:

CGContextRef

CGPathRef

CGContextStrokePath(ctx)

二、Quartz 2D绘图的主要步骤:

     1.获取图形上下文对象

     2.向图形上下文对象中添加路径

     3.渲染(把图形上下文中的图形会通知到对应的设备上)

图形上下文(Graphics Context):是一个CGContextRef类型的数据

         主要包含如下信息

   绘图路径(各种各样的图形)

   绘图状态(颜色、线宽、样式、旋转、缩放、平移、图片裁剪区域等)

   输出目标(绘制到什么地方去?UIView、图片、pdf、打印机等)

          

     (输出目标可以是PDF文件、Bitmap或者显示器的窗口上)

Quartz2D提供了以下几种类型的Graphics Context

    Bitmap Graphics Context

    PDF Graphics Context

    Window Graphics Context

    Layer Graphics Context(UI控件)

    Printer Graphics Context

        

三、使用 Quartz2D绘图有以下两种方式:

方式一:直接调用 Quartz2D API进行绘图(代码量比较大,功能全面)

   步骤:

   1>获取绘图上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

   2>把图形绘制到绘图上下文上(以一条线段为例)

    CGContextMoveToPoint(ctx,10,10);

    CGContextAddLineToPoint(ctx,100,100);

   3>把绘图上下文上的图形渲染到对应的设备上

    CGContextStrokePath(ctx); //StrokeXxxx表示画线(边线)(空心图形)

    //CGContextFillPath(ctx);  //FillXxx表示画填充的图形(实心图形)


方式二:调用 UIKit框架封装好的 API进行绘图(代码使用相对简单)UIBezierPath对象

   只对部分 Quartz2D API 做了封装

   对于没有封装的功能只能调用 Quartz2D原生 API

   比如:画图片、文字到控件上。(UIKit已经封装好了)

   步骤:

   1.获取图形上下文对象

   2.创建 UIBezierPath对象

   3. UIBezierPath对象中绘制图形

   4. UIBezierPath对象添加到上下文中

   5.把上下文对象渲染到设备上

   注意: UIBezierPath对象可以独立使用无需手动获取图形上下文对象,此处为了更好的理解图形上下文对象所以暂时还是采用手动获取图形上下文对象的方式来绘图。

// 1. 获取"图形上下文"
CGContextRef ctx = UIGraphicsGetCurrentContext();

// 2. 创建 UIBezierPath 对象
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 50)];
[path addLineToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(30, 250)];
[path closePath];

// 3. 把路径对象添加到上下文中
CGContextAddPath(ctx, path.CGPath);

// 4. 渲染
CGContextDrawPath(ctx, kCGPathStroke);

- (void)drawRect:(CGRect)rect {
    
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 画椭圆
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 200, 130)];
    CGContextAddPath(ctx, path.CGPath);
    
    // 画圆
    UIBezierPath *path1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 180, 100, 100)];
    CGContextAddPath(ctx, path1.CGPath);
    
    // 设置线宽
    CGContextSetLineWidth(ctx, 5);
    // 设置颜色
    [[UIColor redColor] setFill];
    [[UIColor blueColor] setStroke];
    
    CGContextDrawPath(ctx, kCGPathFillStroke);
}

为什么要实现drawRect:方法才能绘图到view上?

因为在drawRect:方法中才能取得跟view相关联的图形上下文


drawRect:方法在什么时候被调用?

view第一次显示到屏幕上时(加到UIWindow上显示出来)

重绘的时候:调用viewsetNeedsDisplay或者setNeedsDisplayInRect:

drawRect方法小结:

     - view第一次被显示的时候调用(调用一次)

     -重绘事件被触发的时候调用

     -不要手动去调用这个方法,否则可能无法正确的获取绘图上下文

     -手动调用重绘方法 setNeedsDisplay或者 setNeedsDisplayInRect:

     -参数 rect表示当前 UIView bounds

     -为什么要在 - (void)drawRect:(CGRect)rect方法中进行绘图

           -只有在这个方法中才能获取当前 View的绘图上下文

几种不的渲染方式:

    -空心 StrokePath

    -实心 FillPath EOFillPath (填充)

   填充一个路径的时候,路径里面的子路径都是独立填充的。假如是重叠的路径,决定一个点是否被填充,有两种规则

   1nonzero winding number rule(非零绕数规则),假如一个点被从左到右跨过,计数器+1,从右到左跨过,计数器-1,最后,如果结果是0,那么不填充,如果是非零,那么填充。

   2even-odd rule(奇偶规则),假如一个点被跨过1次,被跨过了奇数次,那么要被填充,被跨过偶数次则不填充,和方向没有关系

even-odd rule:奇偶填充规则

           绘制顺序为 1->2->3

参考代码:
- (void)drawRect:(CGRect)rect {
    
    // 1. 获取"图形上下文"
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 200, 100)];
    
    UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(200, 150) radius:80 startAngle:0 endAngle:M_PI * 2 clockwise:1];
    
    UIBezierPath *path2 = [UIBezierPath bezierPathWithRect:CGRectMake(250, 30, 20, 200)];
    
    CGContextAddPath(ctx, path2.CGPath);
    CGContextAddPath(ctx, path1.CGPath);
    CGContextAddPath(ctx, path.CGPath);
    
    // 说明: 被覆盖过奇数次的点填充, 被覆盖过偶数次的点不填充
    CGContextDrawPath(ctx, kCGPathEOFill);
}

nonzero winding number rule:非零绕数规则

             

当一个点被从左到右覆盖过标记为1,从右到左覆盖过标记为-1,当标记为0的时候不填充,其他则填充

简单总结,这个规则与方向有关,与次数无关

- (void)drawRect:(CGRect)rect {
    
    // 1. 获取"图形上下文"
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150) radius:100 startAngle:0 endAngle:M_PI * 2 clockwise:1];
    
    UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150) radius:50 startAngle:0 endAngle:M_PI * 2 clockwise:0];
    
    CGContextAddPath(ctx, path1.CGPath);
    CGContextAddPath(ctx, path.CGPath);
    
    // 默认填充模式: nonzero winding number rule(非零绕数规则)从左到右跨过, +1。从右到左跨过, -1。最后如果为0, 那么不填充, 否则填充
    CGContextDrawPath(ctx, kCGPathFill);
    
}
绘制饼状图:
      
思路:
构建数据,NSArray *data = @[@30, @15, @5, @17, @3, @10, @20];。
根据数据个数绘制“扇形”(弧)
注意:
每个弧的起始、结束弧度都是不一样的
每次绘制完毕一个弧以后都要重新设置下一次的起始弧度为当前的结束弧度
本次绘制的结束弧度,为起始弧度+本次的弧度

参考代码:(第一阶段)
- (void)drawRect:(CGRect)rect {
    // 获取图形上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 饼状图
    // 1. 构建数据
    //NSArray *data = @[@(30), @(15), @(5), @(17), @(3), @(10), @(20)];
    NSArray *data = @[@30, @15, @5, @17, @3, @10, @20];
    
    // 2. 有几个数据, 就要绘制几次弧, 每次的弧度要根据每个数据所占的百分比来计算
    for (int i = 0; i < data.count; i++) {
        // 计算当前数据所占的百分比
        CGFloat percentage = [data[i] floatValue] / 100.0;
        // 计算本次要绘制的弧度
        CGFloat radian = percentage * M_PI * 2;
        // 开始绘制
        // 圆心点
        CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
        // 半径
        CGFloat radius = 150;
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:radian clockwise:1];
        // 把路径添加到上下文中
        CGContextAddPath(ctx, path.CGPath);
        
        // 关闭路径后, 就可以看到每次绘制的效果了
        CGContextClosePath(ctx);
        
        // 渲染
        CGContextDrawPath(ctx, kCGPathStroke);
    }
    
}

参考代码:(第二阶段)

- (void)drawRect:(CGRect)rect {
    // 获取图形上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 饼状图
    // 1. 构建数据
    //NSArray *data = @[@(30), @(15), @(5), @(17), @(3), @(10), @(20)];
    NSArray *data = @[@30, @15, @5, @17, @3, @10, @20];
    CGFloat startRadian = 0;
    
    // 2. 有几个数据, 就要绘制几次弧, 每次的弧度要根据每个数据所占的百分比来计算
    for (int i = 0; i < data.count; i++) {
        // 计算当前数据所占的百分比
        CGFloat percentage = [data[i] floatValue] / 100.0;
        // 计算本次要绘制的弧度
        CGFloat radian = percentage * M_PI * 2 + startRadian;
        // 开始绘制
        // 圆心点
        CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
        // 半径
        CGFloat radius = 150;
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startRadian endAngle:radian clockwise:YES];
        [path addLineToPoint:center];
        // 把路径添加到上下文中
        CGContextAddPath(ctx, path.CGPath);
        // 随机一个颜色
        [[UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0] set];
        // 渲染
        CGContextDrawPath(ctx, kCGPathFill);
        startRadian = radian;
    }
}

绘制柱状图:

     

1.构建数据, NSArray *data = @[@300, @150.65, @55.3, @507.7, @95.8, @700, @650.65];
2.根据数据的个数绘制柱状图。
3.计算每个柱子的 x,y,w,h 即可
方案1:手动指定柱状图的宽度和间距:
- (void)drawRect:(CGRect)rect {
    
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    NSArray *data = @[@300, @150.65, @55.3, @507.7, @95.8, @700, @650.65];
    
    CGFloat totalH = rect.size.height;
    
    for (int i = 0; i < data.count; i++) {
        CGFloat h = [data[i] intValue] / 1000.0 * totalH;
        CGFloat w = 20;
        CGFloat margin = 10;
        CGFloat y = totalH - h;
        CGFloat x = margin + i * (w + margin);
        
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, w, h)];
        
        [[UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0] set];
        
        CGContextAddPath(ctx, path.CGPath);
        
        CGContextDrawPath(ctx, kCGPathFill);
    }
    
}

方案2:平均计算每个 bar 的宽度
- (void)drawRect:(CGRect)rect {
    
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    NSArray *data = @[@300, @150.65, @55.3, @507.7, @95.8, @700, @650.65,@300, @150.65, @55.3, @507.7, @95.8, @700, @650.65];
    
    CGFloat totalH = rect.size.height;
    CGFloat totalW = rect.size.width;
    for (int i = 0; i < data.count; i++) {
        CGFloat h = [data[i] intValue] / 1000.0 * totalH;
        CGFloat w = totalW / (data.count + data.count - 1);
        CGFloat y = totalH - h;
        CGFloat x =  i * (w * 2);
        
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, w, h)];
        
        [[UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0] set];
        
        CGContextAddPath(ctx, path.CGPath);
        
        CGContextDrawPath(ctx, kCGPathFill);
    }
    
}

绘制下载进度条:

         

思路:
在控制器中将 slider 的值传递给自定义 view
在自定义 View中,根据传递过来的值绘制弧。
创建一个与自定义 view 一样大小的 label 来显示下载进度
//
//  SteveZDownloadView.m
//  01下载进度条
//
//  Created by steve zhao
//  Copyright (c) 2015年 czbk. All rights reserved.
//

#import "SteveZDownloadView.h"

@interface SteveZDownloadView ()
@property (nonatomic, strong) UILabel *lblMsg;
@end


@implementation SteveZDownloadView

- (UILabel *)lblMsg
{
    if (_lblMsg == nil) {
        _lblMsg = [[UILabel alloc] init];
        _lblMsg.textColor = [UIColor blueColor];
        _lblMsg.textAlignment = NSTextAlignmentCenter;
        [self addSubview:_lblMsg];
    }
    return _lblMsg;
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.lblMsg.frame = self.bounds;
}

- (void)setProgress:(CGFloat)progress
{
    _progress = progress;
    self.lblMsg.text = [NSString stringWithFormat:@"%.2f%%", progress * 100];
    // 重新绘制
    [self setNeedsDisplay];
    
}
- (void)drawRect:(CGRect)rect {
    // Drawing code
    // 1.
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    
    // 2.
    CGFloat x = rect.size.width * 0.5;
    CGFloat y = rect.size.height * 0.5;
    CGPoint centerP = CGPointMake(x, y);
    
    CGFloat radius = MIN(rect.size.width, rect.size.height) * 0.5 - 10;
    
    CGFloat startAngle = -M_PI_2;
    CGFloat endAngle = M_PI * 2 * self.progress - M_PI_2;
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:centerP radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
    [path addLineToPoint:centerP];
    [path closePath];
    
    
    // 3.
    CGContextAddPath(ctx, path.CGPath);
    
    CGContextSetLineWidth(ctx, 10);
    CGContextSetLineCap(ctx, kCGLineCapRound);
    [[UIColor greenColor] set];
    
    // 4.
    CGContextDrawPath(ctx, kCGPathFill);
}
@end

Quartz2D api常用拼接路径函数

新建一个起点
void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)

添加新的线段到某个点
void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)

添加一个矩形
void CGContextAddRect(CGContextRef c, CGRect rect)

添加一个椭圆
void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)

添加一个圆弧
void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y,
                     CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
Mode参数决定绘制的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)

绘制空心路径
void CGContextStrokePath(CGContextRef c)

绘制实心路径
void CGContextFillPath(CGContextRef c)

提示:一般以CGContextDraw、CGContextStroke、CGContextFill开头的函数,都是用来绘制路径的

利用矩阵操作,能让绘制到上下文中的所有路径一起发生变化
缩放
void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)

旋转
void CGContextRotateCTM(CGContextRef c, CGFloat angle)

平移
void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)

注意 : 此处是先把上下文旋转、缩放、平移了, 然后后面再绘制的所有图形就都是在旋转、缩放、平移之后的上下文中进行了。
矩形操作仿射变换:
// 先把上下文旋转了, 然后再绘制图形, 所以后面绘制的所有图形就都旋转了。
CGContextRotateCTM(ctx, M_PI_4 * 0.5);
CGContextTranslateCTM(ctx, 80, 50);
CGContextScaleCTM(ctx, 0.7, 0.5);
Current transformation matrix (CTM)当前变换矩阵

四、图形上下文栈的基本操作

0.图形上下文栈是什么?

   每一个图形上下文对象都包含一个结构,这个栈结构用来存储当前图形上下文的状态信息。

   (每个图形上下文对象中都包含:1>“图形状态”;2>路径信息;3>输出目标)

1.为什么要学习图形上下文栈?(需求如下的时候)

   1.第一次画图后修改了颜色,后面的图形要再恢复到原来的颜色

   2.第一次画图的时候旋转了上下文,后面的图形不要旋转

   3.第一次画图的时候修改了线宽,线头样式等,后面画图的时候不要这些样式,要默认的样子

总结:前面绘图的时候修改了上下文,后面绘图的时候要再次使用被修改前的上下文对象


例如

1、先绘制图形,设置颜色,线宽,旋转。
2、然后再绘制其他的图形。(其他图形默认也会使用相同的设置(颜色、线宽、旋转等))
解决:
1. 在第一次绘制图形前,保存上下文。
2. 在第二次绘制图形前,恢复上下文。

- (void)drawRect:(CGRect)rect {
    // Drawing code
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 保存上下文
    CGContextSaveGState(ctx);
    
    // 先把上下文旋转了, 然后再绘制图形, 所以后面绘制的所有图形就都旋转了。
    CGContextRotateCTM(ctx, M_PI_4 * 0.5);
    CGContextTranslateCTM(ctx, 80, 50);
    CGContextScaleCTM(ctx, 0.7, 0.5);
    
    UIBezierPath *path1 = [UIBezierPath bezierPathWithRect:CGRectMake(30, 10, 50, 50)];
    
    UIBezierPath *path2 = [[UIBezierPath alloc] init];
    [path2 moveToPoint:CGPointMake(80, 50)];
    [path2 addLineToPoint:CGPointMake(150, 150)];
    
    UIBezierPath *path3 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 50, 100, 100)];
    
    [[UIColor redColor] set];
    CGContextSetLineWidth(ctx, 8);
    
    CGContextAddPath(ctx, path1.CGPath);
    CGContextAddPath(ctx, path2.CGPath);
    CGContextAddPath(ctx, path3.CGPath);
    
    CGContextDrawPath(ctx, kCGPathStroke);
    
    // 恢复上下文
    CGContextRestoreGState(ctx);
    
    UIBezierPath *path4 = [[UIBezierPath alloc] init];
    [path4 moveToPoint:CGPointMake(80, 50)];
    [path4 addLineToPoint:CGPointMake(150, 150)];
    
    CGContextAddPath(ctx, path4.CGPath);
    CGContextDrawPath(ctx, kCGPathStroke);
}

将当前【图形上下文】中的绘图状态信息保存到

void CGContextSaveGState(CGContextRef c)

将栈顶的绘图状态出栈,替换掉当前的图形上下文中的绘图状态

void CGContextRestoreGState(CGContextRef c)

五、Quartz2D的内存管理

使用含有“Create”“Copy”的函数创建的对象,使用完后必须释放,否则将导致内存泄露

使用不含有“Create”“Copy”的函数获取的对象,则不需要释放

如果retain了一个对象,不再使用时,需要将其release

可以使用Quartz2D的函数来指定retainrelease一个对象。例如,如果创建了一个CGColorSpace对象,则使用函数CGColorSpaceRetainCGColorSpaceReleaseretainrelease对象。

也可以使用Core FoundationCFRetainCFRelease。注意不能传递NULL值给这些函数

演示通过CGMutablePathRef实现绘图。通过 Product -> Analyze来进行静态分析。

*通过创建路径对象的方式来绘图

1>绘图方式

-方式一: 是直接把要绘制的图形缓存到"上下文",然后再把上下文中的内容渲染到对应的设备上

-方式二: 先把图形缓存到"路径对象",然后再把路径对象添加到上下文对象中, 最后再把上下文对象渲染到对应的设备上

-相关类: CGPathRef CGMutablePathRef

     -基本使用步骤:

1>创建一个路径对象

2>拼接路径到对应的路径对象中

3>把路径对象添加到上下文对象中

4>渲染

 <span style="font-size:10px;">- (void)drawRect:(CGRect)rect
 {
 // 1. 获取当前绘图上下文
 CGContextRef ctx = UIGraphicsGetCurrentContext();
 
 // 2. 创建画线的 path 对象
 CGMutablePathRef linePath = CGPathCreateMutable();
 // 2.1 拼接路径
 CGPathMoveToPoint(linePath, NULL, 50, 50);
 CGPathAddLineToPoint(linePath, NULL, 150, 150);
 CGPathAddLineToPoint(linePath, NULL, 100, 50);
 // 2.2 把路径添加到上下文对象中
 CGContextAddPath(ctx, linePath);
 
 // 3. 创建一个画圆的路径
 CGMutablePathRef circlePath = CGPathCreateMutable();
 // 3.1 添加路径
 CGPathAddArc(circlePath, NULL, 150, 150, 50, 0, M_PI * 2, 0);
 // 3.2 把路径添加到上下文中
 CGContextAddPath(ctx, circlePath);
 
 // 4. 渲染上下文
 CGContextStrokePath(ctx);
 
 // 5. 释放资源
 CGPathRelease(linePath);
 CGPathRelease(circlePath);
 }</span>

内存管理总结:

-使用Path 对象时的内存管理问题:

    1> 凡是遇到 retain copy create 出的对象, 都需要进行 release

    2> 但是CGPathCreateMutable()不是 OC方法, 所以不是调用 某个对象的 release方法

    3> CGXxxxxCreate 对应的就有 CGXxxxxRelease

    4> 通过 CFRelease(任何类型);可以释放任何类型。

六、实际开发中常用

绘制文字

通过 UIKit 框架来绘制
[str drawAtPoint:]
[str drawInRect:]
// 在某个点的位置开始绘制一段文字
NSString *str = @"你好中国, Hello China.";
NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowColor = [UIColor yellowColor];
shadow.shadowOffset = CGSizeMake(3, 3);
NSDictionary *attrs = @{
                        NSFontAttributeName : [UIFont systemFontOfSize:90],
                        NSForegroundColorAttributeName : [UIColor redColor],
                        NSUnderlineStyleAttributeName : @1,
                        NSShadowAttributeName : shadow,
                        NSStrokeWidthAttributeName : @1
                        };

[str drawAtPoint:CGPointMake(50, 50) withAttributes:attrs];
NSString *str = @"Hello World ! ";

NSShadow *shadow = [[NSShadow alloc] init];
// 设置模糊度
shadow.shadowBlurRadius = 5;
// 设置颜色
shadow.shadowColor = [UIColor greenColor];
// 设置偏移
shadow.shadowOffset = CGSizeMake(15, 15);
NSDictionary *attrs = @{
                        NSForegroundColorAttributeName : [UIColor redColor],
                        NSFontAttributeName : [UIFont systemFontOfSize:50],
                        NSShadowAttributeName : shadow
                        };
[str drawAtPoint:CGPointZero withAttributes:attrs];
绘制图片:

UIImage *imgIcon = [UIImage imageNamed:@"002"];
[imgIcon drawAtPoint:CGPointMake(20, 20)];
[imgIcon drawInRect:rect]; // 拉伸
[imgIcon drawAsPatternInRect:rect]; //平铺
设置 UIView 的背景色为某个图片的屏幕效果。通过[UIColor colorWithXxxxxx:图片]来实现。
//  在 UIView 上绘制一张图片

- (void)drawRect:(CGRect)rect {
    
    //    UIImage *imgIcon = [UIImage imageNamed:@"002"];
    //    //[imgIcon drawAtPoint:CGPointMake(20, 20)];
    //    //[imgIcon drawInRect:rect];
    //    [imgIcon drawAsPatternInRect:rect];
}

自定义一个 View 模拟图片框

1、介绍创建图片框的时候如果直接使用 initWithImage:那么创建好的图片框大小将与图片的大小一致。

2、使用 UIImageView 实现显示图片,点击按钮切换图片。然后通过继承自一个 UIView 实现类似的功能(模拟 UIImageView

3、介绍从媒体库中拖拽一个图片到 view 上(默认大小为图片大小、允许与用户交互、允许多点触摸)

设置 UIView 的背景为一张图片

1、通过[UIColor colorWithPatternImage:]实现

2、通过绘制一张图片到 UIView 上实现

裁剪图片的基本流程:

     

具体步骤:

   1.获取图形上下文

   2.在图形上下文中绘制一个要裁减的图形

   2.1创建一个图形的路径

   2.2把路径添加到上下文

    3.对上下文裁减   CGContextClipctx;

   4.图片绘制到上下文上

核心代码:

void CGContextClip(CGContextRef c)

将当前上下所绘制的路径裁剪出来(超出这个裁剪区域的都不能显示)

图片裁剪思路:

    1 在上下文中绘制一个要裁减的图形

    2》调用void CGContextClip(CGContextRef c)进行裁剪

    3》在裁剪好的上下文中再把图片绘制上去。(注意绘制图片的时候,必须绘制到已经裁剪出的图形位置,否则不显示)

<span style="font-size:10px;">// 图片裁剪
- (void)drawRect:(CGRect)rect {
    // 1. 获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 2. 绘制一个圆形
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 100, 100)];
    // 添加到上下文中
    CGContextAddPath(ctx, path.CGPath);
    // 裁剪上下文
    CGContextClip(ctx);
    
    // 3. 再绘制一个图片
    UIImage *imgIcon = [UIImage imageNamed:@"me"];
    [imgIcon drawAtPoint:CGPointMake(0, 0)];
}</span>

UIView 上显示一个裁剪后的图片

1、获取 UIView 的图形上下文对象

2、在图形上下文对象上绘制一个圆形

3、执行裁剪操作(裁剪的意思是告诉系统,将来只有在被裁减出的区域内绘制的图形才会显示)

4、把图片绘制到上下文上(直接调用 UIImage 对象的绘图方法即可)

直接裁剪图片并保存

       1、加载要裁减的图片

2、根据要裁剪的图片大小开启一个Bitmap 的图形上下文

3、在这个图形上下文上绘制一个圆形(这个圆的圆心应为这个图形上下文的中心点,半径应为这个图形上下文的最短的边的一半)

4、执行裁剪操作

5、把图片绘制到当前的图形上下文中(因为这个图形上下文的大小是按照要裁减的图片的大小来创建的,所以绘图的时候直接从(00)开始绘制即可)

6、从图形上下文中获取图片对象

7、将图片对象转换为 NSData 类型UIImagePNGRepresentation(img)

8、保存图片对象到目标位置(沙盒或者相册)

// 保存到相册中
// UIImageWriteToSavedPhotosAlbum(finalImg, nil, nil, nil);

UIImageWriteToSavedPhotosAlbum(finalImg, self, @selector(image:didFinishSavingWithError:contextInfo:), @"dfasfdsfadsf");

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    NSLog(@"%@", image);
    NSLog(@"%@", error);
    NSLog(@"%@", contextInfo);
}
// 把图片转换成一个NSData类型,保存到沙盒中
NSData *data = UIImagePNGRepresentation(finalImg);

// 获取沙盒路径
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName = [docPath stringByAppendingPathComponent:@"me.png"];

// 保存图片
[data writeToFile:fileName atomically:YES];

// 获取要裁剪的图片
UIImage *imgOriginal = [UIImage imageNamed:@"me"];

// 开启一个图片的绘图上下文
UIGraphicsBeginImageContextWithOptions(imgOriginal.size, NO, 0.0);
// 获取图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();

// 绘制一个圆
// 圆心
CGPoint centerP = CGPointMake(imgOriginal.size.width * 0.5, imgOriginal.size.height * 0.5);
// 半径
CGFloat radius = MIN(imgOriginal.size.width, imgOriginal.size.height) * 0.5;

UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:centerP radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];

//    // 设置圆的线宽
//    CGContextSetLineWidth(ctx, 5);
//    [[UIColor blueColor] set];

// 把路径添加到上下文中
CGContextAddPath(ctx, path.CGPath);

// 对当前上下文执行裁剪操作
CGContextClip(ctx);

// 把图片绘制到当前上下文中
[imgOriginal drawInRect:CGRectMake(0, 0, imgOriginal.size.width, imgOriginal.size.height)];

// 从上下文中把绘制好的图形取出来
UIImage *finalImg = UIGraphicsGetImageFromCurrentImageContext();

// 关闭绘图上下文
UIGraphicsEndImageContext();

// 把图片显示到图片框中
self.imgViewIcon.image = finalImg;

// 把图片转换成一个NSData类型
NSData *data = UIImagePNGRepresentation(finalImg);

// 获取沙盒路径
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName = [docPath stringByAppendingPathComponent:@"me.png"];

// 保存图片
[data writeToFile:fileName atomically:YES];

NSLog(@"ok");
NSLog(@"%@", fileName);

执行裁减保存图片的具体步骤:

1.开启一个图片上下文

UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);

2.获取这个刚刚开启的的图片上下文

CGContextRef *ctx = UIGraphicsGetCurrentContext();

3.向上下文只中绘制一个图形

4.对上下文执行裁减(裁剪的操作就像设置线宽、颜色、旋转、等操作一样,也是属于状态信息)

5.绘制图片到上下文

6.1从上下文中获取到刚才绘制好的图形(Uiimage的方式获取)

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

6.2结束当前上下文   UIGraphicsEndImageContext();

7.UIImage保存到相册

UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo);

8.UIImage保存到沙盒

裁剪一个带圆环的图片

// 加载要裁剪的图片

// 创建一个比原始图片略大的基于 Bitmap 图形上下文

// 获取当前上下文

//开始绘制一个圆环

// 确定圆心

// 确定半径

// 开始绘制圆环路径

// 渲染

// 开始绘制图片

// 从绘图上下文中获取图片

// 结束图形上下文

// 保存到相

// 保存到沙盒

// 保存图片

// 开始裁剪带圆环的图片

- (IBAction)clipImage:(id)sender {
    // 加载要裁剪的图片
    UIImage *imgIcon = [UIImage imageNamed:@"me"];
    
    // 创建一个比原始图片略大的基于 Bitmap 的“图形上下文”
    CGFloat margin = 5;
    CGFloat ctxW = imgIcon.size.width + 2 * margin;
    CGFloat ctxH = imgIcon.size.height + 2 * margin;
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(ctxW, ctxH), NO, 0.0);
    // 获取当前上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // ------------------------------------ 开始绘制一个圆环
    // 确定圆心
    CGPoint centerP = CGPointMake(ctxW * 0.5, ctxH * 0.5);
    // 确定半径
    CGFloat radius = MIN(ctxW, ctxH) * 0.5 - margin;
    // 开始绘制圆环路径
    UIBezierPath *pathOutside = [UIBezierPath bezierPathWithArcCenter:centerP radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    [[UIColor redColor] set];
    CGContextSetLineWidth(ctx, 10); // 设置线宽
    CGContextAddPath(ctx, pathOutside.CGPath);
    // 渲染
    CGContextDrawPath(ctx, kCGPathStroke);
    
    // ------------------------------------ 开始裁剪一个圆
    UIBezierPath *pathInside = [UIBezierPath bezierPathWithArcCenter:centerP radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    CGContextAddPath(ctx, pathInside.CGPath);
    // 开始裁剪
    CGContextClip(ctx);
    
    // ------------------------------------ 开始绘制图片
    [imgIcon drawAtPoint:CGPointMake(margin, margin)];
    
    
    // 从绘图上下文中获取图片
    UIImage *clipedImage =  UIGraphicsGetImageFromCurrentImageContext();
    // 结束图形上下文
    UIGraphicsEndImageContext();
    self.imgIcon.image = clipedImage;
    // 保存到相册
    UIImageWriteToSavedPhotosAlbum(clipedImage, nil, nil, nil);
    // 保存到沙盒
    // 把图片转换成一个NSData类型
    NSData *data = UIImagePNGRepresentation(clipedImage);
    // 获取沙盒路径
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *fileName = [docPath stringByAppendingPathComponent:@"me.png"];
    
    // 保存图片
    [data writeToFile:fileName atomically:YES];
    NSLog(@"%@", docPath);
    
    
}

添加圆环的思路:

先画一个圆然后进行渲染,在进行裁减,最后在渲染

0.先加载需要裁剪的图片

1.开启一个图片上下文

2.获取图形上下文

3.绘制一个圆环

4.绘制一个需要裁剪的圆

5.进行裁剪

6.图片绘制到上下文中

7.从上下文中取出图片

8.图片设置到图片框中

9.保存到相册和沙盒


添加水印:

开启一个基于位图的图形上下文

void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)

从上下文中取得图片(UIImage

UIImage* UIGraphicsGetImageFromCurrentImageContext();

结束基于位图的图形上下文

void     UIGraphicsEndImageContext();

添加水印基本思路:

1、添加文字水印

1> 创建位图上下文

2> 把图片画上去

3> 把文字画上去

4从上下文中取出图片

2、添加图片水印

1> 创建位图上下文

2> 把图片画上去

3> 加载 logo 图片(水印图片),把水印图片也画上去

4从上下文中取出图片

//按钮的单击事件, 生成带水印的图片
- (IBAction)buttonClick:(id)sender {
    
    // 加载大图
    UIImage *imgBig = [UIImage imageNamed:@"dst2"];
    
    
    // 开启绘图上下文(图片的绘图上下文),与大图大小一致
    UIGraphicsBeginImageContextWithOptions(imgBig.size, NO, 0.0);
    //CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 把大图绘制到上下文上
    [imgBig drawInRect:CGRectMake(0, 0, imgBig.size.width, imgBig.size.height)];
    
    // 加载小图(水印图片)
    UIImage *imgLogo = [UIImage imageNamed:@"logo"];
    
    // 把小图绘制到上下文上
    CGFloat margin = 20;
    CGFloat logoW = imgLogo.size.width ;
    CGFloat logoH = imgLogo.size.height;
    CGFloat logoX = imgBig.size.width - margin - logoW;
    CGFloat logoY = imgBig.size.height - margin - logoH;
    [imgLogo drawInRect:CGRectMake(logoX, logoY, logoW, logoH)];
    
    //    NSString *str = "aaaaa";
    //    [str drawAtPoint:<#(CGPoint)#> withAttributes:<#(NSDictionary *)#>]
    
    
    // 把绘制好的图形从上下文中获取
    UIImage *imgWatermark = UIGraphicsGetImageFromCurrentImageContext();
    
    // 关闭上下文
    UIGraphicsEndImageContext();
 
    // 把取出来的图片保存起来
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *fileName = [docPath stringByAppendingPathComponent:@"watermark.png"];
    
    NSData *data = UIImagePNGRepresentation(imgWatermark);
    [data writeToFile:fileName atomically:YES];
    NSLog(@"ok");
    NSLog(@"%@", fileName);
    
}

屏幕截图:

核心代码

- (void)renderInContext:(CGContextRef)ctx;

调用某个viewlayerrenderInContext:方法即可

截图基本思路:

  1、获取控件的 layer 对象

2、调用 layer 对象的 renderInContext:方法渲染到上下文中

**注意:UISegmentedControl 渲染时有问题

因为绘图操作是 CPU密集型操作(会大量使用到 CPU),所以如果可以使用普通 UIView 来代替的就不要自己进行绘图






  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值