[NUI.Scene3D] Add Capture for SceneView
authorSeungho Baek <sbsh.baek@samsung.com>
Tue, 13 Aug 2024 09:40:13 +0000 (18:40 +0900)
committerbshsqa <32317749+bshsqa@users.noreply.github.com>
Tue, 21 Jan 2025 09:53:47 +0000 (18:53 +0900)
Signed-off-by: Seungho Baek <sbsh.baek@samsung.com>
src/Tizen.NUI.Scene3D/src/internal/Interop/Interop.SceneView.cs
src/Tizen.NUI.Scene3D/src/public/Controls/CaptureFinishedEventArgs.cs [new file with mode: 0644]
src/Tizen.NUI.Scene3D/src/public/Controls/SceneView.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/Scene3DCaptureTest.cs [new file with mode: 0755]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/res/models/BoxAnimated.glb [new file with mode: 0644]

index 2923edc3facf456f23c8f6d9b8e4ff3e17709ed1..f6638f8c0637b3a22675508645405f5a79186e7e 100755 (executable)
@@ -96,6 +96,15 @@ namespace Tizen.NUI.Scene3D
 
             [global::System.Runtime.InteropServices.DllImport(Libraries.Scene3D, EntryPoint = "CSharp_Dali_SceneView_GetFramebufferMultiSamplingLevel")]
             public static extern uint GetFramebufferMultiSamplingLevel(global::System.Runtime.InteropServices.HandleRef sceneView);
+
+            [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 (file)
index 0000000..588f9ce
--- /dev/null
@@ -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
+{
+    /// <summary>
+    /// Event arguments of SceneView capture finished event.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class CaptureFinishedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Integer ID of the capture request.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public int CaptureId
+        {
+            get; set;
+        }
+
+        /// <summary>
+        /// ImageUrl of the captured result
+        /// If the capture is failed, it is null.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ImageUrl CapturedImageUrl
+        {
+            get; set;
+        }
+    }
+}
index 27bfbdf9544ae134f87fae71f4be7bb5a0b3937d..1361e3971a1a841e3f233d7909d1460b48f24b52 100755 (executable)
@@ -68,9 +68,16 @@ namespace Tizen.NUI.Scene3D
     /// <since_tizen> 10 </since_tizen>
     public class SceneView : View
     {
+        private bool inCapture = false;
         private bool inCameraTransition = false;
         private Animation cameraTransition;
 
+        // CaptureFinishedEvent
+        private EventHandler<CaptureFinishedEventArgs> captureFinishedEventHandler;
+        private CaptureFinishedEventCallbackType captureFinishedEventCallback;
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        private delegate void CaptureFinishedEventCallbackType(IntPtr data, int captureId, IntPtr capturedImageUrl);
+
         internal SceneView(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
         {
         }
@@ -94,6 +101,29 @@ namespace Tizen.NUI.Scene3D
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
+        /// <summary>
+        /// Dispose Explicit or Implicit
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (Disposed)
+            {
+                return;
+            }
+
+            if (captureFinishedEventCallback != null)
+            {
+                NUILog.Debug($"[Dispose] captureFinishedEventCallback");
+                Interop.SceneView.CaptureFinishedDisconnect(GetBaseHandleCPtrHandleRef, captureFinishedEventCallback.ToHandleRef(this));
+                NDalicPINVOKE.ThrowExceptionIfExistsDebug();
+                captureFinishedEventCallback = null;
+            }
+            LayoutCount = 0;
+
+            base.Dispose(type);
+        }
+
         /// <summary>
         /// Assignment operator.
         /// </summary>
@@ -106,6 +136,38 @@ namespace Tizen.NUI.Scene3D
             return ret;
         }
 
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<CaptureFinishedEventArgs> 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;
+                }
+            }
+        }
+
         /// <summary>
         /// An event emitted when Camera Transition is finished.
         /// </summary>
@@ -469,6 +531,39 @@ namespace Tizen.NUI.Scene3D
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
+        /// <summary>
+        /// Requests to capture this SceneView with the Camera.
+        /// </summary>
+        /// <param name="camera">Camera to be used for capture.</param>
+        /// <param name="size">captured size.</param>
+        /// <remarks>
+        /// The input camera should not be used for any other purpose during Capture.
+        /// The selected camera cannot be used for input camera.
+        /// The camera is required to be added in this SceneView.
+        /// If the SceneView is disconnected from Scene, the left capture request is canceled.
+        /// </remarks>
+        /// <returns> capture id that id unique value to distinguish each request.
+        /// If capture is requested during previous capture, invalid index(-1) is returned.</returns>
+        // 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)
+        {
+            if(inCapture)
+            {
+                Tizen.Log.Error("NUI", "The previous capture request is not finished yet.\n");
+                return -1;  // invalid index
+            }
+            if(camera == null)
+            {
+                Tizen.Log.Error("NUI", "Invalid Camera is used.\n");
+                return -1;  // invalid index
+            }
+            inCapture = true;
+            int id = Interop.SceneView.Capture(SwigCPtr, camera.SwigCPtr, Vector2.getCPtr(size));
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            return id;
+        }
+
         internal void SetUseFramebuffer(bool useFramebuffer)
         {
             Interop.SceneView.UseFramebuffer(SwigCPtr, useFramebuffer);
@@ -611,6 +706,16 @@ namespace Tizen.NUI.Scene3D
             positionKeyFrames.Dispose();
             orientationKeyFrames.Dispose();
         }
+        
+        // Callback for capture finished signal
+        private void OnCaptureFinished(IntPtr data, int captureId, IntPtr capturedImageUrl)
+        {
+            CaptureFinishedEventArgs e = new CaptureFinishedEventArgs();
+            e.CaptureId = captureId;
+            e.CapturedImageUrl = new ImageUrl(capturedImageUrl, false);
+            captureFinishedEventHandler?.Invoke(this, e);
+            inCapture = false;  // To prevent to request capture in the capture finished callback.
+        }
 
         /// <summary>
         /// Release swigCPtr.
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 (executable)
index 0000000..a1f1586
--- /dev/null
@@ -0,0 +1,163 @@
+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;
+
+        Animation rotAnimation;
+        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);
+
+            rotAnimation = new Animation(3000);
+            KeyFrames keyFrames = new KeyFrames();
+            keyFrames.Add(0.0f, new Rotation(new Radian(new Degree(0.0f)), Vector3.YAxis));
+            keyFrames.Add(0.25f, new Rotation(new Radian(new Degree(90.0f)), Vector3.YAxis));
+            keyFrames.Add(0.5f, new Rotation(new Radian(new Degree(180.0f)), Vector3.YAxis));
+            keyFrames.Add(0.75f, new Rotation(new Radian(new Degree(270.0f)), Vector3.YAxis));
+            keyFrames.Add(1.0f, new Rotation(new Radian(new Degree(360.0f)), Vector3.YAxis));
+            rotAnimation.AnimateBetween(model, "Orientation", keyFrames);
+            rotAnimation.Looping = true;
+            rotAnimation.Play();
+
+
+            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(1);
+                }
+                else
+                {
+                    return;
+                }
+            }
+        }
+
+        void SceneCapture(int captureCameraIndex)
+        {
+            captureId = sceneView.Capture(cameras[captureCameraIndex], new Vector2(300, 300));
+            Tizen.Log.Error("NUI", $"Requestd Capture ID : {captureId}\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?.Dispose();
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/res/models/BoxAnimated.glb b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/res/models/BoxAnimated.glb
new file mode 100644 (file)
index 0000000..69481ec
Binary files /dev/null and b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/res/models/BoxAnimated.glb differ