在 iOS 中使用 bezierPath 去除图像背景

Image background removal using bezierPath in iOS

到处搜索后,我发现没有任何特定来源可用于使用 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:)后的结果图像

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

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

将此功能添加到您的 DrawingImageView class:

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

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

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


控制器

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 {
            [=11=].translatesAutoresizingMaskIntoConstraints = false
            view.addSubview([=11=])
        }
        
        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
    }
}

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

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