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で追加
Other Linker Flagsに-all_load
を設定
カラーテーブルを手動で登録する理由
多くの画像フレームをAnimation GIFの終了処理CGImageDestinationFinalize
にかけると大量のメモリを消費してハングする。
どうやら色の自動抽出処理が原因みたいなので、カラーテーブルを先に抽出するかして手動で作る必要がある。
それをするにはkCGImagePropertyGIFHasGlobalColorMap
をfalseにして、kCGImagePropertyGIFImageColorMap
にbinary Arrayを放り込めば良い。
動作確認にはコードのcolorTable
を削ると色が欠けることから動作していることは確認できた。
それでも処理には大量のメモリを消費するのでフレームが増える度にメモリが急激に使われるんだけどね。
許容量の調査はできるのでいきなりハングしないようになるだけマシかな。
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
}
}
できたもの
カラーテーブルから青だけ削る
let colorTable = Data([
255, 0, 0,
255,255,255,
]);