在 CALayer 上过滤,除了形状是(不一定不同的)矩形的并集

Filter on CALayer except for a shape which is an union of (non necessarily distinct) rectangles

我想将 CIFilter 应用到 CALayer,除了一个矩形联合区域。 我 post 我的代码不工作,它的作用恰恰相反,即过滤器仅应用于矩形而不是外部!

func refreshTheFrameWithEffect() {
    self.layer!.masksToBounds = true
    self.layerUsesCoreImageFilters = true
    self.layer!.needsDisplayOnBoundsChange = true

    let filter = CIFilter(name: "CICircleSplashDistortion")
    filter!.setDefaults()
    self.layer!.backgroundFilters = [filter!]

    var excludedRects: [CGRect] = [] //INITIALISE THEM HOW YOU PREFER

    let maskLayer = CAShapeLayer()
    maskLayer.frame = self.bounds
    self.layer!.fillMode = kCAFillRuleEvenOdd
    var maskPath = NSBezierPath()
    for rect in excludedRects {
        maskPath.append(NSBezierPath(rect: rect))
    }

    maskLayer.path = maskPath.CGPath
    self.layer!.mask = maskLayer
    self.layer!.needsDisplay()
}

然后下面的代码来自互联网,因为 NSBezierPath 没有属性 CGPath,不像 UIBezierPath。

public var CGPath: CGPath {
    let path = CGMutablePath()
    var points = [CGPoint](repeating: .zero, count: 3)
    for i in 0 ..< self.elementCount {
        let type = self.element(at: i, associatedPoints: &points)
        switch type {
        case .moveToBezierPathElement: path.move(to: CGPoint(x: points[0].x, y: points[0].y) )
        case .lineToBezierPathElement: path.addLine(to: CGPoint(x: points[0].x, y: points[0].y) )
        case .curveToBezierPathElement: path.addCurve(      to: CGPoint(x: points[2].x, y: points[2].y),
                                                            control1: CGPoint(x: points[0].x, y: points[0].y),
                                                            control2: CGPoint(x: points[1].x, y: points[1].y) )
        case .closePathBezierPathElement: path.closeSubpath()
        }
    }
    return path
}

据我所知,您不能将图层遮盖到路径的反向。

一些观察:

  1. 如果您只是想在整个视图中剔除路径,您可以使用创建一个由整个 CGRect 加上各种内部结构组成的路径的技巧来做到这一点路径并利用 even/odd winding/fill 规则,但如果您的内部路径相互重叠,那将不起作用。

  2. 您可以将图像屏蔽为路径的反向(通过创建单独的“图像屏蔽”),但这不适用于动态 CALayer 屏蔽。它用于屏蔽 NSImage。因此,如果您可以为视图的过滤部分使用快照,那是一个选项。

    有关使用图像遮罩的示例,请参见下面的代码片段。

  3. 另一种方法是将过滤器应用于整个视图,对其进行快照,将该快照放在有问题的视图下方,然后将顶级视图屏蔽到您的内部路径。实际上,将未过滤的视图屏蔽到您的内部路径,在其下方显示您的视图的过滤快照。

  4. 然而,方法是创建一条路径,代表所有内部路径的并集轮廓。如果路径很简单(例如非旋转矩形),这很容易。如果路径很复杂(有些是旋转的,有些是非矩形路径等),这会变得很棘手。但这个微不足道的场景还算不错。不管怎样,如果你这样做了,那么你就可以退回到那个偶数把戏了。

我对这些方法中的任何一个都不完全满意,但我看不到任何其他方法来完成您正在寻找的东西。希望有人会提出一些更好的方法来解决这个问题。


要扩展选项 2(使用通过绘制一些可能重叠的路径创建的图像蒙版),在 Swift 3 中,您可以执行以下操作:

private func maskImageWithPaths(image: NSImage, size: CGSize) -> NSImage {
    // define parameters for two overlapping paths
    
    let center1 = CGPoint(x: size.width * 0.40, y: size.height / 2)
    let radius1 = size.width * 0.20
    
    let center2 = CGPoint(x: size.width * 0.60 , y: size.height / 2)
    let radius2 = size.width * 0.20
    
    // create these two overlapping paths
    
    let path = CGMutablePath()
    path.move(to: center1)
    path.addArc(center: center1, radius: radius1, startAngle: -.pi / 2, endAngle: .pi * 3 / 2, clockwise: false)
    path.move(to: center2)
    path.addArc(center: center2, radius: radius2, startAngle: -.pi / 2, endAngle: .pi * 3 / 2, clockwise: false)
    
    // create image from these paths
    
    let imageRep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSDeviceRGBColorSpace, bitmapFormat: .alphaFirst, bytesPerRow: 4 * Int(size.width), bitsPerPixel: 32)!
    let context = NSGraphicsContext(bitmapImageRep: imageRep)!
    
    context.cgContext.addPath(path)
    context.cgContext.setFillColor(NSColor.blue.cgColor)
    context.cgContext.fillPath()
    let maskImage = context.cgContext.makeImage()!
    
    let mask = CGImage(maskWidth: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: 4 * Int(size.width), provider: maskImage.dataProvider!, decode: nil, shouldInterpolate: true)!
    
    let finalImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)!.masking(mask)!
    return NSImage(cgImage: finalImage, size: size)
}

产生:

这会掩盖绘制的路径。