iOS Developer

Quartz2D --> 二维绘图引擎(二 - 路径、颜色

2017-07-26  本文已影响72人  寻形觅影

一、Paths (路径)

路径定义了一个或多个形状或子路径。子路径可以由直线、曲线或两者兼而有之,它可以是打开的或闭合的。子路径可以是一个简单的形状,比如一条直线,圆,矩形,星型或更复杂的形状,如山脉的轮廓或抽象涂鸦等等。这一部分主要介绍和了解构建路径的模块,如何画和描绘路径,影响路径外观的参数等。

1、The Building Blocks

子路径是由线、弧和曲线构成,同时Quartz也提供了通过一个函数调用添加矩形和椭圆的方法。点也是路径的基本构建块,因为点定义了图形的开始和结束位置。

红色部分为创建的圆弧 三次贝塞尔曲线

2、Creating a Path

当你想在上下文中构建一条path时需要通过调用CG_EXTERN void CGContextBeginPath(CGContextRef cg_nullable c)函数,然后设置起点(当前点)或者通过调用函数CG_EXTERN void CGContextMoveToPoint(CGContextRef cg_nullable c, CGFloat x, CGFloat y)在path中设置子路径,当你建立起第一个点后你就可以进行其他操作了。

当你画一条路径后,需要从图形上下文中刷新时。你可能不希望失去你的路径,尤其是它描绘了你想要多次使用的一个复杂的场景。出于这个原因,Quartz提供了两种数据类型来创建可重用的paths : CGPathRefCGMutablePathRef。你可以调用函数CG_EXTERN CGMutablePathRef CGPathCreateMutable(void)去创建一个可变的CGPath对象然后你就可以添加线、弧、曲线等操作。需要注意的是路径功能操作的是CGPath对象,而不是图形上下文。

下面列举一些CGPath替代CGContext的一些方法:

CGPathCreateMutable, which replacesCGContextBeginPath
CGPathMoveToPoint, which replaces CGContextMoveToPoint
CGPathAddLineToPoint, which replaces CGContextAddLineToPoint
CGPathAddCurveToPoint, which replaces CGContextAddCurveToPoint
CGPathAddEllipseInRect, which replaces CGContextAddEllipseInRect
CGPathAddArc, which replaces CGContextAddArc
CGPathAddRect, which replaces CGContextAddRect
CGPathCloseSubpath, which replaces CGContextClosePath

当你想要为图形上下文添加路径时,可以调用CG_EXTERN void CGContextAddPath(CGContextRef cg_nullable c, CGPathRef cg_nullable path)方法,这样直到你去绘制该上下文前,这一路径都会存在于上下文中。

这里只是一些常用方法,详细可以参考 CoreGraphics框架下的CGContext.h文件和CGPath.h文件!

3、Painting a Path

绘制路径可以通过描线或者填充或者两者兼有的方式,Stroking:仅仅是描绘路径成线,Filling:会填充路径所包含的区域。

影响Stroking绘制的参数:

参数 设置参数值的函数
线宽 CGContextSetLineWidth
线帽类型 CGContextSetLineCap
线段间连接样式 CGContextSetLineJoin
斜接限制 CGContextSetMiterLimit
行缓冲模式 CGContextSetLineDash
绘线颜色空间(stroke) CGContextSetStrokeColorSpace
绘线颜色 CGContextSetStrokeColor、CGContextSetStrokeColorWithColor
纹理 CGContextSetStrokePattern

PS:关于线帽类型、线段间连接样式、斜接限制、行缓冲模式可以参考这篇文章中相关内容

绘制路径的函数:

函数 功能
CGContextStrokePath 绘制路线
CGContextStrokeRect 使用当前线宽和线色绘制矩形
CGContextStrokeRectWithWidth 使用指定线宽绘制矩形
CGContextStrokeEllipseInRect 绘制椭圆
CGContextStrokeLineSegments 绘制线段
CGContextDrawPath 指定模式下渲染路径

PS:绘制路径模型

typedef CF_ENUM (int32_t, CGPathDrawingMode) {
  kCGPathFill,  //只有填充(非零缠绕数填充),不绘制边框
  kCGPathEOFill, //奇偶规则填充
  kCGPathStroke, //只有边框
  kCGPathFillStroke, // 既有边框又有填充
  kCGPathEOFillStroke //奇偶填充并绘制边框
};

设置混合模式:
  使用CG_EXTERN void CGContextSetBlendMode(CGContextRef cg_nullable c, CGBlendMode mode)函数设置混合模式,混合模式是图形状态的一部分,如果在改变混合模式之前使用CG_EXTERN void CGContextSaveGState(CGContextRef cg_nullable c)函数保存了状态,那么需要在完成后使用CG_EXTERN void CGContextRestoreGState(CGContextRef cg_nullable c)函数重置混合模式为默认正常情况。关于混合模式枚举参考该文章最后部分

4、Clipping to a Path

当前剪切区域是一个作为遮罩的路径为基础创建的,允许屏蔽部分你不想绘制展示的页面。常见用于图片处理。
当你绘制时,Quartz只会渲染剪切的区域。同样剪切区域也是图形状态的一部分,为了使剪裁区域恢复到以前的状态,可以在剪辑之前保存图形状态,当完成后,再恢复图形状态即可。
设置剪切函数:

函数 说明
CGContextClip 结果路径即当前剪切路径与上下文路径的交集,使用结果路径作为后续渲染操作的剪切路径。这里遵循非零环绕规则。
CGContextEOClip 使用奇偶规则渲染,除此外与CGContextClip相同
CGContextClipToMask 为上下文剪切区域添加一个转换到指定rect的mask(CGImageRef类型),根据提供的mask是image 还是 image mask不同决定输出的结果不同。
CGContextClipToRect 取当前上下文与rect的交集,注意:这个函数会将上下文的路径重置为空路径
CGContextClipToRects 取当前上下文与由rects中所有元素组成的剪辑区域的交集,同样,这个函数会将上下文的路径重置为空路径
CGContextGetClipBoundingBox 该函数有一个CGRect类型的返回值,The bounding box 是包含剪辑区域内所有点的最小矩形

路径设置举例:

- (void)drawRect:(CGRect)rect {
    
    CGContextRef currentContextRef = UIGraphicsGetCurrentContext();
    CGMutablePathRef mutablePath = CGPathCreateMutable();
    CGPathMoveToPoint(mutablePath, NULL, 0, 0);
    CGPathAddLineToPoint(mutablePath, NULL, self.frame.size.width, 0);
    CGPathAddQuadCurveToPoint(mutablePath, NULL, self.frame.size.width/2, self.frame.size.height/2, self.frame.size.width, self.frame.size.height);
    CGPathCloseSubpath(mutablePath);
    CGContextSetLineWidth(currentContextRef, 5);
    CGFloat lengths[] = {5,1, 3,1, 1,1};
    CGContextSetLineDash(currentContextRef, 0, lengths, 6);
    CGColorSpaceRef colorRef = CGColorSpaceCreateDeviceRGB();
    CGContextSetStrokeColorSpace(currentContextRef, colorRef);
    // component 是指{red, green, blue, alpha};
    CGFloat component[] = {0.0, 0.0 , 1.0, 1.0};
    CGContextSetStrokeColor(currentContextRef, component);
    // 等价于
//    CGContextSetStrokeColorWithColor(currentContextRef, [UIColor blueColor].CGColor);
    CGContextSetFillColorSpace(currentContextRef, colorRef);
    CGContextSetFillColorWithColor(currentContextRef, [UIColor yellowColor].CGColor);
    CGContextAddPath(currentContextRef, mutablePath);
    CGPathRelease(mutablePath);
    CGColorSpaceRelease(colorRef);
    CGContextDrawPath(currentContextRef, kCGPathFillStroke);
}
效果图

剪切举例

- (void)drawRect:(CGRect)rect {
    // 展示整张图
    UIImage * image = [UIImage imageNamed:@"newImage"];
    [image drawInRect:CGRectMake(0, 0, 200, 200)];
    // 设置路径剪切
    CGContextRef currentContextRef = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContextRef);
    CGMutablePathRef mutablePath = CGPathCreateMutable();
    CGPathAddRect(mutablePath, NULL, CGRectMake(0, 200, 100, 100));
    CGContextAddPath(currentContextRef, mutablePath);
    CGPathRelease(mutablePath);
    CGContextClip(currentContextRef);
    [image drawInRect:CGRectMake(0, 200, 200, 200)];
    CGRect contextRect = CGContextGetClipBoundingBox(currentContextRef);
    NSLog(@"%@", NSStringFromCGRect(contextRect));
    CGContextRestoreGState(currentContextRef);
    // CGContextClipToRect剪切
    CGContextSaveGState(currentContextRef);
    CGContextClipToRect(currentContextRef, CGRectMake(150, 300, 120, 150));
    [image drawInRect:CGRectMake(0, 200, 200, 200)];
    CGRect contextRect_1 = CGContextGetClipBoundingBox(currentContextRef);
    NSLog(@"%@", NSStringFromCGRect(contextRect_1));
    CGContextRestoreGState(currentContextRef);
}

使用该View

    TextView_1 * view = [[TextView_1 alloc] initWithFrame:CGRectMake(0, 100, 250, 450)];
    [self.view addSubview:view];
    view.backgroundColor = [UIColor redColor];
两个log打印的结果

在这里可以看到第一个log打印结果,即为路径形成的矩形,因为view的size是(250,450),而{{0, 200}, {100, 100}}中所有点都在该范围内。第二个log打印结果并非我们使用CGContextClipToRect函数设置的rect,因为rect为(150, 300, 120, 150),有一部分已经超出了view的范围,取交集即为剪切区域,即{{150, 300}, {100, 150}}。

二、Color and Color Spaces(颜色与颜色空间)

设备(显示器,打印机、扫描仪、摄像机)不会以同样的方式对待颜色,每种设备都有自己的颜色范围使设备可以切实生产。主要了解Quartz是如何描绘颜色与颜色空间的及什么是透明度组件。

1、关于颜色与颜色空间

Quartz 中的颜色是用一组数值来表示。而颜色空间用于解析这些颜色信息,如果没有颜色空间,那么在解析颜色信息时颜色值将毫无意义。常用颜色空间有 :

颜色空间 成分
HSB Hue, saturation, brightness(色相,饱和度,亮度)
RGB Red, green, blue
CMYK Cyan, magenta, yellow, black(蓝绿色,品红,黄,黑)
BGR Blue, green, red

在颜色空间中颜色值范围是相对的,一般在Quartz这种值的范围是(0.0 - 1.0),在Quartz中颜色也有透明度。

2、颜色透明度

alpha为1.0 表示opaque -- 不透明的,为0.0表示无效的,不可见的。在默认混合模式中设置统一的透明度最终显示为:destination = (alpha * source) + (1 - alpha) * destination,你可以单独设置alpha,也可以使用函数CG_EXTERN void CGContextSetAlpha(CGContextRef cg_nullable c, CGFloat alpha)统一设置alpha,如果同时设置了则结果是两个值得乘积。使用 CGContextClearRect(CGContextRef cg_nullable c, CGRect rect) 清除上下文的 alpha 通道。

3、Creating Color Spaces

iOS不支持与设备无关的(device-independent)或通用的(generic)颜色空间(通用颜色空间是设备无关颜色空间的一种)。iOS应用程序必须使用设备(device)颜色空间。

设备无关颜色空间:

颜色空间分类 创建
L ※ a ※ b CGColorSpaceCreateLab
ICC CGColorSpaceCreateWithICCProfile、CGColorSpaceCreateICCBased
Calibrated RGB CGColorSpaceCreateCalibratedRGB
Calibrated gray CGColorSpaceCreateCalibratedGray

通用颜色空间:使用CGColorSpaceCreateWithName(CFStringRef cg_nullable name)函数创建,常用参数如下:

参数名 描述
kCGColorSpaceGenericGray 单个值的范围,从纯黑(0.0)到纯白(1.0)
kCGColorSpaceGenericRGB 三原色组件(0.0 - 1.0)
kCGColorSpaceGenericCMYK 四分量颜色空间(0.0 - 1.0)

设备颜色空间:
 设备颜色空间主要是由iOS应用程序使用。

颜色空间分类 创建
DeviceGray CGColorSpaceCreateDeviceGray
DeviceRGB CGColorSpaceCreateDeviceRGB
DeviceCMYK CGColorSpaceCreateDeviceCMYK

调用 CGContextSetFillColorSpace(context, colorSpace)CGContextSetStrokeColorSpace(context, colorSpace) 设置颜色空间。

使用如下函数设置指定颜色空间的颜色

函数 **
CGContextSetGrayFillColor(CGContextRef cg_nullable c,CGFloat gray, CGFloat alpha)
CGContextSetGrayStrokeColor(CGContextRef cg_nullable c,CGFloat gray, CGFloat alpha)
CGContextSetRGBFillColor(CGContextRef cg_nullable c, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)
CGContextSetRGBStrokeColor(CGContextRef cg_nullable c, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)
CGContextSetCMYKFillColor(CGContextRef cg_nullable c, CGFloat cyan, CGFloat magenta, CGFloat yellow, CGFloat black, CGFloat alpha)
CGContextSetCMYKStrokeColor(CGContextRef cg_nullable c, CGFloat cyan, CGFloat magenta, CGFloat yellow, CGFloat black, CGFloat alpha)
CGContextSetFillColorWithColor(CGContextRef cg_nullable c, CGColorRef cg_nullable color)
CGContextSetStrokeColorWithColor(CGContextRef cg_nullable c, CGColorRef cg_nullable color)

还有两个不推荐使用的函数CGContextSetFillColor(CGContextRef cg_nullable c, const CGFloat * cg_nullable components)CGContextSetStrokeColor(CGContextRef cg_nullable c, const CGFloat * cg_nullable components)

4、Setting Rendering Intent(设置渲染意图)

渲染意图指Quartz如何将颜色从源颜色空间范围映射到图形上下文的目标颜色空间的颜色范围内。(例:RGB 颜色空间比 CMYK 的颜色空间要大,一些RGB颜色超出了CMYK颜色空间,若想将某一超出范围的颜色映射到CMYK颜色空间需要遵循的规则即渲染意图)
  调用函数CGContextSetRenderingIntent(CGContextRef cg_nullable c, CGColorRenderingIntent intent)设置渲染意图,其中第二个参数是枚举值:

typedef CF_ENUM (int32_t, CGColorRenderingIntent) {
        kCGRenderingIntentDefault,
        kCGRenderingIntentAbsoluteColorimetric,
        kCGRenderingIntentRelativeColorimetric,
        kCGRenderingIntentPerceptual,
        kCGRenderingIntentSaturation
    };
5、位图图形上下文( bitmap graphics context)支持的像素格式(Pixel Formats)

以下仅列举了iOS 使用的情况:

CS - 颜色空间 Pixel format -像素格式 位图信息常量
Null 8 bpp, 8 bps kCGImageAlphaOnly
Gray 8 bpp, 8 bps kCGImageAlphaNone
Gray 8 bpp, 8 bps kCGImageAlphaOnly
RGB 16 bpp, 5 bps kCGImageAlphaNoneSkipFirst
RGB 32 bpp, 8 bps kCGImageAlphaNoneSkipFirst
RGB 32 bpp, 8 bps kCGImageAlphaNoneSkipLast
RGB 32 bpp, 8 bps kCGImageAlphaPremultipliedFirst
RGB 32 bpp, 8 bps kCGImageAlphaPremultipliedLast

PS:

三、Transforms(变换,变形)

Quartz 2D 绘制模型定义了两种独立的坐标空间:用户空间 -- >用于描绘档页和设备空间 -- > 用于描绘设备的本地分辨率。当我们需要一个点或者显示文档时, Quartz 会将用户空间坐标系统映射到设备空间坐标系统。这种当前映射关系的变换矩阵称为 CTM(current transformation matrix)。CTM是关于坐标系的变换,可以独立使用,也可以和仿射变换结合使用(关于仿射变换可以参考这篇文章)。
 在使用CTM做变换时需要在绘制图像之前调用!即,使用CGContextDrawImage (myContext, rect, myImage)函数之前。

1、CTM相关变换函数:

需要注意的是:在需要多次使用不同CTM函数时,调用顺序很重要,可能会导致输出结果完全不同!

2、获取用户空间到设备空间的变换

通常当我们使用Quartz 2D绘制时,只在用户空间工作!Quartz 为我们处理用户空间和设备空间的转换。调用如下函数获取 Quartz 转换用户空间和设备空间的仿射变换。

示例代码:

- (void)drawRect:(CGRect)rect {

    UIImage * image = [UIImage imageNamed:@"newImage"];
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextTranslateCTM(currentContext, 100, 100);
    CGAffineTransform scaleTransform = CGAffineTransformMakeScale(2, 2);
    CGContextConcatCTM(currentContext, scaleTransform);
    CGContextRotateCTM(currentContext, M_PI_4);
    CGContextDrawImage(currentContext, CGRectMake(50, 0, 100, 100), image.CGImage);
    CGContextRestoreGState(currentContext);
    
    // 下面只是调换了一下CTM函数的调用顺序
    CGContextSaveGState(currentContext);
    CGContextRotateCTM(currentContext, M_PI_4);
    CGContextTranslateCTM(currentContext, 100, 100);
    CGContextConcatCTM(currentContext, scaleTransform);
    CGContextDrawImage(currentContext, CGRectMake(50, 0, 100, 100), image.CGImage);
    CGContextRestoreGState(currentContext);
}
效果图
上一篇下一篇

猜你喜欢

热点阅读