Swift4.1 画像のマスク

こんにちは　エンジニアの永田です。

最近は、恵比寿とか代官山あたりで、iOS のアプリ開発をメインにプログラミングを実施しています。
os12では画像処理の技術が向上している事から、画像処理に関する事を中心に自作開発も実施中です。
今回は、マスキングライブラリーの紹介をさせて頂きます。

#操作方法

#### 指でなぞった部分をマスク

#### タップでリセット


![Movie.gif](https://qiita-image-store.s3.amazonaws.com/0/131469/7f1fbb37-e4f7-561a-a9ac-1fe8d7bf1986.gif)



[GitHubのリポジトリ](https://github.com/daisukenagata/MaskingLayer)



アプリのリンクです。os12からAR技術も入れたいと思っています。　
[TalkingRecord](https://itunes.apple.com/jp/app/talkingrecord/id1381215794?mt=8)



#環境

Swift4.1,Xcode9.4.1

# 仕様

#### 1.指でなぞる部分のPathを取得
#### 2.Pathの部分から外側を白色にして、内側を黒色にする
#### 3.黒白になった画像と、元画像を合成させる

# ソースコード

### CGMutablePath() = 変化するグラフィックパス：グラフィックコンテキストで描画する図形または線の数学的記述。

### CAShapeLayer() = 座標空間に3次のベジェスプラインを描画するレイヤーです。

### 基本的にmoveとaddLineで開始とパスをaddLineで追加しています。


```

import UIKit

public class MaskLayer: NSObject {

    open var convertPath = CGMutablePath()
    open var path = CGMutablePath()
    open var clipLayer = CAShapeLayer()


    public override init() {
        clipLayer.backgroundColor = UIColor.clear.cgColor
        clipLayer.name = "clipLayer"
        clipLayer.strokeColor = UIColor.white.cgColor
        clipLayer.fillColor = UIColor.clear.cgColor
        clipLayer.lineWidth = 1
    }
    public func maskConvertPointFromView(viewPoint: CGPoint,view: UIView, imageView: UIImageView,bool: Bool) {

        clipLayer.path = path

        if bool ==  true{
            convertPath.move(to: CGPoint(x: convertPointFromView(viewPoint, view: view, imageView: imageView).x, y: convertPointFromView(viewPoint, view: view, imageView: imageView).y))
            } else {
            convertPath.addLine(to: CGPoint(x: convertPointFromView(viewPoint, view: view, imageView: imageView).x, y: convertPointFromView(viewPoint, view: view, imageView: imageView).y))
        }
    }

    public func maskPath(position: CGPoint) {
         clipLayer.isHidden = false
         path.move(to: CGPoint(x: position.x, y: position.y))
    }

    public func maskAddLine(position: CGPoint){
        path.addLine(to: CGPoint(x: position.x, y: position.y))
    }

    public func convertPath(convertLocation: CGPoint){
        convertPath.move(to: CGPoint(x: convertLocation.x, y: convertLocation.y))
    }

    public func mask(image: UIImage,convertPath: CGMutablePath)-> UIImage {
        clipLayer.isHidden = true
        return clipedMotoImage(image,convertPath:convertPath)
    }

    public func maskImage(color:UIColor, size: CGSize)-> UIImage {
        return image(color: color, size: size)
    }

    public func imageSet(view:UIView, imageView: UIImageView, name: String) {
        imageView.image =  UIImage(named: name)?.mask(image: imageView.image)
        imageView.image = imageView.image?.ResizeUIImage(width: view.frame.width, height: view.frame.height)
        imageView.frame = view.frame
    }

    public func imageReSet(view:UIView, imageView: UIImageView, name: String) {
        imageView.image =  UIImage(named: name)
        imageView.image = imageView.image?.ResizeUIImage(width: view.frame.width, height: view.frame.height)
        imageView.frame = view.frame
        convertPath = CGMutablePath()
        path = CGMutablePath()
    }
```

#### この実装はViewControllerではまだ呼んでませんが、Titleなどの色も変更できるロジックです。情報があまりなかったので、掲載しときます。


```
NSAttributedStringKey = 属性付き文字列内のテキストに適用できる属性。
NSAttributedStringKeyを.foregroundColor と設定して、Any型で、UIColorを設定しています。
NSAttributedString = テキストの一部の属性（ビジュアルスタイル、ハイパーリンク、アクセシビリティデータなど）を関連付けた文字列。
let string = でNSAttributedStringを設定しています。
```


<img width="375" alt="Image-1.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/131469/fdaf730c-17ea-b675-273b-cd07a93e49d8.jpeg">


```
    private func alertSave(views:UIViewController) {
        let alertController = UIAlertController(title: NSLocalizedString("Saved", comment: ""), message: "", preferredStyle: .alert)
        let stringAttributes: [NSAttributedStringKey : Any] = [
            .foregroundColor : UIColor(red: 0/255, green: 136/255, blue: 83/255, alpha: 1.0),
            .font : UIFont.systemFont(ofSize: 22.0)
        ]
        let string = NSAttributedString(string: alertController.title!, attributes:stringAttributes)
        alertController.setValue(string, forKey: "attributedTitle")
        alertController.view.tintColor = UIColor(red: 0/255, green: 136/255, blue: 83/255, alpha: 1.0)

        let otherAction = UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .default) {
            action in
            alertController.dismiss(animated: true, completion: nil)
        }
        alertController.addAction(otherAction)
        views.present(alertController, animated: true, completion: nil)
    }
```


### UIGraphicsBeginImageContextWithOptions = 指定オプションのビットマップベースのクリエイティブです。

### image関数でなぞった内側を黒色にしてます。
### clipedMotoImage関数で外側を白色にしています。

```
    private func image(color: UIColor, size: CGSize) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        let context = UIGraphicsGetCurrentContext()!
        context.setFillColor(color.cgColor)
        context.fill(CGRect(origin: .zero, size: size))
        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }

    private func clipedMotoImage(_ img: UIImage,convertPath: CGMutablePath) -> UIImage{

        let motoImage = img

        UIGraphicsBeginImageContextWithOptions((motoImage.size), false, 0)
        let context = UIGraphicsGetCurrentContext()
        context?.saveGState()

        motoImage.draw(in: CGRect(x: 0, y: 0, width: (motoImage.size.width), height: (motoImage.size.height)))
        context?.addPath(convertPath)

        context?.setFillColor(UIColor.black.cgColor)
        context?.drawPath(using: CGPathDrawingMode.fill)

        let reImage = UIGraphicsGetImageFromCurrentImageContext()
        context?.restoreGState()
        UIGraphicsEndImageContext()

        return reImage!
    }
```

### convertPointFromView関数でなぞっている部分のCGPointを取得します。

```
    private func convertPointFromView(_ viewPoint: CGPoint,view: UIView, imageView: UIImageView) ->CGPoint{

        var imagePoint : CGPoint = viewPoint
        let imageSize = imageView.image?.size
        let viewSize = view.frame.size

        let ratioX : CGFloat = viewSize.width / imageSize!.width
        let ratioY : CGFloat = viewSize.height / imageSize!.height
        let scale : CGFloat = min(ratioX, ratioY)

        imagePoint.x -= (viewSize.width  - imageSize!.width  * scale) / 2
        imagePoint.y -= (viewSize.height - imageSize!.height * scale) / 2

        imagePoint.x /= scale
        imagePoint.y /= scale

        return imagePoint
    }
}

```

### ResizeUIImage関数でUIImageのサイズを可変しています。
### mask関数は、マスク画像をCGImageにしてマスキングしています。
### [参考にさせていただきました。->クラスメソッド社の記事](https://dev.classmethod.jp/smartphone/iphone/ios_swift_image_mask/)

```

public extension UIImage {

    func ResizeUIImage(width : CGFloat, height : CGFloat)-> UIImage!{
        UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height),true,0.0)
        self.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage
    }
    func mask(image: UIImage?) -> UIImage {
        if let maskRef = image?.cgImage,
            let ref = cgImage,
            let mask = CGImage(maskWidth: maskRef.width,
                               height: maskRef.height,
                               bitsPerComponent: maskRef.bitsPerComponent,
                               bitsPerPixel: maskRef.bitsPerPixel,
                               bytesPerRow: maskRef.bytesPerRow,
                               provider: maskRef.dataProvider!,
                               decode: nil,
                               shouldInterpolate: false),
            let output = ref.masking(mask) {
            return UIImage(cgImage: output)
        }
        return self
    }
}

```

#まとめ
設定しているVCはGithubのソースをご覧ください。

画像はリサイズしないとメモリ使用量が多くなりますので、リサイズして調節してます。

マスク用の画像ありきで、マスク機能の情報がありましたが、マスク画像がない場合の技術情報がなかったので、自作ライブラリーとQiitaの記事として、公開させていただきました。

アプリ連携のため、APIも実装したいと思っています。

貴重なお時間お読み下さいまして、ありがとうございます。