ホームページ開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでイメージ処理してみる・遷移系を試す

 Xojo / Real Studio Trial and Error

CocoaのDeclareでイメージ処理してみる・遷移系を試す

目次
 はじめに

 以下は、Xojo Cocoaビルドについての話題です。

 CIFilterのうち、前回積み残しとなっていた遷移系(CICategoryTransition)を試してみました。
 なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + OS X 10.13.6 High Sierra)


 方針

 遷移エフェクトについては、前回もご紹介したように、参考サイト(1)(前回記事では参考サイト(4))に記されています。
 処理自体は、サイトに書かれているそのままを移植することで動作はするのですが、今一つピンとこないと思ったら、答えは参考サイト(2)にありました。(自分も、全く同じ思い違いをしていました。)

 参考サイト(1):Core Image の遷移エフェクトを使う - Qiita
 参考サイト(2):アプリ開発ブログ(仮): CoreImageのTransitionエフェクト

 スッキリしたところで(笑)今回のプロジェクトですが、参考サイト(1)の完全移植ではなく、画面描画とタイマーはXojoのコントロールを用いることとしました。
 他に、CIimageをNSImageに変換する箇所も、前回プロジェクトのやり方とします。
(参考サイト(1)はiOS前提で、Macとは異なるのかもしれないが、)当方で完全移植版を試した限りでは、一部フィルター(CIPageCurlTransitionCIPageCurlWithShadowTransition)でoutputImageのサイズが、inputImageとinputTargetImage全体を包含するものとなるのだが、参考サイト(1)の方法(CIImage → NSCIImageRep → NSImage)では、(NSView描画時には縮小されて)そのまま表示される(左図)。一方、前回プロジェクトの方法(CIImage → CGImageRef → NSImage)ではCGImageRef変換時にCanvasサイズを指定することで、期待する結果になる(右図)。
S Shot1 S Shot2
 あと、これはCIFilterとは直接関係ありませんが、遷移エフェクトの推移(以下、アニメーション)を動画として保存する機能を追加します。
 アニメーションの録画には、AVCaptureSessionを使います。
 これも、スクリーンショットと同様、何もしないと画面全体が対象となりますが、クロップすることで所望の領域を対象にできます。

 参考サイト(3):【MacOS】スクリーンレコーディング 【Swift】 - Qiita

 以上を踏まえ、まずは前回との相違点を挙げると、
 残りの仕様は以下の通りとしました。

 Xojoでの実装
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
 注)本プロジェクトは前回プロジェクトをベースにしていますが、変更点が多いため、新規扱いとしています。
  1. Xojoで新規プロジェクトを作成
  2. Window1に、Canvas(Name:Canvas1)、CheckBox(Name:CheckBox1)、Listbox(Name:Listbox1)、PushButton2個(Name:PushButton1、Name:PushButton2)、Separator(Name:Separator1)、Timer(Name:Timer1)を追加
  3. 以下をCanvas1にペースト(できなければ、Sub - Endの間をPaintイベントに記述)
    Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint
      // フィルター処理実行中なら
      if isRunning then
        Dim pic As Picture = MakePicture()  // 加工画像の生成
        g.DrawPicture pic,0,0  // 加工画像の描画
      else  // 停止中なら
        g.ForeColor=RGB(255,255,255)
        g.FillRect 0,0,me.Width,me.Height  // 白で塗りつぶす
      end if
    End Sub
    
  4. 以下を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は列幅ではなくリストボックス幅だが、実害はないようだとしていましたが、Cellのサイズはgから取るのが作法のようで、改訂しました。
  5. 以下をListbox1にペースト(できなければ、Sub - Endの間をChangeイベントに記述)
    Sub Change() Handles Change
      SetFilter(me.ListIndex)  // 選択されたフィルターのパラメーター用コントロールをセット(選択解除時のクリアもこの中で行なっている)
      if me.ListIndex>=0 then  // 行選択されていれば
        PushButton1.Enabled=true
        CheckBox1.Enabled=true
      else
        PushButton1.Enabled=false
        PushButton2.Enabled=false
        CheckBox1.Enabled=false
      end if
    End Sub
    
  6. 以下を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
    
  7. 前回プロジェクトのWindow1から、メソッド(ValueChangedColor、ValueChangedNumber、ValueChangedVector)をコピー
  8. 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
    Sub Open() Handles Open
      // フィルターデータの初期化
      InitCIFilter()
      
      // フィルターリストの生成
      ListBox1.DeleteAllRows
      for i As Integer = 0 to GetCount()
        ListBox1.AddRow GetValue(i,"Fname").StringValue  // フィルター名
      next
      
      // コンテナ(パラメーターセット用コントロールを収納)リストのリセット(全削除)
      ResetCntn()
      
      // AVCaptureのインスタンス生成
      Dim x As CGFloat = Window1.Left+Canvas1.Left
      Dim y As CGFloat = Screen(0).Height-Window1.Top-Canvas1.Top-Canvas1.Height
      AVCaptureInst = new AVCapture(CGRectMake(x, y, Canvas1.Width, Canvas1.Height))
      
      // ボタンとチェックボックスの無効化
      PushButton1.Enabled=false
      PushButton2.Enabled=false
      CheckBox1.Enabled=false
    End Sub
    
  9. 以下をWindow1にペースト
    Protected Sub GetProcess(idx As Integer, key As String, vv As Variant)
      // フィルターにパラメーターをセット(dic()を変更)
      SetValue(idx,key,vv)
      
      // フィルターにパラメーターをセット(pTransitを変更)
      if isRunning then
        SetContentFiltersV(idx)
      end if
    End Sub
    
  10. 以下をWindow1にペースト
    Protected Function MakePicture() as Picture
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // 開始時刻からの通算時間を算出
      Dim totalTime As Double = (Ticks - baseTime)  // TicksはXojoのMethod
      Dim time As Double = 0.4 * totalTime / 60
      
      // 経過時間に対応したoutputImage(CIImage)を取得
      Dim image As Ptr = imageForTransitionAtTime(ListBox1.ListIndex,time)
      
      // 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を生成する(RectはoutputImageのextentではなく、Canvasサイズを用いる)
      Declare Function extent Lib "Cocoa" Selector "extent" (receiver As Ptr) As CGRect
      Declare Function createCGImage Lib "Cocoa" Selector "createCGImage:fromRect:" (receiver As Ptr, img As Ptr, rect As CGRect) As Ptr
      Dim imageRef As Ptr = createCGImage(ciContext, image, CGRectMake(0,0,Canvas1.Width,Canvas1.Height))
      
      // CGImageRefからNSImageを生成する
      Dim outputImage As Ptr = NSClassFromString("NSImage")
      Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
      Declare Function initWithCGImageSize Lib "Cocoa" Selector "initWithCGImage:size:" (receiver As Ptr, opt As Ptr, opt2 As NSSize) As Ptr
      Declare Function CGImageGetWidth lib "Carbon" (image as Ptr) as Integer
      Declare Function CGImageGetHeight lib "Carbon" (image as Ptr) as Integer
      outputImage = alloc(outputImage)
      outputImage = initWithCGImageSize(outputImage, imageRef, NSMakeSize(CGImageGetWidth(imageRef),CGImageGetHeight(imageRef)))
      
      // 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(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
    
  11. 以下をWindow1にペースト
    Protected Sub ResetCntn()
      // 配列要素を削除
      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
    
  12. 以下をWindow1にペースト
    Protected Sub SetFilter(idx As Integer)
      // まず、リセット(全削除)
      ResetCntn()
      
      // 行選択されていなければ戻る
      if idx<0 then
        return
      end if
      
      // コントロールコンテナを生成して値をセット
      Dim top As Integer = 0
      Dim cnt As Integer = 1
      for i As Integer = 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
    
  13. 以下をWindow1にペースト
    Protected Sub ValueChangedImage(key As Integer, pic As Picture)
      SetExtentValue(pCntn(),Listbox1.ListIndex,pic)  // inputExtentに画像サイズを反映させる
      GetProcess(Listbox1.ListIndex,"Key"+Str(key),pic)  // フィルター処理の実行
    End Sub
    
  14. 以下をWindow1にペースト(できなければプロパティに、名前:AVCaptureInst、データ型:AVCapture、を追加)
    Private Property AVCaptureInst as AVCapture
    
  15. 以下をWindow1にペースト(できなければプロパティに、名前:baseTime、データ型:Integer、を追加)
    Protected Property baseTime as Integer
    
  16. 以下をWindow1にペースト(できなければプロパティに、名前:isRunning、データ型:Integer、を追加)
    Protected Property isRunning as Boolean
    
  17. 以下をWindow1にペースト(できなければプロパティに、名前:pCntn(-1)、データ型:ContainerControl、を追加)
    Protected Property pCntn(-1) as ContainerControl
    
  18. 新規モジュールを作成(名前は、ここでは「CIFilters」とした。)
  19. 前回プロジェクトのCIFiltersから、メソッド(ConvPicToCIImg、FloatToNSNumber、GenDefaultPict、GetCount、GetValue、SetValue)、プロパティ(dic(-1))をコピー
  20. 以下をCIFiltersにペースト
    Public Function GetCIImage(idx As Integer, keyStr As String) as Ptr
      // キー名が一致する要素を取得
      Dim cnt As Integer
      for i As Integer = 1 to dic(idx).Value("Count").IntegerValue
        if dic(idx).Value("Name"+str(i)).StringValue = keyStr then
          cnt=i
          exit
        end if
      next
      
      // 画像を返す
      return ConvPicToCIImg(dic(idx).Value("Key"+str(cnt)))
    End Function
    
  21. 以下をCIFiltersにペースト
    Public Function imageForTransitionAtTime(idx As Integer, time As Double) as Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      Declare Sub setValue Lib "Cocoa" selector "setValue:forKey:" (class_id As Ptr, value As Ptr, keu As CFStringRef)
      Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
      Declare Function fmod Lib "System" (p1 As Double, p2 As Double) As Double
      
      // 遷移前後の画像をtimeによって切り替える
      Dim tmp1 As Ptr = GetCIImage(idx,"inputImage")
      Dim tmp2 As Ptr = GetCIImage(idx,"inputTargetImage")
      if (fmod(time, 2.0) < 1.0) then
        setValue(pTransit, tmp1, "inputImage")
        setValue(pTransit, tmp2, "inputTargetImage")
      else
        setValue(pTransit, tmp2, "inputImage")
        setValue(pTransit, tmp1, "inputTargetImage")
      end if
      release(tmp1)  // clean up
      release(tmp2)  // clean up
      
      // 実数をNSNumber型に変換
      Dim num As Ptr = NSClassFromString("NSNumber")
      Declare Function numberWithReal Lib "Cocoa" Selector "numberWithDouble:" (receiver As Ptr, num As Double) As Ptr
      num = numberWithReal(num, fmod(time, 1.0))
      
      // 遷移アニメーションの時間を指定
      setValue(pTransit, num, "inputTime")
      
      // フィルター処理実行
      Declare Function valueForKey Lib "Cocoa" selector "valueForKey:" (class_id As Ptr, dict As CFStringRef) As Ptr
      Dim transitionImage As Ptr = valueForKey(pTransit, "outputImage")
      
      // outputImageを返す
      return transitionImage
    End Function
    
  22. 以下をCIFiltersにペースト
    (長いので、別ページにしてあります。)
  23. 以下をCIFiltersにペースト
    Public Sub SetContentFilters(idx As Integer)
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // 既に生成済なら、まず解放
      if pTransit<>nil then
        Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
        release(pTransit)
      end if
      
      // CIFilterにフィルターをセット
      pTransit = NSClassFromString("CIFilter")
      Declare Function filterWithName Lib "Cocoa" Selector "filterWithName:" (receiver As Ptr, fnam As CFStringRef) As Ptr
      pTransit = filterWithName(pTransit, dic(idx).Value("Fname"))
      
      // allocしていないから?、フォーカスが切れると不定になる?ようで、明示的にretainしておく
      Declare Sub retain Lib "Cocoa" Selector "retain" (receiver As Ptr)
      retain(pTransit)
      
      // 個々のフィルター処理
      SetContentFiltersV(idx)
    End Sub
    
  24. 以下をCIFiltersにペースト
    Public Sub SetContentFiltersV(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(pTransit, 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(pTransit, 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(pTransit, pnt, dic(idx).Value("Name"+str(i)))
          
        case "CIImage"
          Dim img As Ptr = ConvPicToCIImg(dic(idx).Value("Key"+str(i)))
          setValue(pTransit, 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
    
  25. 以下をCIFiltersにペースト
    Public Sub SetExtentValue(cntn() As ContainerControl, idx As Integer, pic As Picture)
      // dic内のinputExtent要素を探索
      Dim cnt As Integer = -1
      for i As Integer = 1 to dic(idx).Value("Count").IntegerValue
        if dic(idx).Value("Name"+str(i)).StringValue= "inputExtent" then
          cnt=i
          exit
        end if
      next
      if cnt<0 then  // 見つからなければ戻る
        return
      end if
      
      // "0,0,0,0"の場合は何もしない
      if dic(idx).Value("Key"+Str(cnt)) = "0,0,0,0" then
        return
      end if
      
      // inputImageのサイズを反映したinputExtentを再作成
      dic(idx).Value("Key"+Str(cnt)) = "0,0,"+Str(pic.Width)+","+Str(pic.Height)
      
      // inputExtentコンテナを探索
      Dim a As ContainerControl2
      for i As Integer=0 to Ubound(cntn)
        if cntn(i) isA ContainerControl2 then
          a = ContainerControl2(cntn(i))  // キャストして取り出す
          if a.Label1.Text="inputExtent" then
            exit
          end if
        end if
      next
      
      // 値のセット
      a.SetControl(idx,cnt)
    End Sub
    
  26. 以下をCIFiltersにペースト(できなければプロパティに、名前:pTransit、データ型:Ptr、を追加)
    Private Property pTransit as Ptr
    
  27. 新規クラスを作成(名前は、ここでは「AVCapture」とした。)
  28. 以下をAVCaptureにペースト(できなければ移譲に、デリゲート名:ActionDelegate、引数:id As Ptr, SEL As CString, out As Ptr, url As Ptr, conct As Ptr, err As Ptr、戻り値型:なし、を追加)
    Private Sub ActionDelegate(id As Ptr, sel as CString, sender As Ptr)
    
  29. 以下をAVCaptureにペースト
    Public Sub CapEnd()
      // 録画停止
      Declare Sub stopRecording Lib "Cocoa" selector "stopRecording" (class_id As Ptr)
      stopRecording(output)
    End Sub
    
  30. 以下をAVCaptureにペースト
    Public Sub CapStart()
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // 出力ファイルURLセット
      Dim savePath1 As Ptr = NSClassFromString("NSURL")
      Declare Function fileURLWithPath Lib "Cocoa" selector "fileURLWithPath:" (class_id As Ptr, path As CFStringRef) As Ptr
      savePath1 = fileURLWithPath(savePath1, "/Users/hasu/Desktop/sample.mp4")
      
      // セッション開始
      Declare Sub startRunning Lib "Cocoa" selector "startRunning" (class_id As Ptr)
      startRunning(session)
      
      // 録画開始
      Declare Sub startRecording Lib "Cocoa" selector "startRecordingToOutputFileURL:recordingDelegate:" (class_id As Ptr, url As Ptr, del As Ptr)
      startRecording(output, savePath1, InstancePtr)
    End Sub
    
  31. 以下をAVCaptureにペースト
    Public Sub Constructor(rect As CGRect)
      // セッションの初期化
      InitSession(rect)
      
      // デリゲート用カスタムクラスを作成。初回のみ
      makeClass()
      
      // デリゲートのインスタンスを作成
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr
      Dim subclassId As Ptr = init(alloc(AVCaptureClass))
      
      // インスタンスを保持
      InstancePtr = subclassId
      
      // インスタンス側でActionを受け取るメソッドを登録
      ActionHandler = AddressOf FinishRecording
    End Sub
    
  32. 以下をAVCaptureにペースト
    Private Sub FinishRecording(id As Ptr, SEL As CString, out As Ptr, url As Ptr, conct As Ptr, err As Ptr)
      // セッション停止
      Declare Sub stopRunning Lib "Cocoa" selector "stopRunning" (class_id As Ptr)
      stopRunning(session)
    End Sub
    
  33. 以下をAVCaptureにペースト
    Private Sub InitSession(rect As CGRect)
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
      Declare Function init Lib "Cocoa" selector "init" (class_id As Ptr) As Ptr
      
      // セッションの生成
      session = NSClassFromString("AVCaptureSession")
      session = init(alloc(session))
      
      // 出力形式をファイル保存にする
      output = NSClassFromString("AVCaptureMovieFileOutput")
      output = init(alloc(output))
      
      // 録画の画質を指定
      Declare Sub sessionPreset Lib "Cocoa" selector "setSessionPreset:" (class_id As Ptr, parm As CFStringRef)
      sessionPreset(session, "AVCaptureSessionPresetHigh")  // 文字列これでいいか??
      
      // メインディスプレイのIDを取得
      Declare Function CGMainDisplayID Lib "Carbon" () as Integer
      dim displayID As integer = CGMainDisplayID
      
      // 入力ソースをメインディスプレイに設定
      Dim input As Ptr = NSClassFromString("AVCaptureScreenInput")
      Declare Function initWithDisplayID Lib "Cocoa" selector "initWithDisplayID:" (class_id As Ptr, parm As Integer) As Ptr
      input = initWithDisplayID(alloc(input), displayID)
      
      // 録画範囲の指定(yは画面下端から)
      Declare Sub setCropRect Lib "Cocoa" selector "setCropRect:" (class_id As Ptr, parm As CGRect)
      setCropRect(input, rect)
      
      // カーソルは録画対象から外す
      Declare Sub setCapturesCursor Lib "Cocoa" selector "setCapturesCursor:" (class_id As Ptr, flg As Boolean)
      setCapturesCursor(input, false)
      
      // クリックは録画する
      Declare Sub setCapturesMouseClicks Lib "Cocoa" selector "setCapturesMouseClicks:" (class_id As Ptr, flg As Boolean)
      setCapturesMouseClicks(input, true)
      
      // 入力ソースを設定
      Declare Function canAddInput Lib "Cocoa" selector "canAddInput:" (class_id As Ptr, input As Ptr) As Boolean
      Declare Sub addInput Lib "Cocoa" selector "addInput:" (class_id As Ptr, input As Ptr)
      if canAddInput(session, input) then
        addInput(session, input)
      end if
      
      // 出力ソースを設定
      Declare Function canAddOutput Lib "Cocoa" selector "canAddOutput:" (class_id As Ptr, output As Ptr) As Boolean
      Declare Sub addOutput Lib "Cocoa" selector "addOutput:" (class_id As Ptr, output As Ptr)
      if canAddOutput(session, output) then
        addOutput(session, output)
      end if
    End Sub
    
  34. 以下をAVCaptureにペースト(できなければプロパティに、名前:InstancePtr、データ型:Ptr、を追加)
    Private Property InstancePtr as Ptr
    
  35. 以下をAVCaptureにペースト(できなければプロパティに、名前:output、データ型:Ptr、を追加)
    Private Property output as Ptr
    
  36. 以下をAVCaptureにペースト(できなければプロパティに、名前:session、データ型:Ptr、を追加)
    Private Property session as Ptr
    
  37. 以下をAVCaptureにペースト
    Private Shared Sub makeClass()
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      // Declare宣言
      Declare Function objc_allocateClassPair Lib "Cocoa" (superclass As Ptr, name As CString, extraBytes As Integer) as Ptr
      Declare Sub objc_registerClassPair Lib "Cocoa" (cls As Ptr)
      Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean
      
      // 既にクラス作成済なら戻る
      if AVCaptureClass <> nil then
        return
      end if
      
      // クラス名をmyAVCapture、メタクラス名をNSObjectにして、生成
      Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "myAVCapture", 0)
      // ランタイムに登録(参照を可能とするため)
      objc_registerClassPair newClassId
      
      // Delegateの対象となるメソッドを追加(captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:をXojo側で用意したmyCaptureOutputメソッドで受け取る。)
      if not class_addMethod (newClassId, NSSelectorFromString("captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:"), AddressOf myFinishRecording, "v@:@@@@") then
        msgBox "error1."
        return
      end if
      
      // クラスを保持
      AVCaptureClass = newClassId
    End Sub
    
  38. 以下をAVCaptureにペースト
    Private Shared Sub myFinishRecording(id As Ptr, SEL As CString, out As Ptr, url As Ptr, conct As Ptr, err As Ptr)
      // Constructorで登録した、Actionを受け取るメソッドを呼び出す
      ActionHandler.Invoke(id, SEL, out, url, conct, err)
    End Sub
    
  39. 以下をAVCaptureにペースト(できなければ共有プロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
    Private Shared Property ActionHandler as ActionDelegate
    
  40. 以下をAVCaptureにペースト(できなければ共有プロパティに、名前:AVCaptureClass、データ型:Ptr、を追加)
    Private Shared Property AVCaptureClass as Ptr
    
  41. 前回プロジェクトから、ContainerControl1、ContainerControl2、ContainerControl3、ContainerControl4をコピー
  42. 上記各ContainerControlから、SetControlEnableメソッドを削除
  43. ContainerControl2のSetControlメソッドを以下に差し替え
    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
    
  44. 他に、NSMakeSize(メソッド)、CGRect/NSSize(構造体)が必要ですが、それらはmacoslibからコピーさせて頂きました。(上記CIFiltersまたは別途モジュールを用意してコピーする。)
 実行してみたところ、CIFilterによるイメージ処理が機能することを確認しました。
S Shot3


注:エフェクトはCISwipeTransitionです。HTMLの機能でリピート再生しています。


 おわりに

 フィルターの中にはCIRippleTransitionのように、inputShadingImageに不透明な画像を指定するとinputImageとinputTargetImageが見えなくなる、といった、注意が必要なものもあります。

 参考サイト(4):(旧) Cocoaの日々: 波紋(その5)

 動画のキャプチャーについては、録画開始が少し遅れる場合があるようで、その場合は頭が欠けてしまいます。
 スムースにリピート再生したい場合は、長めに録画して、動画編集ソフトで編集するなどの工夫が必要になります。
上掲のサンプル動画も、iMovieで動画の一部をカットした上で書き出している。
その際、書き出したmp4動画がFireFoxで再生されない(SafariはOK)という問題が生じたが、以下の対処で解決された。
共有>QuickTimeを使用して書き出す...メニューからのダイアログで、書き出し:ムービーからMPEG-4、を選んでオプション...で、ファイルフォーマット:MP4、ビデオフォーマット:H.264、イメージサイズ:320x240 QVGA、を指定して保存。

参考サイト(5):動画、MP4形式なのに再生できない!|めも352

 お世話になったサイト

 貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)

 参考サイト(1):Core Image の遷移エフェクトを使う - Qiita
 参考サイト(2):アプリ開発ブログ(仮): CoreImageのTransitionエフェクト
 参考サイト(3):【MacOS】スクリーンレコーディング 【Swift】 - Qiita
 参考サイト(4):(旧) Cocoaの日々: 波紋(その5)
 参考サイト(5):動画、MP4形式なのに再生できない!|めも352


 更新履歴

 2019.11.27 Xojoでの実装の4項に、注を追加
 2019.10.03 新規作成


[Home]  [MacSoft]  [Donation]  [History]  [Privacy Policy]  [Affiliate Policy]