在 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
}
}
}
我尝试使用 CGImage
的 cropping(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
}
}
显然,您还有很多事情要做,但这应该会让您顺利上路。
下一步是将您的路径从图像视图框架转换/转换为原始图像大小,然后将蒙版应用于图像,以便将其保存。这对你来说应该是一个有趣的练习:)
到处搜索后,我发现没有任何特定来源可用于使用 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
}
}
}
我尝试使用 CGImage
的 cropping(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
}
}
显然,您还有很多事情要做,但这应该会让您顺利上路。
下一步是将您的路径从图像视图框架转换/转换为原始图像大小,然后将蒙版应用于图像,以便将其保存。这对你来说应该是一个有趣的练习:)