Search code examples
core-graphicscgcontextcgaffinetransformcgpathstroke

Preserve line width while scaling all points in the context with CGAffineTransform


I have a CGPath in some coordinate system that I'd like to draw. Doing so involves scaling the old coordinate system onto the Context's one. For that purpose, I use CGContextConcatCTM() which does transform all the points as it should. But, as it is a scaling operation, the horizontal/vertical line widths get changed to. E.g. a scale of 10 in x-direction, but of 1 in y-direction would lead to vertical lines being 10 times as thick as horizontal ones. Is there a way to keep the ease of use of translation matrices (e.g. CGAffineTransform) but not scaling line widths at the same time, e.g. a function like CGPathApplyAffineTransformToPoints?

Cheers

MrMage


Solution

  • You can use CGPathApply to iterate through the elements in a path. It's a little bit more complex than just a one-liner but if you package it up in a simple helper function, it might be useful for you. Here is one version that creates a new path and transforms it:

    typedef struct {
        CGMutablePathRef path;
        CGAffineTransform transform;
    } PathTransformInfo;
    
    static void
    PathTransformer(void *info, const CGPathElement *element)
    {
        PathTransformInfo *transformerInfo = info;
    
        switch (element->type) {
            case kCGPathElementMoveToPoint:
                CGPathMoveToPoint(transformerInfo->path, &transformerInfo->transform,
                                  element->points[0].x, element->points[0].y);
                break;
    
            case kCGPathElementAddLineToPoint:
                CGPathAddLineToPoint(transformerInfo->path, &transformerInfo->transform,
                                     element->points[0].x, element->points[0].y);
                break;
    
            case kCGPathElementAddQuadCurveToPoint:
                CGPathAddQuadCurveToPoint(transformerInfo->path, &transformerInfo->transform,
                                          element->points[0].x, element->points[0].y,
                                          element->points[1].x, element->points[1].y);
                break;
    
            case kCGPathElementAddCurveToPoint:
                CGPathAddCurveToPoint(transformerInfo->path, &transformerInfo->transform,
                                      element->points[0].x, element->points[0].y,
                                      element->points[1].x, element->points[1].y,
                                      element->points[2].x, element->points[2].y);
                break;
            case kCGPathElementCloseSubpath:
                CGPathCloseSubpath(transformerInfo->path);
                break;
        }
    }
    

    To use it you would do (this is the part I would put inside a helper function):

        PathTransformInfo info;
        info.path = CGPathCreateMutable();
        info.transform = CGAffineTransformMakeScale(2, 1);
    
        CGPathApply(originalPath, &info,  PathTransformer);
    

    The transformed path is in info.path at this point.