こんにちは エンジニアの永田です。
最近は、恵比寿とか代官山あたりで、iOS のアプリ開発をメインにプログラミングを実施しています。
os12では画像処理の技術が向上している事から、画像処理に関する事を中心に自作開発も実施中です。
今回は、マスキングライブラリーの紹介をさせて頂きます。
操作方法
指でなぞった部分をマスク
タップでリセット
アプリのリンクです。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を設定しています。
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も実装したいと思っています。
貴重なお時間お読み下さいまして、ありがとうございます。