ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでイメージ処理してみる
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareでイメージ処理してみる
はじめに
以下は、Xojo Cocoaビルドについての話題です。
CIFilterを使ったイメージ(画像)処理を試してみました。
なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + OS X 10.13.6 High Sierra)
方針
CIFilterは、以前こちらで使ったのですが、今回はそのとき邪険(笑)に扱っていた、写真等のイメージ処理(こちらが王道?)についてです。
偶々、とある作業でアンシャープマスク処理が必要となり、当初は画像編集ソフトでの手作業も考えたのですが、枚数が多くなってくると、単純作業なのでできればバッチ処理化したいな、というところから、CocoaのAPIを物色していたらここにぶち当たった、という訳です。
とはいえ、CIFilterを作る部分こそ共通ではあるものの、前回のNSViewへの設定に対して今回はイメージ加工なので、使い回せる部分はそれ程多くありません。
さて、入力となるイメージは、最初からNSImageで持つという方法もありますが、今回はXojoのPictureとしました。
画面への描画(CanvasへのPictureの描画)に、毎回変換がかかることになるので、NSViewにNSImageを描画する方がオーバーヘッドは少なくなると思われるが、今回はXojoネイティブのパフォーマンスチェックの意味もあって、こうした。そのため、変換が必要となりますが、CIFilterの入出力はCIImageなので、入力は以下の通りとなります。
Picture → CGImageRef → CIImage
一方、出力は以下の通りです。
CIImage → CGImageRef → NSImage → NSData → Xojo.Core.MemoryBlock → MemoryBlock → Picture
CIImageからNSImage(注:記事中ではUIImage)は、以下のサイトを参考にさせて頂きました。
参考サイト(1):CoreImageで画像の加工をする その2「フィルタの重ねがけ」 - Teratoma
NSImageからPictureへの変換は、こちらでやったものをベースにしています。(ただし、PNGは経由しません。)
当初必要としていたのはバッチ処理ですが、ここではフィルターごとの処理内容や効き具合を確認したいので、パラメータは可変とし、値を変更したらリアルタイムでイメージ処理を行い、結果を表示するものとします。
個々のフィルターについては、以下の記事を参考にさせて頂きながら、(適当に?)選んでいます。
参考サイト(2):CIFilterチートシート(全201種) - Qiita
参考サイト(3):CoreImageのフィルターを試してみる(CICategoryColorAdjustment、その3) - しめ鯖日記(記事内リンクからその2、その1と辿れます。)
CIFilterのパラメータ種別(型)は、(漏れがあるかもしれませんが、確認できたのは)CIColor、CIImage、CIVector、NSNumberでした。
これらのパラメータは設定用のペインを用意し、以下の通りコントロールに割り当てました。
1. CIColorは、Slider(RGBAの4個組)
2. CIImageは、Canvasへのドラッグ&ドロップで、サムネイル表示
3. CIVectorは、TextField(数値入力。4個または2個組)
4. NSNumberは、Slider(注1)
なお、各コントロールは種別ごとにContainerControl上に配置し、必要に応じて動的に生成するものとします。
注1)今回対象としたフィルターは、全てスライダー最小値, 最大値が設定されていたのでSliderを使ったが、設定されていないものを使う場合は、自分で最小値, 最大値を決めてしまうか、TextFieldを割り当てた上で分岐処理するか、といった対応が必要になる。以上を踏まえ、(残りの)仕様は以下の通りとしました。
- ウィンドウは3ペイン構成とし、左にフィルターリスト、中央にイメージ、右にパラメータ設定用コントロールとする。
- フィルターリストでは、
(1) 行を選択するとパラメータ設定用コントロールを表示する。(表示のみで設定は不可)
(2) チェックすると設定可能となり、現在の設定値でフィルター処理がかかる。- 複数のフィルターをチェックした場合は、処理を重畳する。(ただし処理の順番は並び順で、入れ替えはできない。)
- イメージを必要とするフィルターの初期値には、無地の画像を生成して割り当てる。(必要ならカスタマイズして下さい。)
- 処理対象イメージは、中央ペインへのドラッグ&ドロップで取得する。
- 変数は、可能であればVariant型やDictionary(要素はVariant型)で持つようにし、汎用性を高める。
- ContainerControlのインスタンスからメインウィンドウへのアクセスにはDelegateを用いる。(こちらを参照)
- CIFilter関連処理はModule化する。
Xojoでの実装
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、CIFilterによるイメージ処理が機能することを確認しました。
- Xojoで新規プロジェクトを作成
- Window1に、Canvas(Name:Canvas1)、Listbox(Name:Listbox1)を追加
- Canvas1に、言語リファレンス>>ImageWell>Examples>drag and dropの項のコードのうち、DropObject, Openを記述。
- 以下をCanvas1にペースト(できなければ、Sub - Endの間をPaintイベントに記述)
Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint DrawPicture(g) End Sub
- 以下をListbox1にペースト(できなければ、Sub - Endの間をCellActionイベントに記述)
Sub CellAction(row As Integer, column As Integer) Handles CellAction if column=0 then // チェックボックス列なら if row=me.ListIndex then // チェックされた行が選択済なら SetFilterEnable(row,me.CellCheck(row,column)) // チェック値に応じて有効/無効をセット end if GetProcess(row,"Enable",me.CellCheck(row,column)) // フィルター処理の実行 end if End Sub
- 以下をListbox1にペースト(できなければ、Sub - Endの間をCellBackgroundPaintイベントに記述)
注)初出時、3行目をg.FillRect 0,0,me.Width,me.RowHeight // me.Widthは列幅ではなくリストボックス幅だが、実害はないようだとしていましたが、Callのサイズはgから取るのが作法のようで、改訂しました。Function CellBackgroundPaint(g As Graphics, row As Integer, column As Integer) Handles CellBackgroundPaint as Boolean g.ForeColor=RGB(255,255,235) // 淡クリーム色 g.FillRect 0,0,g.Width,g.RowHeight End Function
- 以下をListbox1にペースト(できなければ、Sub - Endの間をChangeイベントに記述)
Sub Change() Handles Change SetFilter(me.ListIndex) // 選択されたフィルターのパラメータ用コントロールをセット(選択解除時のクリアもこの中で行なっている) if me.ListIndex>=0 then // 行選択されていれば SetFilterEnable(me.ListIndex,me.CellCheck(me.ListIndex,0)) // チェックありなら有効に、なしなら無効に end if End Sub
- 以下をListbox1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
Sub Open() Handles Open // ヘッダプレスによるソートを無効化 for i As Integer=0 to me.ColumnCount-1 me.HeaderType(i)=Listbox.HeaderTypes.NotSortable next End Sub
- 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
Sub Open() Handles Open InitCIFilter() // フィルターデータの初期化 InitList() // フィルターリストの初期化 InitCntn() // コンテナ(パラメータセット用コントロールを収納)リストの初期化(全削除) End Sub
- 以下をWindow1にペースト
Protected Sub DrawPicture(g As Graphics) // 背景描画(白) g.ForeColor=RGB(255,255,255) g.FillRect 0,0,Canvas1.Width,Canvas1.Height // 元画像が取得済なら if pPicSrc<>nil then MakePicture() // 加工画像の生成 g.DrawPicture(pPicOut, (Canvas1.Width-pPicOut.Width)/2,(Canvas1.Height-pPicOut.Height)/2) // 加工画像の描画(センタリング) end if // 左右の境界線(グレー) g.ForeColor=RGB(185,185,185) g.DrawLine 0,0,0,Canvas1.Height g.DrawLine Canvas1.Width-1,0,Canvas1.Width-1,Canvas1.Height End Sub
- 以下をWindow1にペースト
Protected Function FilterEnable() as Boolean // リストの行数 for i As Integer = 0 to Listbox1.ListCount-1 if Listbox1.CellCheck(i,0) then // チェックされていたらtrueを返す return true end if next // チェックが一つもなかったので、falseを返す return false End Function
- 以下をWindow1にペースト
Protected Sub GetProcess(idx As Integer, key As String, vv As Variant) // フィルターにパラメータをセット SetValue(idx,key,vv) // フィルターが選択されていたら if FilterEnable() then // フィルター処理を行って処理後の画像を取得 pPicEff=SetContentFilters(pPicSrc) else pPicEff=nil // クリアー end if // 処理後の画像の描画指示 Canvas1.Refresh End Sub
- 以下をWindow1にペースト
Protected Sub InitCntn() // 要素を削除 Dim cntl As ContainerControl for i As Integer = Ubound(pCntn) downto 0 cntl=pCntn(i) cntl.Close pCntn.Remove(i) next ReDim pCntn(-1) End Sub
- 以下をWindow1にペースト
Protected Sub InitList() // リストの読み込み ListBox1.DeleteAllRows for i As Integer = 0 to GetCount() ListBox1.AddRow "" ListBox1.CellType(i,0)=2 // チェックボックス ListBox1.Cell(i,1)=GetValue(i,"Fname") // フィルター名 next End Sub
- 以下をWindow1にペースト
Protected Sub MakePicture() // 加工画像が生成済なら解放 if pPicOut<>nil then pPicOut=nil end if // 加工画像の生成 pPicOut=new Picture(pPicSrc.Width,pPicSrc.Height) // 元画像の描画 if pPicEff<>nil then pPicOut.Graphics.DrawPicture(pPicEff,0,0,pPicOut.Width,pPicOut.Height,0,0,pPicSrc.Width,pPicSrc.Height) else pPicOut.Graphics.DrawPicture(pPicSrc,0,0,pPicOut.Width,pPicOut.Height,0,0,pPicSrc.Width,pPicSrc.Height) end if End Sub
- 以下をWindow1にペースト
Protected Sub SetFilter(idx As Integer) Dim i As Integer // 初期化(全削除) InitCntn() // 行選択されていなければ戻る if idx<0 then return end if // コントロールコンテナを生成して値をセット Dim top As Integer = 0 Dim cnt As Integer = 1 for i=0 to GetValue(idx,"Count")-1 select case GetValue(idx,"Type"+Str(i+1)).StringValue case "NSNumber" // NSNumberならスライダーを割り当てる Dim a As new ContainerControl1(cnt,AddressOf ValueChangedNumber) a.EmbedWithin(self,me.Width-a.Width,top) a.SetControl(idx,cnt) // 値のセット pCntn.Append a top=top+a.Height cnt=cnt+1 case "CIVector" // CIVectorならテキストフィールド列を割り当てる Dim a As new ContainerControl2(cnt,AddressOf ValueChangedVector) a.EmbedWithin(self,me.Width-a.Width,top) a.SetControl(idx,cnt) // 値のセット pCntn.Append a top=top+a.Height cnt=cnt+1 case "CIColor" // CIColorならスライダー列を割り当てる Dim a As new ContainerControl3(cnt,AddressOf ValueChangedColor) a.EmbedWithin(self,me.Width-a.Width,top) a.SetControl(idx,cnt) // 値のセット pCntn.Append a top=top+a.Height cnt=cnt+1 case "CIImage" // CIImageならイメージウェル列を割り当てる Dim a As new ContainerControl4(cnt,AddressOf ValueChangedImage) a.EmbedWithin(self,me.Width-a.Width,top) a.SetControl(idx,cnt) // 値のセット pCntn.Append a top=top+a.Height cnt=cnt+1 end select next End Sub
- 以下をWindow1にペースト
Protected Sub SetFilterEnable(idx As Integer, chk As Boolean) Dim i As Integer // コントロールの有効/無効をセット Dim cnt As Integer = 1 for i=0 to GetValue(idx,"Count")-1 select case GetValue(idx,"Type"+Str(i+1)).StringValue case "NSNumber" Dim a As ContainerControl1 = ContainerControl1(pCntn(i)) // キャストして取り出す a.SetControlEnable(chk) cnt=cnt+1 case "CIVector" Dim a As ContainerControl2 = ContainerControl2(pCntn(i)) // キャストして取り出す a.SetControlEnable(idx,cnt,chk) cnt=cnt+1 case "CIColor" Dim a As ContainerControl3 = ContainerControl3(pCntn(i)) // キャストして取り出す a.SetControlEnable(chk) cnt=cnt+1 case "CIImage" Dim a As ContainerControl4 = ContainerControl4(pCntn(i)) // キャストして取り出す a.SetControlEnable(chk) cnt=cnt+1 end select next End Sub
- 以下をWindow1にペースト
Protected Sub ValueChangedColor(cnt As Integer, no As Integer, vv As Integer) // 現在のRGBA値を取得 Dim ary(-1) As String = GetValue(Listbox1.ListIndex,"Key"+Str(cnt)).StringValue.Split(",") // ary(0)=r, ary(1)=g, ary(2)=b, ary(3)=a // 変更のあった要素だけ書き換え Dim st As String select case no case 1 st=Str(vv/100)+","+ary(1)+","+ary(2)+","+ary(3) case 2 st=ary(0)+","+Str(vv/100)+","+ary(2)+","+ary(3) case 3 st=ary(0)+","+ary(1)+","+Str(vv/100)+","+ary(3) case 4 st=ary(0)+","+ary(1)+","+ary(2)+","+Str(vv/100) end select // フィルター処理の実行 GetProcess(Listbox1.ListIndex,"Key"+Str(cnt),st) End Sub
- 以下をWindow1にペースト
Protected Sub ValueChangedImage(cnt As Integer, pic As Picture) GetProcess(Listbox1.ListIndex,"Key"+Str(cnt),pic) // フィルター処理の実行 End Sub
- 以下をWindow1にペースト
Protected Sub ValueChangedNumber(cnt As Integer, vv As Integer) // Min,Max値を取得 Dim ary(-1) As String = GetValue(Listbox1.ListIndex,"MinMax"+Str(cnt)).StringValue.Split(",") // ary(0)=Min , ary(1)=Max // 値をセット(Min~Max中での値を0~100にマッピング) Dim zz As Variant = (vv/100)*(Val(ary(1))-Val(ary(0))) if Val(ary(0))<0 then zz=zz+Val(ary(0)) // ary(0)はマイナス値なので、+する end if // フィルター処理の実行 GetProcess(Listbox1.ListIndex,"Key"+Str(cnt),zz) End Sub
- 以下をWindow1にペースト
Protected Sub ValueChangedVector(cnt As Integer, no As Integer, txt As String) // 現在のxyzw値を取得 Dim ary(-1) As String = GetValue(Listbox1.ListIndex,"Key"+Str(cnt)).StringValue.Split(",") // ary(0)=x, ary(1)=y, ary(2)=z, ary(3)=w // 変更のあった要素だけ書き換え Dim st As String if Ubound(ary)>1 then // 4要素の場合(x, y, z, w) select case no case 1 st=txt+","+ary(1)+","+ary(2)+","+ary(3) case 2 st=ary(0)+","+txt+","+ary(2)+","+ary(3) case 3 st=ary(0)+","+ary(1)+","+txt+","+ary(3) case 4 st=ary(0)+","+ary(1)+","+ary(2)+","+txt end select else // 2要素の場合(x, y) select case no case 1 st=txt+","+ary(1) case 2 st=ary(0)+","+txt end select end if // フィルター処理の実行 GetProcess(Listbox1.ListIndex,"Key"+Str(cnt),st) End Sub
- 以下をWindow1にペースト(できなければプロパティに、名前:pCntn(-1)、データ型:ContainerControl、を追加)
Protected Property pCntn(-1) as ContainerControl
- 以下をWindow1にペースト(できなければプロパティに、名前:pPicEff、データ型:Picture、を追加)
Protected Property pPicEff as Picture
- 以下をWindow1にペースト(できなければプロパティに、名前:pPicOut、データ型:Picture、を追加)
Protected Property pPicOut as Picture
- 以下をWindow1にペースト(できなければプロパティに、名前:pPicSrc、データ型:Picture、を追加)
Protected Property pPicSrc as Picture
- 新規モジュールを作成(名前は、ここでは「CIFilters」とした。)
- 以下をCIFiltersにペースト(できなければ定数に、名前:kDisableGray、デフォルト値:&cA8A8A8、データ型:Color、を追加)
Public Const kDisableGray as Color = &cA8A8A8
- 以下をCIFiltersにペースト
Private Function ConvPicToCIImg(pic As Picture) as Ptr // 元画像が空なら戻る if pic=nil then return nil end if // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // 元画像をCGImageRefに変換 Dim originImage As Ptr = pic.CopyOSHandle(Picture.HandleType.MacCGImage) // CGImageRefからCIImageを生成する Dim originImage2 As Ptr = NSClassFromString("CIImage") Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr Declare Function initWithCGImage Lib "Cocoa" Selector "initWithCGImage:" (receiver As Ptr, img As Ptr) As Ptr originImage2 = alloc(originImage2) originImage2 = initWithCGImage(originImage2, originImage) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(originImage) // CIImageを返す return originImage2 End Function
- 以下をCIFiltersにペースト
Private Function FloatToNSNumber(vv As CGFloat) as Ptr // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // NSNumberクラスを取得 Dim num As Ptr = NSClassFromString("NSNumber") #if Target32Bit Declare Function numberWithReal Lib "Cocoa" Selector "numberWithFloat:" (receiver As Ptr, num As Single) As Ptr #endif #if Target64Bit Declare Function numberWithReal Lib "Cocoa" Selector "numberWithDouble:" (receiver As Ptr, num As Double) As Ptr #endif // 実数をNSNumber型に変換 num = numberWithReal(num, vv) // NSNumber型で返す return num End Function
- 以下をCIFiltersにペースト
Private Function GenDefaultPict(w As Integer, h As Integer, clr As Color) as Picture Dim pic As new Picture(w,h) // W x H ピクセル pic.Graphics.ForeColor=clr // 指定されたカラー pic.Graphics.FillRect 0,0,pic.Width,pic.Height // ベタ塗り return pic End Function
- 以下をCIFiltersにペースト
Public Function GetCount() as Integer return Ubound(dic) End Function
- 以下をCIFiltersにペースト
Public Function GetValue(idx As Integer, key As String) as Variant return dic(idx).Value(key) End Function
- 以下をCIFiltersにペースト (長いので、別ページにしてあります。)
- 以下をCIFiltersにペースト
Public Function SetContentFilters(picSrc As Picture) as Picture // 元画像が空なら戻る if picSrc=nil then return nil end if // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr // 元画像をCGImageRefに変換 Dim originImage As Ptr = picSrc.CopyOSHandle(Picture.HandleType.MacCGImage) // CGImageRefからCIImageを生成する Dim originImage2 As Ptr = NSClassFromString("CIImage") Declare Function initWithCGImage Lib "Cocoa" Selector "initWithCGImage:" (receiver As Ptr, img As Ptr) As Ptr originImage2 = alloc(originImage2) originImage2 = initWithCGImage(originImage2, originImage) // CIImageにフィルタリング処理を施す Dim filteredImage As Ptr = SetContentFilters2(originImage2) // ciContextを生成する Dim ciContext As Ptr = NSClassFromString("CIContext") Declare Function contextWithOptions Lib "Cocoa" Selector "contextWithOptions:" (receiver As Ptr, opt As Ptr) As Ptr ciContext = contextWithOptions(ciContext, nil) // ciContextを使ってCIImageからCGImageRefを生成する Declare Function extent Lib "Cocoa" Selector "extent" (receiver As Ptr) As CGRect Declare Function createCGImage Lib "Cocoa" Selector "createCGImage:fromRect:" (receiver As Ptr, opt As Ptr, opt2 As CGRect) As Ptr Dim imageRef As Ptr = createCGImage(ciContext, filteredImage, extent(originImage2)) // CGImageRefからNSImageを生成する Dim outputImage As Ptr = NSClassFromString("NSImage") Declare Function initWithCGImageSize Lib "Cocoa" Selector "initWithCGImage:size:" (receiver As Ptr, opt As Ptr, opt2 As NSSize) As Ptr outputImage = alloc(outputImage) outputImage = initWithCGImageSize(outputImage, imageRef, NSMakeSize(picSrc.Width,picSrc.Height)) // NSImageをNSDataに変換する Declare Function TIFFRepresentation Lib "Cocoa" Selector "TIFFRepresentation" (receiver As Ptr) As Ptr Dim data As Ptr = TIFFRepresentation(outputImage) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(originImage) release(originImage2) release(outputImage) release(imageRef) // NSDataのデータ長を取得 Declare Function length Lib "Cocoa" Selector "length" (receiver As Ptr) As Integer Dim lng As Integer = length(data) // NSDataからバイト列を抽出 Declare Function bytes Lib "Cocoa" Selector "bytes" (receiver As Ptr) As Ptr Dim bstream As Ptr = bytes(data) // バイト列をXojo.Core.MemoryBlockに格納後、MemoryBlockに変換 Dim xmb As new Xojo.Core.MemoryBlock(bstream, lng) Dim temp As MemoryBlock = xmb.Data Dim mb As New MemoryBlock(xmb.Size) mb.StringValue(0, mb.Size) = temp.StringValue(0, mb.Size) // MemoryBlockからPictureを生成して返す return Picture.FromData(mb) End Function
- 以下をCIFiltersにペースト
Private Function SetContentFilters2(img As Ptr) as Ptr // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // 初回は生成したイメージを使う Dim img2 As Ptr = img for i As Integer = 0 to Ubound(dic) if dic(i).Value("Enable").BooleanValue then // 有効ならフィルター処理 // CIFilterにフィルターをセット Dim filter As Ptr = NSClassFromString("CIFilter") Declare Function filterWithName Lib "Cocoa" Selector "filterWithName:" (receiver As Ptr, fnam As CFStringRef) As Ptr filter = filterWithName(filter, dic(i).Value("Fname")) // 画像をセット Declare Sub setValue Lib "Cocoa" Selector "setValue:forKey:" (receiver As Ptr, val As Ptr, key As CFStringRef) setValue(filter, img2, "inputImage") // 個々のフィルター処理 SetContentFilters3(filter,i) // フィルター後の画像(CIImage)を取得して、次のフィルターの入力とする Declare Function outputImage Lib "Cocoa" Selector "outputImage" (receiver As Ptr) As Ptr img2 = outputImage(filter) end if next // フィルター処理済イメージを返す return img2 End Function
- 以下をCIFiltersにペースト
Private Sub SetContentFilters3(filter As Ptr, idx As Integer) // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Sub setValue Lib "Cocoa" Selector "setValue:forKey:" (receiver As Ptr, val As Ptr, key As CFStringRef) Declare Function vectorWithXY Lib "Cocoa" Selector "vectorWithX:Y:" (receiver As Ptr, num1 As CGFloat, num2 As CGFloat) As Ptr Declare Function vectorWithXYZ Lib "Cocoa" Selector "vectorWithX:Y:Z:" (receiver As Ptr, num1 As CGFloat, num2 As CGFloat, num3 As CGFloat) As Ptr Declare Function vectorWithXYZW Lib "Cocoa" Selector "vectorWithX:Y:Z:W:" (receiver As Ptr, num1 As CGFloat, num2 As CGFloat, num3 As CGFloat, num4 As CGFloat) As Ptr Declare Function colorWithRGBA Lib "Cocoa" Selector "colorWithRed:green:blue:alpha:" (receiver As Ptr, clrR As CGFloat, clrG As CGFloat, clrB As CGFloat, clrA As CGFloat) As Ptr Dim pnt As Ptr for i As Integer = 1 to dic(idx).Value("Count").IntegerValue select case dic(idx).Value("Type"+str(i)).StringValue case "NSNumber" pnt = FloatToNSNumber(dic(idx).Value("Key"+str(i))) // 実数をNSNumber型に変換 setValue(filter, pnt, dic(idx).Value("Name"+str(i))) case "CIVector" Dim ary(-1) As String = dic(idx).Value("Key"+str(i)).StringValue.Split(",") pnt = NSClassFromString("CIVector") select case Ubound(ary) case 3 pnt = vectorWithXYZW(pnt, Val(ary(0)), Val(ary(1)), Val(ary(2)), Val(ary(3))) case 2 pnt = vectorWithXYZ(pnt, Val(ary(0)), Val(ary(1)), Val(ary(2))) case 1 pnt = vectorWithXY(pnt, Val(ary(0)), Val(ary(1))) end select setValue(filter, pnt, dic(idx).Value("Name"+str(i))) case "CIColor" Dim ary(-1) As String = dic(idx).Value("Key"+str(i)).StringValue.Split(",") pnt = NSClassFromString("CIColor") pnt = colorWithRGBA(pnt, Val(ary(0)), Val(ary(1)), Val(ary(2)), Val(ary(3))) setValue(filter, pnt, dic(idx).Value("Name"+str(i))) case "CIImage" Dim img As Ptr = ConvPicToCIImg(dic(idx).Value("Key"+str(i))) setValue(filter, img, dic(idx).Value("Name"+str(i))) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(img) end select next End Sub
- 以下をCIFiltersにペースト
Public Sub SetValue(idx As Integer, key As String, vv As Variant) dic(idx).Value(key) = vv End Sub
- 以下をCIFiltersにペースト(できなければプロパティに、名前:dic(-1))、データ型:Dictionary、を追加)
Private Property dic(-1) as Dictionary
- 新規ContainerControlを作成(名前は、ここではデフォルトの「ContainerControl1」とした。)
(注:IDEの右ペイン(ライブラリ)にあるContainerControlを、左ペイン(ナビゲーター)にドラッグ&ドロップする。)- ContainerControl1に、Label(Name:Label1)、Slider(Name:Slider1)を追加
- 以下をSlider1にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
Sub ValueChanged() Handles ValueChanged ActionEvent(pCnt,me.Value) End Sub
- 以下をContainerControl1にペースト(できなければ移譲に、名前:ActionDelegate、引数:cnt As Integer, vv As Integer、を追加)
Private Sub ActionDelegate(cnt As Integer, vv As Integer)
- 以下をContainerControl1にペースト
Private Sub ActionEvent(cnt As Integer, vv As Integer) ActionHandler.Invoke(cnt,vv) // クラス生成元でActionを受け取るメソッドを呼び出す End Sub
- 以下をContainerControl1にペースト
Public Sub Constructor(cnt As Integer, action As ActionDelegate) pCnt = cnt // パラメータ番号を保持 ActionHandler = action // クラス生成元でActionを受け取るメソッドを登録 End Sub
- 以下をContainerControl1にペースト
Public Sub SetControl(idx As Integer, cnt As Integer) Label1.Text = GetValue(idx,"Name"+Str(cnt)) // 値を初期化 Label1.TextColor = kDisableGray // 無効化 Dim ary(-1) As String = GetValue(idx,"MinMax"+Str(cnt)).StringValue.Split(",") // ary(0) = Min, ary(1) = Max Dim vv As Variant = GetValue(idx,"Key"+Str(cnt)) if Val(ary(0))<0 then vv=vv-Val(ary(0)) // Min値がマイナスの時は、最小が0になるようにズラす end if Slider1.Value = vv / (Val(ary(1))-Val(ary(0))) * 100 // 値をセット(Min~Max中での値を0~100にマッピング) Slider1.Enabled = false // 無効化 End Sub
- 以下をContainerControl1にペースト
Public Sub SetControlEnable(chk As Boolean) // Labelの有効/無効をセット(LabelはEnabledを変更しても色が変わらないので、文字色を変更する) if chk then Label1.TextColor=RGB(0,0,0) else Label1.TextColor=kDisableGray end if // Sliderの有効/無効をセット Slider1.Enabled=chk End Sub
- 以下をContainerControl1にペースト(できなければプロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
Private Property ActionHandler as ActionDelegate
- 以下をContainerControl1にペースト(できなければプロパティに、名前:pCnt、データ型:Integer、を追加)
Private Property pCnt as Integer
- 新規ContainerControlを作成(名前は、ここではデフォルトの「ContainerControl2」とした。)
- ContainerControl2に、Label(Name:Label1)、TextField4個(Name:TextField1、Name:TextField2、Name:TextField3、Name:TextField4)を追加
- 以下をTextField1にペースト(できなければ、Sub - Endの間をTextChangeイベントに記述)
Sub TextChange() Handles TextChange ActionEvent(pCnt,1,me.Text) End Sub
- 以下をTextField2にペースト(できなければ、Sub - Endの間をTextChangeイベントに記述)
Sub TextChange() Handles TextChange ActionEvent(pCnt,2,me.Text) End Sub
- 以下をTextField3にペースト(できなければ、Sub - Endの間をTextChangeイベントに記述)
Sub TextChange() Handles TextChange ActionEvent(pCnt,3,me.Text) End Sub
- 以下をTextField4にペースト(できなければ、Sub - Endの間をTextChangeイベントに記述)
Sub TextChange() Handles TextChange ActionEvent(pCnt,4,me.Text) End Sub
- 以下をContainerControl2にペースト(できなければ移譲に、名前:ActionDelegate、引数:cnt As Integer, no As Integer, txt As String、を追加)
Private Sub ActionDelegate(cnt As Integer, no As Integer, txt As String)
- 以下をContainerControl2にペースト
Private Sub ActionEvent(cnt As Integer, no As Integer, txt As String) ActionHandler.Invoke(cnt,no,txt) // クラス生成元でActionを受け取るメソッドを呼び出す End Sub
- 以下をContainerControl2にペースト
Public Sub Constructor(cnt As Integer, action As ActionDelegate) pCnt = cnt // パラメータ番号を保持 ActionHandler = action // クラス生成元でActionを受け取るメソッドを登録 End Sub
- 以下をContainerControl2にペースト
Public Sub SetControl(idx As Integer, cnt As Integer) Label1.Text = GetValue(idx,"Name"+Str(cnt)) // 値を初期化 Label1.TextColor=kDisableGray // 無効化 Dim ary(-1) As String = GetValue(idx,"Key"+Str(cnt)).StringValue.Split(",") // ary(0) = x, ary(1) = y, ary(2) = z, ary(3) = w TextField1.Text=ary(0) TextField1.Enabled=false // 無効化 TextField2.Text=ary(1) TextField2.Enabled=false // 無効化 if Ubound(ary)>1 then // 4要素の場合 TextField3.Text=ary(2) TextField3.Enabled=false // 無効化 TextField4.Text=ary(3) TextField4.Enabled=false // 無効化 else // 2要素の場合 TextField3.Visible=false // 非表示 TextField4.Visible=false // 非表示 end if End Sub
- 以下をContainerControl2にペースト
Public Sub SetControlEnable(idx As Integer, cnt As Integer, chk As Boolean) // Labelの有効/無効をセット(LabelはEnabledを変更しても色が変わらないので、文字色を変更する) if chk then Label1.TextColor=RGB(0,0,0) else Label1.TextColor=kDisableGray end if // TextFieldの有効/無効をセット TextField1.Enabled=chk TextField2.Enabled=chk if CountFields(GetValue(idx,"Key"+Str(cnt)).StringValue,",")>1 then // 4要素の場合(必要なのは個数なのでCountFieldsで取得) TextField3.Enabled=chk TextField4.Enabled=chk else // 2要素の場合 TextField3.Visible=false TextField4.Visible=false end if End Sub
- 以下をContainerControl2にペースト(できなければプロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
Private Property ActionHandler as ActionDelegate
- 以下をContainerControl2にペースト(できなければプロパティに、名前:pCnt、データ型:Integer、を追加)
Private Property pCnt as Integer
- 新規ContainerControlを作成(名前は、ここではデフォルトの「ContainerControl3」とした。)
- ContainerControl3に、Label(Name:Label1)、Slider4個(Name:Slider1、Name:Slider2、Name:Slider3、Name:Slider4)を追加
- 以下をSlider1にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
Sub ValueChanged() Handles ValueChanged ActionEvent(pCnt,1,me.Value) End Sub
- 以下をSlider2にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
Sub ValueChanged() Handles ValueChanged ActionEvent(pCnt,2,me.Value) End Sub
- 以下をSlider3にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
Sub ValueChanged() Handles ValueChanged ActionEvent(pCnt,3,me.Value) End Sub
- 以下をSlider4にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
Sub ValueChanged() Handles ValueChanged ActionEvent(pCnt,4,me.Value) End Sub
- 以下をContainerControl3にペースト(できなければ移譲に、名前:ActionDelegate、引数:cnt As Integer, vv As Integer、を追加)
Private Sub ActionDelegate(cnt As Integer, no As Integer, vv As Integer)
- 以下をContainerControl3にペースト
Private Sub ActionEvent(cnt As Integer, no As Integer, vv As Integer) ActionHandler.Invoke(cnt,no,vv) // クラス生成元でActionを受け取るメソッドを呼び出す End Sub
- 以下をContainerControl3にペースト
Public Sub Constructor(cnt As Integer, action As ActionDelegate) pCnt = cnt // パラメータ番号を保持 ActionHandler = action // クラス生成元でActionを受け取るメソッドを登録 End Sub
- 以下をContainerControl3にペースト
Public Sub SetControl(idx As Integer, cnt As Integer) Label1.Text = GetValue(idx,"Name"+Str(cnt)) // 値を初期化 Label1.TextColor=kDisableGray // 無効化 Dim ary(-1) As String = GetValue(idx,"Key"+Str(cnt)).StringValue.Split(",") // ary(0) = r, ary(1) = g, ary(2) = b, ary(3) = a Slider1.Value = Val(ary(0)) * 100 // 値を初期化(常に0 ~ 1の範囲なので単純に100倍する) Slider1.Enabled = false // 無効化 Slider2.Value = Val(ary(1)) * 100 // 値を初期化 Slider2.Enabled = false // 無効化 Slider3.Value = Val(ary(2)) * 100 // 値を初期化 Slider3.Enabled = false // 無効化 Slider4.Value = Val(ary(3)) * 100 // 値を初期化 Slider4.Enabled = false // 無効化 End Sub
- 以下をContainerControl3にペースト
Public Sub SetControlEnable(chk As Boolean) // Labelの有効/無効をセット(LabelはEnabledを変更しても色が変わらないので、文字色を変更する) if chk then Label1.TextColor=RGB(0,0,0) else Label1.TextColor=kDisableGray end if // Sliderの有効/無効をセット Slider1.Enabled=chk Slider2.Enabled=chk Slider3.Enabled=chk Slider4.Enabled=chk End Sub
- 以下をContainerControl3にペースト(できなければプロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
Private Property ActionHandler as ActionDelegate
- 以下をContainerControl3にペースト(できなければプロパティに、名前:pCnt、データ型:Integer、を追加)
Private Property pCnt as Integer
- 新規ContainerControlを作成(名前は、ここではデフォルトの「ContainerControl4」とした。)
- ContainerControl4に、Label(Name:Label1)、Canvas(Name:Canvas1)を追加
- 以下をCanvas1にペースト(できなければ、Sub - Endの間をDropObjectイベントに記述)
Sub DropObject(obj As DragItem, action As Integer) Handles DropObject if obj.PictureAvailable then DrawCanvas(obj.Picture) ActionEvent(pCnt,obj.Picture) elseif obj.FolderItemAvailable then if not obj.FolderItem.Directory then Dim pic As Picture = Picture.Open(obj.FolderItem) DrawCanvas(pic) ActionEvent(pCnt,pic) end if end if End Sub
- 以下をCanvas1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
Sub Open() Handles Open Me.AcceptPictureDrop Me.AcceptFileDrop("image/jpeg") End Sub
- 以下をCanvas1にペースト(できなければ、Sub - Endの間をPaintイベントに記述)
Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint // 背景描画(白) g.ForeColor=RGB(255,255,255) g.FillRect 0,0,me.Width,me.Height // サムネイル画像が作成済なら if pPicTN<>nil then g.DrawPicture(pPicTN, (me.Width-pPicTN.Width)/2,(me.Height-pPicTN.Height)/2) // サムネイル画像の描画(センタリング) end if End Sub
- 以下をContainerControl4にペースト(できなければ移譲に、名前:ActionDelegate、引数:cnt As Integer, pic As Picture、を追加)
Private Sub ActionDelegate(cnt As Integer, pic As Picture)
- 以下をContainerControl4にペースト
Private Sub ActionEvent(cnt As Integer, pic As Picture) ActionHandler.Invoke(cnt,pic) // クラス生成元でActionを受け取るメソッドを呼び出す End Sub
- 以下をContainerControl4にペースト
Public Sub Constructor(cnt As Integer, action As ActionDelegate) pCnt = cnt // パラメータ番号を保持 ActionHandler = action // クラス生成元でActionを受け取るメソッドを登録 End Sub
- 以下をContainerControl4にペースト
Private Sub DrawCanvas(pic As Picture) // サムネイル用Pictureが未作成なら生成 if pPicTN=nil then pPicTN = new Picture(Canvas1.Width-8,Canvas1.Height-8) end if // 入力画像をサムネイルサイズに合わせて描画(縦横比は維持していない) pPicTN.Graphics.DrawPicture(pic,0,0,pPicTN.Width,pPicTN.Height,0,0,pic.Width,pic.Height) // 描画指示 Canvas1.Refresh End Sub
- 以下をContainerControl4にペースト
Public Sub SetControl(idx As Integer, cnt As Integer) Label1.Text = GetValue(idx,"Name"+Str(cnt)) // 値を初期化 Label1.TextColor=kDisableGray // 無効化 DrawCanvas(GetValue(idx,"Key"+Str(cnt))) // 値を初期化 Canvas1.Enabled = false // 無効化 End Sub
- 以下をContainerControl4にペースト
Public Sub SetControlEnable(chk As Boolean) // Labelの有効/無効をセット(LabelはEnabledを変更しても色が変わらないので、文字色を変更する) if chk then Label1.TextColor=RGB(0,0,0) else Label1.TextColor=kDisableGray end if // Canvasの有効/無効をセット(CanvasはEnabledを変更しても色が変わらないので、このままでは意味がない) Canvas1.Enabled=chk End Sub
- 以下をContainerControl3にペースト(できなければプロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
Private Property ActionHandler as ActionDelegate
- 以下をContainerControl3にペースト(できなければプロパティに、名前:pCnt、データ型:Integer、を追加)
Private Property pCnt as Integer
- 以下をContainerControl3にペースト(できなければプロパティに、名前:pPic、データ型:Picture、を追加)
Private Property pPic as Picture
- 以下をContainerControl3にペースト(できなければプロパティに、名前:pPicTN、データ型:Picture、を追加)
Private Property pPicTN as Picture
- 他に、NSMakeSize(メソッド)、CGRect/NSSize(構造体)が必要ですが、それらはmacoslibからコピーさせて頂きました。(上記CIFiltersまたは別途モジュールを用意してコピーする。)
おわりに
640x480ピクセル(650KB)のイメージではそこそこの反応で動くのですが、4,000x3,000ピクセル(4.2MB)では全くダメでした。
NSImage,NSView方式を試してみてもいいのですが、画面上ではリサイズして表示するとかした方が、より現実的かもしれません。
あと、フィルターの中には遷移系(CICategoryTransition)もありますが、今回のケースでは、パラメータのセットは可能ですが、遷移自体(アニメーション)には対応していません。
実現のためにはパラメータセットだけではなく、外部に(タイマー等を含む)仕掛けが必要なようで、これは別の話題と考えた方が良さそうです。
(次項参照)
参考サイト(4):Core Image の遷移エフェクトを使う - Qiita
参考サイト(5):objective c - Does anybody have samples of using CoreImage CICategoryTransition filters in iOS? - Stack Overflow
追記・遷移系について
遷移系(CICategoryTransition)は、当初勘違いしていたこともあって、本稿には含めませんでしたが、改めてここに示しておきます。
遷移系も、効果自体はスタティックなもので、その他のフィルター同様、本稿のやり方で扱うことができます。
遷移の過程は、パラメーターのinputTimeを変化させることで取得できます。アニメーションはこのinputTimeを、タイマー等で連続的に変化させることで、実現しています。
ということで、上記Xojoでの実装の33項を、遷移系フィルターも追加したものに差し替え、アニメーションに関しては、別の話題としてまとめました。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):CoreImageで画像の加工をする その2「フィルタの重ねがけ」 - Teratoma
参考サイト(2):CIFilterチートシート(全201種) - Qiita
参考サイト(3):CoreImageのフィルターを試してみる(CICategoryColorAdjustment、その3) - しめ鯖日記(記事内リンクからその2、その1と辿れます。)
参考サイト(4):Core Image の遷移エフェクトを使う - Qiita
参考サイト(5):objective c - Does anybody have samples of using CoreImage CICategoryTransition filters in iOS? - Stack Overflow
更新履歴
2019.11.27 Xojoでの実装の6項に、注を追加
2019.10.03 Xojoでの実装の33項を改訂
2019.10.03 追記・遷移系について、を追加
2019.04.15 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]