CoreGraphics绘图的理解

2018-06-21  本文已影响0人  Johnny_Wu

一、概念原理

具体的字符和字形的解释可以参考:https://blog.csdn.net/fengsh998/article/details/8701738
我可以大概解析下:

1835430-945506c609eeac5d.gif
如上图,以BaseLine作为分界,往上是Ascent部分,往下是Descent。
Origin:绘制的基线原点,基线上最左侧的点。

二、CoreGraphics绘制字形步骤:

1、先设置上面提到的基线原点:CGContextSetTextPosition(context, lineOrigin.x, lineOrigin.y);
2、然后以基点作为参考,往上绘画字形的上部(Ascent)和下部(Descent),沿着Y轴的方向就是上部。比如说Y轴向上,字形就是我们正常的显示状态;如果Y轴向下,那么字形就会倒过来。这是CoreGraphics绘制特别的地方。

所以使用CoreGraphics绘图,要考虑到坐标的问题:
普通坐标,我们使用最多的坐标就是:原点在左上角,向右为X轴,向下为Y轴。但在这样的坐标系中使用CoreGraphics,会发生什么,我们可以验证下:

    NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:@"iOS程序在启动时会创建一个主线程,而在一个线程只能执行一件事情,如果在主线程执行某些耗时操作。"];
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
    //描绘区域
    CGMutablePathRef Path = CGPathCreateMutable();
    CGPathAddRect(Path, NULL ,CGRectMake(0 , 0 ,self.bounds.size.width , self.bounds.size.height));
    //通过区域和文本得到frame
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), Path, NULL);
    //得到frame中的行数组
    CFArrayRef Lines = CTFrameGetLines(frame);
    //获取数组Lines中的个数,一个多少行
    CFIndex lineCount = CFArrayGetCount(Lines);
    //获取上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGFloat Y = 20;
    for(CFIndex i=0;i<lineCount;i++)
    {
        //取出每一行
        CTLineRef line = CFArrayGetValueAtIndex(Lines, i);
        //设置绘制的基线原点
        CGContextSetTextPosition(context, 0, Y);
        Y += 20;
        CTLineDraw(line, context);//开始绘制一行
    }
    
    // 步骤7.内存管理
    CFRelease(frame);
    CFRelease(Path);
    CFRelease(framesetter);
结果如下图: IMG_0296.PNG

你会发现,所有的文字都倒过来了,所以可以证明CoreGraphics绘制方法是和正常方法相反的。上面代码为了简单,我设置了固定的基点间距为20,其实每一行的基点我们是可以获取到的,下面代码会体现出来。

三、下面我们来讨论一个新的问题:如何让CoreGraphics变现正常呢?

我们首先想到的应该是,把Y轴调转过来就可以了。确实是这样,那我们就把坐标系调整成为CoreGraphics的绘图坐标系:原点在左下角,向右为X轴,向上为Y轴。

CGContextSetTextMatrix(context, CGAffineTransformIdentity); 
CGAffineTransform flipVertical = CGAffineTransformMake(1,0,0,-1,0,self.bounds.size.height);
//将当前context的坐标系进行flip,移动原点到左下角,并进行Y轴的翻转。
CGContextConcatCTM(context, flipVertical);

完整的代码为:

    NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:@"iOS程序在启动时会创建一个主线ppijkpp线程只能执行一件事情,如果在主线程执行某些耗时操作。"];
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
    //描绘区域
    CGMutablePathRef Path = CGPathCreateMutable();
    CGPathAddRect(Path, NULL ,CGRectMake(0 , 0 ,self.bounds.size.width , self.bounds.size.height));
    //通过区域和文本得到frame
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), Path, NULL);
    //得到frame中的行数组
    CFArrayRef Lines = CTFrameGetLines(frame);
    //获取数组Lines中的个数,一个多少行
    CFIndex lineCount = CFArrayGetCount(Lines);
    //获取基线原点的位置
    CGPoint lineOrigins[lineCount]; //绝对的位置
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigins);
    for (int i = 0; i < lineCount; i++){
        CGPoint point = lineOrigins[i];
        NSLog(@"point.y = %f",point.y);
    }
    //获取上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    NSLog(@"当前context的变换矩阵 %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
    //设置字形变换矩阵为CGAffineTransformIdentity,也就是说每一个字形都不做图形变换
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    //转换坐标方法1
//    CGAffineTransform flipVertical = CGAffineTransformMake(1,0,0,-1,0,self.bounds.size.height);
//    CGContextConcatCTM(context, flipVertical);//将当前context的坐标系进行flip
    
    //转换坐标方法2
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    NSLog(@"翻转后context的变换矩阵 %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
    
    for (CFIndex i = 0; i < lineCount; i ++) {
        CTLineRef line = CFArrayGetValueAtIndex(Lines, i);
        CGPoint lineOrigin = lineOrigins[i];
        //设置绘制的基线原点
        CGContextSetTextPosition(context, lineOrigin.x, lineOrigin.y);
        //画横线
        CGContextSetLineWidth(context, 1.0);
        CGContextMoveToPoint(context, lineOrigin.x, lineOrigin.y);
        CGContextAddLineToPoint(context, lineOrigin.x+30, lineOrigin.y);
        CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
        CGContextStrokePath(context);
        //如果要每一行地画,就得通过CGContextSetTextPosition修改每一行的绝对位置
        CTLineDraw(line, context);//开始绘制一行
    }
    
    // 步骤7.内存管理
    CFRelease(frame);
    CFRelease(Path);
    CFRelease(framesetter);
结果如下图: IMG_0298.PNG

字体变现正常了。上面的代码,我故意在基点处画了一条红色的短横线,为了直观展示出基点所在的位置,在一个字体上的体现。在第二行的字符出,可以看出字体的形成是有上部(Ascent)和下部(Descent),当然还有行间隙。
行高=每行的asent + 每行的descent + 行间隙

当然也可以只是颠倒文字:CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1,-1));
这种方法不需要改变坐标,那么也不能使用上面得到的lineOrigins基点数组,需要自己计算每一个基点的位置。

四、如何恢复

上面的代码我都是在UILabel的子类方法:drawRect中执行的。
这里还有一个问题,如果改变了原来的坐标系,那么对后面的代码不是都有影响了?比如说:

    //自己的绘图方法
    [self mytest:rect];
    //调用父类的绘图方法
    [super drawRect:rect];

那么父类的绘图方法也会使用已经改变的坐标系,当然会受影响。
1、你可以在你绘图方法结束后,把坐标修改为原来的坐标;
2、也可以通过压栈保存原来的context,你的方法结束后,再出栈把改变的context恢复到原来的样子:

    //保存原来的上下文
    CGContextSaveGState(context);
    //你的绘图结束后,出栈恢复
    CGContextRestoreGState(context);
上一篇下一篇

猜你喜欢

热点阅读