Изображение маски с пользовательским UIBazierPath swift

Я создал UIBezierPath с пользовательской формой, тогда мне нужно сделать его маской для изображения, всегда у меня есть пустое изображение, вот мой код. Сначала я создал путь, затем создал изображение и, наконец, создал свою маску, но он не работает

вот изображение мне нужно его замаскировать dropbox.com/s/tnxgx7g1uvb1zj7/TeethMask.png?dl=0 вот путь UIBazier dropbox.com/s/nz93n1vgvj6c6y0/… мне нужно замаскировать это изображение по этому пути Вывод выглядит примерно так это https://www.dropbox.com/s/gueyhdmmdcfvyiq/image.png?dl=0

Вот класс ViewController

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let tapGR = UITapGestureRecognizer(target: self, action: #selector(didTap))

        self.view.addGestureRecognizer(tapGR)
    }

    @objc func didTap(tapGR: UITapGestureRecognizer) {

        let tapPoint = tapGR.location(in: self.view)

        if #available(iOS 11.0, *) {
            let shapeView = ShapeView(origin: tapPoint)
             self.view.addSubview(shapeView)
        } else {
            // Fallback on earlier versions
        }


    }

}

Вот класс ShapeView

import UIKit

@available(iOS 11.0, *)
class ShapeView: UIView {

    let size: CGFloat = 150
    let lineWidth: CGFloat = 3
    var fillColor: UIColor!
    var path: UIBezierPath!

    init(origin: CGPoint) {
        super.init(frame: CGRect(x: 0.0, y: 0.0, width: size, height: size))
        self.fillColor = randomColor()
        self.path = mouthPath()
        self.center = origin
        self.backgroundColor = UIColor.clear
    }


    func randomColor() -> UIColor {
        let hue:CGFloat = CGFloat(Float(arc4random()) / Float(UINT32_MAX))
        return UIColor(hue: hue, saturation: 0.8, brightness: 1.0, alpha: 0.8)
    }


    func mouthPath() -> UIBezierPath{
        let pointsArray = [CGPoint(x:36 , y:36 ),CGPoint(x:41 , y:36 ),CGPoint(x:45 , y:36 ),CGPoint(x:49 , y:36 ),CGPoint(x:53 , y:36 ),CGPoint(x:58 , y: 37),CGPoint(x:64 , y:37 ),CGPoint(x:69 , y:36 ),CGPoint(x:65 , y:29 ),CGPoint(x:58 , y:24 ),CGPoint(x:50 , y:22 ),CGPoint(x:42 , y:23 ),CGPoint(x:36 , y:28 ),CGPoint(x:32 , y:35 )]

        let newPath = UIBezierPath()
        let factor:CGFloat = 10
        for i in 0...pointsArray.count - 1 { // last point is 0,0
            let point = pointsArray[i]
            let currentPoint1 = CGPoint(x: point.x  * factor , y: point.y * factor)
        if i == 0 {
            newPath.move(to: currentPoint1)
        } else {
            newPath.addLine(to: currentPoint1)

            }
            }
            newPath.addLine(to: CGPoint(x: pointsArray[0].x  * factor, y: pointsArray[0].y * factor))
            newPath.close()

        let imageTemplate = UIImageView()
        imageTemplate.image =  UIImage(named: "TeethMask")
        self.addSubview(imageTemplate)
        self.bringSubviewToFront(imageTemplate)
        imageTemplate.frame = self.frame

        let mask = CAShapeLayer(layer: self.layer)
        mask.frame = newPath.bounds
        mask.fillColor = UIColor.clear.cgColor
        mask.strokeColor = UIColor.black.cgColor
        mask.path = newPath.cgPath
        mask.shouldRasterize = true
        imageTemplate.layer.mask = mask
        imageTemplate.layer.addSublayer(mask)
    }
}

person yasmina    schedule 06.04.2020    source источник
comment
Не могли бы вы добавить фото, как вы хотите, чтобы ваше изображение выглядело?   -  person shraddha11    schedule 06.04.2020
comment
конечно, вот изображение, которое мне нужно замаскировать dropbox.com/s/ tnxgx7g1uvb1zj7/TeethMask.png?dl=0 здесь находится путь UIBazier dropbox.com/s/nz93n1vgvj6c6y0/ Мне нужно замаскировать это изображение по этому пути, ясно?   -  person yasmina    schedule 06.04.2020
comment
@yasmina - хорошо... что ты имеешь в виду под необходимо маскировать? Вы пытаетесь нарисовать путь вокруг зубов, чтобы были видны только зубы? Или вы просто пытаетесь нарисовать контур вокруг них?   -  person DonMag    schedule 06.04.2020
comment
@DonMag Мне нужны зубы внутри пути dropbox.com/s/tnxgx7g1uvb1zj7/TeethMask.png?dl=0 вот путь dropbox.com/s/nz93n1vgvj6c6y0/… Мне нужно замаскировать это изображение по этому пути, ясно ли   -  person yasmina    schedule 06.04.2020
comment
@yasmina - func в опубликованном вами коде должен возвращать UIBezierPath, но ничего не возвращает. Является ли это частью пользовательского класса UIView? Или вы копировали/вставляли разделы из класса UIViewController? Отредактируйте свой вопрос и опубликуйте полный код класса, который вы пытаетесь использовать.   -  person DonMag    schedule 06.04.2020
comment
@DonMag Я редактирую свой вопрос и добавляю 2 класса   -  person yasmina    schedule 06.04.2020
comment
Все еще не совсем понятно, что вы собираетесь делать... вы хотите, чтобы это выглядело как (A) i.stack.imgur.com/mSbh6.png или (B) i.stack.imgur.com/ERqMg.png ?? Или что-то другое?   -  person DonMag    schedule 06.04.2020
comment
@DonMag точно так же, как i.stack.imgur.com/ERqMg.png   -  person yasmina    schedule 06.04.2020


Ответы (1)


Ну, ты делаешь несколько вещей неправильно...

Изображение "зубов", которое вы связали:

введите здесь описание изображения

имеет собственный размер 461 x 259. Итак, я собираюсь использовать пропорциональный «целевой» размер 200 x 112.

Во-первых, слои формы используют 0,0 в верхнем левом углу. Ваш исходный массив точек:

let pointsArray = [
    CGPoint(x: 36, y: 36),
    CGPoint(x: 41, y: 36),
    CGPoint(x: 45, y: 36),
    CGPoint(x: 49, y: 36),
    CGPoint(x: 53, y: 36),
    CGPoint(x: 58, y: 37),
    CGPoint(x: 64, y: 37),
    CGPoint(x: 69, y: 36),
    CGPoint(x: 65, y: 29),
    CGPoint(x: 58, y: 24),
    CGPoint(x: 50, y: 22),
    CGPoint(x: 42, y: 23),
    CGPoint(x: 36, y: 28),
    CGPoint(x: 32, y: 35),
]

дает такую ​​форму:

введите здесь описание изображения

Если мы инвертируем координаты y:

let pointsArray = [
    CGPoint(x: 36.0, y: 23.0),
    CGPoint(x: 41.0, y: 23.0),
    CGPoint(x: 45.0, y: 23.0),
    CGPoint(x: 49.0, y: 23.0),
    CGPoint(x: 53.0, y: 23.0),
    CGPoint(x: 58.0, y: 22.0),
    CGPoint(x: 64.0, y: 22.0),
    CGPoint(x: 69.0, y: 23.0),
    CGPoint(x: 65.0, y: 30.0),
    CGPoint(x: 58.0, y: 35.0),
    CGPoint(x: 50.0, y: 37.0),
    CGPoint(x: 42.0, y: 36.0),
    CGPoint(x: 36.0, y: 31.0),
    CGPoint(x: 32.0, y: 24.0),
]

получаем такую ​​форму:

введите здесь описание изображения

Будет сложно заставить вещи «выстраиваться» правильно, если ваша фигура смещена таким образом, поэтому мы можем «нормализовать» точки, чтобы они начинались с верхнего левого угла:

let pointsArray: [CGPoint] = [
    CGPoint(x: 4.0, y: 1.0),
    CGPoint(x: 9.0, y: 1.0),
    CGPoint(x: 13.0, y: 1.0),
    CGPoint(x: 17.0, y: 1.0),
    CGPoint(x: 21.0, y: 1.0),
    CGPoint(x: 26.0, y: 0.0),
    CGPoint(x: 32.0, y: 0.0),
    CGPoint(x: 37.0, y: 1.0),
    CGPoint(x: 33.0, y: 8.0),
    CGPoint(x: 26.0, y: 13.0),
    CGPoint(x: 18.0, y: 15.0),
    CGPoint(x: 10.0, y: 14.0),
    CGPoint(x: 4.0, y: 9.0),
    CGPoint(x: 0.0, y: 2.0),
]

в результате чего:

введите здесь описание изображения

Однако мы хотим, чтобы форма соответствовала изображению, поэтому мы можем масштабировать UIBezierPath до границ imageView:

    // need to scale the path to self.bounds
    let scaleW = bounds.width / pth.bounds.width
    let scaleH = bounds.height / pth.bounds.height

    let trans = CGAffineTransform(scaleX: scaleW, y: scaleH)
    pth.apply(trans)

и мы здесь:

введите здесь описание изображения

Осталось только использовать это как маску для изображения.

Я собираюсь предложить подкласс UIImageView вместо UIView... таким образом вы можете установить свойство .image без необходимости добавлять другое представление в качестве подпредставления. Кроме того, я думаю, вам будет намного проще управлять размером пользовательского маскированного изображения в коде вашего контроллера, а не внутри пользовательского класса.

Вот контроллер демо-представления и пользовательский MouthShapeView:

class TeethViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let tapGR = UITapGestureRecognizer(target: self, action: #selector(didTap))

        self.view.addGestureRecognizer(tapGR)
    }

    @objc func didTap(tapGR: UITapGestureRecognizer) {

        let tapPoint = tapGR.location(in: self.view)

        if #available(iOS 11.0, *) {

            // make sure you can load the image
            if let img = UIImage(named: "TeethMask") {
                // create custom ShapeView with image
                let shapeView = MouthShapeView(image: img)
                // if you want to use original image proportions
                // set the width you want and calculate a proportional height
                // based on the original image size
                let targetWidth: CGFloat = 200.0
                let targetHeight: CGFloat = img.size.height / img.size.width * targetWidth
                // set the frame size
                shapeView.frame.size = CGSize(width: targetWidth, height: targetHeight)
                // set the frame center
                shapeView.center = tapPoint
                // add it
                self.view.addSubview(shapeView)
            }

        } else {
            // Fallback on earlier versions
        }


    }
}

@available(iOS 11.0, *) class MouthShapeView: UIImageView {

    let pointsArray: [CGPoint] = [
        CGPoint(x: 4.0, y: 1.0),
        CGPoint(x: 9.0, y: 1.0),
        CGPoint(x: 13.0, y: 1.0),
        CGPoint(x: 17.0, y: 1.0),
        CGPoint(x: 21.0, y: 1.0),
        CGPoint(x: 26.0, y: 0.0),
        CGPoint(x: 32.0, y: 0.0),
        CGPoint(x: 37.0, y: 1.0),
        CGPoint(x: 33.0, y: 8.0),
        CGPoint(x: 26.0, y: 13.0),
        CGPoint(x: 18.0, y: 15.0),
        CGPoint(x: 10.0, y: 14.0),
        CGPoint(x: 4.0, y: 9.0),
        CGPoint(x: 0.0, y: 2.0),
    ]

    let maskLayer = CAShapeLayer()

    override init(image: UIImage?) {
        super.init(image: image)
        maskLayer.fillColor = UIColor.black.cgColor
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let newPath = UIBezierPath()

        pointsArray.forEach { p in
            if p == pointsArray.first {
                newPath.move(to: p)
            } else {
                newPath.addLine(to: p)
            }
        }

        newPath.close()

        // need to scale the path to self.bounds
        let scaleW = bounds.width / newPath.bounds.width
        let scaleH = bounds.height / newPath.bounds.height

        let trans = CGAffineTransform(scaleX: scaleW, y: scaleH)
        newPath.apply(trans)

        maskLayer.path = newPath.cgPath
        layer.mask = maskLayer

    }

}

Когда вы запустите этот код и коснетесь представления, вы получите следующее:

введите здесь описание изображения

person DonMag    schedule 06.04.2020
comment
Действительно большое спасибо Это очень полезно :) :) - person yasmina; 06.04.2020