在Swift中实现UIImage的擦除/恢复绘图

3

我正在尝试制作一个简单的图像橡皮擦工具,用户可以像在下图中那样擦除和还原图像的绘画:

enter image description here

经过多次尝试和测试,我已经在 UI 界面上用以下代码实现了足够的“擦除”功能:

// Drawing code - on user touch
// `currentPath` is a `UIBezierPath` property of the containing class.
guard let image = pickedImage else { return }
UIGraphicsBeginImageContextWithOptions(imageView.frame.size, false, 0)
if let context = UIGraphicsGetCurrentContext() {
    mainImageView.layer.render(in: context)
    context.addPath(currentPath.cgPath)
    context.setBlendMode(.clear)
    context.setLineWidth(translatedBrushWidth)
    context.setLineCap(.round)
    context.setLineJoin(.round)
    context.setStrokeColor(UIColor.clear.cgColor)
    context.strokePath()

    let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
    imageView.image = capturedImage
}

UIGraphicsEndImageContext()

当用户触摸图像后,我会对currentPath应用比例变换,以完整的大小呈现带有剪切部分的图像,以保持UI性能。

现在我想弄清楚的是如何实现“还原”功能。基本上,用户应该在擦除的部分上绘制以显示原始图像。
我尝试查看了CGContextClipToMask,但不确定如何实现。

我还尝试过其他方法来实现在渲染实际图像之前达到“擦除/还原”效果,例如在图像上覆盖CAShapeLayer进行遮罩处理,但是这种方法也存在还原问题。

非常感谢任何帮助,以及在UI级别和渲染级别上使用路径擦除和还原的替代方法。 谢谢!

1个回答

4

是的,我建议在图像层上添加一个CALayer作为掩码。

您可以将掩码层设置为CAShapeLayer并在其中绘制几何形状,或者使用简单的CALayer作为掩码,其中掩码层的内容属性是CGImage。然后,您可以在掩码中绘制不透明像素以显示图像内容,或绘制透明像素以“擦除”相应的图像像素。

这种方法具有硬件加速和相当快的速度。

处理橡皮擦功能的撤销/重做需要您收集对掩码层的更改以及掩码的先前状态。

编辑:

我创建了Github上的一个小演示应用程序,展示了如何在图像视图上使用CGImage作为掩码。

以下是该项目的ReadMe文件:


MaskableImageView

该项目演示了如何使用CALayer来掩盖UIView

它定义了一个名为MaskableView的UIImageView自定义子类。

MaskableView类有一个包含CALayer的maskLayer属性。

MaskableView在其bounds属性上定义了一个didSet方法,因此当视图的边界发生更改时,它会重新调整掩模层以匹配图像视图的大小。

MaskableView具有一个名为installSampleMask的方法,该方法构建与图像视图相同大小的图像,大部分用不透明黑色填充,但中心有一个小矩形,其alpha值为0.7。半透明的中心矩形使图像视图变得部分透明并显示下面的视图。

演示应用程序将几个子视图安装到MaskableView中,其中包括我的一只狗Scampers的样本图像和一个UILabel。它还在MaskableView下方安装了一个棋盘图像,以便您可以更轻松地看到半透明部分。

MaskableView具有属性circleRadiusmaskDrawingAlphadrawingAction,它们用于让用户通过在视图上轻触以更新遮罩来擦除/取消擦除图像。

MaskableViewUIPanGestureRecognizerUITapGestureRecognizer附加到自身,并使用gestureRecognizerUpdate的操作。 gestureRecognizerUpdate方法从手势识别器获取轻拍/拖动位置,并使用它来绘制一个圆形到图像遮罩中,该圆形要么减少图像遮罩的alpha(部分擦除像素),要么增加图像遮罩的alpha(使这些像素更不透明)。

MaskableView的遮罩绘制粗糙,仅用于演示目的。它绘制了一系列离散的圆圈,而不是根据用户的拖动手势在遮罩中渲染路径。更好的解决方案是连接手势识别器中的点,并使用它们将平滑曲线渲染到遮罩中。

应用程序的屏幕如下所示:

enter image description here

编辑 #2:

如果您想要将生成的图像导出到保留透明度的文件中,您可以将CGImage转换为UIImage(使用init(cgImage:)初始化程序),然后使用UIImage函数。

func pngData() -> Data?

将图像转换为PNG数据。如果无法将图像转换为PNG数据,则该函数返回nil。

如果成功,您可以将数据保存到具有.png扩展名的文件中。

我更新了示例项目,以包括将生成的图像保存到磁盘的功能。

首先,我向MaskableView添加了一个image计算属性。它看起来像这样:

    public var image: UIImage? {
        guard let renderer = renderer else { return nil}
        let result = renderer.image {
            context in

            return layer.render(in: context.cgContext)
        }
        return result
    }

然后我在视图控制器中添加了一个保存按钮,它从MaskableView获取图片并将其保存到应用程序的文档目录中:

    @IBAction func handleSaveButton(_ sender: UIButton) {
        print("In handleSaveButton")
        if let image = maskableView.image,
           let pngData = image.pngData(){
            print(image.description)
            let imageURL = getDocumentsDirectory().appendingPathComponent("image.png", isDirectory: false)
            do {
                try pngData.write(to: imageURL)
                print("Wrote png to \(imageURL.path)")
            }
            catch {
                print("Error writing file to \(imageURL.path)")
            }
        }
    }

你也可以将图片保存到用户的相机胶卷中。我已经有一段时间没有这样做了,所以我需要找出具体步骤。


嘿,邓肯,谢谢你的回答。你能举个例子说明一下绘制不透明或透明像素是什么意思吗? - Eilon
@Eilon,你看到我添加的示例项目了吗?那展示了我所说的(不透明像素具有1.0的alpha值,透明像素具有0.0的alpha值。值在0.0和1.0之间的像素是部分透明的)。 - Duncan C
该项目的目的是使图像视图部分透明(具有alpha通道)。JPEG图像无法包含alpha通道,因此将图像保存为JPEG将会失去整个目的。Joaquin,您想要输出带有alpha通道的图像到PNG吗? - Duncan C
嘿@DuncanC。我正在寻找一种导出最终图像的方法。格式可以是PNG。我正在创建一个应用程序,您可以在其中擦除图像的一部分并通过电子邮件发送它。导出图像到PNG的最佳方法是什么?谢谢! - Joaquin Pereira
非常感谢 @DuncanC,它完美地运行了。 - Joaquin Pereira
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接