基于描边(无填充)的CALayer蒙版反转

13

亲爱的stack overflow社区,

我遇到了一个关于iOS(Swift)CAShapeLayer的掩膜属性的问题。

我的目标是通过掩膜来擦除图像层的部分内容。但当我尝试反转它时出现了问题。

我找到了一些很好的关于路径反转的答案,但这些答案仅在使用填充路径时有用。而我想要做的是描边路径并使用其反转形状来遮罩一个图像。描边的线宽应该约为30.0,看起来像个橡皮擦。

我尝试了不同的方法。我当前的版本如下:

  1. 创建一个包含橡皮擦描边路径的CAShapeLayer
  2. 将图层的fill-color设置为nil
  3. 设置stroke-colorline width
  4. 将该图层作为图像层的掩模添加

这样做可以正常工作,但它只使图像中在描边内的部分可见。我想要实现相反的效果。我想过使用黑白掩膜,但这行不通,因为掩膜通过Alpha通道传递。

有没有人有解决这个问题的想法?


你能否添加一些图片来描述你想要实现的内容? - Neil Galiaskarov
我只能代表自己说,我有一条路径是一条线条。我想要剪裁掉圆形(如截图所示)。我想要剪裁掉线条的末端。(我不想修改路径本身)。截图:https://d17oy1vhnax1f7.cloudfront.net/items/0b3A3D2H3B3v01221G1I/Screen%20Shot%202016-09-09%20at%2018.43.19.png?v=1db0e296 - Christian 'fuzi' Orgler
我很乐意帮忙,但如果您能澄清一下,那就太感激了——您是想通过任何可能的方式创建一个橡皮擦,还是重要的是按照您所概述的方式实现橡皮擦? - Marcus
我需要一个能够实现我上述描述的橡皮擦。橡皮擦本身不能有填充颜色。也许你可以有一个有填充颜色的不可见层,它可以遮盖住这一层。不确定,会尝试一下。 - Christian 'fuzi' Orgler
1个回答

7
您可以通过在非不透明图层上使用透明颜色进行绘制来实现此目的。这可以通过使用另一种混合模式来完成绘制。不幸的是,CAShapeLayer不支持此功能。因此,您必须编写自己的形状层类:
@interface ShapeLayer : CALayer

@property(nonatomic) CGPathRef path;
@property(nonatomic) CGColorRef fillColor;
@property(nonatomic) CGColorRef strokeColor;
@property(nonatomic) CGFloat lineWidth;

@end

@implementation ShapeLayer

@dynamic path;
@dynamic fillColor;
@dynamic strokeColor;
@dynamic lineWidth;

- (void)drawInContext:(CGContextRef)inContext {
    CGContextSetGrayFillColor(inContext, 0.0, 1.0);
    CGContextFillRect(inContext, self.bounds);
    CGContextSetBlendMode(inContext, kCGBlendModeSourceIn);
    if(self.strokeColor) {
        CGContextSetStrokeColorWithColor(inContext, self.strokeColor);
    }
    if(self.fillColor) {
        CGContextSetFillColorWithColor(inContext, self.fillColor);
    }
    CGContextSetLineWidth(inContext, self.lineWidth);
    CGContextAddPath(inContext, self.path);
    CGContextDrawPath(inContext, kCGPathFillStroke);
}

@end

创建一个带有透明路径的图层:
ShapeLayer *theLayer = [ShapeLayer layer];

theLayer.path = ...;
theLayer.strokeColor = [UIColor clearColor].CGColor;
theLayer.fillColor = [UIColor colorWithWhite:0.8 alpha:0.5];
theLayer.lineWith = 3.0;
theLayer.opaque = NO; // Important, otherwise you will get a black rectangle

我使用了以下代码在绿色背景前绘制了一个带有透明边框的半透明圆形:

enter image description here

编辑:这是在Swift中用于该层的相应代码:
public class ShapeLayer: CALayer {
    @NSManaged var path : CGPath?
    @NSManaged var fillColor : CGColor?
    @NSManaged var strokeColor : CGColor?
    @NSManaged var lineWidth : CGFloat

    override class func defaultValue(forKey inKey: String) -> Any? {
        return inKey == "lineWidth" ? 1.0 : super.defaultValue(forKey: inKey)
    }

    override class func needsDisplay(forKey inKey: String) -> Bool {
        return inKey == "path" || inKey == "fillColor" || inKey == "strokeColor" || inKey == "lineWidth" || super.needsDisplay(forKey: inKey)
    }

    override public func draw(in inContext: CGContext) {
        if let thePath = path {
            inContext.setFillColor(gray: 0.0, alpha: 1.0)
            inContext.fill(self.bounds)
            inContext.setBlendMode(.sourceIn)
            if let strokeColor = self.strokeColor {
                inContext.setStrokeColor(strokeColor)
            }
            if let fillColor = self.fillColor {
                inContext.setFillColor(fillColor)
            }
            inContext.setLineWidth(self.lineWidth)
            inContext.addPath(thePath)
            inContext.drawPath(using: .fillStroke)
        }
    }
}
注意: 通过使用@NSManaged标记属性,您可以轻松地在Swift中实现needsDisplay(forKey inKey:)或在Objective C中实现needsDisplayForKey:使属性可动画化。我已相应地调整了Swift代码。

但是,即使您不需要动画,最好还是使用@NSManaged标记属性,因为QuartzCore会复制图层并应该将所有属性一起复制。Swift中的@NSManaged是Objective C中@dynamic的对应物,因为它避免了创建属性实现。相反,CALayer分别使用value(forKey:)setValue(_:forKey:)获取和设置属性值。


1
非常感谢,这正是我所寻找的。运行得很好。 - adam.wulf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接