オープンソースのGPUImage.framework(GPUImage1)はAppleScriptから呼び出すのも簡単ですし、機能セットも充実しており、非常に重宝して使っています。
ただし、GPUImage自体はSwiftで書き直されたGPUImage2、OSの変更を受けてOpenGLではなくMetalを用いるように書き直され、いまだ発展途上のGPUImage3と変化してきました。
・GPUImageのバージョンと搭載フィルタ数の推移
GPUImage1:■■■■■■■■■■■■ 125 GPUImage2:■■■■■■■■■■ 100 GPUImage3:■■■■ 45
GPUImage2/3とバージョンが進むにつれて搭載フィルタ数が減っており、かつGPUImage2以降はAppleScriptから呼べないため、GPUImage3は(自分の)移行先になりません。
Objective-CベースのGPUImage1系のプログラムを使い続けるという判断をしている開発者は多いようで、Github上で派生プログラムが大量にみつかります。ただ、GPUImage1系はmacOSがOpenGLを完全廃止にしたとたんに動かなくなるはずです。
このため、GPUImage1系に依存しているプログラムは別の部品を呼び出して処理する必要が出てきます。
一番有望なのは、macOS標準搭載のCIFilter。CIFilterについては、実際にプログラムを組んでみて「組みにくい」という印象を受けていました(機能はちゃんとしていそうなものの、機能数にドキュメントが見合っていません)。あと、AppleScriptからパラメータを指定しにくいという印象を受けていました(パラメータに指定するオブジェクトにAppleScriptから直接指定できないものがあるので、少しラッパーを書く必要がある)。
その後、各種フィルタを呼び出す汎用ルーチンを整備することで、利用難易度を下げる努力を自主的に行なっています。
・GPUImage3とCoreImageの搭載フィルタ数の比較
GPUImage3:■■■■ 45 CoreImage:■■■■■■■■■■■■■■■■■ 174
そこで、実際に処理速度の面でどうなのか、CIFilterとGPUImageを比較するとどうなのか、調べてみました。
まずは、ざっくりありもののルーチンを引っ張り出してきて実行。どちらも期待どおりの動作をしてくれます。ただし、周辺ルーチンの仕様が違っていたのでそこも合わせます。
乱数ファイル名を作成する定番のshell commandの「uuidgen」はお手軽な反面、呼び出すのに時間がかかるので(手元の時間で0.1秒程度強)、より高速なCocoaのUUIDStringを用いる部品に置き換えました。
それぞれのフレームワークごとの挙動の違いもあります。
経験上、GPUImage.frameworkは初回実行時だけは時間が長くかかる傾向があり(1秒以上かかる)、2回目以降はより短い時間で処理できるようになることを確認しています。一方、CIFilterは初回およびそれ以降の呼び出しでもさほど処理時間が変わりません。
これら2つを実際に同じデータで同一条件下で比較してみたところ、同一の処理(画像の差分計算)を行なったかぎりでは処理時間に差はほとんど有意な差はありませんでした。
Script Geek 2.0.1によるScriptの処理時間比較
First:GPUImage Version , Second:CoreImage Version MacBookPro10,1, macOS Version 10.14.6 (Build 18G2012), 10 iterations First Run Total Time Average First 0.969 1.194 0.119 Second 0.121 1.221 0.122 Ratio (excluding first run): 1:1.02
自分のメインマシン(MacBook Pro Retina 2012、Core i7 2.66GHz、RAM 8GB)ではどちらもテストデータ(1192x1684pixel 8bit color image x 2)を0.1〜0.2秒程度で処理できています。個人的には十分なスピードが出ていると感じていますが、不満がある方はデータをRAM Disk上に置いたり、並列処理するとよいのではないでしょうか(シングルスレッド処理が高速なマシンを用意するというのが一番効きそうではあります。iMac 5Kの上位モデルとか)。
すべての類似フィルタで比較を行なったわけではありませんが(どれとどれが類似、という情報を整理するのに手間がかかる)、一番使っている「空白検出のためのヒストグラム計算フィルタ」はGPUImageにしか存在しませんでした(作成当時の話で現在はCoreImageにもCIAreaHistogramなどの機能があります)。
空白検出処理を高速に行えることで巨大なメリットが生じています。用途については枚挙にいとまがありませんが、Markdown書類をPDFにレンダリングした場合に、末尾に無用な空白のページが生じるケースがあり、これを検出しつつ末尾のページを自動削除することで、膨大な無駄手間を削減できています。空白検出処理を高速に行う手段を用意していないと、Photoshopで指定ページをレンダリングしてヒストグラムを計算する必要があるわけで、Photoshopのない環境に進出できなくなります。
空白画像の検出処理はGPUImage.framework抜きで(AppleScriptだけで)2〜3倍高速な処理を行える部品「画像の空白判定 v3」を作成済みなので、GPUImageなしでもさほど困りません。
▲同一環境にて、Photoshop CC 2018、GPUImage、AppleScriptだけで処理している「画像の空白判定 v3」で各種サイズの画像の空白検出を実行(単位:秒)
Objective-C/Swift系の開発者の方々に「GPUImageを経由してGPUのパワーを使って処理するよりも、AppleScript単体で空白の比較演算処理したほうが速かった」という事実をお話しすると、最初は「嘘つけ」というひきつった笑顔の反応なのですが、実際にコードを動かして動作原理を丁寧に説明すると分かっていただけるようです。
–> Download dffFilterDemoPrograms(Two Code-Signed AppleScript Applet Executables with Framework in its bundle)
AppleScript名:CoreImageでCIDifferenceBlendMode.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/22 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppKit" use framework "QuartzCore" use scripting additions property CIFilter : a reference to current application’s CIFilter property NSUUID : a reference to current application’s NSUUID property |NSURL| : a reference to current application’s |NSURL| property CIImage : a reference to current application’s CIImage property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSPNGFileType : a reference to current application’s NSPNGFileType property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep set mePath to POSIX path of (path to me) set aImg to mePath & "Contents/Resources/book1_v2.0_0011.jpg" set bImg to mePath & "Contents/Resources/book1_v2.1_0011.jpg" –https://developer.apple.com/library/archive/documentation/ –GraphicsImaging/Reference/CoreImageFilterReference/index.html –#//apple_ref/doc/filter/ci/CIDifferenceBlendMode set cifilterName to "CIDifferenceBlendMode" set imgRes to filterBlendImageFiles(aImg, bImg, cifilterName) of me set outPath to retUUIDfilePath(POSIX path of (path to desktop), "png") of me set sRes to saveNSImageAtPathAsPNG(imgRes, outPath) of me –CIFilterをかけたJPEG画像を生成 on filterBlendImageFiles(aPath, bPath, aFilterName) –CIImage(A)を生成 set aURL to |NSURL|’s fileURLWithPath:aPath –Input set aCIImage to CIImage’s alloc()’s initWithContentsOfURL:aURL –CIImage(B)を生成 set bURL to |NSURL|’s fileURLWithPath:bPath –Input set bCIImage to CIImage’s alloc()’s initWithContentsOfURL:bURL — CIFilter をフィルタの名前で生成 set aFilter to CIFilter’s filterWithName:aFilterName aFilter’s setDefaults() –各フィルタのパラメータはデフォルト –Blend Filterを実行 aFilter’s setValue:(aCIImage) forKey:"inputImage" aFilter’s setValue:(bCIImage) forKey:"inputBackgroundImage" set aOutImage to aFilter’s valueForKey:"outputImage" set outNSImage to convCIimageToNSImage(aOutImage) of me return outNSImage end filterBlendImageFiles on retUUIDfilePath(aPath, aEXT) set aUUIDstr to (NSUUID’s UUID()’s UUIDString()) as string set aPath to ((NSString’s stringWithString:aPath)’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT return aPath end retUUIDfilePath on convCIimageToNSImage(aCIImage) set aRep to NSBitmapImageRep’s alloc()’s initWithCIImage:aCIImage set tmpSize to aRep’s |size|() set newImg to NSImage’s alloc()’s initWithSize:tmpSize newImg’s addRepresentation:aRep return newImg end convCIimageToNSImage on convNSImageToCIimage(aNSImage) set tiffDat to aNSImage’s TIFFRepresentation() set aRep to NSBitmapImageRep’s imageRepWithData:tiffDat set newImg to CIImage’s alloc()’s initWithBitmapImageRep:aRep return newImg end convNSImageToCIimage –NSImageを指定パスにPNG形式で保存 on saveNSImageAtPathAsPNG(anImage, outPath) set imageRep to anImage’s TIFFRepresentation() set aRawimg to NSBitmapImageRep’s imageRepWithData:imageRep set pathString to NSString’s stringWithString:outPath set newPath to pathString’s stringByExpandingTildeInPath() set myNewImageData to (aRawimg’s representationUsingType:(NSPNGFileType) |properties|:(missing value)) set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean return aRes –true/false end saveNSImageAtPathAsPNG |
AppleScript名:(比較用)GPUImageによる差分計算.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/22 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use scripting additions use framework "Foundation" use framework "AppKit" use framework "GPUImage" –https://github.com/BradLarson/GPUImage property NSUUID : a reference to current application’s NSUUID property |NSURL| : a reference to current application’s |NSURL| property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSPNGFileType : a reference to current application’s NSPNGFileType property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep set mePath to POSIX path of (path to me) set aImagePath to mePath & "Contents/Resources/book1_v2.0_0011.jpg" set bImagePath to mePath & "Contents/Resources/book1_v2.1_0011.jpg" set imgRes to composeImageWithBlendFilter(aImagePath, bImagePath, "GPUImageDifferenceBlendFilter") of me set outPath to retUUIDfilePath(POSIX path of (path to desktop), "png") of me set sRes to saveNSImageAtPathAsPNG(imgRes, outPath) of me on composeImageWithBlendFilter(aImagePath, bImagePath, filterName) set aImage to NSImage’s alloc()’s initWithContentsOfFile:aImagePath set bImage to NSImage’s alloc()’s initWithContentsOfFile:bImagePath set aClass to current application’s NSClassFromString(filterName) set blendFilter to aClass’s alloc()’s init() set pictureA to current application’s GPUImagePicture’s alloc()’s initWithImage:aImage pictureA’s addTarget:blendFilter pictureA’s processImage() set imgRes to blendFilter’s imageByFilteringImage:bImage return imgRes end composeImageWithBlendFilter on retUUIDfilePath(aPath, aEXT) set aUUIDstr to (NSUUID’s UUID()’s UUIDString()) as string set aPath to ((NSString’s stringWithString:aPath)’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT return aPath end retUUIDfilePath –NSImageを指定パスにPNG形式で保存 on saveNSImageAtPathAsPNG(anImage, outPath) set imageRep to anImage’s TIFFRepresentation() set aRawimg to NSBitmapImageRep’s imageRepWithData:imageRep set pathString to NSString’s stringWithString:outPath set newPath to pathString’s stringByExpandingTildeInPath() set myNewImageData to (aRawimg’s representationUsingType:(NSPNGFileType) |properties|:(missing value)) set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean return aRes –true/false end saveNSImageAtPathAsPNG |