ホームページ>開発ツール>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とは異なるのかもしれないが、)当方で完全移植版を試した限りでは、一部フィルター(CIPageCurlTransitionとCIPageCurlWithShadowTransition)でoutputImageのサイズが、inputImageとinputTargetImage全体を包含するものとなるのだが、参考サイト(1)の方法(CIImage → NSCIImageRep → NSImage)では、(NSView描画時には縮小されて)そのまま表示される(左図)。一方、前回プロジェクトの方法(CIImage → CGImageRef → NSImage)ではCGImageRef変換時にCanvasサイズを指定することで、期待する結果になる(右図)。あと、これはCIFilterとは直接関係ありませんが、遷移エフェクトの推移(以下、アニメーション)を動画として保存する機能を追加します。
アニメーションの録画には、AVCaptureSessionを使います。
これも、スクリーンショットと同様、何もしないと画面全体が対象となりますが、クロップすることで所望の領域を対象にできます。
参考サイト(3):【MacOS】スクリーンレコーディング 【Swift】 - Qiita
以上を踏まえ、まずは前回との相違点を挙げると、
残りの仕様は以下の通りとしました。
- フィルターの複数選択は、なし。(チェックボックス機能は無くし、行選択のみでパラメーターセット可能とする。)
- 表示領域は、320x240ピクセル固定。(画像が大きいとはみ出します。必要ならカスタマイズして下さい。)
- inputImageは、中央ペインへのドロップではなく、パラメーターの一部として右ペインで指定する。
- アニメーション用に、実行/停止ボタンを別途設ける。
- パラメーターの並び順を変更し、イメージを上部、残りはアルファベット順とする。
- 画像ドロップ時は、まず市松模様の画像を描画する。(透明画像をドロップした場合の対策。)
- inputExtentは、inputImageドロップ時にサイズを反映させる。
- 録画機能はオプションとする。
- 録画場所は、メインディスプレーに固定。(必要ならカスタマイズして下さい。)
- 動画の保存場所は、デスクトップに固定。(必要ならカスタマイズして下さい。)
- 動画のクロップ領域は、(例によって、)現物合わせとする。
- AVCaptureSession関連処理はClass化する。
Xojoでの実装
【ソースコードのコピー&ペーストについて】注)本プロジェクトは前回プロジェクトをベースにしていますが、変更点が多いため、新規扱いとしています。
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、CIFilterによるイメージ処理が機能することを確認しました。
- Xojoで新規プロジェクトを作成
- Window1に、Canvas(Name:Canvas1)、CheckBox(Name:CheckBox1)、Listbox(Name:Listbox1)、PushButton2個(Name:PushButton1、Name:PushButton2)、Separator(Name:Separator1)、Timer(Name:Timer1)を追加
- 以下を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
- 以下をListbox1にペースト(できなければ、Sub - Endの間をCellBackgroundPaintイベントに記述)
注)初出時、3行目をg.FillRect 0,0,me.Width,me.RowHeight // me.Widthは列幅ではなくリストボックス幅だが、実害はないようだとしていましたが、Cellのサイズは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 // 行選択されていれば PushButton1.Enabled=true CheckBox1.Enabled=true else PushButton1.Enabled=false PushButton2.Enabled=false CheckBox1.Enabled=false 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から、メソッド(ValueChangedColor、ValueChangedNumber、ValueChangedVector)をコピー
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下をWindow1にペースト(できなければプロパティに、名前:AVCaptureInst、データ型:AVCapture、を追加)
Private Property AVCaptureInst as AVCapture
- 以下をWindow1にペースト(できなければプロパティに、名前:baseTime、データ型:Integer、を追加)
Protected Property baseTime as Integer
- 以下をWindow1にペースト(できなければプロパティに、名前:isRunning、データ型:Integer、を追加)
Protected Property isRunning as Boolean
- 以下をWindow1にペースト(できなければプロパティに、名前:pCntn(-1)、データ型:ContainerControl、を追加)
Protected Property pCntn(-1) as ContainerControl
- 新規モジュールを作成(名前は、ここでは「CIFilters」とした。)
- 前回プロジェクトのCIFiltersから、メソッド(ConvPicToCIImg、FloatToNSNumber、GenDefaultPict、GetCount、GetValue、SetValue)、プロパティ(dic(-1))をコピー
- 以下を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
- 以下を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
- 以下をCIFiltersにペースト
(長いので、別ページにしてあります。)- 以下を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
- 以下を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
- 以下を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
- 以下をCIFiltersにペースト(できなければプロパティに、名前:pTransit、データ型:Ptr、を追加)
Private Property pTransit as Ptr
- 新規クラスを作成(名前は、ここでは「AVCapture」とした。)
- 以下を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)
- 以下をAVCaptureにペースト
Public Sub CapEnd() // 録画停止 Declare Sub stopRecording Lib "Cocoa" selector "stopRecording" (class_id As Ptr) stopRecording(output) End Sub
- 以下を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
- 以下を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
- 以下を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
- 以下を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
- 以下をAVCaptureにペースト(できなければプロパティに、名前:InstancePtr、データ型:Ptr、を追加)
Private Property InstancePtr as Ptr
- 以下をAVCaptureにペースト(できなければプロパティに、名前:output、データ型:Ptr、を追加)
Private Property output as Ptr
- 以下をAVCaptureにペースト(できなければプロパティに、名前:session、データ型:Ptr、を追加)
Private Property session as Ptr
- 以下を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
- 以下を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
- 以下をAVCaptureにペースト(できなければ共有プロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
Private Shared Property ActionHandler as ActionDelegate
- 以下をAVCaptureにペースト(できなければ共有プロパティに、名前:AVCaptureClass、データ型:Ptr、を追加)
Private Shared Property AVCaptureClass as Ptr
- 前回プロジェクトから、ContainerControl1、ContainerControl2、ContainerControl3、ContainerControl4をコピー
- 上記各ContainerControlから、SetControlEnableメソッドを削除
- 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
- 他に、NSMakeSize(メソッド)、CGRect/NSSize(構造体)が必要ですが、それらはmacoslibからコピーさせて頂きました。(上記CIFiltersまたは別途モジュールを用意してコピーする。)
注:エフェクトは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]