如何组合/嵌套 CALayer 面具?

How to combine / nest CALayer masks?

提问人:Andrei Herford 提问时间:3/7/2023 最后编辑:Andrei Herford 更新时间:3/8/2023 访问量:170

问:

是否可以使用嵌套蒙版创建一个组合蒙版?我的目标是通过组合/遮罩其他复杂形状并对结果应用渐变来创建复杂的形状。CALayer

例如,一个我拿着一条画独角兽头的路径,第二个我拿着一条画一朵花的路径。通过将花层作为面具贴在独角兽层上,我得到了一只“花独角兽”。在下一步中,我可以将这种复杂的形状应用于“花独角兽彩虹”。CAShapeLayerCAShapeLayerCAGradientLayer

但是,当应用独角兽层(带有花朵蒙版)作为渐变的蒙版时,结果是独角兽彩虹。已经涂在独角兽身上的花面具被跳过了。

有没有其他方法可以组合/嵌套图层蒙版来创建这样的形状?


下面的代码使用一些更简单的形状来说明该问题:

let rectLayer = CAShapeLayer()
rectLayer = // ... a simple rect shape

let circleLayer = CAShapeLayer()
circleLayer = // ... a circle shape, half overlapping the rect

// Apply rect as mask to create half circle
circleLayer.mask = rectLayer

// Some gradient
let gradientLayer = CAGradientLayer()
gradientLayer = // ... set up gradient with some colors

// Apply masked circle as mask to get a half circle with gradient
gradientLayer.mask = circleLayer

现在我假设得到一个带有渐变的半圆形状。但是,我得到了一个带有渐变的圆圈。似乎在将 as 蒙版应用于矩形蒙版时不考虑/应用......circleLayergradientLayer

我做错了什么吗?有没有其他方法可以解决这个问题?在许多不同的应用中,遮罩和组合面罩是一项非常常见的任务。因此,我想知道像 CA 这样强大的框架是否有办法做到这一点。

Swift UIView Calayer

评论

0赞 Andrei Herford 3/7/2023
我发现,不可能以这种特定方式嵌套掩码。这并不意味着可能没有其他方法可以做到这一点。也许我只是做错了?在许多不同的应用程序中,掩蔽是一项非常常见的任务。因此,像 Core Animation 这样强大的框架似乎很有可能做到这一点......
0赞 matt 3/7/2023
“这怎么解决?”如何解决?目标是什么?这具有 xy 问题的所有特征......我想你追求的是一个充满渐变的半圆;这很简单。但是不要问嵌套蒙版,而是要问带有渐变的半圆。
0赞 matt 3/7/2023
“我发现,不可能以这种特定方式嵌套面具。这并不意味着可能没有其他方法可以做到这一点。没错,这就是使这个问题成为一个 xy 问题的原因。你知道那是什么吗?xyproblem.info你建议这样做的方式特别麻烦,因为简单地画一个半圆(不使用蒙版来帮忙)是微不足道的。
0赞 Andrei Herford 3/7/2023
我完全理解你想说什么。但是,我认为问题和目标在我的问题中得到了明确的描述:组合多个蒙版以创建具有渐变的复杂形状。当然,半圆只是一个例子。正如现在所编辑的那样,创建花-独角兽-彩虹是另一个更复杂的方法。
0赞 matt 3/8/2023
但遮罩不是如何创建复杂的形状。尽管我活得很长,但我从未见过花-独角兽-彩虹。所以再一次,我不知所措。

答:

0赞 Duncan C 3/7/2023 #1

答:你不能嵌套掩码。您将需要为每个图层创建一个唯一的蒙版,以蒙蔽要隐藏的该图层的部分。

评论

0赞 Andrei Herford 3/7/2023
好的,我知道掩码不能嵌套。但是你回答的其余部分听起来好像面具仍然可以用来通过以不同的方式组合/应用面具来解决我描述的情况?这是对的吗?是否可以使用蒙版创建渐变半圆,或者这是否需要其他自定义绘图技术?
0赞 Duncan C 3/8/2023
渐变半圆?你是说径向渐变,其中一半被遮盖了?确定。您始终可以将任意形状的蒙版应用于任何单个图层。创建一个半圆形作为形状图层,并将其作为蒙版应用于径向渐变图层。
2赞 Andrei Herford 3/8/2023 #2

正如@DuncanC所指出的,掩码不能嵌套。因此,将 A 层作为掩码应用于层 B,而不是将 B 层作为掩码应用于层 C 并不能按预期工作。这将仅使用 B 的原始内容作为掩码,而不考虑 A 层所做的更改。

我能够通过首先“展平”图层 B + 遮罩图层 A 的结果来绕过这个限制。这是通过创建此结果的图像并将其作为掩码应用于图层 C 来完成的:

let layerA = CAShapeLayer() // ... add some shapes
let layerB = CAShapeLayer() // ... add some shapes

// Apply A as mask to B
layerB.mask = layerA

// Flatten B
let flatB = layerB.flatten()

// Apply flatB as mask to C
let layerC = CAShapeLayer() // ... add some shapes
layerC.mask = flatB

扁平化是使用此 CALayer 扩展完成的:

extension CALayer {
    func flatten() -> CALayer {
        guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
              let ctx = CGContext(data: nil, width: Int(bounds.width), height: Int(bounds.height), bitsPerComponent: 8, bytesPerRow: 4*Int(bounds.width), space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return CALayer() }
    
        ctx.translateBy(x: 0, y: bounds.height)
        ctx.scaleBy(x: 1.0, y: -1.0)

        render(in: ctx)
        let image = ctx.makeImage()
    
        let flattenedLayer = CALayer()
        flattenedLayer.frame = frame
        flattenedLayer.contents = image
    
        return flattenedLayer
    }
}

这可能不是完美的解决方案,但在我的情况下效果很好。也许这也可能对其他人有所帮助。