Quartz2D --> 二维绘图引擎(三-图案与阴影)

一、Patterns(图案)

图案(Pattern)是反复绘制的单元,这些绘制单元重复地绘制到一个图形上下文中。我们可以像使用颜色一样使用这些图案。当我们使用图案来绘制时,Quartz 将页面分割成图案单元的集合,其中每个单元的大小为图案的大小,然后使用我们提供的回调函数来绘制这些单元格。

1、The Anatomy of a Pattern(图案骨骼)
  • 图案单元格是图案的基本组成,如下图所示,但是黑色矩形不是图案的一部分,它代表图案单元格的边界。当你创建一个图案单元时,需要定义单元的大小范围和其中绘制图案的大小范围。


    A pattern cell
  • 我们可以在水平和垂直方向上为两个图案单元之间的间距指定不同的间隔值。我们也可以指定间距为负数,这样图案单元便会重叠。

设置间隔后
  • 当我们绘制一个图案单元时,Quartz 使用图案空间的坐标系统。图案空间是一个抽象空间,它使用我们创建图案时指定的变换矩阵来映射到默认用户空间。当我们在图案空间上应用变换时,Quartz 只将变换应用于图案空间。
2、Colored Patterns and Stencil (Uncolored) Patterns(颜色图案和模板图案)
  • Colored Patterns 有和它们关联的固有颜色,颜色图案中的颜色是图案单元创建流程的一部分,而不是绘制流程的一部分。
  • 其它图案只限定了形状,因此可以被认为是模板图案或者是非着色图案、甚至图像蒙板。颜色是作为图案绘制过程中的一部分被指定的,而不是作为图案创建过程的一部分被指定的。
3、Tiling(镶嵌,平铺)

Tiling是将图案单元格绘制到页面的某个部分的过程,当用户空间定义的图案单元将要渲染到设备空间时可能无法精确匹配,这是由用户空间单元和设备像素之间的差异导致的。 Quartz 提供了三个Tiling选项可以在必要的时候调整图案。

typedef CF_ENUM (int32_t, CGPatternTiling) {
    kCGPatternTilingNoDistortion, // 不失真平铺
    kCGPatternTilingConstantSpacingMinimalDistortion, // 最小失真恒定间距平铺
    kCGPatternTilingConstantSpacing // 恒定间距平铺
};
  • kCGPatternTilingNoDistortion:细微调整图案单元之间的间距,但通常不超过一个设备像素。
  • kCGPatternTilingConstantSpacingMinimalDistortion :在设置设定图案单元之间恒定间距的基础上保持kCGPatternTilingNoDistortion。
  • kCGPatternTilingConstantSpacing:以调整图案单元大小为代价设定图案单元之间的间距,以求尽快的平铺。
4、How Patterns Work(图案工作原理)

当我们使用图案填充或描边时,Quartz 会按照以下步骤绘制每个图案单元:

  • 保存图形状态
  • 将当前转换矩阵应用到原始的图案单元上
  • 连接 CTM 与图案变换矩阵
  • 裁剪图案单元的边界矩形
  • 调用绘制回调函数来绘制图案单元
  • 恢复图形状态

当绘制的区域过小而图案的空白或间距过大时可能由于裁剪而导致图案无法正常显示。比如描边线宽是2,图案单元size是(10, 10)

5、Painting Colored Patterns (绘制颜色图案 )

(1、)编写一个绘制颜色图案单元的回调函数

回调函数需遵循这种形式:typedef void (*CGPatternDrawPatternCallback)(void * __nullable info, CGContextRef cg_nullable context);当然函数名是可以随意取~。这一回调函数有两个参数:

  • info:这是一个指向与图案相关联的私有数据的通用指针。这个参数是可选的,可以传递NULL。
  • context:绘制图案单元的上下文。

在这里有一些重要的细节需要注意:

  • 图案的size是已声明的。
  • 在代码执行绘制时设置颜色使之成为颜色图案。

颜色图案回调函数示例:

void MyDrawColoredPattern (void *info, CGContextRef myContext)
{
    CGFloat subunit = 5;
    CGRect  myRect1 = {{0,0}, {subunit, subunit}},
    myRect2 = {{subunit, subunit}, {subunit, subunit}},
    myRect3 = {{0,subunit}, {subunit, subunit}},
    myRect4 = {{subunit,0}, {subunit, subunit}};
    CGContextSetRGBFillColor (myContext, 0, 0, 1, 0.5);
    CGContextFillRect (myContext, myRect1);
    CGContextSetRGBFillColor (myContext, 1, 0, 0, 0.5);
    CGContextFillRect (myContext, myRect2);
    CGContextSetRGBFillColor (myContext, 0, 1, 0, 0.5);
    CGContextFillRect (myContext, myRect3);
    CGContextSetRGBFillColor (myContext, .5, 0, .5, 0.5);
    CGContextFillRect (myContext, myRect4);
}

(2、)Set Up the Colored Pattern (设置颜色图案)
 上面回调函数代码中使用颜色来绘制图案单元。我们必须设置基本的模式颜色空间为 NULL,以确保Quartz 使用绘制路径指定的颜色来绘制。

创建一个基础图案颜色空间:

CGColorSpaceRef patternSpace;
patternSpace = CGColorSpaceCreatePattern (NULL);
CGContextSetFillColorSpace (myContext, patternSpace);
CGColorSpaceRelease (patternSpace);

(3、)Set Up the Anatomy of the Colored Pattern(设置颜色图案的骨骼?)
 即保持一个CGPattern对象。使用函数CGPatternRef __nullable CGPatternCreate(void * __nullable info, CGRect bounds, CGAffineTransform matrix, CGFloat xStep, CGFloat yStep, CGPatternTiling tiling, bool isColored, const CGPatternCallbacks * cg_nullable callbacks)来创建一个CGPattern对象。

各个参数的含义:

  • info:即你想要传递给回调的信息参数,可为NULL。
  • bounds:指定图案单元的size。
  • matrix:指定图案的变换矩阵,它将图案空间坐标系统映射到图形上下文的默认坐标系统。如果希望两个坐标系统是一样的,则可以使用单位矩阵。
  • xStep:指定在图案坐标系统中图案单元间的水平方向间距(单元左边距离另一相邻单元左边的距离!!!也就是说若设的大小小于size.width就会重叠!!!)。
  • yStep:指定在图案坐标系统中图案单元间的垂直方向间距(同上)。
  • tiling:平铺模式。枚举类型。
  • isColored:指定图案单元是颜色图案(YES)还是模板图案(NO) 。
  • callbacks:是一个指向 CGPatternCallbacks 结构体的指针
    • 该结构体定义:
struct CGPatternCallbacks {
    unsigned int version;
    CGPatternDrawPatternCallback __nullable drawPattern;
    CGPatternReleaseInfoCallback __nullable releaseInfo;
};
typedef struct CGPatternCallbacks CGPatternCallbacks;
  • version:版本号,可设为0。
  • drawPattern:绘制的回调。
  • releaseInfo:释放的回调。该回调在释放 CGPattern 对象时被调用,以释放存储在我们传递给绘制回调的 info 参数中的数据。如果在这个参数中没有传递任何数据,则设置为 NULL。
6、Specify the Colored Pattern as a Fill or Stroke Pattern And Draw It(指定颜色图案是填充或描边图案然后绘制它)

首先需要调用函数CGContextSetFillPattern(CGContextRef cg_nullable c, CGPatternRef cg_nullable pattern, const CGFloat * cg_nullable components)CGContextSetStrokePattern(CGContextRef cg_nullable c, CGPatternRef cg_nullable pattern, const CGFloat * cg_nullable components)设置图案,然后调用函数CGContextFillRect(CGContextRef cg_nullable c, CGRect rect)CGContextStrokeRect(CGContextRef cg_nullable c, CGRect rect)或者其他快捷绘制方法绘制(CGContextStrokePathCGContextFillPath)。

其中,设置图案函数的第三个参数:components为颜色数组,虽然颜色图案提供了自己的颜色,但仍然需要传递一个单一的 alpha 值来告诉 Quartz 在绘制时颜色图案的透明度。

CGFloat alpha = 1;
CGContextSetFillPattern (myContext, myPattern, &alpha);
7、绘制颜色图案示例:
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    MyColoredPatternPainting(currentContext, self.bounds);
}

#define H_PATTERN_SIZE 10
#define V_PATTERN_SIZE 10

void MyDrawColoredPattern (void *info, CGContextRef myContext)
{
    CGFloat subunit = 5;
    CGRect  myRect1 = {{0,0}, {subunit, subunit}},
    myRect2 = {{subunit, subunit}, {subunit, subunit}},
    myRect3 = {{0,subunit}, {subunit, subunit}},
    myRect4 = {{subunit,0}, {subunit, subunit}};
    CGContextSetRGBFillColor (myContext, 0, 0, 1, 0.5);
    CGContextFillRect (myContext, myRect1);
    CGContextSetRGBFillColor (myContext, 1, 0, 0, 0.5);
    CGContextFillRect (myContext, myRect2);
    CGContextSetRGBFillColor (myContext, 0, 1, 0, 0.5);
    CGContextFillRect (myContext, myRect3);
    CGContextSetRGBFillColor (myContext, .5, 0, .5, 0.5);
    CGContextFillRect (myContext, myRect4);
}

void MyColoredPatternPainting (CGContextRef myContext, CGRect rect)
{
    CGPatternRef    pattern;
    CGColorSpaceRef patternSpace;
    CGFloat         alpha = 1;
    static const    CGPatternCallbacks callbacks = {0, &MyDrawColoredPattern, NULL};
    
    CGContextSaveGState (myContext);
    patternSpace = CGColorSpaceCreatePattern (NULL);
    CGContextSetFillColorSpace (myContext, patternSpace);
    CGColorSpaceRelease (patternSpace);
    
    pattern = CGPatternCreate (NULL, CGRectMake (0, 0, H_PATTERN_SIZE, V_PATTERN_SIZE), CGAffineTransformIdentity, H_PATTERN_SIZE+5, V_PATTERN_SIZE+10, kCGPatternTilingConstantSpacing, YES, &callbacks);
    
    CGContextSetFillPattern (myContext, pattern, &alpha);
    CGPatternRelease (pattern);
    CGContextFillRect (myContext, rect);
    CGContextRestoreGState (myContext);
}
效果图
8、Painting Stencil Patterns(绘制模板图案)

步骤与绘制颜色图案基本相同,只是有几点需要注意不同:
(1、回调函数不需要指定颜色值:)

#define PSIZE 16 

void MyDrawStencilPattern (void *info, CGContextRef myContext)
{
    int k;
    double r, theta; // 半径和角度
    r = 0.8 * PSIZE / 2;
    theta = 2 * M_PI * (2.0 / 5.0); // 144 degrees
    CGContextTranslateCTM (myContext, PSIZE/2, PSIZE/2);
    CGContextMoveToPoint(myContext, 0, r);
    for (k = 1; k < 5; k++) {
        CGContextAddLineToPoint (myContext, r * sin(k * theta), r * cos(k * theta));
    }
    CGContextClosePath(myContext);
    CGContextFillPath(myContext);
}

(2、)模板图案要求必须设置一个图案颜色空间用于 Quartz 的绘制:

    CGColorSpaceRef baseSpace;
    CGColorSpaceRef patternSpace;
    baseSpace = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB);
    patternSpace = CGColorSpaceCreatePattern (baseSpace); // 图案颜色空间创建必须依赖此函数~
    CGContextSetFillColorSpace (myContext, patternSpace);
    CGColorSpaceRelease(patternSpace);
    CGColorSpaceRelease(baseSpace);

**(3、)模板图案骨骼设置时与颜色图案唯一的区别就是参数 isColored传false(NO) **

(4、)指定颜色图案是填充或描边图案然后绘制它时,因为模板图案不提供颜色的回调,所以这里需要设置颜色数组

// 因为使用的是RGB所以这里是四个元素,最后一个是alpha,如果使用CMYK颜色空间应该是五个元素~~
static const CGFloat color[4] = { 0, 1, 1, 0.5 }; 
CGContextSetFillPattern (myContext, myPattern, color);
9、绘制模板图案示例:
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    MyStencilPatternPainting(currentContext, self.bounds);
}

#define PSIZE 16 

void MyDrawStencilPattern (void *info, CGContextRef myContext)
{
    int k;
    double r, theta; // 半径和角度
    r = 0.8 * PSIZE / 2;
    theta = 2 * M_PI * (2.0 / 5.0); // 144 degrees
    CGContextTranslateCTM (myContext, PSIZE/2, PSIZE/2);
    CGContextMoveToPoint(myContext, 0, r);
    for (k = 1; k < 5; k++) {
        CGContextAddLineToPoint (myContext, r * sin(k * theta), r * cos(k * theta));
    }
    CGContextClosePath(myContext);
    CGContextFillPath(myContext);
}
void MyStencilPatternPainting (CGContextRef myContext, CGRect rect)
{
    CGPatternRef pattern;
    CGColorSpaceRef baseSpace;
    CGColorSpaceRef patternSpace;
    static const CGFloat color[4] = { 1, 0, 0, 1 };
    static const CGPatternCallbacks callbacks = {0, &MyDrawStencilPattern, NULL};
    baseSpace = CGColorSpaceCreateDeviceRGB ();
    patternSpace = CGColorSpaceCreatePattern (baseSpace);
    CGContextSetFillColorSpace (myContext, patternSpace);
    CGColorSpaceRelease (patternSpace);
    CGColorSpaceRelease (baseSpace);
    pattern = CGPatternCreate(NULL, CGRectMake(0, 0, PSIZE, PSIZE), CGAffineTransformIdentity, PSIZE, PSIZE, kCGPatternTilingConstantSpacing, false, &callbacks); // 这里必须是false or NO
    CGContextSetFillPattern (myContext, pattern, color);
    CGPatternRelease (pattern);
    CGContextFillRect (myContext,rect);
}
效果图

二、Shadows(阴影)

阴影是模拟图形对象受到光源影响产生的阴影图形对象,阴影可以使图像产生三维或悬浮效果。
 阴影有三个特性:

  • x偏移量:水平方向上阴影的偏移量。
  • y偏移量:垂直方向上阴影的偏移量。
  • 模糊值:阴影边缘硬边化程度。
左边是硬边化效果
1、阴影绘制
  • 通过调用函数CGContextSetShadow(CGContextRef cg_nullable c, CGSize offset, CGFloat blur)。c - 图形上下文、offset - 阴影偏移量、blur - 模糊值。该函数默认阴影是RGB颜色空间的1/3透明度的黑色。
  • 通过调用函数CGContextSetShadowWithColor(CGContextRef cg_nullable c, CGSize offset, CGFloat blur, CGColorRef __nullable color)。可设置阴影的颜色,同时可以通过设置阴影颜色为NULL来禁用阴影。
2、基于图形上下文的阴影绘制惯例
  • 阴影的坐标系跟随图形上下文坐标系。正值的 x 偏移量表示阴影位于图形对象的右侧。

  • 在 Mac OS X 中,正值的 y 偏移表示阴影位于图形对象的上方,这与 Quartz 2D 默认的坐标系匹配。

  • 在 iOS 中,如果是uartz 2D API 来创建 PDF 或位图图形上下文,则正值的 y 表示阴影位于图形对象的上方。如果图形上下文是由 UIKit 创建的,则正值的 y 表示阴影位于图形对象的下方。这与 UIKit 坐标系统相匹配。

  • 阴影绘制约定不受 CTM 影响

3、示例
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    UIImage * newImage = [UIImage imageNamed:@"poem"];
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGSize shadowSize = CGSizeMake(20, 30);
    CGColorRef color;
    CGColorSpaceRef colorSpace;
    
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, shadowSize, 10);
    [newImage drawInRect:CGRectMake(50, 50, 100, 200)];
    CGContextRestoreGState(currentContext);
    
    CGContextSaveGState(currentContext);
    colorSpace = CGColorSpaceCreateDeviceRGB();
    CGFloat colorValue[] = {1, 0, 0, 1};
    color = CGColorCreate(colorSpace, colorValue);
    CGContextSetShadowWithColor(currentContext, shadowSize, 10, color);
    [newImage drawInRect:CGRectMake(200, 50, 100, 200)];
    CGContextRestoreGState(currentContext);
}
效果图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容