在iOS中使用bezierPath移除图片背景

问题描述

在所有地方搜索之后,我发现没有可用的特定来源使用bezierPath删除背景。基本上,我正在尝试实现类似的功能,例如图像抠图(您可以查看PicsArt >>图像编辑器>> 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
        }
    }
}

要切出所选区域,我尝试使用cropping(to:)的{​​{1}}方法。但是仅清除整个图像是行不通的。您可以在下面查看我的信息

普通图片

Without selection

选定区域[绘图]

With Selection

使用CGImage后的结果图像

Result

我不确定我是否正确执行了此操作。我也对其他方式持开放态度。

解决方法

要“删除背景”,您需要将形状图层用作遮罩。

将此功能添加到您的DrawingImageView类中:

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

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

enter image description here

enter image description here

这是一个完整的工作示例...我还在DrawingImageView中添加了一个bool变量和此func,以允许在“绘图”和“蒙版”模式之间进行切换,因此我可以返回并向路径添加更多


控制器

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
    }
}

很显然,您还有很多事情要做,但这应该可以助您一臂之力。

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

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...