[SwiftUI] CAShapeLayerとUIImageを使って、画像を合成する

## 環境
Swift5
SwiftUI
Xcode11.5

## 実現すること
SwiftUIフレームワークを使って、任意の画像に図形を合成する。本記事では、このかわいい犬を円の図形で囲むようにします。
![Untitled.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195876/892a7009-da35-b40f-b293-df5bf7607d1c.png)




## 実装

`ComposedImageModel.swift`が以下になります。

``` ComposedImageModel.swift
import Foundation
import UIKit

struct ComposedImageModel {
    // 犬の画像格納用
    var image:         UIImage 
    // CAShapeLayer画像格納用
    var layerImage:    UIImage
    // 合成した画像格納用
    var composedImage: UIImage
    
    init() {
        self.image         = UIImage()
        self.layerImage    = UIImage()
        self.composedImage = UIImage()
    }
}
```

次に以下`ComposedImageViewModel.swift`でUIImageを合成する処理を記載していきます。
[主な処理]
- 犬の画像を格納
- CAShapeLayerを使って円形の図形を作成 > UIImageにレンダリング
- 上記2つの画像を1つの画像に合成

``` ComposedImageViewModel.swift
import Foundation
import UIKit

class ComposedImageViewModel {
    
    var composedImageModel: ComposedImageModel
    
    init() {
        self.composedImageModel = ComposedImageModel()
        composedImages()
    }
    
    // 2つのUIImageを1つのUIImageに合成
    private func composedImages() {
        // いっぬの画像を取得
        self.composedImageModel.image = UIImage(named: "dog")!
        
        // CAShapeLayerと使って円形を描画
        let calayer         = CAShapeLayer()
        let size            = self.composedImageModel.image.size
        calayer.frame       = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        calayer.path        = UIBezierPath(ovalIn: CGRect.init(x: size.width/2 - size.height/2, y: 0, width: size.height, height: size.height)).cgPath
        calayer.fillColor   = UIColor.clear.cgColor
        calayer.strokeColor = UIColor.black.cgColor

        // 円形のCAShapeをUIImageにレンダリング
        UIGraphicsBeginImageContextWithOptions(calayer.frame.size, false, 1)
        let context = UIGraphicsGetCurrentContext()!
        calayer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.composedImageModel.layerImage = image!
        
        // いっぬのUIImageとレンダリングされた円形のUIImageを合成
        UIGraphicsBeginImageContext(CGSize(width: size.width, height: size.height))
        self.composedImageModel.image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        self.composedImageModel.layerImage.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        self.composedImageModel.composedImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
    }
        
}
```

次に以下`ComposedImageView.swift`で比較できるように
- 犬のUIImage
- CAShapeLayerから作成した円形のUIImage
- 上記2つを合成したUIImage
を表示します。CAShapeLayerのUIImageに関しては、サイズの大きさがわかりやすいように意図的に背景を赤くして表示しています。

``` ComposedImageView.swift
import SwiftUI

struct ComposedImageView: View {
    
    private var imageVM = ComposedImageViewModel()
    
    var body: some View {
        VStack {
            // いっぬのImageを表示
            Image(uiImage: self.imageVM.composedImageModel.image)
            // CAShapeからレンタリングしたImageを表示
            Image(uiImage: self.imageVM.composedImageModel.layerImage)
                .background(Color.red)
            // 上記二つのImageを合成したImageを表示
            Image(uiImage: self.imageVM.composedImageModel.composedImage)
        }
    }
}

struct ComposedImageView_Previews: PreviewProvider {
    static var previews: some View {
        ComposedImageView()
    }
}

```
`ContentView.swift`に`ComposedImageView`を記載して、表示させる。

``` ContentView.swift
import SwiftUI

struct ContentView: View {
    
    var body: some View {
        ComposedImageView()            
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```



## シミュレーター又は実機で実行
これまで作成した画像が、以下のように表示されます。

<img width="347" alt="Screen Shot 2020-06-15 at 19.43.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195876/524393a4-25d0-ea2a-0ff1-5c07274b991d.png">

## 所感
自作アプリを作成している段階で、撮影した写真のUIImageと合成した図形の位置関係でごちゃごちゃしたので、本記事でまとめさせてもらいました。

(*短足でかわいいよね)

## 参考文献
SwiftUIでMVVM
 http://sinnderu.hatenablog.com/entry/2019/12/22/133157
【Swift4】UIViewをUIImageに変換し、画像としてカメラロールに保存する方法
 https://program-life.com/410