• iOS 绘画学习(4)



    颜色和图案

    一个颜色就是一个CGColor(实际上是CGColorRef)。CGColor非常容易使用,而且也可以通过UIColor的 colorWithCGColor: 方法和 CGColor的相关方法来相互转换。

    一个图案其实就是一个CGPattern(实际上是CGPatternRef)。你可以创建一个图案并描边或者填充它。这个过程非常复杂,这里我把箭头变为一个红,蓝相接的三角形来说明,为了绘制这个图案,把下面这一行去掉:

    CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);
    

    在这一行的位置,写上如下代码:

    CGColorSpaceRef sp2 = CGColorSpaceCreatePattern(nil);
    CGContextSetFillColorSpace (con, sp2);
    CGColorSpaceRelease (sp2);
    CGPatternCallbacks callback = {
        0, drawStripes, nil
    };
    CGAffineTransform tr = CGAffineTransformIdentity;
    CGPatternRef patt = CGPatternCreate(nil,  CGRectMake(0,0,4,4), tr, 4, 4, kCGPatternTilingConstantSpacingMinimalDistortion, true, &callback);
    CGFloat alph = 1.0;
    CGContextSetFillPattern(con, patt, &alph);
    CGPatternRelease(patt);
    

    上面的代码有点啰嗦,但几乎可以算是一个完整的样板。为了理解这段代码,我们从后面读起。我们看到 CGContextSetFillPattern,设置一个填充图案,而不是一个填充颜色来填充一个路径(这里就是一个三角箭头)。第三个参数是一个指向CGFloat的指针,所以我们需要在之前设置这个CGFloat。第二个参数是一个CGPatternRef,所以我们需要在之前创建一个CGPatternRef(然后在之后释放它)。

    现在让我们来看看这个CGPatternCreate调用。一个图案是绘制在一个三角形的“单元”里;我们必须确定这个单元的大小(第二个参数),还有每个单元原点之间的距离(第四和第五个参数)。这样,这个单元就是4x4的,每个单元在垂直和水平方向上都紧靠一起。我们还需要提供一个转换来应用到单元上(第三个参数)。我们还提供一个平铺规则(第六个参数)。我们必须确定这是一个颜色图案,还是一个模版图案,如果是颜色图案,第七个参数就是true。我们还要提供一个指向回调函数的指针,这个函数就是实际上在单元里绘制图案(第八个参数)。

    对于第八个参数,为了让事情现在变得不那么复杂,我们实际上只是提供了一个指向CGPatternCallbacks 结构体的指针,这个结构体包含一个数字0,和两个函数的指针,一个会被调用来在它的单元里绘制图案,另一个会在这个图案释放的时候被调用。这里我们没有指定第二个参数,因为是用来内存管理的,这个简单的例子里不需要用到。

    往前一点,我们还需要给一个图案的色彩空间设置上下文的填充色彩空间。如果你忽略了这个,当你调用CGContextSetFillPattern.时会得到一个错误。所以我们创建一个色彩空间,设置它为上下文的填充色彩空间,然后释放它。

    但是我们还没有完成,因为我还没有向你展示实际绘制图案单元的方法!

    void drawStripes (void *info, CGContextRef con) {
        // assume 4 x 4 cell
        CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);
        CGContextFillRect(con, CGRectMake(0,0,4,4));
        CGContextSetFillColorWithColor(con, [[UIColor blueColor] CGColor]);
        CGContextFillRect(con, CGRectMake(0,0,4,2));
    }
    

    如你所见,实际的图案代码非常简单,唯一需要注意的地方就是绘制的大小必须和CGPatternCreate创建的图案时指定的单元大小一样,否则绘制出来的图案不是我们想要的。这里我们的单元大小是4x4,所以我们用红色填充它,然后用蓝色填充下半部。当这些单元在水平和垂直方向上紧挨着平铺后,就是我们看到的图案。

    最后,需要注意,我们应该用 CGContextSaveGState 和 CGContextRestoreGState.把我们的代码包起来。

    你可能注意到上图中的条纹并没有刚好适应三角形的箭头里,底部的一些条纹像是半个蓝色条纹。这是因为一个图案并不是绘制在你指定填充或者描边的形状里,而是整个图形上下文。我们可以通过CGContextSetPatternPhase 来在绘制之前移动这个图案的位置。

    对于一个这么简单的图案,我们也可以很简单地通过UIColor的 colorWithPatternImage:方法来获得一个UIImage:

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(4,4), NO, 0);
    drawStripes(nil, UIGraphicsGetCurrentContext());
    UIImage* stripes = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    UIColor* stripesPattern = [UIColor colorWithPatternImage:stripes];
    [stripesPattern setFill];
    UIBezierPath* p = [UIBezierPath bezierPath];
    [p moveToPoint:CGPointMake(80,25)];
    [p addLineToPoint:CGPointMake(100,0)];
    [p addLineToPoint:CGPointMake(120,25)];
    [p fill];
    

    图形上下文转换

    正如UIView可以有转换,图形上下文也可以有转换。但是,给一个图形上下文提供一个转换并不会影响已经绘制在上面的图像,它仅仅影响之后的绘制。一个图形上下文的转换,称为这个图形上下文的CTM,“current transformation matrix 当前的转换矩阵”。

    充分利用图形上下文的CTM,可以减少你的很多计算。你可以调用CGContextConcatCTM 用任意的CGAffineTransform 乘以当前的转换;还有很多方便的函数对当前的转换进行平移,拉伸和旋转。

    当你获得一个上下文时,一个基本的转换已经设置好了,使得系统能够把上下文的绘制坐标和屏幕的坐标对应起来。所有的转换都是应用到当前的转换,所有这个基本的转换仍然有效。通过用CGContextSaveGState 和 CGContextRestoreGState.包围起你的代码,你可以在最后恢复原始的基本转换。

    例如,我们的向上箭头,左上方的三角形位置是写死{80,0}。这样不好理解,为了能够更好地重用我们的代码,我们可以理解箭头原来的绘制坐标是{0,0},只是在x轴方向上平移了80.那么现在把箭头绘制在{80,0}的位置,可以使用转换:

    CGContextTranslateCTM(con, 80, 0);
    // now draw the arrow at (0,0)
    

    下面重复绘制不同旋转角度下的箭头。由于这个箭头需要绘制多次,我会把这个箭头包装成一个UIImage,这样可以大大减少重复工作,同时也让绘制更快

    - (UIImage*) arrowImage {
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(40,100), NO, 0.0);
        // obtain the current graphics context
        CGContextRef con = UIGraphicsGetCurrentContext();
        // draw the arrow into the image context
        // draw it at (0,0)! adjust all x-values by subtracting 80
        // ... actual code omitted ...
        UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return im;
    }
    

    我们生成了这个箭头的图像,把它保存在一个实例变量中,用self.arrow来访问:

    - (void)drawRect:(CGRect)rect {
        CGContextRef con = UIGraphicsGetCurrentContext();
        [self.arrow drawAtPoint:CGPointMake(0,0)];
        for (int i=0; i<3; i++) {
            CGContextTranslateCTM(con, 20, 100);
            CGContextRotateCTM(con, 30 * M_PI/180.0);
            CGContextTranslateCTM(con, -20, -100);
            [self.arrow drawAtPoint:CGPointMake(0,0)];
        }
    }
    

    转换也可以作为我们之前提到的使用CGContextDrawImage时产生的“反转”问题的一个解决方法。我们反转图形上下文,而不是绘画视图。你可以向下移动上下文的顶部,然后通过拉伸转换,反转y坐标方向:

    CGContextTranslateCTM(con, 0, theHeight);
    CGContextScaleCTM(con, 1.0, -1.0);
    

    至于你想顶部移动多少(theHeight)取决于你打算怎么绘制图像。

  • 相关阅读:
    小程序开发 access_token 统一管理
    python操作mysql
    Mac版本的idea非正常关闭后,idea打开项目大面积报红
    PySpider爬取去哪儿攻略数据项目
    Python3.9安装PySpider步骤及问题解决
    Selenium 自动化测试工具
    Python 抓取猫眼电影排行
    Python爬虫基本库
    Python 创建一个Django项目
    Python 数据可视化
  • 原文地址:https://www.cnblogs.com/YungMing/p/4008217.html
Copyright © 2020-2023  润新知