Genta Hirauchi

公開日:2020/04/06
更新日:2020/04/09

【Swift CIFilter】CIToneCurveでトーンカーブを補正する方法を解説

  • 画像のトーンカーブを補正する方法が知りたい。

SwiftのCore Imageには、CIFilterという、画像の補正を行うことができるクラスがあります。

CIFilterには、さまざなま画像補正のフィルタが用意されております。本記事では、その中のCIToneCurveというフィルタを使用し、画像のトーンカーブを補正する方法を解説致します。

目次

CIToneCurveについて

CIToneCurveは、トーンカーブを補正する際に使用されるCore Image Filterです。

CIToneCurveには、以下のパラメータがあります。

inputImage 入力画像(補正をかける画像)
inputPoint0 ポイント0のトーンカーブ値
デフォルト値: [0.0, 0.0]、最小値: [0, 0]、最大値: [1, 1]
inputPoint1 ポイント1のトーンカーブ値
デフォルト値: [0.25, 0.25]、最小値: [0, 0]、最大値: [1, 1]
inputPoint2 ポイント2のトーンカーブ値
デフォルト値: [0.5, 0.5]、最小値: [0, 0]、最大値: [1, 1]
inputPoint3 ポイント3のトーンカーブ値
デフォルト値: [0.75, 0.75]、最小値: [0, 0]、最大値: [1, 1]
inputPoint4 ポイント4のトーンカーブ値
デフォルト値: [1.0, 1.0]、最小値: [0, 0]、最大値: [1, 1]

Reference: CIToneCurve | Core Image Filter Reference

CIToneCurveでトーンカーブを補正する方法

では、CIToneCurveで、トーンカーブを補正する方法を解説致します。

以下は、スライダーを動かすと、その値をトーンカーブ値として、画像の補正を行うサンプルコードです。

  • ViewController.swift
import UIKit

class ViewController: UIViewController, ToneCurveDelegate {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var tableView: UITableView!

    private var ciFilter: CIFilter!

    private let toneCurveTitleList: [String] = [
        "inputPoint0",
        "inputPoint1",
        "inputPoint2",
        "inputPoint3",
        "inputPoint4"
    ]

    public var toneCurveValueList: [Float] = [0.0, 0.25, 0.50, 0.75, 1.0]

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "トーンカーブ"

        guard let uiImage = UIImage(named: "sample"), let ciImage = uiImage.ciImage ?? CIImage(image: uiImage) else { return }

        imageView.image = uiImage

        // CIFilterの生成
        ciFilter = CIFilter(name: "CIToneCurve")

        // 入力画像の設定
        ciFilter.setValue(ciImage, forKey: kCIInputImageKey)
    }

    func valueSliderChanged(tag: Int, value: Float) {
        toneCurveValueList[tag] = value

        // トーンカーブの設定
        ciFilter.setValue(CIVector(x: 0.0,  y: CGFloat(toneCurveValueList[0])), forKey: "inputPoint0")
        ciFilter.setValue(CIVector(x: 0.25, y: CGFloat(toneCurveValueList[1])), forKey: "inputPoint1")
        ciFilter.setValue(CIVector(x: 0.5,  y: CGFloat(toneCurveValueList[2])), forKey: "inputPoint2")
        ciFilter.setValue(CIVector(x: 0.75, y: CGFloat(toneCurveValueList[3])), forKey: "inputPoint3")
        ciFilter.setValue(CIVector(x: 1.0,  y: CGFloat(toneCurveValueList[4])), forKey: "inputPoint4")

        // Filter適応後の画像を表示
        if let filteredImage = ciFilter.outputImage {
            imageView.image = UIImage(ciImage: filteredImage)
        }
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return toneCurveTitleList.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "ToneCurveTableViewCell") as? ToneCurveTableViewCell else {
            return UITableViewCell()
        }

        cell.delegate = self
        cell.valueSlider.tag = indexPath.row
        cell.titleLabel.text = toneCurveTitleList[indexPath.row]
        cell.valueSlider.value = toneCurveValueList[indexPath.row]
        cell.valueLabel.text = String(round(cell.valueSlider.value * 100) / 100)

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}



protocol ToneCurveDelegate: class {
    func valueSliderChanged(tag: Int, value: Float)
}

class ToneCurveTableViewCell: UITableViewCell {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var valueSlider: UISlider!
    @IBOutlet weak var valueLabel: UILabel!

    public weak var delegate: ToneCurveDelegate?

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    @IBAction func valueChanged(_ sender: UISlider) {
        valueLabel.text = String(round(sender.value * 100) / 100)
        delegate?.valueSliderChanged(tag: sender.tag, value: sender.value)
    }
}

CIFilter(name: “CIToneCurve”)で、CIToneCurveフィルタのインスタンスを生成しております。

その後、生成したCIFilterのインスタンスに対し、setValue(ciImage, forKey: kCIInputImageKey)で、入力画像の設定を行っております。

スライダーの値が変更されたら、setValue(CIVector(x: デフォルト値, y: トーンカーブ値), forKey: キー)で、トーンカーブ値の設定を行います。

そして、outputImageで、トーンカーブが補正されたCIImageを取得し、UIImageに変換後、画面に表示しております。

Point
  • CIFilter(name: “CIToneCurve”)で、フィルタを生成する
  • setValue(CIImage, forKey: kCIInputImageKey)で、入力画像を設定する
  • setValue(CIVector(x: デフォルト値, y: トーンカーブ値), forKey: キー)で、トーンカーブ値を設定する
  • outputImageで、補正されたCIImageを取得する

まとめ

  • CIFilter(name: “CIToneCurve”)で、フィルタを生成する
  • setValue(CIImage, forKey: kCIInputImageKey)で、入力画像を設定する
  • setValue(CIVector(x: デフォルト値, y: トーンカーブ値), forKey: キー)で、トーンカーブ値を設定する
  • outputImageで、補正されたCIImageを取得する