ios 如何将图像分配给用户绘制的Bezier路径

xa9qqrwz  于 2023-08-08  发布在  iOS
关注(0)|答案(2)|浏览(119)

我正在使用贝塞尔曲线在图像视图中绘制任何形状。当触摸结束时,我已经关闭了路径以获得形状。现在我想实现的空白区域是在贝塞尔路径,以便我可以取代它与另一个图像以后。图像的其余部分保持不变。
“替换为另一个图像“我的意思是,我想分配一个新的图像到裁剪部分或黑洞什么的,..其结果将类似于imageView上的贴纸,其中包含用户绘制的路径。
我想要裁剪封闭路径内的内容,以便在为路径指定新图像时,它会调整大小并调整为封闭路径。请不要在新分配的图像中使用蒙版。我将不得不创建一个方法来调整它的框架大小,但这也在bezierpath中。我也附上了预期结果的图像。
背景图像
1st User draws a bezier path
当路径结束时,新图像将被指定给闭合路径,并将确切的帧作为闭合路径。
2nd grass image gets assigned to path and adjusts its frame
然后,用户可以调整框架的大小。但是草的图像不应该移动到路径之外。
3rd resizable frame of grass image inside path
我希望我说的有道理。

t3psigkw

t3psigkw1#

要实现这一点,您可以按照以下步骤操作:

**Bezier路径创建遮罩图:**用户绘制完路径后,可以从Bezier路径创建遮罩图。此遮罩图像将在路径内部具有透明区域,而在外部具有不透明区域。

// Assuming `bezierPath` is your completed UIBezierPath
let maskLayer = CAShapeLayer()
maskLayer.path = bezierPath.cgPath

let maskImage = UIImage.image(fromLayer: maskLayer)

字符串

**将蒙版应用于原始图像:**将蒙版图像应用于原始图像,以便原始图像的内容位于用户绘制的路径内,其余内容位于路径外。

let maskedImage = originalImage.applyMask(maskImage: maskImage)

**用另一张图片覆盖结果:**现在,您可以将maskedImage与另一张图片(您的贴纸)覆盖,以实现所需的效果。

let overlayedImage = maskedImage.overlayWith(image: stickerImage)

以下是您可以使用的辅助扩展和方法:

extension UIImage {
    func applyMask(maskImage: UIImage) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        let maskRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        
        guard let context = UIGraphicsGetCurrentContext() else {
            return self
        }
        
        context.translateBy(x: 0, y: size.height)
        context.scaleBy(x: 1.0, y: -1.0)
        
        context.clip(to: maskRect, mask: maskImage.cgImage!)
        draw(in: maskRect)
        
        let maskedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return maskedImage ?? self
    }
    
    func overlayWith(image: UIImage) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        
        draw(in: CGRect(origin: CGPoint.zero, size: size))
        image.draw(in: CGRect(origin: CGPoint.zero, size: size))
        
        let overlayedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return overlayedImage ?? self
    }
}

extension UIImage {
    static func image(fromLayer layer: CALayer) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, 0)
        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }
        
        layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return image
    }
}

tcomlyy6

tcomlyy62#

我们遗漏了很多关于你最终目标的细节,但这可能会让你走上正轨...
而不是试图“挖一个洞并填满它”,我们可以:

  • 在“街道”图像的顶部上叠加图像视图中的“草”图像
  • 从一个空的路径为草“面具”,所以它是完全清楚
  • 在街道图像上绘制路径
  • 当用户停止绘制时,关闭路径并将其设置为草图像视图上的遮罩层路径

因此,视图层次结构如下所示(草地上没有设置遮罩):
x1c 0d1x的数据
当我们为草层蒙版设置路径时,它看起来像这样:



运行时,用户正在绘制:



用户停止绘制,我们关闭路径,并将其应用于草视图:



示例代码来查看实际操作...
//FillImageView class-这将保存“草”图像,并将应用图层蒙版:

class FillImageView: UIImageView {
    
    public var userPath: UIBezierPath = UIBezierPath() {
        didSet {
            maskLayer.path = userPath.cgPath
        }
    }
    private let maskLayer: CAShapeLayer = CAShapeLayer()
    
    init() {
        super.init(frame: .zero)
        commonInit()
    }
    override init(image: UIImage?) {
        super.init(image: image)
        commonInit()
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        layer.mask = maskLayer
    }
    
}

字符串
//DrawImageView class-这将保存“街道”图像,并处理绘制虚线:

class DrawImageView: UIImageView {
    
    // closure used when drawing stops
    public var drawEnded: ((UIBezierPath) -> ())?
    
    // adjust drawing-line-width as desired
    public var lineWidth: CGFloat = 3.0 {
        didSet {
            dashedLineLayer.lineWidth = lineWidth
        }
    }
    
    public var userPath: UIBezierPath = UIBezierPath() {
        didSet {
            dashedLineLayer.path = userPath.cgPath
        }
    }
    private let dashedLineLayer: CAShapeLayer = CAShapeLayer()
    
    init() {
        super.init(frame: .zero)
        commonInit()
    }
    override init(image: UIImage?) {
        super.init(image: image)
        commonInit()
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        isUserInteractionEnabled = true
        
        dashedLineLayer.fillColor = UIColor.clear.cgColor
        dashedLineLayer.strokeColor = UIColor.white.cgColor
        dashedLineLayer.lineWidth = lineWidth
        dashedLineLayer.lineDashPattern = [16, 16]
        
        layer.addSublayer(dashedLineLayer)
        
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let currentPoint = touch.location(in: self)
        userPath.move(to: currentPoint)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let currentPoint = touch.location(in: self)
        // add line to our maskPath
        userPath.addLine(to: currentPoint)
        // update the mask layer path
        dashedLineLayer.path = userPath.cgPath
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        // close the path
        userPath.close()
        // update the mask
        dashedLineLayer.path = userPath.cgPath
        // closure
        drawEnded?(userPath)
    }
    
}


//DrawMaskView class-管理DrawImageViewFillImageView

class DrawMaskView: UIView {
    
    public var drawImage: UIImage? {
        didSet {
            drawImageView.image = drawImage
        }
    }
    public var fillImage: UIImage? {
        didSet {
            fillImageView.image = fillImage
        }
    }
    
    private let drawImageView = DrawImageView()
    private let fillImageView = FillImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        [drawImageView, fillImageView].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            addSubview(v)
        }
        NSLayoutConstraint.activate([
            drawImageView.topAnchor.constraint(equalTo: topAnchor),
            drawImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
            drawImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
            drawImageView.bottomAnchor.constraint(equalTo: bottomAnchor),

            fillImageView.topAnchor.constraint(equalTo: topAnchor),
            fillImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
            fillImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
            fillImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
        
        drawImageView.drawEnded = { [weak self] pth in
            guard let self = self else { return }
            fillImageView.userPath = pth
        }
    }
    
    public func reset() {
        drawImageView.userPath = UIBezierPath()
        fillImageView.userPath = UIBezierPath()
    }
    
}


//简单示例视图控制器-使用名为“street”和“grass”的图像:

class ExampleVC: UIViewController {
    
    let testView = DrawMaskView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let drawImg = UIImage(named: "street"),
              let fillImg = UIImage(named: "grass")
        else {
            fatalError("Could not load images!!!")
        }
        
        testView.drawImage = drawImg
        testView.fillImage = fillImg
        
        testView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(testView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            testView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            testView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            testView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -120.0),
        ])
        
        // let's add a Reset button at the bottom
        let btn = UIButton()
        btn.backgroundColor = .systemBlue
        btn.setTitleColor(.white, for: .normal)
        btn.setTitleColor(.lightGray, for: .highlighted)
        btn.setTitle("Reset", for: [])
        btn.layer.cornerRadius = 6
        
        btn.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(btn)

        NSLayoutConstraint.activate([
            btn.topAnchor.constraint(equalTo: testView.bottomAnchor, constant: 20.0),
            btn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
            btn.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
            btn.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
        ])

        btn.addTarget(self, action: #selector(reset(_:)), for: .touchUpInside)
    }
    
    @objc func reset(_ sender: Any?) {
        testView.reset()
    }
    
}

编辑

如果你想让“填充”图像缩放以适应路径边界,我们可以对上面的类做一些修改……
我们将把“草”图像视图设置为与“街道”图像视图相同的帧,而不是将其帧设置为贝塞尔路径的边界,然后在将其用作遮罩时偏移路径。
//FillImageView class-同上
//ScaledDrawImageView class-大部分与上面相同,但是我们添加了一个“start”闭包,这样我们每次都会启动一个新的路径(而不是添加路径):

class ScaledDrawImageView: UIImageView {
    
    // closure used when drawing starts
    public var drawStart: (() -> ())?
    
    // closure used when drawing stops
    public var drawEnded: ((UIBezierPath) -> ())?
    
    // adjust drawing-line-width as desired
    public var lineWidth: CGFloat = 3.0 {
        didSet {
            dashedLineLayer.lineWidth = lineWidth
        }
    }
    
    public var userPath: UIBezierPath = UIBezierPath() {
        didSet {
            dashedLineLayer.path = userPath.cgPath
        }
    }
    private let dashedLineLayer: CAShapeLayer = CAShapeLayer()
    
    init() {
        super.init(frame: .zero)
        commonInit()
    }
    override init(image: UIImage?) {
        super.init(image: image)
        commonInit()
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        isUserInteractionEnabled = true
        
        dashedLineLayer.fillColor = UIColor.clear.cgColor
        dashedLineLayer.strokeColor = UIColor.white.cgColor
        dashedLineLayer.lineWidth = lineWidth
        dashedLineLayer.lineDashPattern = [16, 16]
        
        layer.addSublayer(dashedLineLayer)
        
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let currentPoint = touch.location(in: self)
        userPath = UIBezierPath()
        userPath.move(to: currentPoint)
        drawStart?()
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let currentPoint = touch.location(in: self)
        // add line to our maskPath
        userPath.addLine(to: currentPoint)
        // update the mask layer path
        dashedLineLayer.path = userPath.cgPath
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        // close the path
        userPath.close()
        // update the mask
        dashedLineLayer.path = userPath.cgPath
        // closure
        drawEnded?(userPath)
    }
    
}


//ScaledDrawMaskView class-调整“草”图像视图的帧并转换路径:

class ScaledDrawMaskView: UIView {

    public var drawImage: UIImage? {
        didSet {
            drawImageView.image = drawImage
        }
    }
    public var fillImage: UIImage? {
        didSet {
            fillImageView.image = fillImage
        }
    }

    private let drawImageView = ScaledDrawImageView()
    private let fillImageView = FillImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        addSubview(drawImageView)
        addSubview(fillImageView)
        
        drawImageView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            drawImageView.topAnchor.constraint(equalTo: topAnchor),
            drawImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
            drawImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
            drawImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
        
        // subImageView will NOT use constraints
        
        drawImageView.drawStart = { [weak self] in
            self?.fillImageView.userPath = UIBezierPath()
        }
        drawImageView.drawEnded = { [weak self] pth in
            guard let self = self else { return }
            self.fillImageView.frame = pth.bounds
            let tr = CGAffineTransform(translationX: -pth.bounds.origin.x, y: -pth.bounds.origin.y)
            pth.apply(tr)
            fillImageView.userPath = pth
        }

    }
    
    public func reset() {
        drawImageView.userPath = UIBezierPath()
        fillImageView.userPath = UIBezierPath()
    }
    
}


//简单的视图控制器示例-与上面相同,除了我们使用ScaledDrawMaskView而不是DrawMaskView

class ExampleVC: UIViewController {
    
    //let testView = DrawMaskView()
    let testView = ScaledDrawMaskView()

    // everything else is the same


现在,使用此图像作为“草”图像,因此我们可以看到缩放:
x1c4d 1x型
我们得到几种不同路径形状的输出:





相关问题