如何掩盖和删除Cashapelayer?

发布于 2025-02-03 05:25:21 字数 997 浏览 7 评论 0原文

我正在尝试使擦除功能与CashApelayer合作。当前代码:

class MaskTestVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white
        let image = UIImage(named: "test.jpg")!

        let testLayer = CAShapeLayer()
        testLayer.contents = image.cgImage
        testLayer.frame = view.bounds

        let maskLayer = CAShapeLayer()
        maskLayer.opacity = 1.0
        maskLayer.lineWidth = 20.0
        maskLayer.strokeColor = UIColor.black.cgColor
        maskLayer.fillColor = UIColor.clear.cgColor
        maskLayer.frame = view.bounds
        testLayer.mask = maskLayer

        view.layer.addSublayer(testLayer)
    }

}

我在想如果我创建掩码层,那么我可以在掩码层上绘制一条路径,并删除图像的一部分:

let path = UIBezierPath()
path.move(to: CGPoint(x: 100.0, y: 100.0))
path.addLine(to: CGPoint(x: 200.0, y: 200.0))
maskLayer.path = path.cgPath

但是,似乎当我添加MaskLayer时,它涵盖了整个图像,我看不到下面的内容。我在这里做错了什么?

I am trying to get erase functionality working with CAShapeLayer. Current code:

class MaskTestVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white
        let image = UIImage(named: "test.jpg")!

        let testLayer = CAShapeLayer()
        testLayer.contents = image.cgImage
        testLayer.frame = view.bounds

        let maskLayer = CAShapeLayer()
        maskLayer.opacity = 1.0
        maskLayer.lineWidth = 20.0
        maskLayer.strokeColor = UIColor.black.cgColor
        maskLayer.fillColor = UIColor.clear.cgColor
        maskLayer.frame = view.bounds
        testLayer.mask = maskLayer

        view.layer.addSublayer(testLayer)
    }

}

I was thinking if I create a mask layer then I could draw a path on the mask layer and erase parts of the image eg:

let path = UIBezierPath()
path.move(to: CGPoint(x: 100.0, y: 100.0))
path.addLine(to: CGPoint(x: 200.0, y: 200.0))
maskLayer.path = path.cgPath

However it seems that when I add the maskLayer it covers the entire image and I can't see the contents below it. What am I doing wrong here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

负佳期 2025-02-10 05:25:21

您可以通过创建自定义calayer subriding draw(在CTX:CGCONTEXT)

class MyCustomLayer: CALayer {
    
    var myPath: CGPath?
    var lineWidth: CGFloat = 24.0

    override func draw(in ctx: CGContext) {

        // fill entire layer with solid color
        ctx.setFillColor(UIColor.gray.cgColor)
        ctx.fill(self.bounds);

        // we want to "clear" the stroke
        ctx.setStrokeColor(UIColor.clear.cgColor);
        // any color will work, as the mask uses the alpha value
        ctx.setFillColor(UIColor.white.cgColor)
        ctx.setLineWidth(self.lineWidth)
        ctx.setLineCap(.round)
        ctx.setLineJoin(.round)
        if let pth = self.myPath {
            ctx.addPath(pth)
        }
        ctx.setBlendMode(.sourceIn)
        ctx.drawPath(using: .fillStroke)

    }
    
}

这是一个完整的示例...我们将创建一个uiview子类,并且由于效果将“刮擦图像”,我们将其称为scratchOffimageView

class ScratchOffImageView: UIView {

    public var image: UIImage? {
        didSet {
            self.scratchOffImageLayer.contents = image?.cgImage
        }
    }

    // adjust drawing-line-width as desired
    //  or set from
    public var lineWidth: CGFloat = 24.0 {
        didSet {
            maskLayer.lineWidth = lineWidth
        }
    }

    private class MyCustomLayer: CALayer {
        
        var myPath: CGPath?
        var lineWidth: CGFloat = 24.0

        override func draw(in ctx: CGContext) {

            // fill entire layer with solid color
            ctx.setFillColor(UIColor.gray.cgColor)
            ctx.fill(self.bounds);

            // we want to "clear" the stroke
            ctx.setStrokeColor(UIColor.clear.cgColor);
            // any color will work, as the mask uses the alpha value
            ctx.setFillColor(UIColor.white.cgColor)
            ctx.setLineWidth(self.lineWidth)
            ctx.setLineCap(.round)
            ctx.setLineJoin(.round)
            if let pth = self.myPath {
                ctx.addPath(pth)
            }
            ctx.setBlendMode(.sourceIn)
            ctx.drawPath(using: .fillStroke)

        }
        
    }

    private let maskPath: UIBezierPath = UIBezierPath()
    private let maskLayer: MyCustomLayer = MyCustomLayer()
    private let scratchOffImageLayer: CALayer = CALayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {

        // Important, otherwise you will get a black rectangle
        maskLayer.isOpaque = false
        
        // add the image layer
        layer.addSublayer(scratchOffImageLayer)
        // assign the layer mask
        scratchOffImageLayer.mask = maskLayer
        
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()

        // set frames for mask and image layers
        maskLayer.frame = bounds
        scratchOffImageLayer.frame = bounds

        // triggers drawInContext
        maskLayer.setNeedsDisplay()
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let currentPoint = touch.location(in: self)
        maskPath.move(to: currentPoint)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let currentPoint = touch.location(in: self)
        // add line to our maskPath
        maskPath.addLine(to: currentPoint)
        // update the mask layer path
        maskLayer.myPath = maskPath.cgPath
        // triggers drawInContext
        maskLayer.setNeedsDisplay()
    }
    
}

,示例视图控制器:

class ScratchOffViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        guard let img = UIImage(named: "test.jpg") else {
            fatalError("Could not load image!!!!")
        }

        let scratchOffView = ScratchOffImageView()
        
        // set the "scratch-off" image
        scratchOffView.image = img
        
        // default line width is 24.0
        //  we can set it to a different width here
        //scratchOffView.lineWidth = 12

        // let's add a light-gray label with red text
        //  we'll overlay the scratch-off-view on top of the label
        //  so we can see the text "through" the image
        let backgroundLabel = UILabel()
        backgroundLabel.font = .italicSystemFont(ofSize: 36)
        backgroundLabel.text = "This is some text in a label so we can see that the path is clear -- so it appears as if the image is being \"scratched off\""
        backgroundLabel.numberOfLines = 0
        backgroundLabel.textColor = .red
        backgroundLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        
        [backgroundLabel, scratchOffView].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
        }
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            backgroundLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.7),
            backgroundLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            backgroundLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            scratchOffView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.8),
            scratchOffView.heightAnchor.constraint(equalTo: scratchOffView.widthAnchor, multiplier: 2.0 / 3.0),
            scratchOffView.centerXAnchor.constraint(equalTo: backgroundLabel.centerXAnchor),
            scratchOffView.centerYAnchor.constraint(equalTo: backgroundLabel.centerYAnchor),
        ])
        
    }

}

和 看起来像这样开始 - 我使用了3:2映像,然后在带有红色文本的浅灰色标签上叠加,以便我们可以看到我们正在“刮擦”图像:

​经过一点“刮擦”之后:

”输入图像描述在此处”

以及在很多“划痕”之后:

”在此处输入图像说明”

You can use a bezier path mask to "erase" the path by creating a custom CALayer subclass and overriding draw(in ctx: CGContext):

class MyCustomLayer: CALayer {
    
    var myPath: CGPath?
    var lineWidth: CGFloat = 24.0

    override func draw(in ctx: CGContext) {

        // fill entire layer with solid color
        ctx.setFillColor(UIColor.gray.cgColor)
        ctx.fill(self.bounds);

        // we want to "clear" the stroke
        ctx.setStrokeColor(UIColor.clear.cgColor);
        // any color will work, as the mask uses the alpha value
        ctx.setFillColor(UIColor.white.cgColor)
        ctx.setLineWidth(self.lineWidth)
        ctx.setLineCap(.round)
        ctx.setLineJoin(.round)
        if let pth = self.myPath {
            ctx.addPath(pth)
        }
        ctx.setBlendMode(.sourceIn)
        ctx.drawPath(using: .fillStroke)

    }
    
}

Here's a complete example... we'll create a UIView subclass and, since the effect will be "scratching off the image" we'll call it ScratchOffImageView:

class ScratchOffImageView: UIView {

    public var image: UIImage? {
        didSet {
            self.scratchOffImageLayer.contents = image?.cgImage
        }
    }

    // adjust drawing-line-width as desired
    //  or set from
    public var lineWidth: CGFloat = 24.0 {
        didSet {
            maskLayer.lineWidth = lineWidth
        }
    }

    private class MyCustomLayer: CALayer {
        
        var myPath: CGPath?
        var lineWidth: CGFloat = 24.0

        override func draw(in ctx: CGContext) {

            // fill entire layer with solid color
            ctx.setFillColor(UIColor.gray.cgColor)
            ctx.fill(self.bounds);

            // we want to "clear" the stroke
            ctx.setStrokeColor(UIColor.clear.cgColor);
            // any color will work, as the mask uses the alpha value
            ctx.setFillColor(UIColor.white.cgColor)
            ctx.setLineWidth(self.lineWidth)
            ctx.setLineCap(.round)
            ctx.setLineJoin(.round)
            if let pth = self.myPath {
                ctx.addPath(pth)
            }
            ctx.setBlendMode(.sourceIn)
            ctx.drawPath(using: .fillStroke)

        }
        
    }

    private let maskPath: UIBezierPath = UIBezierPath()
    private let maskLayer: MyCustomLayer = MyCustomLayer()
    private let scratchOffImageLayer: CALayer = CALayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {

        // Important, otherwise you will get a black rectangle
        maskLayer.isOpaque = false
        
        // add the image layer
        layer.addSublayer(scratchOffImageLayer)
        // assign the layer mask
        scratchOffImageLayer.mask = maskLayer
        
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()

        // set frames for mask and image layers
        maskLayer.frame = bounds
        scratchOffImageLayer.frame = bounds

        // triggers drawInContext
        maskLayer.setNeedsDisplay()
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let currentPoint = touch.location(in: self)
        maskPath.move(to: currentPoint)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let currentPoint = touch.location(in: self)
        // add line to our maskPath
        maskPath.addLine(to: currentPoint)
        // update the mask layer path
        maskLayer.myPath = maskPath.cgPath
        // triggers drawInContext
        maskLayer.setNeedsDisplay()
    }
    
}

and, an example view controller:

class ScratchOffViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        guard let img = UIImage(named: "test.jpg") else {
            fatalError("Could not load image!!!!")
        }

        let scratchOffView = ScratchOffImageView()
        
        // set the "scratch-off" image
        scratchOffView.image = img
        
        // default line width is 24.0
        //  we can set it to a different width here
        //scratchOffView.lineWidth = 12

        // let's add a light-gray label with red text
        //  we'll overlay the scratch-off-view on top of the label
        //  so we can see the text "through" the image
        let backgroundLabel = UILabel()
        backgroundLabel.font = .italicSystemFont(ofSize: 36)
        backgroundLabel.text = "This is some text in a label so we can see that the path is clear -- so it appears as if the image is being \"scratched off\""
        backgroundLabel.numberOfLines = 0
        backgroundLabel.textColor = .red
        backgroundLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        
        [backgroundLabel, scratchOffView].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
        }
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            backgroundLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.7),
            backgroundLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            backgroundLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            scratchOffView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.8),
            scratchOffView.heightAnchor.constraint(equalTo: scratchOffView.widthAnchor, multiplier: 2.0 / 3.0),
            scratchOffView.centerXAnchor.constraint(equalTo: backgroundLabel.centerXAnchor),
            scratchOffView.centerYAnchor.constraint(equalTo: backgroundLabel.centerYAnchor),
        ])
        
    }

}

It will look like this to start - I used a 3:2 image, and overlaid it on a light-gray label with red text so we can see that we are "scratching off" the image:

enter image description here

then, after a little bit of "scratching":

enter image description here

and after a lot of "scratching":

enter image description here

ㄟ。诗瑗 2025-02-10 05:25:21

Masklayer 没有初始路径,因此其内容没有填充,也可以将其填充。CLEAR将完全掩盖图像。

这对我有用:

override func viewDidLoad() {
   super.viewDidLoad()
        
   view.backgroundColor = .white
   let image = UIImage(named: "test.jpg")!
       
   let testLayer = CAShapeLayer()
   testLayer.contents = image.cgImage
   testLayer.frame = view.bounds
        
   let maskLayer = CAShapeLayer()
   maskLayer.strokeColor = UIColor.black.cgColor
   maskLayer.fillColor = UIColor.black.cgColor
   maskLayer.path = UIBezierPath(rect: view.bounds).cgPath
   testLayer.mask = maskLayer
        
   view.layer.addSublayer(testLayer)
     
   /* optional for testing   
   let path = UIBezierPath()
   path.move(to: CGPoint(x: 100.0, y: 100.0))
   path.addLine(to: CGPoint(x: 200.0, y: 200.0))
   maskLayer.path = path.cgPath
   */
}

实际上不确定中风和填充的组合是否有效,但也许其他人提出了解决方案。如果您想从路径上切出形状,则可以尝试这样的事情:

override func viewDidLoad() {
   super.viewDidLoad()
        
   view.backgroundColor = .white
   let image = UIImage(named: "test.jpg")!
        
   let testLayer = CAShapeLayer()
   testLayer.contents = image.cgImage
   testLayer.frame = view.bounds
        
   let maskLayer = CAShapeLayer()
   maskLayer.fillColor = UIColor.black.cgColor
   maskLayer.fillRule = .evenOdd
   testLayer.mask = maskLayer
        
   view.layer.addSublayer(testLayer)
        
   let path = UIBezierPath(rect: CGRect(x: 100, y: 100, width: 200, height: 20))
   let fillPath = UIBezierPath(rect: view.bounds)
   fillPath.append(path)
   maskLayer.path = fillPath.cgPath
}

maskLayer has no initial path therefore its content is not filled, also filling it with .clear will completely mask the image.

This works for me:

override func viewDidLoad() {
   super.viewDidLoad()
        
   view.backgroundColor = .white
   let image = UIImage(named: "test.jpg")!
       
   let testLayer = CAShapeLayer()
   testLayer.contents = image.cgImage
   testLayer.frame = view.bounds
        
   let maskLayer = CAShapeLayer()
   maskLayer.strokeColor = UIColor.black.cgColor
   maskLayer.fillColor = UIColor.black.cgColor
   maskLayer.path = UIBezierPath(rect: view.bounds).cgPath
   testLayer.mask = maskLayer
        
   view.layer.addSublayer(testLayer)
     
   /* optional for testing   
   let path = UIBezierPath()
   path.move(to: CGPoint(x: 100.0, y: 100.0))
   path.addLine(to: CGPoint(x: 200.0, y: 200.0))
   maskLayer.path = path.cgPath
   */
}

Not actually sure if a combination of stroke and fill works but maybe someone else comes up with a solution. If you want to cut out shapes from your path you could try something like this:

override func viewDidLoad() {
   super.viewDidLoad()
        
   view.backgroundColor = .white
   let image = UIImage(named: "test.jpg")!
        
   let testLayer = CAShapeLayer()
   testLayer.contents = image.cgImage
   testLayer.frame = view.bounds
        
   let maskLayer = CAShapeLayer()
   maskLayer.fillColor = UIColor.black.cgColor
   maskLayer.fillRule = .evenOdd
   testLayer.mask = maskLayer
        
   view.layer.addSublayer(testLayer)
        
   let path = UIBezierPath(rect: CGRect(x: 100, y: 100, width: 200, height: 20))
   let fillPath = UIBezierPath(rect: view.bounds)
   fillPath.append(path)
   maskLayer.path = fillPath.cgPath
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文