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

作者: 寻形觅影 | 来源:发表于2017-07-28 11:09 被阅读161次

    一、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);
    }
    
    效果图

    相关文章

      网友评论

        本文标题:Quartz2D --> 二维绘图引擎(三-图案与阴影)

        本文链接:https://www.haomeiwen.com/subject/aeoakxtx.html