iOS如何使用轮廓绘制笔触

时间:2014-06-28 05:12:28

标签: ios

我正在查找此图像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

我理解混合模式'清除'给人一种橡皮擦的效果。我想要的是,笔划的两侧应有一个坚实的轮廓,中间应清晰。它不应该融入它下面的路径。它下面的路径应该仍然是可见的。 我怎样才能达到这个结果?

1 个答案:

答案 0 :(得分:3)

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

我想你需要的是:创建一条线的多边形,而不是线条。使用CGPathCreateCopyByStrokingPath获取多边形的路径并对其进行描边。

好的,因为CGPathCreateCopyByStrokingPath似乎有点bug,你可以创建自己的实现。类似于以下算法,假设您在NSArray个对象中包含CGPointNSValue。 它很好用,直到你开始添加短的重叠线,这当然可以在绘图时轻松发生。您可以通过仅将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;
}