🔨

[Swift] CIImage の EXIF とかの書き換えが楽になるライブラリ作ってみた

2023/07/02に公開

概要

Swift で、生成した CIImage の Exif を書き換えたいときって、ありますよね。
CIImage.properties をコピーしたものを書き換えた上で、settingProperties(:) に書き換えた properties を渡して実行すれば、書き換えられた CIImage をゲット出来るんですが、この properties の書き換えがまあ面倒くさいんですよ。

  • 書き換えたいデータのキーは ImageIO のドキュメント をにらめっこしながら探さないといけない
  • Exif の DateTimeOriginalDateTimeDigitized は String で、しかもフォーマットが yyyy:MM:dd HH:mm:ss っていうまあクセつよなフォーマットなので DateFormatter をいじってパースするしかない (TIFF の他の日付系のタグもほぼ全部これ)

とまあこんな感じでタイプセーフじゃないし書き換えるコードが長くなりがちで面倒くさすぎるので、ライブラリーを作りました

作ったライブラリーの概要

地味に人生で初めて作った Swift Package でした。

このライブラリーは、Codable を使い、CIImage.properties をパースしてタイプセーフに扱えるようにしたものです。

CIImage.properties をいじるときは必ず Image I/O が絡むので、名前は SwiftyImageIO としました。

https://github.com/Uhucream/SwiftyImageIO

[String: Any] なものをどうやってパースするか、その方法の模索に時間がかかりましたが、今回のケースにぴったりな DictionaryCoder が見つかったのでありがたかったです。

導入するとどうなるか

  • プロパティの書き換えが楽にできる

    例として、Exif の書き換えるときのコードはこんな感じになります。

    • 導入前

      import CoreImage
      import Foundation
      import ImageIO
      
      extension DateFormatter {
          static var tiff: DateFormatter {
      	let formatter: DateFormatter = .init()
      
      	formatter.locale = NSLocale.system
      	formatter.dateFormat =  "yyyy:MM:dd HH:mm:ss"
      
      	return formatter
          }
      }
      
      let testCIImage: CIImage? = .init(contentsOf: URL(string: "file:///path/to/image"))
      
      guard var imageProperties: [String: Any] = testCIImage?.properties else { return }
      
      var exifDictionary: [String: Any]? = imageProperties[kCGImagePropertyExifDictionary as String] as? [String: Any]
      
      exifDictionary?[kCGImagePropertyExifDateTimeDigitized as String] = DateFormatter.tiff.date(from: .now)
      
      imageProperties[kCGImagePropertyExifDictionary as String] = exifDictionary
      
      let metadataModifiedCIImage: CIImage = testCIImage?.settingProperties(imageProperties)
      
    • 導入後

      import CoreImage
      import Foundation
      import SwiftyImageIO
      
      let testCIImage: CIImage? = .init(contentsOf: URL(string: "file:///path/to/image"))
      
      guard var imageProperties: ImageIOProperties = testCIImage?.swiftyImageProperties else { return }
      
      imageProperties.exif?.dateTimeDigitized = .now
      
      let metadataModifiedCIImage: CIImage? = try? testCIImage?.settingProperties(imageProperties)
      

    いかがでしょう。
    導入前はぐちゃぐちゃだったものが、たった数行で日付が書き換わった CIImage が手に入ります🤩

    気持ちいいですね🥰

  • CGImagePropertyOrientation のデバッグ時、ちゃんと case の名前が表示されるようになる

    CGImagePropertyOrientation は、Vision Framework をいじる際や、CIImage の画像の向きを扱う際に使うことになるものなのですが、普通に print() してもこんな感じでケース名を表示してくれないんです。

    デバッグのとき、地味に厄介です。
    SwiftyImageIO ではこれを解消しました。

ライブラリー作成して得た学び

  • enum の rawValue は Computed Property を使えばリテラルじゃなくても問題ない

    Codable な struct を作るにあたって、CodingKeys には Image I/O で用意されている辞書のキーの定数群をそのまま使いたい思いがありました。
    ただ、以下のように書くとコンパイルエラーが発生します。

    (中略)
    enum CodingKeys: String, CodingKey {
        case gps = kCGImagePropertyGPSDictionary
        ...
    }
    (中略)
    

    何かいい方法はないかとググっていたら、これを見つけました。

    https://stackoverflow.com/a/31216091/18698351

    Computed Property を使えば、定数もそのまま活用できる、というのが今回の学びでした。

    (中略)
    enum CodingKeys: String, CodingKey {
        case gps
        
        var rawValue: String {  //  ← これ!!
            switch self {
            case .gps:
      	      return kCGImagePropertyGPSDictionary
            }
        }
        ...
    }
    (中略)
    

    これのおかげで、辞書のキーの内容を知らなくても実装できたので、めちゃくちゃ助かりました

余談

まだ作りかけではあるのですが、とりあえず EXIF, GPS, TIFF は Codable な struct への書き下しが終わっています。

今後もぼちぼち書き下ししていきたいと思っています

Discussion