ホームページ開発ツール>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を割り当てた上で分岐処理するか、といった対応が必要になる。
 以上を踏まえ、(残りの)仕様は以下の通りとしました。

 Xojoでの実装
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. Xojoで新規プロジェクトを作成
  2. Window1に、Canvas(Name:Canvas1)、Listbox(Name:Listbox1)を追加
  3. Canvas1に、言語リファレンス>>ImageWell>Examples>drag and dropの項のコードのうち、DropObject, Openを記述。
  4. 以下をCanvas1にペースト(できなければ、Sub - Endの間をPaintイベントに記述)
    Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint
      DrawPicture(g)
    End Sub
    
  5. 以下を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
    
  6. 以下をListbox1にペースト(できなければ、Sub - Endの間をCellBackgroundPaintイベントに記述)
    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
    
    注)初出時、3行目をg.FillRect 0,0,me.Width,me.RowHeight // me.Widthは列幅ではなくリストボックス幅だが、実害はないようだとしていましたが、Callのサイズはgから取るのが作法のようで、改訂しました。
  7. 以下を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
    
  8. 以下を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
    
  9. 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
    Sub Open() Handles Open
      InitCIFilter()  // フィルターデータの初期化
      InitList()  // フィルターリストの初期化
      InitCntn()  // コンテナ(パラメータセット用コントロールを収納)リストの初期化(全削除)
    End Sub
    
  10. 以下を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
    
  11. 以下を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
    
  12. 以下を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
    
  13. 以下を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
    
  14. 以下を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
    
  15. 以下を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
    
  16. 以下を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
    
  17. 以下を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
    
  18. 以下を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
    
  19. 以下をWindow1にペースト
    Protected Sub ValueChangedImage(cnt As Integer, pic As Picture)
      GetProcess(Listbox1.ListIndex,"Key"+Str(cnt),pic)  // フィルター処理の実行
    End Sub
    
  20. 以下を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
    
  21. 以下を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
    
  22. 以下をWindow1にペースト(できなければプロパティに、名前:pCntn(-1)、データ型:ContainerControl、を追加)
    Protected Property pCntn(-1) as ContainerControl
    
  23. 以下をWindow1にペースト(できなければプロパティに、名前:pPicEff、データ型:Picture、を追加)
    Protected Property pPicEff as Picture
    
  24. 以下をWindow1にペースト(できなければプロパティに、名前:pPicOut、データ型:Picture、を追加)
    Protected Property pPicOut as Picture
    
  25. 以下をWindow1にペースト(できなければプロパティに、名前:pPicSrc、データ型:Picture、を追加)
    Protected Property pPicSrc as Picture
    
  26. 新規モジュールを作成(名前は、ここでは「CIFilters」とした。)
  27. 以下をCIFiltersにペースト(できなければ定数に、名前:kDisableGray、デフォルト値:&cA8A8A8、データ型:Color、を追加)
    Public Const kDisableGray as Color = &cA8A8A8
    
  28. 以下を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
    
  29. 以下を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
    
  30. 以下を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
    
  31. 以下をCIFiltersにペースト
    Public Function GetCount() as Integer
      return Ubound(dic)
    End Function
    
  32. 以下をCIFiltersにペースト
    Public Function GetValue(idx As Integer, key As String) as Variant
      return dic(idx).Value(key)
    End Function
    
  33. 以下をCIFiltersにペースト (長いので、別ページにしてあります。)
  34. 以下を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
    
  35. 以下を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
    
  36. 以下を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
    
  37. 以下をCIFiltersにペースト
    Public Sub SetValue(idx As Integer, key As String, vv As Variant)
      dic(idx).Value(key) = vv
    End Sub
    
  38. 以下をCIFiltersにペースト(できなければプロパティに、名前:dic(-1))、データ型:Dictionary、を追加)
    Private Property dic(-1) as Dictionary
    
  39. 新規ContainerControlを作成(名前は、ここではデフォルトの「ContainerControl1」とした。)
    (注:IDEの右ペイン(ライブラリ)にあるContainerControlを、左ペイン(ナビゲーター)にドラッグ&ドロップする。)
  40. ContainerControl1に、Label(Name:Label1)、Slider(Name:Slider1)を追加
  41. 以下をSlider1にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
    Sub ValueChanged() Handles ValueChanged
      ActionEvent(pCnt,me.Value)
    End Sub
    
  42. 以下をContainerControl1にペースト(できなければ移譲に、名前:ActionDelegate、引数:cnt As Integer, vv As Integer、を追加)
    Private Sub ActionDelegate(cnt As Integer, vv As Integer)
    
  43. 以下をContainerControl1にペースト
    Private Sub ActionEvent(cnt As Integer, vv As Integer)
      ActionHandler.Invoke(cnt,vv)  // クラス生成元でActionを受け取るメソッドを呼び出す
    End Sub
    
  44. 以下をContainerControl1にペースト
    Public Sub Constructor(cnt As Integer, action As ActionDelegate)
      pCnt = cnt  // パラメータ番号を保持
      
      ActionHandler = action  // クラス生成元でActionを受け取るメソッドを登録
    End Sub
    
  45. 以下を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
    
  46. 以下を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
    
  47. 以下をContainerControl1にペースト(できなければプロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
    Private Property ActionHandler as ActionDelegate
    
  48. 以下をContainerControl1にペースト(できなければプロパティに、名前:pCnt、データ型:Integer、を追加)
    Private Property pCnt as Integer
    
  49. 新規ContainerControlを作成(名前は、ここではデフォルトの「ContainerControl2」とした。)
  50. ContainerControl2に、Label(Name:Label1)、TextField4個(Name:TextField1、Name:TextField2、Name:TextField3、Name:TextField4)を追加
  51. 以下をTextField1にペースト(できなければ、Sub - Endの間をTextChangeイベントに記述)
    Sub TextChange() Handles TextChange
      ActionEvent(pCnt,1,me.Text)
    End Sub
    
  52. 以下をTextField2にペースト(できなければ、Sub - Endの間をTextChangeイベントに記述)
    Sub TextChange() Handles TextChange
      ActionEvent(pCnt,2,me.Text)
    End Sub
    
  53. 以下をTextField3にペースト(できなければ、Sub - Endの間をTextChangeイベントに記述)
    Sub TextChange() Handles TextChange
      ActionEvent(pCnt,3,me.Text)
    End Sub
    
  54. 以下をTextField4にペースト(できなければ、Sub - Endの間をTextChangeイベントに記述)
    Sub TextChange() Handles TextChange
      ActionEvent(pCnt,4,me.Text)
    End Sub
    
  55. 以下をContainerControl2にペースト(できなければ移譲に、名前:ActionDelegate、引数:cnt As Integer, no As Integer, txt As String、を追加)
    Private Sub ActionDelegate(cnt As Integer, no As Integer, txt As String)
    
  56. 以下をContainerControl2にペースト
    Private Sub ActionEvent(cnt As Integer, no As Integer, txt As String)
      ActionHandler.Invoke(cnt,no,txt)  // クラス生成元でActionを受け取るメソッドを呼び出す
    End Sub
    
  57. 以下をContainerControl2にペースト
    Public Sub Constructor(cnt As Integer, action As ActionDelegate)
      pCnt = cnt  // パラメータ番号を保持
      
      ActionHandler = action  // クラス生成元でActionを受け取るメソッドを登録
    End Sub
    
  58. 以下を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
    
  59. 以下を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
    
  60. 以下をContainerControl2にペースト(できなければプロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
    Private Property ActionHandler as ActionDelegate
    
  61. 以下をContainerControl2にペースト(できなければプロパティに、名前:pCnt、データ型:Integer、を追加)
    Private Property pCnt as Integer
    
  62. 新規ContainerControlを作成(名前は、ここではデフォルトの「ContainerControl3」とした。)
  63. ContainerControl3に、Label(Name:Label1)、Slider4個(Name:Slider1、Name:Slider2、Name:Slider3、Name:Slider4)を追加
  64. 以下をSlider1にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
    Sub ValueChanged() Handles ValueChanged
      ActionEvent(pCnt,1,me.Value)
    End Sub
    
  65. 以下をSlider2にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
    Sub ValueChanged() Handles ValueChanged
      ActionEvent(pCnt,2,me.Value)
    End Sub
    
  66. 以下をSlider3にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
    Sub ValueChanged() Handles ValueChanged
      ActionEvent(pCnt,3,me.Value)
    End Sub
    
  67. 以下をSlider4にペースト(できなければ、Sub - Endの間をValueChangedイベントに記述)
    Sub ValueChanged() Handles ValueChanged
      ActionEvent(pCnt,4,me.Value)
    End Sub
    
  68. 以下をContainerControl3にペースト(できなければ移譲に、名前:ActionDelegate、引数:cnt As Integer, vv As Integer、を追加)
    Private Sub ActionDelegate(cnt As Integer, no As Integer, vv As Integer)
    
  69. 以下をContainerControl3にペースト
    Private Sub ActionEvent(cnt As Integer, no As Integer, vv As Integer)
      ActionHandler.Invoke(cnt,no,vv)  // クラス生成元でActionを受け取るメソッドを呼び出す
    End Sub
    
  70. 以下をContainerControl3にペースト
    Public Sub Constructor(cnt As Integer, action As ActionDelegate)
      pCnt = cnt  // パラメータ番号を保持
      
      ActionHandler = action  // クラス生成元でActionを受け取るメソッドを登録
    End Sub
    
  71. 以下を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
    
  72. 以下を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
    
  73. 以下をContainerControl3にペースト(できなければプロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
    Private Property ActionHandler as ActionDelegate
    
  74. 以下をContainerControl3にペースト(できなければプロパティに、名前:pCnt、データ型:Integer、を追加)
    Private Property pCnt as Integer
    
  75. 新規ContainerControlを作成(名前は、ここではデフォルトの「ContainerControl4」とした。)
  76. ContainerControl4に、Label(Name:Label1)、Canvas(Name:Canvas1)を追加
  77. 以下を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
    
  78. 以下をCanvas1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
    Sub Open() Handles Open
      Me.AcceptPictureDrop
      Me.AcceptFileDrop("image/jpeg")
    End Sub
    
  79. 以下を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
    
  80. 以下をContainerControl4にペースト(できなければ移譲に、名前:ActionDelegate、引数:cnt As Integer, pic As Picture、を追加)
    Private Sub ActionDelegate(cnt As Integer, pic As Picture)
    
  81. 以下をContainerControl4にペースト
    Private Sub ActionEvent(cnt As Integer, pic As Picture)
      ActionHandler.Invoke(cnt,pic)  // クラス生成元でActionを受け取るメソッドを呼び出す
    End Sub
    
  82. 以下をContainerControl4にペースト
    Public Sub Constructor(cnt As Integer, action As ActionDelegate)
      pCnt = cnt  // パラメータ番号を保持
      
      ActionHandler = action  // クラス生成元でActionを受け取るメソッドを登録
      
    End Sub
    
  83. 以下を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
    
  84. 以下を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
    
  85. 以下を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
    
  86. 以下をContainerControl3にペースト(できなければプロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
    Private Property ActionHandler as ActionDelegate
    
  87. 以下をContainerControl3にペースト(できなければプロパティに、名前:pCnt、データ型:Integer、を追加)
    Private Property pCnt as Integer
    
  88. 以下をContainerControl3にペースト(できなければプロパティに、名前:pPic、データ型:Picture、を追加)
    Private Property pPic as Picture
    
  89. 以下をContainerControl3にペースト(できなければプロパティに、名前:pPicTN、データ型:Picture、を追加)
    Private Property pPicTN as Picture
    
  90. 他に、NSMakeSize(メソッド)、CGRect/NSSize(構造体)が必要ですが、それらはmacoslibからコピーさせて頂きました。(上記CIFiltersまたは別途モジュールを用意してコピーする。)
 実行してみたところ、CIFilterによるイメージ処理が機能することを確認しました。
S Shot1


 おわりに

 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]