zoukankan      html  css  js  c++  java
  • iOS开发之Quartz2D

    1、         Quartz2D概述及作用

    Quartz2D的API是纯C语言的,Quartz2D的API来自于Core Graphics框架。

    数据类型和函数基本都以CG作为前缀,比如:

    CGContextRef

    CGPathRef

    CGContextStrokePath(ctx);

    ……

    Quartz 2D是一个二维绘图引擎,同时支持iOS和Mac系统。

    Quartz 2D能完成的工作:

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

    绘制文字;

    绘制生成图片(图像);

    读取生成PDF;

    截图裁剪图片;

    自定义UI控件;

    … …

    2Quartz2DiOS开发中的价值

    为了便于搭建美观的UI界面,iOS提供了UIKit框架,里面有各种各样的UI控件,比如:

    UILabel:显示文字;

    UIImageView:显示图片;

    UIButton:同时显示图片和文字(能点击);

    … …

    利用UIKit框架提供的控件,拼拼凑凑,能搭建和现实一些简单、常见的UI界面。

    但是,有些UI界面极其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术将控件内部的结构画出来,自定义控件的样子。其实,iOS中大部分控件的内容都是通过Quartz2D画出来的。因此,Quartz2DiOS开发中很重要的一个价值是:自定义view(自定义UI控件)。

    3、图形上下文

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

    图形上下文的作用:

    (1)保存绘图信息、绘图状态

    (2)决定绘制的输出目标(绘制到什么地方去?)

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

    相同的一套绘图序列,指定不同的Graphics Context,就可将相同的图像绘制到不同的目标上。

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

    (1)Bitmap Graphics Context

    (2)PDF Graphics Context

    (3)Window Graphics Context

    (4)Layer Graphics Context

    (5)Printer Graphics Context

    4、自定义view

    如何利用Quartz2D自定义view?(自定义UI控件)如何利用Quartz2D绘制东西到view上?

    首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去。

    其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面。

    自定义view的步骤:

    (1)新建一个类,继承自UIView

    (2)实现- (void)drawRect:(CGRect)rect方法,然后在这个方法中

    (a)取得跟当前view相关联的图形上下文

    例如:CGContextRef ctx = UIGraphicsGetCurrentContext();

    (a) 绘制相应的图形内容

      例如:画1/4圆

        CGContextMoveToPoint(ctx, 100, 100);

        CGContextAddLineToPoint(ctx, 100, 150);

        CGContextAddArc(ctx, 100, 100, 50, -M_PI_2, M_PI, 1);

        CGContextClosePath(ctx);

       

        [[UIColor redColor] set];

    (b)利用图形上下文将绘制的所有内容渲染显示到view上面

      例如:CGContextFillPath(ctx);

    5drawRect

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

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

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

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

    (2)调用view的setNeedsDisplay或者setNeedsDisplayInRect:时。

    setNeedsDisplay常被调用来刷新View界面。

    绘图顺序:

    3、         drawRect:中取得的上下文

    在drawRect:方法中取得上下文后,就可以绘制东西到view上。View内部有个layer(图层)属性,drawRect:方法中取得的是一个Layer Graphics Context,因此,绘制的东西其实是绘制到view的layer上去了。View之所以能显示东西,完全是因为它内部的layer。

    4、         Quartz2D绘图的代码步骤

    第一步:获得图形上下文:

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    第二步:拼接路径(下面代码是搞一条线段):

    CGContextMoveToPoint(ctx, 10, 10);

    CGContextAddLineToPoint(ctx, 100, 100);

    第三步:绘制路径:

    CGContextStrokePath(ctx); // CGContextFillPath(ctx);

    7、常用拼接路径函数

    • 新建一个起点

    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)

    8、常用绘制路径函数

    • Mode参数决定绘制的模式

    void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)

    • 绘制空心路径

    void CGContextStrokePath(CGContextRef c)       

    • 绘制实心路径

    void CGContextFillPath(CGContextRef c)

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

    其他常用函数:

    设置线段宽度:

        CGContextSetLineWidth(ctx, 10);

       

    设置线段头尾部的样式:

        CGContextSetLineCap(ctx, kCGLineCapRound);

       

    设置线段转折点的样式:

        CGContextSetLineJoin(ctx, kCGLineJoinRound);

       

    设置颜色:

    CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);

    9、图形上下文栈的操作

    将当前的上下文copy一份,保存到栈顶(那个栈叫做”图形上下文栈”):

    void CGContextSaveGState(CGContextRef c)

    将栈顶的上下文出栈,替换掉当前的上下文(清空之前对于上下文设置):

    void CGContextRestoreGState(CGContextRef c)

    10、矩阵操作

    利用矩阵操作,能让绘制到上下文中的所有路径一起发生变化:

    缩放:

    void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)

    旋转:

    void CGContextRotateCTM(CGContextRef c, CGFloat angle)

    平移:

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

    11Quartz2D的内存管理

    关于内存管理,有以下原则:

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

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

    (3)如果retain了一个对象,不再使用时,需要将其release掉。

    (4)可以使用Quartz 2D的函数来指定retain和release一个对象。例如,如果创建了一个CGColorSpace对象,则使用函数CGColorSpaceRetain和CGColorSpaceRelease来retain和release对象。

    (5)也可以使用Core Foundation的CFRetain和CFRelease。注意不能传递NULL值给这些函数。

    11、    图片水印

    有时候,在手机客户端app中也需要用到水印技术,比如,用户拍完照片后,可以在照片上打个水印,标识这个图片是属于哪个用户的

    实现方式:

    利用Quartz2D,将水印(文字、LOGO)画到图片的右下角

    核心代码:

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

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

    从上下文中取得图片(UIImage):

    UIImage* UIGraphicsGetImageFromCurrentImageContext();

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

    void     UIGraphicsEndImageContext();

    例如:

    UIImage *bgImage = [UIImage imageNamed:@"scene"];

       

        // 上下文 : 基于位图(bitmap) ,  所有的东西需要绘制到一张新的图片上去

       

        // 1.创建一个基于位图的上下文(开启一个基于位图的上下文)

        // size : 新图片的尺寸

        // opaque : YES : 不透明,  NO : 透明

        // 这行代码过后.就相当于常见一张新的bitmap,也就是新的UIImage对象

        UIGraphicsBeginImageContextWithOptions(bgImage.size, NO, 0.0);

       

    // 2.画背景

    //特别注意的是使用OC自带的画图方法不用传上下文,系统会自动检测并传入上下文

        [bgImage drawInRect:CGRectMake(0, 0, bgImage.size.width, bgImage.size.height)];

       

        // 3.画右下角的水印

        UIImage *waterImage = [UIImage imageNamed:@"logo"];

        CGFloat scale = 0.2;

        CGFloat margin = 5;

        CGFloat waterW = waterImage.size.width * scale;

        CGFloat waterH = waterImage.size.height * scale;

        CGFloat waterX = bgImage.size.width - waterW - margin;

        CGFloat waterY = bgImage.size.height - waterH - margin;

        [waterImage drawInRect:CGRectMake(waterX, waterY, waterW, waterH)];

       

        // 4.从上下文中取得制作完毕的UIImage对象

    UIImage *newImage =

    UIGraphicsGetImageFromCurrentImageContext();

       

        // 5.结束上下文

        UIGraphicsEndImageContext();

       

        // 6.显示到UIImageView

        self.iconView.image = newImage;

       

        // 7.将image对象压缩为PNG格式的二进制数据

        NSData *data = UIImagePNGRepresentation(newImage);

        //UIImageJPEGRepresentation(<#UIImage *image#>, <#CGFloat compressionQuality#>)

       

        // 8.写入文件

    NSString *path =

    [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]

    stringByAppendingPathComponent: @"new.png"];

       [data writeToFile:path atomically:YES];

    13、图片裁剪

    有时候我们需要把一张普通的图片刻意裁剪成圆形,比如把用户头像做成圆形的,如下图:

    核心代码:

    void CGContextClip(CGContextRef c)

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

    示例代码1(效果如上图):

        CGContextRef ctx = UIGraphicsGetCurrentContext();   

        // 画圆

        CGContextAddEllipseInRect(ctx, CGRectMake(100, 100, 50, 50));

        // 裁剪

        CGContextClip(ctx);

        CGContextFillPath(ctx);

       

        // 显示图片

        UIImage *image = [UIImage imageNamed:@"me"];

    [image drawAtPoint:CGPointMake(100, 100)];

    示例代码2(产生图像圆截图,效果和上面图片效果一样但在图像周围加个圆边框,并保存到本地):

    // 1.加载原图

        UIImage *oldImage = [UIImage imageNamed:@"me"];

       

        // 2.开启上下文

        CGFloat borderW = 2; // 圆环的宽度

        CGFloat imageW = oldImage.size.width + 2 * borderW;

        CGFloat imageH = oldImage.size.height + 2 * borderW;

        CGSize imageSize = CGSizeMake(imageW, imageH);

        UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);

       

        // 3.取得当前的上下文

        CGContextRef ctx = UIGraphicsGetCurrentContext();

       

        // 4.画边框(大圆)

        [[UIColor whiteColor] set];

        CGFloat bigRadius = imageW * 0.5; // 大圆半径

        CGFloat centerX = bigRadius; // 圆心

        CGFloat centerY = bigRadius;

        CGContextAddArc(ctx, centerX, centerY, bigRadius, 0, M_PI * 2, 0);

        CGContextFillPath(ctx); // 画圆

       

        // 5.小圆

        CGFloat smallRadius = bigRadius - borderW;

        CGContextAddArc(ctx, centerX, centerY, smallRadius, 0, M_PI * 2, 0);

        // 裁剪(后面画的东西才会受裁剪的影响)

        CGContextClip(ctx);

       

        // 6.画图

        [oldImage drawInRect:CGRectMake(borderW, borderW, oldImage.size.width, oldImage.size.height)];

       

        // 7.取图

    UIImage *newImage =

    UIGraphicsGetImageFromCurrentImageContext();

       

        // 8.结束上下文

        UIGraphicsEndImageContext();

       

        // 9.显示图片

        self.iconView.image = newImage;

       

        // 10.写出文件

        NSData *data = UIImagePNGRepresentation(newImage);

    NSString *path =

    [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,

    NSUserDomainMask, YES) lastObject]

    stringByAppendingPathComponent:@"new.png"];

        [data writeToFile:path atomically:YES];

    14、屏幕截图

    有时候需要截取屏幕上的某一块内容,比如捕鱼达人游戏:

    核心代码:

    - (void)renderInContext:(CGContextRef)ctx;

    调用某个view的layer的renderInContext:方法即可

    示例代码:

    // 1.开启上下文

        UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0.0);

       

        // 2.将控制器view的layer渲染到上下文

        [view.layer renderInContext:UIGraphicsGetCurrentContext()];

       

        // 3.取出图片

    UIImage *newImage =

    UIGraphicsGetImageFromCurrentImageContext();

    // 4.写文件

    NSData *data = UIImagePNGRepresentation(newImage);

    NSString *path =

    [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,

    NSUserDomainMask, YES) lastObject]

    stringByAppendingPathComponent:@"new.png"];

    [data writeToFile:path atomically:YES];

       

        // 5.结束上下文

    UIGraphicsEndImageContext();

    15OC中自带画图方法

    // 1.获得当前的触摸点

        UITouch *touch = [touches anyObject];

        CGPoint startPos = [touch locationInView:touch.view];

       

        // 2.创建一个新的路径

        UIBezierPath *currenPath = [UIBezierPath bezierPath];//也可用alloc init方法创建

        currenPath.lineCapStyle = kCGLineCapRound;

        currenPath.lineJoinStyle = kCGLineJoinRound;

       

        // 3.设置起点

    [currenPath moveToPoint:startPos];

    //4.连线

    [currentPath addLineToPoint:endPos];

    判断一个点是否在一个矩形框内:

    CGRectContainsPoint(rect,point);//判断point这个点是否在rect这个矩形框内

  • 相关阅读:
    ES6学习之数组扩展
    js三元表达式
    ES6学习之函数扩展
    TS学习之for..of
    TS学习之Symbol
    Visual Studio编译时报错“函数名:重定义;不同的基类型”
    Windows Visual Studio中修改PostgreSQL代码后调试报错
    Windows安装MySQL5.7.17
    289. Game of Life -- In-place计算游戏的下一个状态
    Swift的 convenience && designated init
  • 原文地址:https://www.cnblogs.com/lifengfneg/p/4773956.html
Copyright © 2011-2022 走看看