[NUI](Internal) Implement AsyncImageLoader ImageLoaded signal
authorEunki, Hong <eunkiki.hong@samsung.com>
Tue, 11 Feb 2025 06:54:22 +0000 (15:54 +0900)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Tue, 25 Mar 2025 06:44:47 +0000 (15:44 +0900)
Let we bind AsyncImageLoader's ImageLoaded signal,
so we can make it hidden easly in future.

Relative dali patches :
- https://review.tizen.org/gerrit/c/platform/core/uifw/dali-csharp-binder/+/320850

Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
src/Tizen.NUI/src/internal/Common/AsyncImageLoader.cs
src/Tizen.NUI/src/internal/Interop/Interop.AsyncImageLoader.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/AsyncImageLoaderTest.cs [new file with mode: 0755]

index 0a02ef47c19aa8b312a7c3d292835cbdd8cf7733..6ae22b6df6791a261a917ec4d0ca1b4738bc9487 100755 (executable)
  *
  */
 
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
 namespace Tizen.NUI
 {
+    [EditorBrowsable(EditorBrowsableState.Never)]
     internal class AsyncImageLoader : BaseHandle
     {
+        private EventHandler<ImageLoadedEventArgs> imageLoadedEventHandler;
+        private EventHandler<PixelBufferLoadedEventArgs> pixelBufferLoadedEventHandler;
+        private ImageLoadedSignalType imageLoadedCallback;
+        private PixelBufferLoadedSignalType pixelBufferLoadedCallback;
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        private delegate void ImageLoadedSignalType(uint loadingTaskId, IntPtr pixelData);
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        private delegate void PixelBufferLoadedSignalType(uint loadingTaskId, uint pixelBufferCount, IntPtr pixelBuffer0, IntPtr pixelBuffer1, IntPtr pixelBuffer2);
+
         internal AsyncImageLoader(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
         {
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         protected override void ReleaseSwigCPtr(System.Runtime.InteropServices.HandleRef swigCPtr)
         {
             Interop.AsyncImageLoader.DeleteAsyncImageLoader(swigCPtr);
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public AsyncImageLoader() : this(Interop.AsyncImageLoader.New(), true)
         {
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-
-        }
-        public AsyncImageLoader(AsyncImageLoader handle) : this(Interop.AsyncImageLoader.NewAsyncImageLoader(AsyncImageLoader.getCPtr(handle)), true)
-        {
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-        }
-
-        public AsyncImageLoader Assign(AsyncImageLoader handle)
-        {
-            AsyncImageLoader ret = new AsyncImageLoader(Interop.AsyncImageLoader.Assign(SwigCPtr, AsyncImageLoader.getCPtr(handle)), false);
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-            return ret;
-        }
-
-        public static AsyncImageLoader DownCast(BaseHandle handle)
-        {
-            AsyncImageLoader ret = Registry.GetManagedBaseHandleFromNativePtr(handle) as AsyncImageLoader;
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-            return ret;
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public uint Load(string url)
         {
             uint ret = Interop.AsyncImageLoader.Load(SwigCPtr, url);
@@ -59,6 +60,7 @@ namespace Tizen.NUI
             return ret;
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public uint Load(string url, Uint16Pair dimensions)
         {
             uint ret = Interop.AsyncImageLoader.Load(SwigCPtr, url, Uint16Pair.getCPtr(dimensions));
@@ -66,6 +68,7 @@ namespace Tizen.NUI
             return ret;
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public uint Load(string url, Uint16Pair dimensions, FittingModeType fittingMode, SamplingModeType samplingMode, bool orientationCorrection)
         {
             uint ret = Interop.AsyncImageLoader.Load(SwigCPtr, url, Uint16Pair.getCPtr(dimensions), (int)fittingMode, (int)samplingMode, orientationCorrection);
@@ -73,6 +76,7 @@ namespace Tizen.NUI
             return ret;
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public bool Cancel(uint loadingTaskId)
         {
             bool ret = Interop.AsyncImageLoader.Cancel(SwigCPtr, loadingTaskId);
@@ -80,11 +84,171 @@ namespace Tizen.NUI
             return ret;
         }
 
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public void CancelAll()
         {
             Interop.AsyncImageLoader.CancelAll(SwigCPtr);
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
+        /// <summary>
+        /// You can override it to clean-up your own resources
+        /// </summary>
+        /// <param name="type">DisposeTypes</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (Disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                //Called by User
+                //Release your own managed resources here.
+                //You should release all of your own disposable objects here.
+
+                if (imageLoadedCallback != null)
+                {
+                    Interop.AsyncImageLoader.ImageLoadedSignalDisconnect(SwigCPtr, imageLoadedCallback.ToHandleRef(this));
+                    imageLoadedCallback = null;
+                    imageLoadedEventHandler = null;
+                    NDalicPINVOKE.ThrowExceptionIfExists();
+                }
+
+                if (pixelBufferLoadedCallback != null)
+                {
+                    Interop.AsyncImageLoader.PixelBufferLoadedSignalDisconnect(SwigCPtr, pixelBufferLoadedCallback.ToHandleRef(this));
+                    pixelBufferLoadedCallback = null;
+                    pixelBufferLoadedEventHandler = null;
+                    NDalicPINVOKE.ThrowExceptionIfExists();
+                }
+            }
+
+            //Release your own unmanaged resources here.
+            //You should not access any managed member here except static instance.
+            //because the execution order of Finalizes is non-deterministic.
+            CancelAll();
+
+            base.Dispose(type);
+        }
+
+        /// <summary>
+        /// Event when pixel data are loaded.
+        /// null or empty PixelData if load failed.
+        /// </summary>
+        /// <remarks>
+        /// We cannot use both ImageLoaded and PixelBufferLoaded events.
+        /// If we use PixelBufferLoaded, ImageLoaded will be ignored.
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<ImageLoadedEventArgs> ImageLoaded
+        {
+            add
+            {
+                if (imageLoadedEventHandler == null)
+                {
+                    imageLoadedCallback = OnImageLoaded;
+                    Interop.AsyncImageLoader.ImageLoadedSignalConnect(SwigCPtr, imageLoadedCallback.ToHandleRef(this));
+                    NDalicPINVOKE.ThrowExceptionIfExists();
+                }
+                imageLoadedEventHandler += value;
+            }
+
+            remove
+            {
+                imageLoadedEventHandler -= value;
+                if (imageLoadedEventHandler == null && imageLoadedCallback != null)
+                {
+                    Interop.AsyncImageLoader.ImageLoadedSignalDisconnect(SwigCPtr, imageLoadedCallback.ToHandleRef(this));
+                    imageLoadedCallback = null;
+                    NDalicPINVOKE.ThrowExceptionIfExists();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Event when pixel buffers are loaded.
+        /// null or empty PixelBuffer list if load failed.
+        /// </summary>
+        /// <remarks>
+        /// We cannot use both ImageLoaded and PixelBufferLoaded events.
+        /// If we use PixelBufferLoaded, ImageLoaded will be ignored.
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<PixelBufferLoadedEventArgs> PixelBufferLoaded
+        {
+            add
+            {
+                if (pixelBufferLoadedEventHandler == null)
+                {
+                    pixelBufferLoadedCallback = OnPixelBufferLoaded;
+                    Interop.AsyncImageLoader.PixelBufferLoadedSignalConnect(SwigCPtr, pixelBufferLoadedCallback.ToHandleRef(this));
+                    NDalicPINVOKE.ThrowExceptionIfExists();
+                }
+                pixelBufferLoadedEventHandler += value;
+            }
+
+            remove
+            {
+                pixelBufferLoadedEventHandler -= value;
+                if (pixelBufferLoadedEventHandler == null && pixelBufferLoadedCallback != null)
+                {
+                    Interop.AsyncImageLoader.PixelBufferLoadedSignalDisconnect(SwigCPtr, pixelBufferLoadedCallback.ToHandleRef(this));
+                    pixelBufferLoadedCallback = null;
+                    NDalicPINVOKE.ThrowExceptionIfExists();
+                }
+            }
+        }
+
+        private void OnImageLoaded(uint loadingTaskId, IntPtr pixelData)
+        {
+            ImageLoadedEventArgs e = new ImageLoadedEventArgs();
+            e.LoadingTaskId = loadingTaskId;
+            e.PixelData = pixelData != IntPtr.Zero ? new PixelData(pixelData, false) : null;
+
+            imageLoadedEventHandler?.Invoke(this, e);
+        }
+
+        private void OnPixelBufferLoaded(uint loadingTaskId, uint pixelBufferListCount, IntPtr pixelBuffer0, IntPtr pixelBuffer1, IntPtr pixelBuffer2)
+        {
+            PixelBufferLoadedEventArgs e = new PixelBufferLoadedEventArgs();
+            e.LoadingTaskId = loadingTaskId;
+            e.PixelBuffers = null;
+
+            if (pixelBufferListCount > 0u)
+            {
+                e.PixelBuffers = new List<PixelBuffer>();
+                if (pixelBufferListCount > 0u)
+                {
+                    e.PixelBuffers.Add(pixelBuffer0 != IntPtr.Zero ? new PixelBuffer(pixelBuffer0, false) : null);
+                }
+                if (pixelBufferListCount > 1u)
+                {
+                    e.PixelBuffers.Add(pixelBuffer1 != IntPtr.Zero ? new PixelBuffer(pixelBuffer0, false) : null);
+                }
+                if (pixelBufferListCount > 2u)
+                {
+                    e.PixelBuffers.Add(pixelBuffer2 != IntPtr.Zero ? new PixelBuffer(pixelBuffer0, false) : null);
+                }
+            }
+
+            pixelBufferLoadedEventHandler?.Invoke(this, e);
+        }
+
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public class ImageLoadedEventArgs : EventArgs
+        {
+            public uint LoadingTaskId { get; internal set; }
+            public PixelData PixelData { get; internal set; }
+        }
+
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public class PixelBufferLoadedEventArgs : EventArgs
+        {
+            public uint LoadingTaskId { get; internal set; }
+            public List<PixelBuffer> PixelBuffers { get; internal set; }
+        }
     }
 }
index 16448fc02d3e6945f3abf0dfa4a701d2f6bf9fe2..908f452bc6bd27f15ae5b053d4a6469d3147984d 100755 (executable)
@@ -25,12 +25,6 @@ namespace Tizen.NUI
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_delete_AsyncImageLoader")]
             public static extern void DeleteAsyncImageLoader(global::System.Runtime.InteropServices.HandleRef jarg1);
 
-            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_new_AsyncImageLoader__SWIG_1")]
-            public static extern global::System.IntPtr NewAsyncImageLoader(global::System.Runtime.InteropServices.HandleRef jarg1);
-
-            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_AsyncImageLoader_Assign")]
-            public static extern global::System.IntPtr Assign(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2);
-
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_AsyncImageLoader_New")]
             public static extern global::System.IntPtr New();
 
@@ -50,8 +44,17 @@ namespace Tizen.NUI
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_AsyncImageLoader_CancelAll")]
             public static extern void CancelAll(global::System.Runtime.InteropServices.HandleRef jarg1);
 
-            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_AsyncImageLoader_ImageLoadedSignal")]
-            public static extern global::System.IntPtr ImageLoadedSignal(global::System.Runtime.InteropServices.HandleRef jarg1);
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_AsyncImageLoader_ImageLoadedSignal_Connect")]
+            public static extern void ImageLoadedSignalConnect(global::System.Runtime.InteropServices.HandleRef asyncImageLoader, global::System.Runtime.InteropServices.HandleRef handler);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_AsyncImageLoader_ImageLoadedSignal_Disconnect")]
+            public static extern void ImageLoadedSignalDisconnect(global::System.Runtime.InteropServices.HandleRef asyncImageLoader, global::System.Runtime.InteropServices.HandleRef handler);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_AsyncImageLoader_PixelBufferLoadedSignal_Connect")]
+            public static extern void PixelBufferLoadedSignalConnect(global::System.Runtime.InteropServices.HandleRef asyncImageLoader, global::System.Runtime.InteropServices.HandleRef handler);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_AsyncImageLoader_PixelBufferLoadedSignal_Disconnect")]
+            public static extern void PixelBufferLoadedSignalDisconnect(global::System.Runtime.InteropServices.HandleRef asyncImageLoader, global::System.Runtime.InteropServices.HandleRef handler);
         }
     }
 }
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/AsyncImageLoaderTest.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/AsyncImageLoaderTest.cs
new file mode 100755 (executable)
index 0000000..bbfff8c
--- /dev/null
@@ -0,0 +1,424 @@
+using System;
+using System.Runtime.InteropServices;
+using Tizen.NUI.BaseComponents;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.Samples
+{
+    public class AsyncImageLoaderTest : IExample
+    {
+        static readonly string VERTEX_SHADER =
+        "//@name AsyncImageLoaderTest.vert\n" +
+        "\n" +
+        "//@version 100\n" +
+        "\n" +
+        "precision highp float;\n" +
+        "INPUT highp vec2 aPosition;\n" +
+        "OUTPUT highp vec2 vTexCoord;\n" +
+        "\n" +
+        "UNIFORM_BLOCK VertBlock\n" +
+        "{\n" +
+        "  UNIFORM highp mat4 uMvpMatrix;\n" +
+        "  UNIFORM highp vec3 uSize;\n" +
+        "};\n" +
+        "void main()\n" +
+        "{\n" +
+        "    vec4 pos = vec4(aPosition, 0.0, 1.0) * vec4(uSize, 1.0);\n" +
+        "    vTexCoord = aPosition + vec2(0.5);\n" +
+        "    gl_Position = uMvpMatrix * pos;\n" +
+        "}\n";
+
+        static readonly string FRAGMENT_SHADER =
+        "//@name AsyncImageLoaderTest.frag\n" +
+        "\n" +
+        "//@version 100\n" +
+        "\n" +
+        "precision highp float;\n" +
+        "\n" +
+        "INPUT highp vec2 vTexCoord;\n" +
+        "UNIFORM sampler2D sTexture;\n" +
+        "UNIFORM_BLOCK FragBlock\n" +
+        "{\n" +
+        "  UNIFORM lowp vec4 uColor;\n" +
+        "};\n" +
+        "\n" +
+        "void main()\n" +
+        "{\n" +
+        "    gl_FragColor = TEXTURE( sTexture, vTexCoord ) * uColor;\n" +
+        "}\n";
+
+        struct SimpleQuadVertex
+        {
+            public UIVector2 position;
+        }
+
+        private PropertyBuffer CreateQuadPropertyBuffer()
+        {
+            /* Create Property buffer */
+            PropertyMap vertexFormat = new PropertyMap();
+            vertexFormat.Add("aPosition", (int)PropertyType.Vector2);
+
+            PropertyBuffer vertexBuffer = new PropertyBuffer(vertexFormat);
+            
+            SimpleQuadVertex vertex1 = new SimpleQuadVertex();
+            SimpleQuadVertex vertex2 = new SimpleQuadVertex();
+            SimpleQuadVertex vertex3 = new SimpleQuadVertex();
+            SimpleQuadVertex vertex4 = new SimpleQuadVertex();
+            vertex1.position = new UIVector2(-0.5f, -0.5f);
+            vertex2.position = new UIVector2(-0.5f, 0.5f);
+            vertex3.position = new UIVector2(0.5f, -0.5f);
+            vertex4.position = new UIVector2(0.5f, 0.5f);
+
+            SimpleQuadVertex[] texturedQuadVertexData = new SimpleQuadVertex[4] { vertex1, vertex2, vertex3, vertex4 };
+
+            int size = Marshal.SizeOf(vertex1);
+            IntPtr pA = Marshal.AllocHGlobal(checked(size * texturedQuadVertexData.Length));
+
+            try
+            {
+                for (int i = 0; i < texturedQuadVertexData.Length; i++)
+                {
+                    Marshal.StructureToPtr(texturedQuadVertexData[i], pA + i * size, true);
+                }
+
+                vertexBuffer.SetData(pA, (uint)texturedQuadVertexData.Length);
+            }
+            catch(Exception e)
+            {
+                Tizen.Log.Error("NUI", "Exception in CreateQuadPropertyBuffer : " + e.Message);
+            }
+            finally
+            {
+                // Free AllocHGlobal memory after call PropertyBuffer.SetData()
+                Marshal.FreeHGlobal(pA);
+            }
+
+            vertexFormat.Dispose();
+
+            return vertexBuffer;
+        }
+
+        private Window win;
+        private View root;
+
+        private static uint numberOfLoaderType = 3u;
+        private static uint numberOfImagesPerEachType = 12u;
+        private static uint totalSubViewCounts = numberOfLoaderType * numberOfImagesPerEachType;
+
+        // Per each view
+        private View[] subView = new View[totalSubViewCounts];
+        private TextureSet[] subViewTextureSet = new TextureSet[totalSubViewCounts];
+        private uint[] subViewLoadId = new uint[totalSubViewCounts];
+
+        // Per each image loader
+        private uint[] subViewUrlIndex = new uint[numberOfLoaderType];
+        private AsyncImageLoader[] imageLoader = new AsyncImageLoader[numberOfLoaderType];
+
+        private static uint InvalidLoadId = 0u;
+
+        private static string DEMO_IMAGE_DIR = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "images/";
+
+        private static readonly string[] ImageUrlList = {
+            DEMO_IMAGE_DIR + "Dali/ContactCard/gallery-small-2.jpg",
+            DEMO_IMAGE_DIR + "Dali/ContactCard/gallery-small-3.jpg",
+            DEMO_IMAGE_DIR + "Dali/ContactCard/contact-cards-mask.png", /// RGBA8888
+            DEMO_IMAGE_DIR + "AGIF/dali-logo-anim.gif",                 /// Animated image, but will be loaded as single frame.
+            "invalid.jpg",                                              /// Invalid image
+        };
+
+        public void Activate()
+        {
+            win = NUIApplication.GetDefaultWindow();
+            win.KeyEvent += WindowKeyEvent;
+
+            root = new View()
+            {
+                Name = "root",
+                WidthResizePolicy = ResizePolicyType.FillToParent,
+                HeightResizePolicy = ResizePolicyType.FillToParent,
+            };
+            win.Add(root);
+
+            AddViews();
+            var infoLabel = new TextLabel()
+            {
+                Text = "1'st : ImageLoaded event.\n2'nd : PixelBufferLoaded event.\n3'rd : PixelBufferLoaded event and change it's own logic.\n\nPress 1~3 key to load each images.\nPress 0 key to load whole images",
+                MultiLine = true,
+            };
+            root.Add(infoLabel);
+
+            for(uint i = 0u; i < numberOfLoaderType; ++i)
+            {
+                imageLoader[i] = new AsyncImageLoader();
+            }
+
+            imageLoader[0].ImageLoaded += OnImageLoaded;
+            imageLoader[1].PixelBufferLoaded += OnPixelBufferLoaded;
+            imageLoader[2].PixelBufferLoaded += OnPixelBufferLoadedWithCustom;
+        }
+
+        public void Deactivate()
+        {
+            for(uint i = 0u; i < numberOfLoaderType; ++i)
+            {
+                imageLoader[i]?.Dispose();
+            }
+            root?.Unparent();
+            root?.Dispose();
+
+            win.KeyEvent -= WindowKeyEvent;
+        }
+
+        private void WindowKeyEvent(object sender, Window.KeyEventArgs e)
+        {
+            if (e.Key.State == Key.StateType.Down)
+            {
+                try
+                {
+                    if (e.Key.KeyPressedName == "0" || e.Key.KeyPressedName == "1")
+                    {
+                        RequestLoad(0);
+                    }
+                    if (e.Key.KeyPressedName == "0" || e.Key.KeyPressedName == "2")
+                    {
+                        RequestLoad(1);
+                    }
+                    if (e.Key.KeyPressedName == "0" || e.Key.KeyPressedName == "3")
+                    {
+                        RequestLoad(2);
+                    }
+                }
+                catch(Exception ex)
+                {
+                    Tizen.Log.Error("NUI", "Exception in RequestLoad : " + ex.Message);
+                }
+            }
+        }
+
+        private void AddViews()
+        {
+            for (uint i = 0u; i < numberOfLoaderType; ++i)
+            {
+                for (uint j = 0u; j < numberOfImagesPerEachType; ++j)
+                {
+                    // View area is red, and additional renderer area is yellow.
+                    View view = new View()
+                    {
+                        Name = $"subView{i}x{j}",
+                        BackgroundColor = Color.Red,
+                        SizeWidth = 100.0f,
+                        SizeHeight = 100.0f,
+                        PositionX = j * 100.0f,
+                        PositionY = i * 250.0f + 50.0f,
+                    };
+
+                    var renderer = GenerateRenderer();
+
+                    view.AddRenderer(renderer);
+                    root.Add(view);
+
+                    uint viewIndex = i * numberOfImagesPerEachType + j;
+
+                    subView[viewIndex] = view;
+                    subViewTextureSet[viewIndex] = renderer.GetTextures();
+                    subViewLoadId[viewIndex] = InvalidLoadId;
+                }
+                subViewUrlIndex[i] = (uint)(ImageUrlList.Length - 1);
+            }
+        }
+
+        private void RequestLoad(uint loaderIndex)
+        {
+            if (loaderIndex >= 3)
+            {
+                throw new ArgumentException($"Invalid subViewIndex comes! {loaderIndex}");
+            }
+
+            if (imageLoader[loaderIndex] == null)
+            {
+                Tizen.Log.Info("NUITest", $"Image loader Disposed!\n");
+                return;
+            }
+
+            for (uint j = 0u; j < numberOfImagesPerEachType; ++j)
+            {
+                uint viewIndex = loaderIndex * numberOfImagesPerEachType + j;
+                if (subViewLoadId[viewIndex] != InvalidLoadId)
+                {
+                    Tizen.Log.Info("NUITest", $"Cancel {viewIndex}'th loadId {subViewLoadId[viewIndex]}\n");
+                    imageLoader[loaderIndex].Cancel(subViewLoadId[viewIndex]);
+                    subViewLoadId[viewIndex] = InvalidLoadId;
+                }
+            }
+
+            subViewUrlIndex[loaderIndex] = (uint)((subViewUrlIndex[loaderIndex] + 1u) % ImageUrlList.Length);
+
+            for (uint j = 0u; j < numberOfImagesPerEachType; ++j)
+            {
+                uint viewIndex = loaderIndex * numberOfImagesPerEachType + j;
+                subViewLoadId[viewIndex] = imageLoader[loaderIndex].Load(ImageUrlList[subViewUrlIndex[loaderIndex]]);
+                Tizen.Log.Info("NUITest", $"Load {viewIndex}'th loadId {subViewLoadId[viewIndex]}\n");
+            }
+        }
+
+        private void OnImageLoaded(object o, AsyncImageLoader.ImageLoadedEventArgs e)
+        {
+            uint viewIndex = 0u;
+            for (uint j = 0u; j < numberOfImagesPerEachType; ++j)
+            {
+                viewIndex = 0u * numberOfImagesPerEachType + j;
+                if (subViewLoadId[viewIndex] == e.LoadingTaskId)
+                {
+                    break;
+                }
+            }
+
+            if (subViewLoadId[viewIndex] != e.LoadingTaskId)
+            {
+                // Should never comes here!
+                Tizen.Log.Error("NUITest", $"{viewIndex}'th loadId not matched with callback input {e.LoadingTaskId}!!\n");
+                return;
+            }
+            if (e.PixelData == null)
+            {
+                Tizen.Log.Info("NUITest", $"Load Fail {viewIndex}'th loadId {subViewLoadId[viewIndex]}\n");
+
+                subViewLoadId[viewIndex] = InvalidLoadId;
+                return;
+            }
+
+            var width = e.PixelData.GetWidth();
+            var height = e.PixelData.GetHeight();
+            var pixelFormat = e.PixelData.GetPixelFormat();
+            Tizen.Log.Info("NUITest", $"Load Complete {viewIndex}'th loadId {subViewLoadId[viewIndex]} : {width}x{height} with format {pixelFormat}\n");
+
+            subViewLoadId[viewIndex] = InvalidLoadId;
+
+            Texture texture = new Texture(TextureType.TEXTURE_2D, pixelFormat, width, height);
+            texture.Upload(e.PixelData);
+
+            subViewTextureSet[viewIndex].SetTexture(0u, texture);
+        }
+
+        private void OnPixelBufferLoaded(object o, AsyncImageLoader.PixelBufferLoadedEventArgs e)
+        {
+            uint viewIndex = 0u;
+            for (uint j = 0u; j < numberOfImagesPerEachType; ++j)
+            {
+                viewIndex = 1u * numberOfImagesPerEachType + j;
+                if (subViewLoadId[viewIndex] == e.LoadingTaskId)
+                {
+                    break;
+                }
+            }
+
+            if (subViewLoadId[viewIndex] != e.LoadingTaskId)
+            {
+                // Should never comes here!
+                Tizen.Log.Error("NUITest", $"{viewIndex}'th loadId {subViewLoadId[viewIndex]} not matched with callback input {e.LoadingTaskId}!!\n");
+                return;
+            }
+            if (e.PixelBuffers == null || e.PixelBuffers.Count == 0 || e.PixelBuffers[0] == null)
+            {
+                Tizen.Log.Info("NUITest", $"Load Fail {viewIndex}'th loadId {subViewLoadId[viewIndex]}\n");
+
+                subViewLoadId[viewIndex] = InvalidLoadId;
+                return;
+            }
+
+            PixelData pixelData = PixelBuffer.Convert(e.PixelBuffers[0]);
+
+            var width = pixelData.GetWidth();
+            var height = pixelData.GetHeight();
+            var pixelFormat = pixelData.GetPixelFormat();
+            Tizen.Log.Info("NUITest", $"Complete {viewIndex}'th loadId {subViewLoadId[viewIndex]} : {width}x{height} with format {pixelFormat}\n");
+
+            subViewLoadId[viewIndex] = InvalidLoadId;
+
+            Texture texture = new Texture(TextureType.TEXTURE_2D, pixelFormat, width, height);
+            texture.Upload(pixelData);
+
+            subViewTextureSet[viewIndex].SetTexture(0u, texture);
+        }
+
+        private void OnPixelBufferLoadedWithCustom(object o, AsyncImageLoader.PixelBufferLoadedEventArgs e)
+        {
+            uint viewIndex = 0u;
+            for (uint j = 0u; j < numberOfImagesPerEachType; ++j)
+            {
+                viewIndex = 2u * numberOfImagesPerEachType + j;
+                if (subViewLoadId[viewIndex] == e.LoadingTaskId)
+                {
+                    break;
+                }
+            }
+
+            if (subViewLoadId[viewIndex] != e.LoadingTaskId)
+            {
+                // Should never comes here!
+                Tizen.Log.Error("NUITest", $"{viewIndex}'th loadId {subViewLoadId[viewIndex]} not matched with callback input {e.LoadingTaskId}!!\n");
+                return;
+            }
+            if (e.PixelBuffers == null || e.PixelBuffers.Count == 0 || e.PixelBuffers[0] == null)
+            {
+                Tizen.Log.Info("NUITest", $"Load Fail {viewIndex}'th loadId {subViewLoadId[viewIndex]}\n");
+
+                subViewLoadId[viewIndex] = InvalidLoadId;
+                return;
+            }
+
+            PixelBuffer pixelBuffer = e.PixelBuffers[0];
+
+            // Do something custom actions for this pixelBuffer.
+            pixelBuffer.Rotate(new Degree((float)viewIndex * 20.0f));
+
+            PixelData pixelData = PixelBuffer.Convert(pixelBuffer);
+
+            var width = pixelData.GetWidth();
+            var height = pixelData.GetHeight();
+            var pixelFormat = pixelData.GetPixelFormat();
+            Tizen.Log.Info("NUITest", $"Complete {viewIndex}'th loadId {subViewLoadId[viewIndex]} : {width}x{height} with format {pixelFormat}\n");
+
+            subViewLoadId[viewIndex] = InvalidLoadId;
+
+            Texture texture = new Texture(TextureType.TEXTURE_2D, pixelFormat, width, height);
+            texture.Upload(pixelData);
+
+            subViewTextureSet[viewIndex].SetTexture(0u, texture);
+        }
+
+        private Geometry GenerateGeometry()
+        {
+            using PropertyBuffer vertexBuffer = CreateQuadPropertyBuffer();
+            Geometry geometry = new Geometry();
+            geometry.AddVertexBuffer(vertexBuffer);
+            geometry.SetType(Geometry.Type.TRIANGLE_STRIP);
+
+            return geometry;
+        }
+
+        private Shader GenerateShader()
+        {
+            Shader shader = new Shader(VERTEX_SHADER, FRAGMENT_SHADER, "RendererUpdateAreaTest");
+            return shader;
+        }
+
+        private Renderer GenerateRenderer()
+        {
+            Renderer renderer = new Renderer();
+            Geometry geometry = GenerateGeometry();
+            Shader shader = GenerateShader();
+            TextureSet textureSet = new TextureSet();
+
+            // Set some invalid texture so we can ignore rendering.
+            Texture texture = new Texture(TextureType.TEXTURE_2D, PixelFormat.RGBA8888, 1u, 1u);
+            textureSet.SetTexture(0u, texture);
+
+            renderer.SetGeometry(geometry);
+            renderer.SetShader(shader);
+            renderer.SetTextures(textureSet);
+
+            return renderer;
+        }
+    }
+}