【Swift】CIFilterで画像のトーンカーブを調整する

# はじめに
昨日のノイズに続き、今回はトーンカーブです。

https://qiita.com/SNQ-2001/items/bc2206232e551f5b62d4

# サンプルアプリ
![Simulator Screen Recording - iPhone 15 - 2023-10-13 at 21.41.03.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/a26ca27c-00fb-e08d-222a-b7135a24a40d.gif)

# 実装
```swift
import SwiftUI

struct ToneCurveView: View {
    private let image = UIImage(named: "sample")!
    
    @State private var uiImage: UIImage?
    
    @State private var inputPoint0x: CGFloat = 0.0
    @State private var inputPoint0y: CGFloat = 0.0
    @State private var inputPoint1x: CGFloat = 0.0
    @State private var inputPoint1y: CGFloat = 0.0
    @State private var inputPoint2x: CGFloat = 0.0
    @State private var inputPoint2y: CGFloat = 0.0
    @State private var inputPoint3x: CGFloat = 0.0
    @State private var inputPoint3y: CGFloat = 0.0
    @State private var inputPoint4x: CGFloat = 0.0
    @State private var inputPoint4y: CGFloat = 0.0
    
    var body: some View {
        ScrollView {
            VStack {
                if let uiImage {
                    Image(uiImage: uiImage)
                        .resizable()
                        .scaledToFit()
                } else {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                }

                Button {
                    applyToneCurve()
                } label: {
                    Text("更新")
                }

                VStack(spacing: 0) {
                    Text("Point0(x): \(inputPoint0x.description)")
                    Slider(value: $inputPoint0x, in: 0.0...1.0)
                }
                
                VStack(spacing: 0) {
                    Text("Point0(y): \(inputPoint0y.description)")
                    Slider(value: $inputPoint0y, in: 0.0...1.0)
                }
                
                VStack(spacing: 0) {
                    Text("Point1(x): \(inputPoint1x.description)")
                    Slider(value: $inputPoint1x, in: 0.0...1.0)
                }
                
                VStack(spacing: 0) {
                    Text("Point1(y): \(inputPoint1y.description)")
                    Slider(value: $inputPoint1y, in: 0.0...1.0)
                }
                
                VStack(spacing: 0) {
                    Text("Point2(x): \(inputPoint2x.description)")
                    Slider(value: $inputPoint2x, in: 0.0...1.0)
                }
                
                VStack(spacing: 0) {
                    Text("Point2(y): \(inputPoint2y.description)")
                    Slider(value: $inputPoint2y, in: 0.0...1.0)
                }
                 
                VStack(spacing: 0) {
                    Text("Point3(x): \(inputPoint3x.description)")
                    Slider(value: $inputPoint3x, in: 0.0...1.0)
                }
                
                VStack(spacing: 0) {
                    Text("Point3(y): \(inputPoint3y.description)")
                    Slider(value: $inputPoint3y, in: 0.0...1.0)
                }
                
                VStack(spacing: 0) {
                    Text("Point4(x): \(inputPoint4x.description)")
                    Slider(value: $inputPoint4x, in: 0.0...1.0)
                }
                
                VStack(spacing: 0) {
                    Text("Point4(y): \(inputPoint4y.description)")
                    Slider(value: $inputPoint4y, in: 0.0...1.0)
                }
            }
            .padding(.horizontal, 20)
        }
    }
    
    func applyToneCurve() {
        let context = CIContext()
        let inputImage = CIImage(image: image)!
        // トーンカーブを調整
        let toneCurveFilter = CIFilter(name: "CIToneCurve", parameters: [
            kCIInputImageKey: inputImage,
            "inputPoint0": CIVector(x: inputPoint0x,  y: inputPoint0y),
            "inputPoint1": CIVector(x: inputPoint1x, y: inputPoint1y),
            "inputPoint2": CIVector(x: inputPoint2x,  y: inputPoint2y),
            "inputPoint3": CIVector(x: inputPoint3x, y: inputPoint3y),
            "inputPoint4": CIVector(x: inputPoint4x,  y: inputPoint4y)
        ])!
        // 出力
        guard let outputImage = toneCurveFilter.outputImage else { return }
        guard let cgImage = context.createCGImage(outputImage, from: inputImage.extent) else { return }
        self.uiImage = UIImage(cgImage: cgImage)
    }
}
```

# おわり
`CIToneCurve`というフィルターを使って画像のトーンカーブを編集することができます