AppleScript + Core Image

Cocoaフレームワークを簡単に AppleScript で利用できるようになった...のは確かなんですが、実際のコードがなきゃもう一つ理解が進みませんし、利用もされません。

AppleScript に新機能が追加されるのはいいのですが、いつもこのあたりの情報が少なすぎていまいち浸透しない。

ってなわけで色々と公開してみます。実際の利用例ってやつを、ね。

簡単に効果が分かるってことで Core Image を使ってみます。Mac OS X 10.4 の頃に鳴り物入りで導入されましたね。GPU を使った高速な画像処理がウリです。AppleScript で Core Image を使うことで Image Events 以上の画像処理が可能になります。

Core Image の分かりやすい解説は Apple のものだったりします。英語なんですがね。

では、使ってみましょう。

iOS 7 に新しく追加された カメラフィルタがあります。これらは Core Image のフィルタとして提供されています。

iPhone カメラフィルタ一覧

もちろん、これらのフィルタは iOS だけではなく、OS X でも利用できます。手順としては次のようになります。

  1. 画像ファイルを開く
  2. フィルタを生成
  3. 画像にフィルタを適用
  4. 適用された画像を保存

それぞれ見ていきます。

画像ファイルを開く

扱う画像ファイルは JPEG のみとします。

とりあえず、新規スクリプトファイルを作ります。作成したら以下のように記述し、スクリプトバンドル形式で ~/Library/Script Libraries に CoreImage.scptd という名前で保存します。

Script Editor で開く

use framework "QuartzCore"

property CIImage : class "CIImage"

property NSString : class "NSString"
property NSURL : class "NSURL"

保存します。

スクリプト保存ダイアログ

保存したら「バンドルの内容」を表示し、「AppleScript/Objective-C ライブラリ」にチェックを入れます。

ライブラリとして利用するため「AppleScript/Objective-C ライブラリ」をチェック

再度、保存します。これでライブラリとして活用できるようになりました。では、説明。

まず、Core Image を使うので Core Image を含むフレームワークの QuartzCore.framework を取り込んでいます。Objective-C では

import <QuartzCore/CoreImage.h>

で CoreImage だけを取り込めますが、どうもそういうことができないようで QuartzCore.framework 全てを取り込んでいます。

property として CIImage、NSString、NSURL を定義していますが、これは単純に

  • タイプを短くするため
  • スクリプトを少しでも見やすくするため

です。別名を付けてもいいのですが、分かりやすい方がいいでしょう。

では、AppleScript から渡した画像ファイルを CIImage として開きます。

Script Editor で開く

on openImageFile(imageFile)
    -- imageFile: POSIX path 形式のファイルパス

    -- AppleScript text を NSString に変換
    set imageFile to NSString's stringWithString:(imageFile)

    -- ファイルパスを NSURL(file://)に変換
    set fileURL to NSURL's fileURLWithPath:imageFile

    -- CIImage を生成
    return CIImage's alloc()'s initWithContentsOfURL:fileURL
end openImageFile

なんでいちいち NSString に変換しているのか?

変換しなくてもいきなり NSURL に AppleScript の text クラスを渡しても大丈夫なようなのですが、念のためです。基本的には AppleScriptObjC には Cocoa - AppleScript での型の自動変換といった機能はありません。そのため両者では明示的に型を変換しておいた方が変なエラーで悩まなくてすみます。

CIImage を生成するメソッドはいくつかありますが、ここでは initWithContentsOfURL: を使っています。ここで気になるのは『init で作ったオブジェクトの解放は誰が担うんだ』ということですが、多分、気にしなくてもいいと思います。

ちょっと調べきれていないのですが、ASOC は ARC を採用しています。いつの間にか GC じゃなくなっていました。確か、登場当初は GC だったと思うのですが、ARC が導入されたあたりで ASOC も ARC になったんだと思います。

なので、参照されなくなった時点で解放されると思います。ほんまかいな?

AppleScriptObjC でのメモリ管理に関する情報が少なすぎてなんともいえないのですが、少なくともやってはいけないことは分かっています。それは、明示的にオブジェクトを release することです。一発で落ちるということはありませんが、そのうち AppleScript Editor が落ちます。

ARC については以下のサイトを。

ARC が採用されているからといって ARC が AppleScript で利用できるというわけではありません。あくまで AppleScript から利用している Cocoa のオブジェクトが ARC で管理されているということです。

AppleScriptObjC のメモリ管理に関しては以下を。

これぐらいしか情報がない...。OS X Mavericks 以前は、また異なるんですけどね、状況が...。

画像ファイルから CIImage が生成できたので次は ファイルタを作成します。

フィルタを生成

Core Image ではフィルタの入力に画像(または動画)を渡し、出力でフィルタが適用された画像を取り出します。基本的には、

  1. フィルタに画像を渡す
  2. フィルタの設定値を変更する
  3. フィルタから画像を取り出す
  4. 取り出した画像を次の画像に渡す(1 に戻る)...

これらの繰り返しで処理が行われます。

フィルタは CIFilter を利用します。ライブラリスクリプトの先頭の property に CIFilter を追加します。

(* 前略 *)
....
property CIImage : class "CIImage"
property CIFilter : class "CIFilter"
...
(* 後略 *)

CIFilter は「フィルタの名前を指定して生成」と「フィルタの名前と入力値を同時に指定して生成」する 2 種類の方法があります。後者はスクリプトが読みにくくなるので前者を採用します。

Script Editor で開く

on filterWithName(filterName)
    -- CIFilter をフィルタの名前で生成
    set filter to CIFilter's filterWithName:filterName
    -- フィルタの設定を初期値に
    filter's setDefaults()

    return filter
end filterWithName

このハンドラをライブラリスクリプトに追加します。

フィルタを生成した後、フィルタの設定値をデフォルトにしています。iOS では必要ないのですが、OS X では必須です。

フィルタにどんな種類があるかは以下を参照。

iOS より OS X の方が利用できるフィルタは多いです。これでフィルタも生成できるようになったので画像に適用してみましょう。

画像にフィルタを適用

iOS 7 のカメラで利用できるフィルタは以下の通りです。

  • モノ(CIPhotoEffectMono)
  • 色調(CIPhotoEffectTonal)
  • ノアール(CIPhotoEffectNoir)
  • フェード(CIPhotoEffectFade)
  • クローム(CIPhotoEffectChrome)
  • プロセス(CIPhotoEffectProcess)
  • トランスファー(CIPhotoEffectTransfer)
  • インスタント(CIPhotoEffectInstant)

括弧内は CIFiltet を生成するときに必要なフィルタの名前です。

ライブラリスクリプトにフィルタに画像を渡し、フィルタを適用し、画像を出力するハンドラを追加します。

Script Editor で開く

on applyEffect(imageFile, photoFilterName)
    -- CIImage を生成
    set imageRef to my openImageFile(imageFile)

    -- CIFiter を生成
    set filter to my filterWithName(photoFilterName)

    -- フィルタの設定値を入力
    filter's setValue:imageRef forKey:"inputImage"

    -- フィルタが適用された画像を取得
    return filter's valueForKey:"outputImage"
end applyEffect

CIFilter には色々な入力値があります。これらの値を変更することで画像に色々な処理を施すことができます。今回利用するカメラ用のフィルタには画像以外の入力値がありません。

フィルタが適用された画像は CIFilter の outputImage メソッドで取り出すことができます。

最後にこの画像をファイルに保存します。

適用された画像を保存

スクリプトライブラリでは画面への描画が行えません。Xcode で ASOC アプリケーションを作れば話は別ですが、ウィンドウやビューがない AppleScript Editor で作る ASOC アプリケーションや AppleScript のライブラリでは直接ディスプレイ上に画像を描画できません(方法はないわけではないですが、それなら Xcode で ASOC アプリケーションを作った方が手っ取り早い)。

NSBitmapImageRep は CIImage から生成するメソッドがあるのでこれを利用し、ファイルに保存します。

Script Editor で開く

on saveAsJPEG(imageRef, theFile)
    -- NSBitmapImageRep を CIImage から生成
    set rep to NSBitmapImageRep's alloc()'s initWithCIImage:imageRef

    -- NSBitmapImageRep から JPEG データを取得
    set jpegData to rep's representationUsingType:(current application's NSJPEGFileType) |properties|:(missing value)

    -- ファイルに保存
    jpegData's writeToFile:theFile atomically:true
end saveAsJPEG

このハンドラをライブラリスクリプトに追加します。また property に NSBitmapImageRep も追加します。

(* 前略 *)
....
property NSString : class "NSString"
property NSURL : class "NSURL"
property NSBitmapImageRep : class "NSBitmapImageRep"
...
(* 後略 *)

JPEG 決めうちですが、NSBitmapImageRep は他の画像形式も扱えます。また、このハンドラではもとの画像が持っている情報(Exif や位置情報)などは失われます。元の画像に上書きするのではないので問題はないと思いますが。

これで必要なハンドラが揃いました。最後にこれを利用するスクリプトを作ります。

クライアントスクリプト

Script Editor で開く

on run
    set inputFile to choose file of type {"public.jpeg"} with prompt "フィルタを適用する画像を選んでください"
    set outputFile to choose file name with prompt "保存先:" default name "Output.jpg"

    tell script "CoreImage"
        set imageRef to applyEffect(POSIX path of inputFile, "CIPhotoEffectFade")
        saveAsJPEG(imageRef, POSIX path of outputFile)
    end tell
end run

選択した画像にフェードフィルタを適用しています。実行すると以下のようになります。

フィルタ適用前/適用後

ちょっと画像が小さくて分かりにくいですが...。

スクリプトを書くのが嫌だというナマケモノには、これらの機能を簡単に試せるようなスクリプトをご用意しました。

バンドル形式になっています。バンドルの中にライブラリを入れています。スクリプトメニューに登録、もしくはそのまま開いて AppleScript Editor で実行してみてください。

0 件のコメント :

コメントを投稿