From fa818e20ecdd7f38e0b035d8a2fa9d74be495256 Mon Sep 17 00:00:00 2001 From: Seungho Baek Date: Tue, 13 Aug 2024 18:40:13 +0900 Subject: [PATCH] [NUI.Scene3D] Add Capture for SceneView Signed-off-by: Seungho Baek --- .../src/internal/Interop/Interop.SceneView.cs | 9 + .../Controls/CaptureFinishedEventArgs.cs | 48 +++++ .../src/public/Controls/SceneView.cs | 176 ++++++++++++++++++ .../src/internal/Interop/Interop.ImageUrl.cs | 4 + .../BaseComponents/VisibilityChangeType.cs | 1 - .../Samples/Scene3DCaptureTest.cs | 171 +++++++++++++++++ 6 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 src/Tizen.NUI.Scene3D/src/public/Controls/CaptureFinishedEventArgs.cs create mode 100755 test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/Scene3DCaptureTest.cs diff --git a/src/Tizen.NUI.Scene3D/src/internal/Interop/Interop.SceneView.cs b/src/Tizen.NUI.Scene3D/src/internal/Interop/Interop.SceneView.cs index 0d10ba48259..d844df74cf3 100755 --- a/src/Tizen.NUI.Scene3D/src/internal/Interop/Interop.SceneView.cs +++ b/src/Tizen.NUI.Scene3D/src/internal/Interop/Interop.SceneView.cs @@ -117,6 +117,15 @@ internal static partial class SceneView [global::System.Runtime.InteropServices.DllImport(Libraries.Scene3D, EntryPoint = "CSharp_Dali_SceneView_CameraTransitionFinishedSignal_Disconnect")] public static extern void CameraTransitionFinishedDisconnect(global::System.Runtime.InteropServices.HandleRef actor, global::System.Runtime.InteropServices.HandleRef handler); + + [global::System.Runtime.InteropServices.DllImport(Libraries.Scene3D, EntryPoint = "CSharp_Dali_SceneView_Capture")] + public static extern int Capture(global::System.Runtime.InteropServices.HandleRef sceneView, global::System.Runtime.InteropServices.HandleRef camera, global::System.Runtime.InteropServices.HandleRef size); + + [global::System.Runtime.InteropServices.DllImport(Libraries.Scene3D, EntryPoint = "CSharp_Dali_SceneView_CaptureFinishedSignal_Connect")] + public static extern void CaptureFinishedConnect(global::System.Runtime.InteropServices.HandleRef actor, global::System.Runtime.InteropServices.HandleRef handler); + + [global::System.Runtime.InteropServices.DllImport(Libraries.Scene3D, EntryPoint = "CSharp_Dali_SceneView_CaptureFinishedSignal_Disconnect")] + public static extern void CaptureFinishedDisconnect(global::System.Runtime.InteropServices.HandleRef actor, global::System.Runtime.InteropServices.HandleRef handler); } } } diff --git a/src/Tizen.NUI.Scene3D/src/public/Controls/CaptureFinishedEventArgs.cs b/src/Tizen.NUI.Scene3D/src/public/Controls/CaptureFinishedEventArgs.cs new file mode 100644 index 00000000000..588f9cefbbd --- /dev/null +++ b/src/Tizen.NUI.Scene3D/src/public/Controls/CaptureFinishedEventArgs.cs @@ -0,0 +1,48 @@ +/* + * Copyright(c) 2024 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +using System; +using System.ComponentModel; + +namespace Tizen.NUI.Scene3D +{ + /// + /// Event arguments of SceneView capture finished event. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public class CaptureFinishedEventArgs : EventArgs + { + /// + /// Integer ID of the capture request. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public int CaptureId + { + get; set; + } + + /// + /// ImageUrl of the captured result + /// If the capture is failed, it is null. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public ImageUrl CapturedImageUrl + { + get; set; + } + } +} diff --git a/src/Tizen.NUI.Scene3D/src/public/Controls/SceneView.cs b/src/Tizen.NUI.Scene3D/src/public/Controls/SceneView.cs index d3698100ebd..e0086234e0a 100755 --- a/src/Tizen.NUI.Scene3D/src/public/Controls/SceneView.cs +++ b/src/Tizen.NUI.Scene3D/src/public/Controls/SceneView.cs @@ -22,6 +22,9 @@ using Tizen.NUI; using Tizen.NUI.Binding; using Tizen.NUI.BaseComponents; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Diagnostics; namespace Tizen.NUI.Scene3D { @@ -68,14 +71,24 @@ namespace Tizen.NUI.Scene3D /// 10 public class SceneView : View { + private Dictionary> asyncCaptureIds = new(); private string skyboxUrl; + // CameraTransitionFinishedEvent private EventHandler cameraTransitionFinishedEventHandler; private CameraTransitionFinishedEventCallbackType cameraTransitionFinishedEventCallback; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void CameraTransitionFinishedEventCallbackType(IntPtr data); + // CaptureFinishedEvent + private EventHandler captureFinishedEventHandler; + private EventHandler asyncCaptureFinishedEventHandler; + private CaptureFinishedEventCallbackType captureFinishedEventCallback; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CaptureFinishedEventCallbackType(IntPtr data, int captureId, IntPtr capturedImageUrl); + internal SceneView(global::System.IntPtr cPtr, bool cMemoryOwn) : this(cPtr, cMemoryOwn, cMemoryOwn) { } @@ -104,6 +117,15 @@ protected override void Dispose(DisposeTypes type) cameraTransitionFinishedEventCallback = null; } + if (captureFinishedEventCallback != null) + { + NUILog.Debug($"[Dispose] captureFinishedEventCallback"); + + Interop.SceneView.CaptureFinishedDisconnect(GetBaseHandleCPtrHandleRef, captureFinishedEventCallback.ToHandleRef(this)); + NDalicPINVOKE.ThrowExceptionIfExistsDebug(); + captureFinishedEventCallback = null; + } + LayoutCount = 0; base.Dispose(type); @@ -170,6 +192,64 @@ public event EventHandler CameraTransitionFinished } } + /// + /// An event emitted when Capture is finished. + /// If Capture is successed, CaptureFinishedEventArgs includes finished capture ID and ImageUrl of the captured image. + /// If Capture is failed, the ImageUrl is null. + /// + // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API) + [EditorBrowsable(EditorBrowsableState.Never)] + public event EventHandler CaptureFinished + { + add + { + if (captureFinishedEventHandler == null) + { + captureFinishedEventCallback = OnCaptureFinished; + Interop.SceneView.CaptureFinishedConnect(SwigCPtr, captureFinishedEventCallback.ToHandleRef(this)); + NDalicPINVOKE.ThrowExceptionIfExists(); + } + captureFinishedEventHandler += value; + } + + remove + { + captureFinishedEventHandler -= value; + if (captureFinishedEventHandler == null && captureFinishedEventCallback != null) + { + Interop.SceneView.CaptureFinishedDisconnect(SwigCPtr, captureFinishedEventCallback.ToHandleRef(this)); + NDalicPINVOKE.ThrowExceptionIfExists(); + captureFinishedEventCallback = null; + } + } + } + + /// + /// An event emitted when CaptureAsync is finished. + /// + private event EventHandler AsyncCaptureFinished + { + add + { + if (asyncCaptureFinishedEventHandler == null) + { + CaptureFinished += dummy; + } + asyncCaptureFinishedEventHandler += value; + } + + remove + { + asyncCaptureFinishedEventHandler -= value; + if (asyncCaptureFinishedEventHandler == null) + { + CaptureFinished += dummy; + } + } + } + + void dummy(object sender, CaptureFinishedEventArgs e) {} + /// /// Set/Get the ImageBasedLight ScaleFactor. /// Scale factor controls light source intensity in [0.0f, 1.0f] @@ -573,6 +653,82 @@ public void ResetResolution() if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve(); } + /// + /// Requests to capture this SceneView with the Camera. + /// When the capture is finished, CaptureFinished Event is emited. + /// includes and . + /// If the capture is successful, the contains url of captured image. + /// If the capture fails, the is null. + /// + /// Camera to be used for capture. + /// captured size. + /// + /// The input camera should not be used for any other purpose during Capture. + /// (Simultaneous usage elsewhere may result in incorrect rendering.) + /// The camera is required to be added in this SceneView. (Not need to be a selected camera) + /// If the SceneView is disconnected from Scene, the left capture requests are canceled with fail. + /// + /// capture id that id unique value to distinguish each requiest. + // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API) + [EditorBrowsable(EditorBrowsableState.Never)] + public int Capture(Scene3D.Camera camera, Vector2 size) + { + int id = Interop.SceneView.Capture(SwigCPtr, camera.SwigCPtr, Vector2.getCPtr(size)); + if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve(); + return id; + } + + /// + /// Requests to capture this SceneView with the Camera asynchronously. + /// + /// Camera to be used for capture. + /// captured size. + /// + /// The input camera should not be used for any other purpose during Capture. + /// (Simultaneous usage elsewhere may result in incorrect rendering.) + /// The camera is required to be added in this SceneView. (Not need to be a selected camera) + /// If the SceneView is disconnected from Scene, the left capture requests are canceled with fail. + /// + /// + /// A task that represents the asynchronous operation. The task result contains the URL of the captured image. + /// If the capture is successful, the task result is the ImageURL. + /// If the capture fails, the task will complete with an + /// + // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API) + [EditorBrowsable(EditorBrowsableState.Never)] + public Task CaptureAsync(Scene3D.Camera camera, Vector2 size) + { + void Handler(object _, CaptureFinishedEventArgs e) + { + if (asyncCaptureIds.TryGetValue(e.CaptureId, out var tcs)) + { + try + { + if (e.CapturedImageUrl != null) + { + tcs.SetResult(e.CapturedImageUrl); + } + else + { + tcs.SetException(new InvalidOperationException("Fail to Capture")); + } + } + finally + { + AsyncCaptureFinished -= Handler; + asyncCaptureIds.Remove(e.CaptureId); + } + } + }; + + AsyncCaptureFinished += Handler; + var captureId = Interop.SceneView.Capture(SwigCPtr, camera.SwigCPtr, Vector2.getCPtr(size)); + TaskCompletionSource ret = new TaskCompletionSource(); + asyncCaptureIds.Add(captureId, ret); + + return ret.Task; + } + internal void SetUseFramebuffer(bool useFramebuffer) { Interop.SceneView.UseFramebuffer(SwigCPtr, useFramebuffer); @@ -693,5 +849,25 @@ private void OnCameraTransitionFinished(IntPtr data) { cameraTransitionFinishedEventHandler?.Invoke(this, EventArgs.Empty); } + + // Callback for capture finished signal + private void OnCaptureFinished(IntPtr data, int captureId, IntPtr capturedImageUrl) + { + CaptureFinishedEventArgs e = new CaptureFinishedEventArgs(); + ImageUrl imageUrl = new ImageUrl(NUI.Interop.ImageUrl.NewImageUrl(new ImageUrl(capturedImageUrl, false).SwigCPtr), true); + NDalicPINVOKE.ThrowExceptionIfExists(); + + e.CaptureId = captureId; + e.CapturedImageUrl = imageUrl.HasBody() ? imageUrl : null; + + if (asyncCaptureIds.ContainsKey(e.CaptureId)) + { + asyncCaptureFinishedEventHandler?.Invoke(this, e); + } + else + { + captureFinishedEventHandler?.Invoke(this, e); + } + } } } diff --git a/src/Tizen.NUI/src/internal/Interop/Interop.ImageUrl.cs b/src/Tizen.NUI/src/internal/Interop/Interop.ImageUrl.cs index adec6aeb37c..7f2d38d13f8 100644 --- a/src/Tizen.NUI/src/internal/Interop/Interop.ImageUrl.cs +++ b/src/Tizen.NUI/src/internal/Interop/Interop.ImageUrl.cs @@ -24,6 +24,10 @@ internal static partial class Interop { internal static partial class ImageUrl { + + [DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_new_ImageUrl_Copy")] + public static extern global::System.IntPtr NewImageUrl(global::System.Runtime.InteropServices.HandleRef csImageUrl); + [DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_delete_ImageUrl")] public static extern void Delete(HandleRef jarg1); diff --git a/src/Tizen.NUI/src/public/BaseComponents/VisibilityChangeType.cs b/src/Tizen.NUI/src/public/BaseComponents/VisibilityChangeType.cs index e700b444791..cba1079e63d 100755 --- a/src/Tizen.NUI/src/public/BaseComponents/VisibilityChangeType.cs +++ b/src/Tizen.NUI/src/public/BaseComponents/VisibilityChangeType.cs @@ -34,5 +34,4 @@ public enum VisibilityChangeType /// 3 PARENT } - } diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/Scene3DCaptureTest.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/Scene3DCaptureTest.cs new file mode 100755 index 00000000000..f2fc108e29a --- /dev/null +++ b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/Scene3DCaptureTest.cs @@ -0,0 +1,171 @@ +using System.Numerics; +using System.Reflection.Metadata.Ecma335; +using System.Threading.Tasks; +using global::System; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.NUI.Scene3D; + +namespace Tizen.NUI.Samples +{ + using log = Tizen.Log; + public class Scene3DCaptureTest : IExample + { + private Window window; + private SceneView sceneView; + private static readonly string resourcePath = Tizen.Applications.Application.Current.DirectoryInfo.Resource; + private Tizen.NUI.Scene3D.Camera[] cameras; + private string[] cameraName; + private int cameraIndex; + int captureId; + ImageView imageView; + ImageUrl imageUrl; + bool inCapture = false; + + public void Activate() + { + window = NUIApplication.GetDefaultWindow(); + Size2D windowSize = window.Size; + + sceneView = new SceneView() + { + Size = new Size(windowSize.Width, windowSize.Height), + PivotPoint = PivotPoint.TopLeft, + ParentOrigin = ParentOrigin.TopLeft, + PositionUsesPivotPoint = true, + BackgroundColor = new Color(0.85f, 0.85f, 0.85f, 1.0f), + UseFramebuffer = true, + }; + window.Add(sceneView); + + Light light = new Light() + { + Color = new Vector4(0.4f, 0.4f, 0.4f, 1.0f), + Position = new Vector3(-1.0f, 0.0f, 1.1f), + PositionUsesPivotPoint = true, + }; + light.LookAt(new Vector3(0.0f, 0.0f, 0.0f)); + sceneView.Add(light); + + cameras = new Scene3D.Camera[2]; + cameraName = new string[]{"camera1", "camera2"}; + Vector3[] cameraPosition = new Vector3[]{new Vector3(1.5f, 0.0f, 1.5f), new Vector3(-1.5f, -1.5f, 1.5f)}; + Vector3 modelPosition = new Vector3(-1.5f, 0.0f, 0.0f); + + cameraIndex = 0; + for(uint i = 0; i<2; ++i) + { + cameras[i] = new Scene3D.Camera() + { + Name = cameraName[i], + Position = cameraPosition[i], + NearPlaneDistance = 1.0f, + FarPlaneDistance = 10.0f, + }; + sceneView.AddCamera(cameras[i]); + } + cameras[1].FieldOfView = new Radian(new Degree(70.0f)); + + Model model = new Model(resourcePath + "models/BoxAnimated.glb") + { + PositionUsesPivotPoint = true, + Position = modelPosition, + Size = new Size(0.5f, 0.5f, 0.5f), + }; + sceneView.Add(model); + model.Add(cameras[0]); + sceneView.SelectCamera(cameraName[0]); + model.ResourcesLoaded += (s, e) => + { + SceneCapture(1); + }; + sceneView.Add(cameras[1]); + + cameras[0].LookAt(modelPosition); + cameras[1].LookAt(modelPosition); + + sceneView.CaptureFinished += (s, e) => + { + Tizen.Log.Error("NUI", $"Finished Capture ID : {e.CaptureId}\n"); + if(e.CapturedImageUrl == null) + { + Tizen.Log.Error("NUI", $"Capture Failed\n"); + return; + } + CreateImageView(e.CapturedImageUrl); + }; + + window.KeyEvent += WindowKeyEvent; + } + + private void WindowKeyEvent(object sender, Window.KeyEventArgs e) + { + if (e.Key.State == Key.StateType.Down) + { + if (e.Key.KeyPressedName == "1") + { + SceneCapture(cameraIndex); + } + else if (e.Key.KeyPressedName == "2") + { + SceneCaptureAsync(cameraIndex); + } + else + { + return; + } + + cameraIndex = 1 - cameraIndex; + sceneView.SelectCamera(cameraName[cameraIndex]); + } + } + + void SceneCapture(int captureCameraIndex) + { + captureId = sceneView.Capture(cameras[captureCameraIndex], new Vector2(300, 300)); + Tizen.Log.Error("NUI", $"Requestd Capture ID : {captureId}\n"); + } + + async void SceneCaptureAsync(int captureCameraIndex) + { + try + { + var url = await sceneView.CaptureAsync(cameras[captureCameraIndex], new Vector2(300, 300)); + Tizen.Log.Error("NUI", $"Finished Capture url : {url.ToString()}\n"); + CreateImageView(url); + } + catch (InvalidOperationException e) + { + Tizen.Log.Error("NUI", "Oops Capture is failed.\n"); + } + } + + void CreateImageView(ImageUrl capturedImageUrl) + { + if (imageView != null) + { + imageView.Dispose(); + } + if (imageUrl != null) + { + imageUrl.Dispose(); + } + imageUrl = capturedImageUrl; + + imageView = new ImageView(imageUrl.ToString()) + { + Size = new Size(300, 300), + PositionUsesPivotPoint = true, + ParentOrigin = ParentOrigin.BottomLeft, + PivotPoint = PivotPoint.BottomLeft + }; + window.Add(imageView); + } + + public void Deactivate() + { + window.KeyEvent -= WindowKeyEvent; + sceneView?.DisposeRecursively(); + } + } +}