【问题标题】:Image background removal using bezierPath in iOS在 iOS 中使用 uibezierPath 去除图像背景
【发布时间】:2021-01-12 11:27:45
【问题描述】:

在到处搜索之后,我发现没有任何特定的来源可用于使用 bezierPath 删除背景。基本上我正在尝试实现类似图像剪切的功能(您可以检查 PicsArt >> Image editor >> CutOut)。在此用户可以在图像上绘制任何形状,并且可以突出显示选定区域并删除其余部分。

这是我用来在图像上画线的方法

class DrawingImageView: UIImageView {
    var path = UIBezierPath()
    var previousTouchPoint = CGPoint.zero
    var shapeLayer = CAShapeLayer()
    var isClear: Bool = false {
        didSet {
            updateView()
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        setupView()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    func updateView() {
        self.shapeLayer.shadowOffset = .init(width: 1, height: 1)
        self.shapeLayer.shadowColor = UIColor.black.cgColor
        self.shapeLayer.shadowOpacity = 1

        self.shapeLayer.lineWidth = 20
        self.shapeLayer.lineCap = .round
        self.shapeLayer.strokeColor = isClear ? UIColor.clear.cgColor : UIColor.blue.cgColor
        self.shapeLayer.opacity = 0.3
        self.isUserInteractionEnabled = true
    }

    func setupView() {
        self.layer.addSublayer(shapeLayer)
        updateView()
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        if let location = touches.first?.location(in: self){
            previousTouchPoint = location
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)

        if let location = touches.first?.location(in: self){
            path.move(to: location)
            path.addLine(to: previousTouchPoint)
            previousTouchPoint = location
            shapeLayer.path = path.cgPath
        }
    }
}

为了剪切一个选定的区域,我尝试使用CGImagecropping(to:) 方法。但它只是清除整个图像不起作用。您可以在下面查看我的内容

普通图像

选定区域[绘图]

使用cropping(to:) 后的结果图像

我不确定我这样做是否正确。我也对其他方式持开放态度。

【问题讨论】:

    标签: ios swift image-processing uiimageview uiimage


    【解决方案1】:

    要“移除背景”,您需要将形状图层应用为蒙版。

    将此函数添加到您的 DrawingImageView 类中:

    func applyMask() -> Void {
        // set shape opacity to 1.0
        shapeLayer.opacity = 1.0
        // use it as a mask
        layer.mask = shapeLayer
    }
    

    然后,在您的控制器中,添加一个按钮操作来调用该函数。你应该会看到你想要的结果:

    这是一个完整的工作示例...我还在DrawingImageView 中添加了一个布尔变量和这个函数,以允许在“绘图”和“蒙版”模式之间切换,所以我可以返回并添加更多到路径中。


    控制器

    class RemoveBackgroundViewController: UIViewController {
        
        var theDrawingView: DrawingImageView = DrawingImageView(frame: .zero)
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            guard let img = UIImage(named: "musk") else {
                fatalError("Could not load image!!!")
            }
            
            theDrawingView.image = img
            
            let btn = UIButton()
            btn.setTitle("Apply Mask", for: [])
            btn.setTitleColor(.white, for: .normal)
            btn.setTitleColor(.gray, for: .highlighted)
            btn.backgroundColor = .red
            
            [btn, theDrawingView].forEach {
                $0.translatesAutoresizingMaskIntoConstraints = false
                view.addSubview($0)
            }
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
    
                // center the drawing image view, with 20-pts on each side
                //  sized proportionally to the loaded image
                theDrawingView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                theDrawingView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                theDrawingView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
                theDrawingView.heightAnchor.constraint(equalTo: theDrawingView.widthAnchor, multiplier: img.size.height / img.size.width),
                
                // constrain button above the image
                btn.bottomAnchor.constraint(equalTo: theDrawingView.topAnchor, constant: -8.0),
                btn.centerXAnchor.constraint(equalTo: g.centerXAnchor),
                btn.widthAnchor.constraint(equalToConstant: 160.0),
                
            ])
            
            btn.addTarget(self, action: #selector(self.toggleActivity(_:)), for: .touchUpInside)
            
        }
        
        @objc func toggleActivity(_ sender: Any) {
            guard let btn = sender as? UIButton else { return }
            if theDrawingView.isDrawing {
                theDrawingView.applyMask()
                btn.setTitle("Draw More", for: [])
            } else {
                theDrawingView.drawMore()
                btn.setTitle("Apply Mask", for: [])
            }
        }
    }
    

    DrawingImageView(修改)

    class DrawingImageView: UIImageView {
        var path = UIBezierPath()
        var previousTouchPoint = CGPoint.zero
        var shapeLayer = CAShapeLayer()
        
        var isDrawing: Bool = true
        
        var isClear: Bool = false {
            didSet {
                updateView()
            }
        }
        
        override func awakeFromNib() {
            super.awakeFromNib()
            setupView()
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            setupView()
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
        
        func updateView() {
            self.shapeLayer.shadowOffset = .init(width: 1, height: 1)
            self.shapeLayer.shadowColor = UIColor.black.cgColor
            self.shapeLayer.shadowOpacity = 1
            
            self.shapeLayer.lineWidth = 20
            self.shapeLayer.lineCap = .round
            self.shapeLayer.strokeColor = isClear ? UIColor.clear.cgColor : UIColor.blue.cgColor
            self.shapeLayer.opacity = 0.3
            self.isUserInteractionEnabled = true
        }
        
        func setupView() {
            self.layer.addSublayer(shapeLayer)
            updateView()
        }
        
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesBegan(touches, with: event)
            if !isDrawing { return }
            if let location = touches.first?.location(in: self){
                previousTouchPoint = location
            }
        }
        
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesMoved(touches, with: event)
            if !isDrawing { return }
            if let location = touches.first?.location(in: self){
                path.move(to: location)
                path.addLine(to: previousTouchPoint)
                previousTouchPoint = location
                shapeLayer.path = path.cgPath
            }
        }
    
        func applyMask() -> Void {
            // set shape opacity to 1.0
            shapeLayer.opacity = 1.0
            // use it as a mask
            layer.mask = shapeLayer
            isDrawing = false
        }
        
        func drawMore() -> Void {
            // remove the mask
            layer.mask = nil
            // set opacity back to 0.3
            shapeLayer.opacity = 0.3
            // add shapeLayer back as sublayer
            self.layer.addSublayer(shapeLayer)
            isDrawing = true
        }
    }
    

    显然,您还有很多事情要做,但这应该能让您顺利上路。

    下一步是将您的路径从图像视图框架转换/转换为原始图像大小,然后将蒙版应用于图像,以便将其保存出来。这对你来说应该是一个有趣的练习:)

    【讨论】:

    • 嘿@DonMag,这很好用,谢谢。我只是想知道如果我对生成的图像执行擦除功能,那么当我绘制它应该在绘图中显示图像区域时,是否可以使用相同的形状层?
    • @Nick - 如果您从touchesBegantouchesMoved 中删除(或注释掉)if !isDrawing { return },您将能够继续在图层上“绘制”其他路径段作为掩码活动。
    • 嘿@DonMag 我明白了,但我的意思是假设整个图像都被蓝色形状层覆盖,现在我想用手指执行擦除操作,所以如果形状层不透明,它应该可以工作是 0。但就我而言,它不起作用
    • @Nick - 你的意思是你想删除你绘制的部分路径吗?
    • @Nick - 好的,这是一个完全不同的任务。使用这种方法,您不是“绘制到表面”......您正在创建一个矢量路径,然后让 UIKit 填充/描边路径。要“擦除”该路径的一部分,您必须找到路径段并将其删除。不久前其他人正在研究类似的东西:stackoverflow.com/a/57415125/6257435 ...根据最后的 cmets,看来他使用 PencilKit ...可能值得研究。
    猜你喜欢
    • 1970-01-01
    • 2020-04-11
    • 2021-07-27
    • 1970-01-01
    • 2015-04-14
    • 1970-01-01
    • 2013-12-04
    • 2021-03-03
    • 1970-01-01
    相关资源
    最近更新 更多