【问题标题】:iOS How to draw a stroke with an outlineiOS 如何用轮廓画笔触
【发布时间】:2014-07-02 02:34:30
【问题描述】:

我正在寻找此图像中的输出Expected Result

我需要一个我的笔画的轮廓。我的代码如下

- (void)awakeFromNib {
    self.strokeArray = [NSMutableArray array];
    self.layerIndex = 0;
    self.isSolid = false;
    self.path = [[UIBezierPath alloc] init];
    self.innerPath = [[UIBezierPath alloc] init];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint p = [touch locationInView:self];
    [self.path moveToPoint:p];
    [self.innerPath moveToPoint:p];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint p = [touch locationInView:self];
    [self.path addLineToPoint:p];
    [self.innerPath addLineToPoint:p];
    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint p = [touch locationInView:self];
    [self.path addLineToPoint:p];
    [self.innerPath addLineToPoint:p];
    [self drawBitmap];
    [self setNeedsDisplay];
    [self.path removeAllPoints];
    [self.innerPath removeAllPoints];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self touchesEnded:touches withEvent:event];
}
- (void)drawRect:(CGRect)rect
{
    [self.incrementalImage drawInRect:rect];
    [self.brushColor setStroke];
    self.path.lineWidth = self.brushWidth;
    if(self.isEraser)
        [self.path strokeWithBlendMode:kCGBlendModeClear alpha:0.0];
    else
        [self.path stroke];
    self.innerPath.lineWidth = self.brushWidth - 10;
    [[UIColor clearColor] setStroke];
    [self.innerPath strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
}
- (void)drawBitmap
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    if (!self.incrementalImage)
    {
        CGContextClearRect(context,       CGRectMake(0,0,self.bounds.size.width,self.bounds.size.height));
    }
    [self.incrementalImage drawAtPoint:CGPointZero];
    [self.brushColor setStroke];
    self.path.lineWidth = self.brushWidth;
    if(self.isEraser)
        [self.path strokeWithBlendMode:kCGBlendModeClear alpha:0.0];
    else
        [self.path stroke];
    self.innerPath.lineWidth = self.brushWidth - 10;
    [[UIColor clearColor] setStroke];
    [self.innerPath strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
    self.incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}

在屏幕上我得到的就是这张图片,Actual Result

我了解混合模式“清除”会产生橡皮擦效果。我想要的是笔画的侧面应该有一个坚实的轮廓,并且在中间是清晰的。它不应该融合到它正下方的路径中。它下面的路径应该仍然可见。 我怎样才能达到这个结果?

【问题讨论】:

    标签: ios


    【解决方案1】:

    看看这个问题:Generate a CGPath from another path's line width outline

    我想这就是您需要的:创建线条的多边形并用线条代替线条。使用CGPathCreateCopyByStrokingPath 获取多边形的路径并对其进行描边。

    好的,由于 CGPathCreateCopyByStrokingPath 似乎有点错误,您可以创建自己的实现。类似于以下算法,假设您有一个 NSArrayCGPoint 包装在 NSValue 对象中。 它工作得很好,直到你开始添加短的重叠线,这在绘图时当然很容易发生。您可以通过仅在touchesMoved 中添加与前一个添加点具有较大距离(lineWidth/2?)的点来减少这种影响。另一个缺点是线条很直(没有 kCGLineJoinRound 或 kCGLineCapRound),但也许你可以调整算法来做到这一点。

    + (UIBezierPath*)polygonForLine:(NSArray *)points withLineWidth:(CGFloat)lineWidth{
        UIBezierPath *path = [UIBezierPath bezierPath];
    
        //stores the starting point to close the path
        CGPoint startPoint;
    
        //get the points to a c-array for easier access
        CGPoint cpoints[points.count];
        int numPoints = 0;
        for(NSValue *v in points){
            cpoints[numPoints++] = [v CGPointValue];
        }
    
        //store the last intersection to apply it for the next segement
        BOOL hasIntersection = NO;
        CGPoint intersectionPoint;
    
        for (int i=0;i<numPoints-1;i++){
            //get the current line segment
            CGPoint p1 = cpoints[i];
            CGPoint p2 = cpoints[i+1];
            CGPoint l1p1,l1p2;
            getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l1p1,&l1p2);
    
            //if there had been an intersection with the previous segement, start here to get a nice outline
            if(hasIntersection){
                l1p1 = intersectionPoint;
            }
    
            //is there a next segment?
            if(i+2<numPoints){
                //get the next line segment
                p1 = cpoints[i+1];
                p2 = cpoints[i+2];
                CGPoint l2p1,l2p2;
                getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l2p1,&l2p2);
    
                //calculate the intersection point with the current line segment
                hasIntersection = getLineIntersection(l1p1, l1p2, l2p1, l2p2, &intersectionPoint);
    
                //if they intersect, the current linesegment has to end here to get a nice outline
                if(hasIntersection){
                    l1p2 = intersectionPoint;
                }
            }
    
            //write the current linesegment to the path
            if(i==0){
                //first point, move to it and store it for closing the path later on
                startPoint = l1p1;
                [path moveToPoint:startPoint];
            }else{
                [path addLineToPoint:l1p1];
            }
            [path addLineToPoint:l1p2];
        }
    
        //now do the same for the other side of the future polygon
        hasIntersection = NO;//reset intersections
        for (int i=numPoints-1;i>0;i--){
            //get the current line segment
            CGPoint p1 = cpoints[i];
            CGPoint p2 = cpoints[i-1];
            CGPoint l1p1,l1p2;
            getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l1p1,&l1p2);
    
            //if there had been an intersection with the previous segement, start here to get a nice outline
            if(hasIntersection){
                l1p1 = intersectionPoint;
            }
    
            //is there a next segment?
            if(i-2>=0){
                //get the next line segment
                p1 = cpoints[i-1];
                p2 = cpoints[i-2];
                CGPoint l2p1,l2p2;
                getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l2p1,&l2p2);
    
                //calculate the intersection point with the current line segment
                hasIntersection = getLineIntersection(l1p1, l1p2, l2p1, l2p2, &intersectionPoint);
                //if they intersect, the current linesegment has to end here to get a nice outline
                if(hasIntersection){
                    l1p2 = intersectionPoint;
                }
            }
    
            //write the current linesegment to the path
            [path addLineToPoint:l1p1];
            [path addLineToPoint:l1p2];
        }
    
        //close the path
        [path addLineToPoint:startPoint];
    
        //we're done
        return path;
    }
    
    void getOffsetLineSegmentForPoints(CGPoint p1, CGPoint p2, CGFloat lineWidth, CGPoint *linep1, CGPoint *linep2){
        CGPoint offset = CGPointSub(p2, p1);
        offset = CGPointNorm(offset);
        offset = CGPointOrthogonal(offset);
        offset = CGPointMultiply(offset, lineWidth/2);
    
        (*linep1) = CGPointAdd(p1, offset);
        (*linep2) = CGPointAdd(p2, offset);
    }
    
    CGPoint CGPointSub(CGPoint p1, CGPoint p2){
        return CGPointMake(p1.x-p2.x, p1.y-p2.y);
    }
    
    CGPoint CGPointAdd(CGPoint p1, CGPoint p2){
        return CGPointMake(p1.x+p2.x, p1.y+p2.y);
    }
    
    CGFloat CGPointLength(CGPoint p){
        return sqrtf(powf(p.x,2)+powf(p.y,2));
    }
    
    CGPoint CGPointNorm(CGPoint p){
        CGFloat length = CGPointLength(p);
        if(length==0)
            return CGPointZero;
        return CGPointMultiply(p, 1/length);
    }
    
    CGPoint CGPointMultiply(CGPoint p, CGFloat f){
        return CGPointMake(p.x*f, p.y*f);
    }
    
    CGPoint CGPointOrthogonal(CGPoint p){
        return CGPointMake(p.y, -p.x);
    }
    
    BOOL getLineIntersection(CGPoint l1p1, CGPoint l1p2, CGPoint l2p1,
                           CGPoint l2p2, CGPoint *intersection)
    {
        CGPoint s1 = CGPointSub(l1p2, l1p1);
        CGPoint s2 = CGPointSub(l2p2, l2p1);
        float determinant = (-s2.x * s1.y + s1.x * s2.y);
        if(determinant==0)
            return NO;
        CGPoint l2p1l1p1 = CGPointSub(l1p1, l2p1);
        float s, t;
        s = (-s1.y * l2p1l1p1.x + s1.x * l2p1l1p1.y) / determinant;
        t = ( s2.x * l2p1l1p1.y - s2.y * l2p1l1p1.x) / determinant;
    
        if (s >= 0 && s <= 1 && t >= 0 && t <= 1){
            if (intersection != NULL){
                (*intersection).x = l1p1.x + (t * s1.x);
                (*intersection).y = l1p1.y + (t * s1.y);
            }
    
            return YES;
        }
    
        return NO;
    }
    

    【讨论】:

    • 嗨。一小段代码会很有帮助。我不确定我是否完全理解您的建议
    • 我试过你的建议,我得到的输出很接近,但问题是我认为的斜接。 output
    • 我明白了,看起来这可能是他们在链接问题的已接受答案的 cmets 中谈论的错误。我想这对你来说意味着,你必须重新实现这个方法才能正常工作。所以从现有的....中创建一个新路径。
    • 要提供更多详细信息,这是我当前的代码current code,输出在这里output 您是否在此代码中看到问题。或者它是错误..会尝试你的建议。
    • ok - 使用不会产生错误的算法更新了答案。试试看是否可以根据需要进行调整
    猜你喜欢
    • 1970-01-01
    • 2020-12-02
    • 1970-01-01
    • 2012-11-09
    • 1970-01-01
    • 1970-01-01
    • 2015-07-06
    • 2018-12-11
    • 2022-01-20
    相关资源
    最近更新 更多