LoginSignup
0
1

More than 1 year has passed since last update.

iOSでAnimation GIFを実装してみる(Swift)

Last updated at Posted at 2023-02-11

iOSでAnimation GIFを作る機会があったのでメモを残す。

前回はAnimation PNGを作ったのだが無駄にファイルサイズがでかいので色数的にはGIFで足りたのでGIFにした。
ファイルサイズは10分の1程度になった。

SwiftでOpenCVの導入

今回はSwiftでOpenCVも導入して遊んでみた。

以前ImageProcが使える程度のを作ったのがあったのでこちらからダウンロードした。
https://github.com/mbotsu/KeypointDecoder/releases/download/0.0.1/opencv2.xcframework.zip

opencv2.xcframework.zipの作り方はこちら
https://github.com/mbotsu/KeypointDecoder/blob/main/minimum_opencv.md

OpenCVをSwiftで利用する設定

  • libc++.tbdを追加
  • opencv2.xcframeworkをDo Not Embedで追加

スクリーンショット 2023-02-11 15.54.18.png

Other Linker Flagsに-all_loadを設定

スクリーンショット 2023-02-11 15.55.04.png

カラーテーブルを手動で登録する理由

多くの画像フレームをAnimation GIFの終了処理CGImageDestinationFinalizeにかけると大量のメモリを消費してハングする。
どうやら色の自動抽出処理が原因みたいなので、カラーテーブルを先に抽出するかして手動で作る必要がある。
それをするにはkCGImagePropertyGIFHasGlobalColorMapをfalseにして、kCGImagePropertyGIFImageColorMapにbinary Arrayを放り込めば良い。

動作確認にはコードのcolorTableを削ると色が欠けることから動作していることは確認できた。
それでも処理には大量のメモリを消費するのでフレームが増える度にメモリが急激に使われるんだけどね。
許容量の調査はできるのでいきなりハングしないようになるだけマシかな。

ContentView.swift
import SwiftUI
import opencv2
import UniformTypeIdentifiers

struct ContentView: View {
  var body: some View {
    VStack {
      Image(systemName: "globe")
        .imageScale(.large)
        .foregroundColor(.accentColor)
      Text("Hello, world!")
    }
    .padding()
    .onAppear {
      test()
    }
  }
  
  func test(){
    
    var images = [UIImage]()
    for p:Int32 in 0..<100 {
      let img = Mat(size: Size(width: 100, height: 100),
                    type: CvType.CV_8UC3, scalar: Scalar(255,255,255))
      Imgproc.circle(img: img, center: Point2i(x: p, y: 50), radius: 5, color: Scalar(255,0,0), thickness: -1)
      Imgproc.circle(img: img, center: Point2i(x: 50, y: p), radius: 5, color: Scalar(0,0,255), thickness: -1)
      let uiImage = img.toUIImage()
      images.append(uiImage)
    }
    
    let fileUrl: URL = FileManager.default.temporaryDirectory.appendingPathComponent("test.gif")
    
    let colorTable = Data([
      255,  0,  0,
        0,  0,255,
      255,255,255,
    ]);
    
    do {
      try Animation(images, toUrl: fileUrl, colorTable: colorTable, fps: 10)
    } catch {
      print(error.localizedDescription)
    }
    
    print(fileUrl.path)
  }
}

enum AnimationError: Error {
  case create
  case finalize
}

func Animation(_ images: [UIImage], toUrl: URL, colorTable: Data, fps: Float = 30, loopCount: Int = 0) throws {
  let frameCount = images.count
  
  let fileDict = NSMutableDictionary()
  fileDict.setObject(false, forKey: kCGImagePropertyGIFHasGlobalColorMap as NSString)
  fileDict.setObject(loopCount, forKey: kCGImagePropertyGIFLoopCount as NSString)
  let fileProperties = NSDictionary(object: fileDict, forKey: kCGImagePropertyGIFDictionary as NSString)
  
  let frameDict = NSMutableDictionary()
  frameDict.setObject( 1.0 / fps, forKey: kCGImagePropertyGIFDelayTime as NSString)
  frameDict.setObject(colorTable, forKey: kCGImagePropertyGIFImageColorMap as NSString)
  let frameProperties = NSDictionary(object: frameDict, forKey: kCGImagePropertyGIFDictionary as NSString)
  
  guard let destination = CGImageDestinationCreateWithURL(toUrl as CFURL, UTType.gif.identifier as CFString , frameCount, nil) else {
    throw AnimationError.create
  }
  
  CGImageDestinationSetProperties(destination, fileProperties.copy() as? NSDictionary)
  
  for image in images {
    autoreleasepool {
      CGImageDestinationAddImage(destination, image.cgImage!, frameProperties)
    }
  }
  
  if !CGImageDestinationFinalize(destination) {
    throw AnimationError.finalize
  }
}

できたもの

test.gif

カラーテーブルから青だけ削る

let colorTable = Data([
  255,  0,  0,
  255,255,255,
]);

test2.gif

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1