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

Invert CALayer mask that is based on a stroke (no fill)

亲爱的堆栈溢出社区,

关于 iOS (Swift) 中 CAShapeLayer 的掩码 属性 我有一个问题。

我想要实现的是一个 橡皮擦,它通过遮罩来擦除部分图像层。当我试图反转它时,问题就来了。

我找到了一些关于反转路径的好答案,但这些仅在使用 filled 路径时有用。我尝试做的是 描边 一条路径并使用倒置的路径来掩盖图像。笔画上的线条宽度应该在 30.0 左右,所以它看起来像橡皮擦。

我尝试了不同的东西。我当前的版本如下所示:

  1. 创建一个 CAShapeLayer 保存橡皮擦笔画的路径
  2. 设置图层的fill-colornil
  3. 设置stroke-colorline width
  4. 添加图层作为图像图层的遮罩

这工作正常,但它只使图像的部分 在笔画 内可见。我想反转。我想到了黑白蒙版,但是这个不行,因为蒙版是通过alpha通道传递的。

有人知道如何解决这个问题吗?

您可以通过将透明颜色绘制到非不透明层来实现此目的。这可以通过使用另一种混合模式进行绘图来完成。不幸的是 CAShapeLayer 不支持这个。因此,您必须编写自己的形状层 class:

@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

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

编辑:下面是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 或 needsDisplayForKey: 在 Objective C 中。我相应地调整了 Swift 代码。

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