LoginSignup
2
1

More than 5 years have passed since last update.

Swift4.1 画像のマスク

Last updated at Posted at 2018-08-11

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

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

操作方法

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

タップでリセット

Movie.gif

GitHubのリポジトリ

アプリのリンクです。os12からAR技術も入れたいと思っています。 
TalkingRecord

環境

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を設定しています。

Image-1.jpg

    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にしてマスキングしています。

参考にさせていただきました。->クラスメソッド社の記事


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も実装したいと思っています。

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1