美文网首页
CoreGraphics绘图的理解

CoreGraphics绘图的理解

作者: Johnny_Wu | 来源:发表于2018-06-21 14:48 被阅读0次

    一、概念原理

    具体的字符和字形的解释可以参考: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);
    

    相关文章

      网友评论

          本文标题:CoreGraphics绘图的理解

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