[dali_1.2.49] Merge branch 'devel/master' 55/140055/1
authorDavid Steele <david.steele@samsung.com>
Fri, 21 Jul 2017 13:25:18 +0000 (14:25 +0100)
committerDavid Steele <david.steele@samsung.com>
Fri, 21 Jul 2017 13:25:18 +0000 (14:25 +0100)
Change-Id: I7f3cf6dc4fbbac8de9b8af584f545bc17368e81d

44 files changed:
automated-tests/resources/application-icon-20.png [new file with mode: 0644]
automated-tests/resources/application-icon-21.png [new file with mode: 0644]
automated-tests/resources/application-icon-22.png [new file with mode: 0644]
automated-tests/resources/application-icon-23.png [new file with mode: 0644]
automated-tests/resources/application-icon-24.png [new file with mode: 0644]
automated-tests/resources/application-icon-25.png [new file with mode: 0644]
automated-tests/resources/application-icon-26.png [new file with mode: 0644]
automated-tests/resources/application-icon-27.png [new file with mode: 0644]
automated-tests/resources/application-icon-28.png [new file with mode: 0644]
automated-tests/resources/application-icon-29.png [new file with mode: 0644]
automated-tests/resources/application-icon-30.png [new file with mode: 0644]
automated-tests/src/dali-toolkit-internal/utc-Dali-TextureManager.cpp
automated-tests/src/dali-toolkit/CMakeLists.txt
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-gl-abstraction.cpp
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-gl-abstraction.h
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.h
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-timer.cpp
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-timer.h
automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp [new file with mode: 0644]
automated-tests/src/dali-toolkit/utc-Dali-Visual.cpp
dali-toolkit/devel-api/visuals/image-visual-properties-devel.h
dali-toolkit/internal/controls/image-view/image-view-impl.cpp
dali-toolkit/internal/controls/image-view/image-view-impl.h
dali-toolkit/internal/file.list
dali-toolkit/internal/text/text-controller.cpp
dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp
dali-toolkit/internal/visuals/animated-image/animated-image-visual.h
dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp [new file with mode: 0644]
dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h [new file with mode: 0644]
dali-toolkit/internal/visuals/animated-image/image-cache.cpp [new file with mode: 0644]
dali-toolkit/internal/visuals/animated-image/image-cache.h [new file with mode: 0644]
dali-toolkit/internal/visuals/animated-image/rolling-image-cache.cpp [new file with mode: 0644]
dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h [new file with mode: 0644]
dali-toolkit/internal/visuals/image/image-visual.cpp
dali-toolkit/internal/visuals/image/image-visual.h
dali-toolkit/internal/visuals/texture-manager.cpp
dali-toolkit/internal/visuals/texture-upload-observer.h
dali-toolkit/internal/visuals/visual-factory-impl.cpp
dali-toolkit/internal/visuals/visual-string-constants.cpp
dali-toolkit/internal/visuals/visual-string-constants.h
dali-toolkit/public-api/dali-toolkit-version.cpp
dali-toolkit/public-api/visuals/image-visual-properties.h
packaging/dali-toolkit.spec

diff --git a/automated-tests/resources/application-icon-20.png b/automated-tests/resources/application-icon-20.png
new file mode 100644 (file)
index 0000000..aecb4a6
Binary files /dev/null and b/automated-tests/resources/application-icon-20.png differ
diff --git a/automated-tests/resources/application-icon-21.png b/automated-tests/resources/application-icon-21.png
new file mode 100644 (file)
index 0000000..f5b1418
Binary files /dev/null and b/automated-tests/resources/application-icon-21.png differ
diff --git a/automated-tests/resources/application-icon-22.png b/automated-tests/resources/application-icon-22.png
new file mode 100644 (file)
index 0000000..4221262
Binary files /dev/null and b/automated-tests/resources/application-icon-22.png differ
diff --git a/automated-tests/resources/application-icon-23.png b/automated-tests/resources/application-icon-23.png
new file mode 100644 (file)
index 0000000..4e7507b
Binary files /dev/null and b/automated-tests/resources/application-icon-23.png differ
diff --git a/automated-tests/resources/application-icon-24.png b/automated-tests/resources/application-icon-24.png
new file mode 100644 (file)
index 0000000..680257c
Binary files /dev/null and b/automated-tests/resources/application-icon-24.png differ
diff --git a/automated-tests/resources/application-icon-25.png b/automated-tests/resources/application-icon-25.png
new file mode 100644 (file)
index 0000000..a404573
Binary files /dev/null and b/automated-tests/resources/application-icon-25.png differ
diff --git a/automated-tests/resources/application-icon-26.png b/automated-tests/resources/application-icon-26.png
new file mode 100644 (file)
index 0000000..10138e5
Binary files /dev/null and b/automated-tests/resources/application-icon-26.png differ
diff --git a/automated-tests/resources/application-icon-27.png b/automated-tests/resources/application-icon-27.png
new file mode 100644 (file)
index 0000000..5dbb3b3
Binary files /dev/null and b/automated-tests/resources/application-icon-27.png differ
diff --git a/automated-tests/resources/application-icon-28.png b/automated-tests/resources/application-icon-28.png
new file mode 100644 (file)
index 0000000..1d71a31
Binary files /dev/null and b/automated-tests/resources/application-icon-28.png differ
diff --git a/automated-tests/resources/application-icon-29.png b/automated-tests/resources/application-icon-29.png
new file mode 100644 (file)
index 0000000..040c36b
Binary files /dev/null and b/automated-tests/resources/application-icon-29.png differ
diff --git a/automated-tests/resources/application-icon-30.png b/automated-tests/resources/application-icon-30.png
new file mode 100644 (file)
index 0000000..445590d
Binary files /dev/null and b/automated-tests/resources/application-icon-30.png differ
index c38a70c..2d06a44 100644 (file)
@@ -34,7 +34,7 @@ public:
   {
   }
 
-  void UploadComplete( bool loadSuccess, TextureSet textureSet, bool useAtlasing, const Vector4& atlasRect )
+  void UploadComplete( bool loadSuccess, int32_t textureId, TextureSet textureSet, bool useAtlasing, const Vector4& atlasRect )
   {
     mLoaded = loadSuccess;
     mObserverCalled = true;
index 39b37de..d3b5bba 100755 (executable)
@@ -8,6 +8,7 @@ SET(CAPI_LIB "dali-toolkit")
 # List of test case sources (Only these get parsed for test cases)
 SET(TC_SOURCES
    utc-Dali-Alignment.cpp
+   utc-Dali-AnimatedImageVisual.cpp
    utc-Dali-BloomView.cpp
    utc-Dali-BubbleEmitter.cpp
    utc-Dali-Builder.cpp
index 6e9b9f0..c4bc01d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 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.
@@ -67,6 +67,7 @@ void TestGlAbstraction::Initialize()
   mLastBlendFuncSrcAlpha  = 0;
   mLastBlendFuncDstAlpha  = 0;
   mLastAutoTextureIdUsed = 0;
+  mNumGeneratedTextures = 0;
   mLastShaderIdUsed = 0;
   mLastProgramIdUsed = 0;
   mLastUniformIdUsed = 0;
@@ -108,7 +109,7 @@ bool BlendEnabled(const Dali::TraceCallStack& callStack)
 {
   std::stringstream out;
   out << GL_BLEND;
-  bool blendEnabled = callStack.FindMethodAndParams("Enable", out.str());
+  bool blendEnabled = callStack.FindMethodAndParams( "Enable", out.str() );
   return blendEnabled;
 }
 
@@ -116,6 +117,6 @@ bool BlendDisabled(const Dali::TraceCallStack& callStack)
 {
   std::stringstream out;
   out << GL_BLEND;
-  bool blendEnabled = callStack.FindMethodAndParams("Disable", out.str());
+  bool blendEnabled = callStack.FindMethodAndParams( "Disable", out.str() );
   return blendEnabled;
 }
index 5e71779..8408b10 100644 (file)
@@ -2,7 +2,7 @@
 #define TEST_GL_ABSTRACTION_H
 
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 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.
@@ -426,6 +426,7 @@ public:
       paramName<<"texture["<<i<<"]";
       namedParams[paramName.str()] = ToString(textures[i]);
       mDeletedTextureIds.push_back(textures[i]);
+      mNumGeneratedTextures--;
     }
     out << "]";
 
@@ -642,6 +643,7 @@ public:
       {
         *(textures+i) = ++mLastAutoTextureIdUsed;
       }
+      mNumGeneratedTextures++;
     }
 
     TraceCallStack::NamedParams namedParams;
@@ -663,6 +665,15 @@ public:
     mTextureTrace.PushCall("GenTextures", out.str(), namedParams);
   }
 
+  inline GLuint GetLastGenTextureId()
+  {
+    return mLastAutoTextureIdUsed;
+  }
+  inline GLuint GetNumGeneratedTextures()
+  {
+    return mNumGeneratedTextures;
+  }
+
   inline void GetActiveAttrib(GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, char* name)
   {
   }
@@ -2158,6 +2169,7 @@ private:
 
   // Data for manipulating the IDs returned by GenTextures
   GLuint mLastAutoTextureIdUsed;
+  GLuint mNumGeneratedTextures;
   std::vector<GLuint> mNextTextureIds;
   std::vector<GLuint> mDeletedTextureIds;
   std::vector<GLuint> mBoundTextures;
index 0f037d3..5b8a324 100644 (file)
@@ -99,10 +99,8 @@ CallbackBase* EventThreadCallback::GetCallback()
 namespace Test
 {
 
-bool WaitForEventThreadTrigger( int triggerCount )
+bool WaitForEventThreadTrigger( int triggerCount, int timeoutInSeconds )
 {
-  const int TEST_TIMEOUT(30);
-
   struct timespec startTime;
   struct timespec now;
   clock_gettime( CLOCK_REALTIME, &startTime );
@@ -129,7 +127,7 @@ bool WaitForEventThreadTrigger( int triggerCount )
       }
     }
     clock_gettime( CLOCK_REALTIME, &now );
-    if( now.tv_sec - startTime.tv_sec > TEST_TIMEOUT )
+    if( now.tv_sec - startTime.tv_sec > timeoutInSeconds )
     {
       // Ensure we break out of the loop if elapsed time has passed
       break;
index 1ae27d2..d718e10 100644 (file)
@@ -66,7 +66,7 @@ namespace Test
  *
  * Will wait for a maximum of 30s before failing the test and returning.
  */
-bool WaitForEventThreadTrigger( int triggerCount );
+bool WaitForEventThreadTrigger( int triggerCount, int timeoutInSeconds=30 );
 
 }
 
index f46dacd..2259719 100644 (file)
@@ -234,3 +234,18 @@ void Timer::MockEmitSignal()
 
 } // namespace Dali
 
+
+namespace Test
+{
+
+int GetTimerCount()
+{
+  return Dali::Internal::Adaptor::gTimerCount;
+}
+
+void EmitGlobalTimerSignal()
+{
+  Dali::Internal::Adaptor::gTickSignal.Emit();
+}
+
+}
index e0c886c..0110eaa 100644 (file)
@@ -62,4 +62,10 @@ private:
 
 } // namespace Dali
 
+namespace Test
+{
+int GetTimerCount();
+void EmitGlobalTimerSignal();
+}
+
 #endif // __DALI_TOOLKIT_TOOLKIT_TIMER_H__
diff --git a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp
new file mode 100644 (file)
index 0000000..97f04ac
--- /dev/null
@@ -0,0 +1,553 @@
+
+/*
+ * Copyright (c) 2017 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.
+ */
+
+#include <iostream>
+#include <stdlib.h>
+#include <dali-toolkit-test-suite-utils.h>
+#include <toolkit-timer.h>
+#include <toolkit-event-thread-callback.h>
+#include <dali-toolkit/dali-toolkit.h>
+#include <dali-toolkit/devel-api/visuals/visual-properties-devel.h>
+#include <dali-toolkit/devel-api/visuals/image-visual-properties-devel.h>
+#include <dali-toolkit/devel-api/visual-factory/visual-factory.h>
+#include <dali-toolkit/devel-api/controls/control-devel.h>
+#include "dummy-control.h"
+
+using namespace Dali;
+using namespace Dali::Toolkit;
+
+void dali_animated_image_visual_startup(void)
+{
+  test_return_value = TET_UNDEF;
+}
+
+void dali_animated_image_visual_cleanup(void)
+{
+  test_return_value = TET_PASS;
+}
+
+namespace
+{
+const char* TEST_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR  "/application-icon-%02d.png";
+const char* TEST_GIF_FILE_NAME = TEST_RESOURCE_DIR "/anim.gif";
+}
+
+
+void CopyUrlsIntoArray( Property::Array& urls, int startIndex=0 )
+{
+  for( int i=20+startIndex;i<=30;++i)
+  {
+    char* url;
+    if(asprintf(&url, TEST_IMAGE_FILE_NAME, i) > 0)
+    {
+      Property::Value value(url);
+      urls.Add(value);
+      free(url);
+    }
+  }
+}
+
+int UtcDaliAnimatedImageVisualGetPropertyMap01(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline( "UtcDaliAnimatedImageVisualGetPropertyMap" );
+
+  // request AnimatedImageVisual with a property map
+  VisualFactory factory = VisualFactory::Get();
+  Visual::Base animatedImageVisual = factory.CreateVisual(
+    Property::Map()
+    .Add( Visual::Property::TYPE, DevelVisual::ANIMATED_IMAGE )
+    .Add( ImageVisual::Property::URL, TEST_GIF_FILE_NAME )
+    .Add( ImageVisual::Property::PIXEL_AREA, Vector4() )
+    .Add( ImageVisual::Property::WRAP_MODE_U, WrapMode::REPEAT )
+    .Add( ImageVisual::Property::WRAP_MODE_V, WrapMode::DEFAULT ));
+
+  Property::Map resultMap;
+  animatedImageVisual.CreatePropertyMap( resultMap );
+  // check the property values from the returned map from a visual
+  Property::Value* value = resultMap.Find( Visual::Property::TYPE,  Property::INTEGER );
+  DALI_TEST_CHECK( value );
+  DALI_TEST_CHECK( value->Get<int>() == DevelVisual::ANIMATED_IMAGE );
+
+  value = resultMap.Find( ImageVisual::Property::URL,  Property::STRING );
+  DALI_TEST_CHECK( value );
+  DALI_TEST_CHECK( value->Get<std::string>() == TEST_GIF_FILE_NAME );
+
+  // request AnimatedImageVisual with an URL
+  Visual::Base animatedImageVisual2 = factory.CreateVisual( TEST_GIF_FILE_NAME, ImageDimensions() );
+  resultMap.Clear();
+  animatedImageVisual2.CreatePropertyMap( resultMap );
+  // check the property values from the returned map from a visual
+  value = resultMap.Find( Visual::Property::TYPE,  Property::INTEGER );
+  DALI_TEST_CHECK( value );
+  DALI_TEST_CHECK( value->Get<int>() == DevelVisual::ANIMATED_IMAGE );
+
+  value = resultMap.Find( ImageVisual::Property::URL,  Property::STRING );
+  DALI_TEST_CHECK( value );
+  DALI_TEST_CHECK( value->Get<std::string>() == TEST_GIF_FILE_NAME );
+
+  END_TEST;
+}
+
+
+int UtcDaliAnimatedImageVisualGetPropertyMap02(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline( "UtcDaliAnimatedImageVisualGetPropertyMap for multi image" );
+
+  // request AnimatedImageVisual with a property map
+  VisualFactory factory = VisualFactory::Get();
+  Property::Array urls;
+  CopyUrlsIntoArray( urls );
+
+  Visual::Base animatedImageVisual = factory.CreateVisual(
+    Property::Map()
+    .Add( Visual::Property::TYPE, DevelVisual::ANIMATED_IMAGE )
+    .Add( "url", urls )
+    .Add( "batchSize", 4 )
+    .Add( "cacheSize", 8 )
+    .Add( "frameDelay", 200 )
+    .Add( "pixelArea", Vector4() )
+    .Add( "wrapModeU", WrapMode::REPEAT )
+    .Add( "wrapModeV", WrapMode::DEFAULT ));
+
+  Property::Map resultMap;
+  animatedImageVisual.CreatePropertyMap( resultMap );
+  // check the property values from the returned map from a visual
+  Property::Value* value = resultMap.Find( Visual::Property::TYPE,  Property::INTEGER );
+  DALI_TEST_CHECK( value );
+  DALI_TEST_CHECK( value->Get<int>() == DevelVisual::ANIMATED_IMAGE );
+
+  value = resultMap.Find( DevelImageVisual::Property::URL, "url" );
+  DALI_TEST_CHECK( value );
+  Property::Array* resultUrls = value->GetArray();
+  DALI_TEST_CHECK( resultUrls );
+  DALI_TEST_EQUALS( resultUrls->Count(), urls.Count(), TEST_LOCATION );
+
+  value = resultMap.Find( DevelImageVisual::Property::BATCH_SIZE, "batchSize" );
+  DALI_TEST_CHECK( value );
+  DALI_TEST_EQUALS( value->Get<int>(), 4, TEST_LOCATION );
+
+  value = resultMap.Find( DevelImageVisual::Property::CACHE_SIZE, "cacheSize" );
+  DALI_TEST_CHECK( value );
+  DALI_TEST_EQUALS( value->Get<int>(), 8, TEST_LOCATION );
+
+  value = resultMap.Find( DevelImageVisual::Property::FRAME_DELAY, "frameDelay" );
+  DALI_TEST_CHECK( value );
+  DALI_TEST_EQUALS( value->Get<int>(), 200, TEST_LOCATION );
+
+  END_TEST;
+}
+
+
+
+
+int UtcDaliAnimatedImageVisualMultiImage01(void)
+{
+  ToolkitTestApplication application;
+  TestGlAbstraction& gl = application.GetGlAbstraction();
+
+  Property::Array urls;
+  CopyUrlsIntoArray( urls );
+
+  {
+    Property::Map propertyMap;
+    propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE );
+    propertyMap.Insert( DevelImageVisual::Property::URL, Property::Value(urls) );
+    propertyMap.Insert( DevelImageVisual::Property::BATCH_SIZE, 4);
+    propertyMap.Insert( DevelImageVisual::Property::CACHE_SIZE, 8);
+    propertyMap.Insert( DevelImageVisual::Property::FRAME_DELAY, 100);
+
+    VisualFactory factory = VisualFactory::Get();
+    Visual::Base visual = factory.CreateVisual( propertyMap );
+
+    // Expect that a batch of 4 textures has been requested. These will be serially loaded
+    // below.
+
+    DummyControl dummyControl = DummyControl::New(true);
+    Impl::DummyControl& dummyImpl = static_cast<Impl::DummyControl&>(dummyControl.GetImplementation());
+    dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual );
+
+    dummyControl.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
+    Stage::GetCurrent().Add( dummyControl );
+    application.SendNotification();
+    application.Render(16);
+
+    tet_infoline( "Ready the visual after the visual is on stage" );
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 4 ), true, TEST_LOCATION );
+
+    tet_infoline( "Test that a timer has been started" );
+    DALI_TEST_EQUALS( Test::GetTimerCount(), 1, TEST_LOCATION );
+
+    TraceCallStack& textureTrace = gl.GetTextureTrace();
+    textureTrace.Enable(true);
+
+    application.SendNotification();
+    application.Render(16);
+
+    DALI_TEST_EQUALS( gl.GetLastGenTextureId(), 4, TEST_LOCATION );
+    DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION );
+
+    tet_infoline( "Test that after 1 tick, and file loads completed, that we have 7 textures" );
+    Test::EmitGlobalTimerSignal();
+
+    // Expect the second batch has been requested
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 4 ), true, TEST_LOCATION );
+
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 7, TEST_LOCATION );
+
+
+    tet_infoline( "Test that after 2 ticks that we have 6 textures" );
+
+    Test::EmitGlobalTimerSignal();
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 6, TEST_LOCATION );
+
+    tet_infoline("And that at least 2 textures were requested");
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION );
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 8, TEST_LOCATION );
+
+
+    tet_infoline( "Test that after 3rd tick that we have 7 textures and 1 request" );
+    Test::EmitGlobalTimerSignal();
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 7, TEST_LOCATION );
+
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 8, TEST_LOCATION );
+
+    dummyControl.Unparent();
+  }
+  tet_infoline("Test that removing the visual from stage deletes all textures");
+  application.SendNotification();
+  application.Render(16);
+  DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 0, TEST_LOCATION );
+
+  END_TEST;
+}
+
+int UtcDaliAnimatedImageVisualMultiImage02(void)
+{
+  ToolkitTestApplication application;
+  TestGlAbstraction& gl = application.GetGlAbstraction();
+
+  tet_infoline( "Test that the animated visual still works with zero sized cache" );
+
+  {
+    Property::Array urls;
+    CopyUrlsIntoArray( urls );
+
+    Property::Map propertyMap;
+    propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE );
+    propertyMap.Insert( DevelImageVisual::Property::URL, Property::Value(urls) );
+    propertyMap.Insert( DevelImageVisual::Property::BATCH_SIZE, 0);
+    propertyMap.Insert( DevelImageVisual::Property::CACHE_SIZE, 0);
+    propertyMap.Insert( DevelImageVisual::Property::FRAME_DELAY, 100);
+
+    VisualFactory factory = VisualFactory::Get();
+    Visual::Base visual = factory.CreateVisual( propertyMap ); // TexMgr::Request load tId:0
+
+    // Expect that each image is loaded each tick
+
+    DummyControl dummyControl = DummyControl::New(true);
+    Impl::DummyControl& dummyImpl = static_cast<Impl::DummyControl&>(dummyControl.GetImplementation());
+    dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual );
+
+    dummyControl.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
+    Stage::GetCurrent().Add( dummyControl );
+    application.SendNotification();
+    application.Render(16);
+
+    tet_infoline( "Ready the visual after the visual is on stage" );
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+    application.SendNotification();
+    application.Render(16);//glGenTextures 1
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 1, TEST_LOCATION );
+
+    tet_infoline( "Test that each tick, a new image is requested" );
+    Test::EmitGlobalTimerSignal(); // TexMgr::Remove tId:0
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1, 10 ), true, TEST_LOCATION );
+    application.SendNotification();
+    application.Render(16);//glGenTextures 2
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 1, TEST_LOCATION );
+
+    tet_infoline( "Test that each tick, a new image is requested" );
+    Test::EmitGlobalTimerSignal(); // Internal::~TextureSet()
+    application.SendNotification();
+    application.Render(16);//glDeleteTextures 2
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1, 10 ), true, TEST_LOCATION );
+    application.SendNotification();
+    application.Render(16);//glGenTextures 3
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 1, TEST_LOCATION );
+
+    tet_infoline( "Test that each tick, a new image is requested" );
+    Test::EmitGlobalTimerSignal();
+    application.SendNotification();
+    application.Render(16);//glDeleteTextures 3
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1, 10 ), true, TEST_LOCATION );
+    application.SendNotification();
+    application.Render(16);//Gen4
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 1, TEST_LOCATION );
+    dummyControl.Unparent();
+  }
+  tet_infoline("Test that removing the visual from stage deletes all textures");
+  application.SendNotification();
+  application.Render(16);
+  DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 0, TEST_LOCATION );
+
+  END_TEST;
+}
+
+int UtcDaliAnimatedImageVisualMultiImage03(void)
+{
+  ToolkitTestApplication application;
+  TestGlAbstraction& gl = application.GetGlAbstraction();
+
+  {
+    Property::Array urls1, urls2;
+    CopyUrlsIntoArray( urls1 );
+    CopyUrlsIntoArray( urls2 );
+
+    Property::Map animatedImageMap1;
+    animatedImageMap1.Insert(Visual::Property::TYPE, Visual::IMAGE );
+    animatedImageMap1.Insert( DevelImageVisual::Property::URL, Property::Value(urls1) );
+    animatedImageMap1.Insert( DevelImageVisual::Property::BATCH_SIZE, 3);
+    animatedImageMap1.Insert( DevelImageVisual::Property::CACHE_SIZE, 3);
+    animatedImageMap1.Insert( DevelImageVisual::Property::FRAME_DELAY, 100);
+
+    Property::Map animatedImageMap2;
+    animatedImageMap2.Insert(Visual::Property::TYPE, Visual::IMAGE );
+    animatedImageMap2.Insert( DevelImageVisual::Property::URL, Property::Value(urls2) );
+    animatedImageMap2.Insert( DevelImageVisual::Property::BATCH_SIZE, 2);
+    animatedImageMap2.Insert( DevelImageVisual::Property::CACHE_SIZE, 2);
+    animatedImageMap2.Insert( DevelImageVisual::Property::FRAME_DELAY, 100);
+
+    VisualFactory factory = VisualFactory::Get();
+    Visual::Base animatedImageVisual1 = factory.CreateVisual( animatedImageMap1 );
+
+    tet_infoline( "Create two image views with the same URLs, offset by 1 frame.");
+
+    DummyControl dummyControl1 = DummyControl::New(true);
+    Impl::DummyControl& dummyImpl1 = static_cast<Impl::DummyControl&>(dummyControl1.GetImplementation());
+    dummyImpl1.RegisterVisual( DummyControl::Property::TEST_VISUAL, animatedImageVisual1 );
+    dummyControl1.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
+    Stage::GetCurrent().Add( dummyControl1 );
+
+    application.SendNotification();
+    application.Render(16);
+
+    tet_infoline( "Ready the requested image after the first visual is on stage" );
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 3 ), true, TEST_LOCATION );
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 3, TEST_LOCATION );
+
+    Visual::Base animatedImageVisual2 = factory.CreateVisual( animatedImageMap2 );
+    DummyControl dummyControl2 = DummyControl::New(true);
+    Impl::DummyControl& dummyImpl2 = static_cast<Impl::DummyControl&>(dummyControl2.GetImplementation());
+    dummyImpl2.RegisterVisual( DummyControl::Property::TEST_VISUAL, animatedImageVisual2 );
+    dummyControl2.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
+    Stage::GetCurrent().Add( dummyControl2 );
+    application.SendNotification();
+    application.Render(16);
+
+    tet_infoline( "The texture cache should be holding the requested images; check that the renderer has a texture" );
+    TextureSet ts = dummyControl2.GetRendererAt(0).GetTextures();
+    Texture t1 = ts.GetTexture( 0 );
+    DALI_TEST_EQUALS( ts.GetTextureCount(), 1, TEST_LOCATION );
+
+    tet_infoline( "Test that on the first tick, 1 new image is requested" );
+    Test::EmitGlobalTimerSignal(); // Both visuals should tick
+
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 3, TEST_LOCATION );
+
+    ts = dummyControl2.GetRendererAt(0).GetTextures();
+    Texture t2 = ts.GetTexture( 0 );
+    DALI_TEST_CHECK( t1 != t2 );
+
+    dummyControl1.Unparent();
+    dummyControl2.Unparent();
+  }
+  tet_infoline("Test that removing the visual from stage deletes all textures");
+  application.SendNotification();
+  application.Render(16);
+  DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 0, TEST_LOCATION );
+
+  END_TEST;
+}
+
+
+int UtcDaliAnimatedImageVisualMultiImage04(void)
+{
+  ToolkitTestApplication application;
+  TestGlAbstraction& gl = application.GetGlAbstraction();
+  TraceCallStack& textureTrace = gl.GetTextureTrace();
+  textureTrace.Enable(true);
+
+  tet_infoline( "Test that if the cache size is the same as the number of urls, that once the cache is full, no new images are loaded" );
+
+  Property::Array urls;
+  CopyUrlsIntoArray( urls );
+
+  {
+    Property::Map propertyMap;
+    propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE );
+    propertyMap.Insert( DevelImageVisual::Property::URL, Property::Value(urls) );
+    propertyMap.Insert( DevelImageVisual::Property::BATCH_SIZE, 6);
+    propertyMap.Insert( DevelImageVisual::Property::CACHE_SIZE, 11);
+    propertyMap.Insert( DevelImageVisual::Property::FRAME_DELAY, 100);
+
+    VisualFactory factory = VisualFactory::Get();
+    Visual::Base visual = factory.CreateVisual( propertyMap );
+
+    tet_infoline( "Expect that a batch of 7 textures has been requested." );
+
+    DummyControl dummyControl = DummyControl::New(true);
+    Impl::DummyControl& dummyImpl = static_cast<Impl::DummyControl&>(dummyControl.GetImplementation());
+    dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual );
+
+    dummyControl.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
+    Stage::GetCurrent().Add( dummyControl );
+    application.SendNotification();
+    application.Render(16);
+
+    tet_infoline( "Wait for the first batch to complete" );
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 6 ), true, TEST_LOCATION );
+
+    tet_infoline( "Test that a timer has been started" );
+    DALI_TEST_EQUALS( Test::GetTimerCount(), 1, TEST_LOCATION );
+
+    application.SendNotification();
+    application.Render(16);
+
+    DALI_TEST_EQUALS( gl.GetLastGenTextureId(), 6, TEST_LOCATION );
+    tet_infoline( "Test that after 1 tick, and 5 file loads completed, that we have 11 textures" );
+    Test::EmitGlobalTimerSignal();
+    application.SendNotification();
+    application.Render(16);
+
+    // Expect the second batch has been requested
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 5 ), true, TEST_LOCATION );
+
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 11, TEST_LOCATION );
+
+    tet_infoline( "Test that after 2 ticks that we have 11 textures and no requests" );
+
+    Test::EmitGlobalTimerSignal();
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1, 5 ), false, TEST_LOCATION );
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 11, TEST_LOCATION );
+
+    tet_infoline( "Test that after 3rd tick that we have 11 textures and no requests" );
+    Test::EmitGlobalTimerSignal();
+    application.SendNotification();
+    application.Render(16);
+
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1, 5 ), false, TEST_LOCATION );
+    application.SendNotification();
+    application.Render(16);
+    DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 11, TEST_LOCATION );
+
+    dummyControl.Unparent();
+  }
+
+  tet_infoline("Test that removing the visual from stage deletes all textures");
+  application.SendNotification();
+  application.Render(16);
+  DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 0, TEST_LOCATION );
+
+  END_TEST;
+}
+
+
+int UtcDaliAnimatedImageVisualMultiImage05(void)
+{
+  ToolkitTestApplication application;
+  TestGlAbstraction& gl = application.GetGlAbstraction();
+
+  tet_infoline( "Test that if the cache size is the same as the number of urls, that removing a partially loaded visual removes all textures" );
+
+  Property::Array urls;
+  CopyUrlsIntoArray( urls );
+
+  {
+    Property::Map propertyMap;
+    propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE );
+    propertyMap.Insert( DevelImageVisual::Property::URL, Property::Value(urls) );
+    propertyMap.Insert( DevelImageVisual::Property::BATCH_SIZE, 4);
+    propertyMap.Insert( DevelImageVisual::Property::CACHE_SIZE, 11);
+    propertyMap.Insert( DevelImageVisual::Property::FRAME_DELAY, 100);
+
+    VisualFactory factory = VisualFactory::Get();
+    Visual::Base visual = factory.CreateVisual( propertyMap );
+
+    tet_infoline( "Expect that a batch of 4 textures has been requested." );
+
+    DummyControl dummyControl = DummyControl::New(true);
+    Impl::DummyControl& dummyImpl = static_cast<Impl::DummyControl&>(dummyControl.GetImplementation());
+    dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual );
+
+    dummyControl.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
+    Stage::GetCurrent().Add( dummyControl );
+    application.SendNotification();
+    application.Render(16);
+
+    tet_infoline( "Wait for the first batch to complete" );
+    DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 4 ), true, TEST_LOCATION );
+
+    tet_infoline( "Test that a timer has been started" );
+    DALI_TEST_EQUALS( Test::GetTimerCount(), 1, TEST_LOCATION );
+
+    application.SendNotification();
+    application.Render(16);
+
+    tet_infoline( "Test that a timer has been started" );
+    Test::EmitGlobalTimerSignal();
+    application.SendNotification();
+    application.Render(16);
+
+    dummyControl.Unparent();
+  }
+
+  application.SendNotification();
+  application.Render(16);
+  DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 0, TEST_LOCATION );
+
+  tet_infoline( "Test that pending batch of image loads are cancelled instead of uploaded");
+  DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 4 ), true, TEST_LOCATION );
+  application.SendNotification();
+  application.Render(16);
+  DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 0, TEST_LOCATION );
+
+  END_TEST;
+}
index f8e61b8..3c774c7 100644 (file)
@@ -39,10 +39,10 @@ using namespace Dali::Toolkit;
 
 namespace
 {
+const char* TEST_GIF_FILE_NAME = TEST_RESOURCE_DIR "/anim.gif";
 const char* TEST_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR "/gallery-small-1.jpg";
 const char* TEST_NPATCH_FILE_NAME =  "gallery_image_01.9.jpg";
 const char* TEST_SVG_FILE_NAME = TEST_RESOURCE_DIR "/svg1.svg";
-const char* TEST_GIF_FILE_NAME = TEST_RESOURCE_DIR "/anim.gif";
 const char* TEST_OBJ_FILE_NAME = TEST_RESOURCE_DIR "/Cube.obj";
 const char* TEST_MTL_FILE_NAME = TEST_RESOURCE_DIR "/ToyRobot-Metal.mtl";
 const char* TEST_RESOURCE_LOCATION = TEST_RESOURCE_DIR "/";
@@ -1081,44 +1081,6 @@ int UtcDaliVisualGetPropertyMap10(void)
   END_TEST;
 }
 
-int UtcDaliVisualGetPropertyMap11(void)
-{
-  ToolkitTestApplication application;
-  tet_infoline( "UtcDaliVisualGetPropertyMap11: AnimatedImageVisual" );
-
-  // request AnimatedImageVisual with a property map
-  VisualFactory factory = VisualFactory::Get();
-  Visual::Base animatedImageVisual = factory.CreateVisual( Property::Map()
-                                                 .Add( Visual::Property::TYPE, DevelVisual::ANIMATED_IMAGE )
-                                                 .Add( ImageVisual::Property::URL, TEST_GIF_FILE_NAME ) );
-
-  Property::Map resultMap;
-  animatedImageVisual.CreatePropertyMap( resultMap );
-  // check the property values from the returned map from a visual
-  Property::Value* value = resultMap.Find( Visual::Property::TYPE,  Property::INTEGER );
-  DALI_TEST_CHECK( value );
-  DALI_TEST_CHECK( value->Get<int>() == DevelVisual::ANIMATED_IMAGE );
-
-  value = resultMap.Find( ImageVisual::Property::URL,  Property::STRING );
-  DALI_TEST_CHECK( value );
-  DALI_TEST_CHECK( value->Get<std::string>() == TEST_GIF_FILE_NAME );
-
-  // request AnimatedImageVisual with an URL
-  Visual::Base animatedImageVisual2 = factory.CreateVisual( TEST_GIF_FILE_NAME, ImageDimensions() );
-  resultMap.Clear();
-  animatedImageVisual2.CreatePropertyMap( resultMap );
-  // check the property values from the returned map from a visual
-  value = resultMap.Find( Visual::Property::TYPE,  Property::INTEGER );
-  DALI_TEST_CHECK( value );
-  DALI_TEST_CHECK( value->Get<int>() == DevelVisual::ANIMATED_IMAGE );
-
-  value = resultMap.Find( ImageVisual::Property::URL,  Property::STRING );
-  DALI_TEST_CHECK( value );
-  DALI_TEST_CHECK( value->Get<std::string>() == TEST_GIF_FILE_NAME );
-
-  END_TEST;
-}
-
 int UtcDaliVisualAnimateBorderVisual01(void)
 {
   ToolkitTestApplication application;
index 0471ae2..501e61c 100644 (file)
@@ -76,6 +76,31 @@ enum Type
    */
 
   ALPHA_MASK_URL = WRAP_MODE_V + 3,
+
+  /**
+   * @brief Defines the batch size for pre-loading images in the AnimatedImageVisual
+   * @details Name "batchSize", type Property::INTEGER, number of images to pre-load
+   * before starting to play. Default value: 1
+   */
+  BATCH_SIZE = WRAP_MODE_V + 4,
+
+  /**
+   * @brief Defines the cache size for loading images in the AnimatedImageVisual
+   * @details Name "cacheSize", type Property::INTEGER, number of images to keep
+   * cached ahead during playback. Default value: 1
+   *
+   * @note, cacheSize should be >= batchSize.
+   * If it isn't, then the cache will automatically be changed to batchSize.
+   * @note, because of the defaults, it is expected that the application developer
+   * tune the batch and cache sizes to their particular use case.
+   */
+  CACHE_SIZE = WRAP_MODE_V + 5,
+
+  /**
+   * @brief The number of milliseconds between each frame in the AnimatedImageVisual
+   * @details Name "frameDelay", type Property::INTEGER, The number of milliseconds between each frame. Note, this is only used with the URLS property above.
+   */
+  FRAME_DELAY = WRAP_MODE_V + 6,
 };
 
 } //namespace Property
index 4183b89..7934109 100644 (file)
@@ -64,7 +64,8 @@ DALI_TYPE_REGISTRATION_END()
 using namespace Dali;
 
 ImageView::ImageView()
-: Control( ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) )
+: Control( ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) ),
+  mRelayoutRequired(true)
 {
 }
 
@@ -227,6 +228,11 @@ void ImageView::OnRelayout( const Vector2& size, RelayoutContainer& container )
 
 void ImageView::OnResourceReady( Toolkit::Control control )
 {
+  if( mRelayoutRequired)
+  {
+    mRelayoutRequired = false;
+    RelayoutRequest();
+  }
 }
 
 ///////////////////////////////////////////////////////////
index f335ce7..dc449e9 100644 (file)
@@ -166,6 +166,7 @@ private:
   std::string      mUrl;          ///< the url for the image if the image came from a URL, empty otherwise
   Image            mImage;        ///< the Image if the image came from a Image, null otherwise
   Property::Map    mPropertyMap;  ///< the Property::Map if the image came from a Property::Map, empty otherwise
+  bool             mRelayoutRequired; ///< True if relayout is required, e.g. due to unreadiness
 };
 
 } // namespace Internal
index 67b6c9e..9b6809f 100644 (file)
@@ -12,6 +12,9 @@ toolkit_src_files = \
    $(toolkit_src_dir)/builder/tree-node-manipulator.cpp \
    $(toolkit_src_dir)/builder/replacement.cpp \
    $(toolkit_src_dir)/visuals/animated-image/animated-image-visual.cpp \
+   $(toolkit_src_dir)/visuals/animated-image/image-cache.cpp \
+   $(toolkit_src_dir)/visuals/animated-image/fixed-image-cache.cpp \
+   $(toolkit_src_dir)/visuals/animated-image/rolling-image-cache.cpp \
    $(toolkit_src_dir)/visuals/border/border-visual.cpp \
    $(toolkit_src_dir)/visuals/color/color-visual.cpp \
    $(toolkit_src_dir)/visuals/gradient/gradient-visual.cpp \
index b2fec63..a0daf14 100644 (file)
@@ -1792,7 +1792,8 @@ float Controller::GetHeightForWidth( float width )
   ProcessModifyEvents();
 
   Size layoutSize;
-  if( fabsf( width - mImpl->mModel->mVisualModel->mControlSize.width ) > Math::MACHINE_EPSILON_1000 )
+  if( fabsf( width - mImpl->mModel->mVisualModel->mControlSize.width ) > Math::MACHINE_EPSILON_1000 ||
+                                                           mImpl->mTextUpdateInfo.mFullRelayoutNeeded )
   {
     // Operations that can be done only once until the text changes.
     const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( CONVERT_TO_UTF32  |
index 0ba7833..be736e1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 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.
 
 // EXTERNAL INCLUDES
 #include <dali/devel-api/adaptor-framework/gif-loading.h>
+#include <dali/devel-api/adaptor-framework/image-loading.h>
+#include <dali/integration-api/debug.h>
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/public-api/visuals/image-visual-properties.h>
+#include <dali-toolkit/devel-api/visuals/image-visual-properties-devel.h>
 #include <dali-toolkit/devel-api/visuals/visual-properties-devel.h>
 #include <dali-toolkit/internal/visuals/visual-factory-impl.h>
 #include <dali-toolkit/internal/visuals/visual-factory-cache.h>
 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
 #include <dali-toolkit/internal/visuals/visual-base-data-impl.h>
+#include <dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h>
+#include <dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h>
 #include <dali-toolkit/internal/visuals/image/image-visual.h>
 #include <dali-toolkit/devel-api/image-loader/image-atlas.h>
 
@@ -52,8 +57,42 @@ DALI_ENUM_TO_STRING_TABLE_END( WRAP_MODE )
 
 const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
 
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_ANIMATED_IMAGE");
+#endif
 }
 
+
+/**
+ * Multi-image  Flow of execution
+ *
+ *   | DoSetProperties()
+ *   |   LoadFirstBatch()
+ *   |     cache->LoadBatch()
+ *   |
+ *   | DoSetOnStage()
+ *   |   CreateRenderer()    (Doesn't become ready until first frame loads)
+ *   |
+ *   | FrameReady(textureSet)
+ *   |   start first frame:
+ *   |     actor.AddRenderer
+ *   |     start timer
+ *   |   mRenderer.SetTextures(textureSet)
+ *   |
+ *   | Timer ticks
+ *   |   DisplayNextFrame()
+ *   |     if front frame is ready,
+ *   |       mRenderer.SetTextures( front frame's texture )
+ *   |     else
+ *   |       mWaitingForTexture=true
+ *   |     cache->LoadBatch()
+ *   |
+ *   | FrameReady(textureSet)
+ *   |   mRenderer.SetTextures(textureSet)
+ *   V
+ *  Time
+ */
+
 AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const VisualUrl& imageUrl, const Property::Map& properties )
 {
   AnimatedImageVisual* visual = new AnimatedImageVisual( factoryCache );
@@ -63,6 +102,25 @@ AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCach
   return visual;
 }
 
+AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const Property::Array& imageUrls, const Property::Map& properties )
+{
+  AnimatedImageVisual* visual = new AnimatedImageVisual( factoryCache );
+  visual->mImageUrls = new ImageCache::UrlList();
+  visual->mImageUrls->reserve( imageUrls.Count() );
+
+  for( unsigned int i=0; i < imageUrls.Count(); ++i)
+  {
+    ImageCache::UrlStore urlStore;
+    urlStore.mTextureId = TextureManager::INVALID_TEXTURE_ID;
+    urlStore.mUrl = imageUrls[i].Get<std::string>();
+    visual->mImageUrls->push_back( urlStore );
+  }
+
+  visual->SetProperties( properties );
+  // starts loading immediately
+  return visual;
+}
+
 AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const VisualUrl& imageUrl )
 {
   AnimatedImageVisual* visual = new AnimatedImageVisual( factoryCache );
@@ -74,23 +132,40 @@ AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCach
 AnimatedImageVisual::AnimatedImageVisual( VisualFactoryCache& factoryCache )
 : Visual::Base( factoryCache ),
   mFrameDelayTimer(),
+  mPlacementActor(),
   mPixelArea( FULL_TEXTURE_RECT ),
   mImageUrl(),
-  mImageSize(),
   mCurrentFrameIndex( 0 ),
+  mImageUrls( NULL ),
+  mImageCache( NULL ),
+  mCacheSize( 1 ),
+  mBatchSize( 1 ),
+  mFrameDelay( 100 ),
+  mUrlIndex( 0 ),
+  mImageSize(),
   mWrapModeU( WrapMode::DEFAULT ),
-  mWrapModeV( WrapMode::DEFAULT )
+  mWrapModeV( WrapMode::DEFAULT ),
+  mStartFirstFrame(false)
 {}
 
 AnimatedImageVisual::~AnimatedImageVisual()
 {
+  delete mImageCache;
+  delete mImageUrls;
 }
 
 void AnimatedImageVisual::GetNaturalSize( Vector2& naturalSize )
 {
   if( mImageSize.GetWidth() == 0 &&  mImageSize.GetHeight() == 0)
   {
-    mImageSize = Dali::GetGifImageSize( mImageUrl.GetUrl() );
+    if( mImageUrl.IsValid() )
+    {
+      mImageSize = Dali::GetGifImageSize( mImageUrl.GetUrl() );
+    }
+    else if( mImageUrls && mImageUrls->size() > 0 )
+    {
+      mImageSize = Dali::GetClosestImageSize( (*mImageUrls)[0].mUrl );
+    }
   }
 
   naturalSize.width = mImageSize.GetWidth();
@@ -107,10 +182,24 @@ void AnimatedImageVisual::DoCreatePropertyMap( Property::Map& map ) const
   {
     map.Insert( Toolkit::ImageVisual::Property::URL, mImageUrl.GetUrl() );
   }
+  if( mImageUrls != NULL && ! mImageUrls->empty() )
+  {
+    Property::Array urls;
+    for( unsigned int i=0; i<mImageUrls->size(); ++i)
+    {
+      urls.Add( (*mImageUrls)[i].mUrl );
+    }
+    Property::Value value( const_cast<Property::Array&>(urls) );
+    map.Insert( Toolkit::ImageVisual::Property::URL, value );
+  }
 
   map.Insert( Toolkit::ImageVisual::Property::PIXEL_AREA, mPixelArea );
   map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_U, mWrapModeU );
   map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_V, mWrapModeV );
+
+  map.Insert( Toolkit::DevelImageVisual::Property::BATCH_SIZE, static_cast<int>(mBatchSize) );
+  map.Insert( Toolkit::DevelImageVisual::Property::CACHE_SIZE, static_cast<int>(mCacheSize) );
+  map.Insert( Toolkit::DevelImageVisual::Property::FRAME_DELAY, static_cast<int>(mFrameDelay) );
 }
 
 void AnimatedImageVisual::DoCreateInstancePropertyMap( Property::Map& map ) const
@@ -120,84 +209,126 @@ void AnimatedImageVisual::DoCreateInstancePropertyMap( Property::Map& map ) cons
 
 void AnimatedImageVisual::DoSetProperties( const Property::Map& propertyMap )
 {
-  // url already passed in from constructor
+  // url[s] already passed in from constructor
 
-  Property::Value* pixelAreaValue = propertyMap.Find( Toolkit::ImageVisual::Property::PIXEL_AREA, PIXEL_AREA_UNIFORM_NAME );
-  if( pixelAreaValue )
+  for( Property::Map::SizeType iter = 0; iter < propertyMap.Count(); ++iter )
   {
-    pixelAreaValue->Get( mPixelArea );
-  }
-
-  Property::Value* wrapModeValueU = propertyMap.Find( Toolkit::ImageVisual::Property::WRAP_MODE_U, IMAGE_WRAP_MODE_U );
-  if( wrapModeValueU )
-  {
-    int value;
-    Scripting::GetEnumerationProperty( *wrapModeValueU, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, value );
-    mWrapModeU = Dali::WrapMode::Type( value );
+    KeyValuePair keyValue = propertyMap.GetKeyValue( iter );
+    if( keyValue.first.type == Property::Key::INDEX )
+    {
+      DoSetProperty( keyValue.first.indexKey, keyValue.second );
+    }
+    else
+    {
+      if( keyValue.first == PIXEL_AREA_UNIFORM_NAME )
+      {
+        DoSetProperty( Toolkit::ImageVisual::Property::PIXEL_AREA, keyValue.second );
+      }
+      else if( keyValue.first == IMAGE_WRAP_MODE_U )
+      {
+        DoSetProperty( Toolkit::ImageVisual::Property::WRAP_MODE_U, keyValue.second );
+      }
+      else if( keyValue.first == IMAGE_WRAP_MODE_V )
+      {
+        DoSetProperty( Toolkit::ImageVisual::Property::WRAP_MODE_V, keyValue.second );
+      }
+      else if( keyValue.first == BATCH_SIZE_NAME )
+      {
+        DoSetProperty( Toolkit::DevelImageVisual::Property::BATCH_SIZE, keyValue.second );
+      }
+      else if( keyValue.first == CACHE_SIZE_NAME )
+      {
+        DoSetProperty( Toolkit::DevelImageVisual::Property::CACHE_SIZE, keyValue.second );
+      }
+      else if( keyValue.first == FRAME_DELAY_NAME )
+      {
+        DoSetProperty( Toolkit::DevelImageVisual::Property::FRAME_DELAY, keyValue.second );
+      }
+    }
   }
 
-  Property::Value* wrapModeValueV = propertyMap.Find( Toolkit::ImageVisual::Property::WRAP_MODE_V, IMAGE_WRAP_MODE_V );
-  if( wrapModeValueV )
+  if( mImageUrls && mImageUrls->size() > 0 )
   {
-    int value;
-    Scripting::GetEnumerationProperty( *wrapModeValueV, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, value );
-    mWrapModeV = Dali::WrapMode::Type( value );
+    LoadFirstBatch();
   }
 }
 
-void AnimatedImageVisual::DoSetOnStage( Actor& actor )
+void AnimatedImageVisual::DoSetProperty( Property::Index index,
+                                         const Property::Value& value )
 {
-  Texture texture = PrepareAnimatedImage();
-  if( texture ) // if the image loading is successful
+  switch(index)
   {
-    bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE;
-    Shader shader = ImageVisual::GetImageShader( mFactoryCache, true, defaultWrapMode );
-
-    Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
-
-    TextureSet textureSet = TextureSet::New();
-    textureSet.SetTexture( 0u, texture );
-
-    mImpl->mRenderer = Renderer::New( geometry, shader );
-    mImpl->mRenderer.SetTextures( textureSet );
-
-    // Register transform properties
-    mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT );
+    case Toolkit::ImageVisual::Property::PIXEL_AREA:
+    {
+      value.Get( mPixelArea );
+      break;
+    }
+    case Toolkit::ImageVisual::Property::WRAP_MODE_U:
+    {
+      int wrapMode;
+      Scripting::GetEnumerationProperty( value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode );
+      mWrapModeU = Dali::WrapMode::Type( wrapMode );
+      break;
+    }
+    case Toolkit::ImageVisual::Property::WRAP_MODE_V:
+    {
+      int wrapMode;
+      Scripting::GetEnumerationProperty( value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode );
+      mWrapModeV = Dali::WrapMode::Type( wrapMode );
+      break;
+    }
 
-    if( !defaultWrapMode ) // custom wrap mode
+    case Toolkit::DevelImageVisual::Property::BATCH_SIZE:
     {
-      Vector2 wrapMode(mWrapModeU-WrapMode::CLAMP_TO_EDGE, mWrapModeV-WrapMode::CLAMP_TO_EDGE);
-      wrapMode.Clamp( Vector2::ZERO, Vector2( 2.f, 2.f ) );
-      mImpl->mRenderer.RegisterProperty( WRAP_MODE_UNIFORM_NAME, wrapMode );
+      int batchSize;
+      if( value.Get( batchSize ) )
+      {
+        mBatchSize = batchSize;
+      }
+      break;
     }
 
-    if( mPixelArea != FULL_TEXTURE_RECT )
+    case Toolkit::DevelImageVisual::Property::CACHE_SIZE:
     {
-      mImpl->mRenderer.RegisterProperty( PIXEL_AREA_UNIFORM_NAME, mPixelArea );
+      int cacheSize;
+      if( value.Get( cacheSize ) )
+      {
+        mCacheSize = cacheSize;
+      }
+      break;
     }
 
-    mCurrentFrameIndex = 0;
-    mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, mTextureRectContainer[mCurrentFrameIndex] );
-    if( mFrameDelayContainer.Count() > 1 )
+    case Toolkit::DevelImageVisual::Property::FRAME_DELAY:
     {
-      mFrameDelayTimer = Timer::New( mFrameDelayContainer[0] );
-      mFrameDelayTimer.TickSignal().Connect( this, &AnimatedImageVisual::DisplayNextFrame );
-      mFrameDelayTimer.Start();
+      int frameDelay;
+      if( value.Get( frameDelay ) )
+      {
+        mFrameDelay = frameDelay;
+      }
+      break;
     }
+  }
+}
 
-    actor.AddRenderer( mImpl->mRenderer );
+void AnimatedImageVisual::DoSetOnStage( Actor& actor )
+{
+  mPlacementActor = actor;
+  TextureSet textureSet = PrepareTextureSet();
+  CreateRenderer(); // Always create a renderer when on stage
 
-    // Animated Image loaded and ready to display
-    ResourceReady();
+  if( textureSet ) // if the image loading is successful
+  {
+    StartFirstFrame( textureSet );
+  }
+  else
+  {
+    mStartFirstFrame = true;
   }
 }
 
 void AnimatedImageVisual::DoSetOffStage( Actor& actor )
 {
-  if( !mImpl->mRenderer )
-  {
-    return;
-  }
+  DALI_ASSERT_DEBUG( (bool)mImpl->mRenderer && "There should always be a renderer whilst on stage");
 
   if( mFrameDelayTimer )
   {
@@ -210,6 +341,7 @@ void AnimatedImageVisual::DoSetOffStage( Actor& actor )
 
   actor.RemoveRenderer( mImpl->mRenderer );
   mImpl->mRenderer.Reset();
+  mPlacementActor.Reset();
 }
 
 void AnimatedImageVisual::OnSetTransform()
@@ -220,8 +352,117 @@ void AnimatedImageVisual::OnSetTransform()
   }
 }
 
-Texture AnimatedImageVisual::PrepareAnimatedImage()
+void AnimatedImageVisual::CreateRenderer()
 {
+  bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE;
+  bool atlasing = (mTextureRectContainer.Count() > 0) ;
+  Shader shader = ImageVisual::GetImageShader( mFactoryCache, atlasing, defaultWrapMode );
+
+  Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
+
+  mImpl->mRenderer = Renderer::New( geometry, shader );
+
+  // Register transform properties
+  mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT );
+
+  if( !defaultWrapMode ) // custom wrap mode
+  {
+    Vector2 wrapMode(mWrapModeU-WrapMode::CLAMP_TO_EDGE, mWrapModeV-WrapMode::CLAMP_TO_EDGE);
+    wrapMode.Clamp( Vector2::ZERO, Vector2( 2.f, 2.f ) );
+    mImpl->mRenderer.RegisterProperty( WRAP_MODE_UNIFORM_NAME, wrapMode );
+  }
+
+  if( mPixelArea != FULL_TEXTURE_RECT )
+  {
+    mImpl->mRenderer.RegisterProperty( PIXEL_AREA_UNIFORM_NAME, mPixelArea );
+  }
+
+  mCurrentFrameIndex = 0;
+
+  if( mTextureRectContainer.Count() > 0 )
+  {
+    mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, mTextureRectContainer[mCurrentFrameIndex] );
+  }
+}
+
+void AnimatedImageVisual::LoadFirstBatch()
+{
+  DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::LoadFirstBatch()\n");
+
+  // Ensure the batch size and cache size are no bigger than the number of URLs,
+  // and that the cache is at least as big as the batch size.
+  uint16_t numUrls = mImageUrls->size();
+  uint16_t batchSize = std::min( mBatchSize, numUrls );
+  uint16_t cacheSize = std::min( std::max( batchSize, mCacheSize ), numUrls );
+
+  mUrlIndex = 0;
+  TextureManager& textureManager = mFactoryCache.GetTextureManager();
+
+  if( batchSize > 0 && cacheSize > 0 )
+  {
+    if( cacheSize < numUrls )
+    {
+      mImageCache = new RollingImageCache( textureManager, *mImageUrls, *this, cacheSize, batchSize );
+    }
+    else
+    {
+      mImageCache = new FixedImageCache( textureManager, *mImageUrls, *this, batchSize );
+    }
+  }
+  else
+  {
+    mImageCache = new RollingImageCache( textureManager, *mImageUrls, *this, 1, 1 );
+  }
+}
+
+void AnimatedImageVisual::StartFirstFrame( TextureSet& textureSet )
+{
+  DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::StartFirstFrame()\n");
+
+  mStartFirstFrame = false;
+  mImpl->mRenderer.SetTextures( textureSet );
+  Actor actor = mPlacementActor.GetHandle();
+  if( actor )
+  {
+    actor.AddRenderer( mImpl->mRenderer );
+    mPlacementActor.Reset();
+  }
+
+  int frameDelay = mFrameDelay;
+  if( mFrameDelayContainer.Count() > 1 )
+  {
+    frameDelay = mFrameDelayContainer[0];
+  }
+  mFrameDelayTimer = Timer::New( frameDelay );
+  mFrameDelayTimer.TickSignal().Connect( this, &AnimatedImageVisual::DisplayNextFrame );
+  mFrameDelayTimer.Start();
+
+  DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"ResourceReady()\n");
+  ResourceReady();
+}
+
+TextureSet AnimatedImageVisual::PrepareTextureSet()
+{
+  TextureSet textureSet;
+  if( mImageUrl.IsValid() )
+  {
+    textureSet = PrepareAnimatedGifImage();
+  }
+  else
+  {
+    textureSet = mImageCache->FirstFrame();
+    if( textureSet )
+    {
+      SetImageSize( textureSet );
+    }
+  }
+  return textureSet;
+}
+
+TextureSet AnimatedImageVisual::PrepareAnimatedGifImage()
+{
+  TextureSet textureSet;
+
   // load from image file
   std::vector<Dali::PixelData> pixelDataList;
 
@@ -232,22 +473,70 @@ Texture AnimatedImageVisual::PrepareAnimatedImage()
       mImageSize.SetWidth( pixelDataList[0].GetWidth() );
       mImageSize.SetHeight( pixelDataList[0].GetHeight() );
 
-      return Toolkit::ImageAtlas::PackToAtlas( pixelDataList, mTextureRectContainer );
+      Texture texture = Toolkit::ImageAtlas::PackToAtlas( pixelDataList, mTextureRectContainer );
+      textureSet = TextureSet::New();
+      textureSet.SetTexture( 0, texture );
     }
   }
 
-  return Texture();
+  return textureSet;
+}
+
+void AnimatedImageVisual::SetImageSize( TextureSet& textureSet )
+{
+  if( textureSet )
+  {
+    Texture texture = textureSet.GetTexture( 0 );
+    if( texture )
+    {
+      mImageSize.SetWidth( texture.GetWidth() );
+      mImageSize.SetHeight( texture.GetHeight() );
+    }
+  }
+}
+
+void AnimatedImageVisual::FrameReady( TextureSet textureSet )
+{
+  SetImageSize( textureSet );
+
+  if( mStartFirstFrame )
+  {
+    StartFirstFrame( textureSet );
+  }
+  else
+  {
+    mImpl->mRenderer.SetTextures( textureSet );
+  }
 }
 
 bool AnimatedImageVisual::DisplayNextFrame()
 {
-  mCurrentFrameIndex = (mCurrentFrameIndex+1) % mFrameDelayContainer.Count();
-  mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, mTextureRectContainer[mCurrentFrameIndex] );
-  if( mFrameDelayTimer.GetInterval() != mFrameDelayContainer[mCurrentFrameIndex] )
+  DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::DisplayNextFrame() start\n");
+
+  if( mFrameDelayContainer.Count() > 0 )
+  {
+    // Wrap the frame index
+    ++mCurrentFrameIndex;
+    mCurrentFrameIndex %= mFrameDelayContainer.Count();
+
+    mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, mTextureRectContainer[mCurrentFrameIndex] );
+
+    if( mFrameDelayTimer.GetInterval() != mFrameDelayContainer[mCurrentFrameIndex] )
+    {
+      mFrameDelayTimer.SetInterval( mFrameDelayContainer[mCurrentFrameIndex] );
+    }
+  }
+  else if( mImageCache )
   {
-    mFrameDelayTimer.SetInterval( mFrameDelayContainer[mCurrentFrameIndex] );
+    TextureSet textureSet = mImageCache->NextFrame();
+    if( textureSet )
+    {
+      SetImageSize( textureSet );
+      mImpl->mRenderer.SetTextures( textureSet );
+    }
   }
 
+  // Keep timer ticking
   return true;
 }
 
index 4e11cce..2796c20 100644 (file)
  */
 
 // EXTERNAL INCLUDES
-#include <dali/public-api/common/intrusive-ptr.h>
 #include <dali/public-api/common/dali-vector.h>
+#include <dali/public-api/common/intrusive-ptr.h>
 #include <dali/public-api/math/vector4.h>
 #include <dali/public-api/adaptor-framework/timer.h>
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/visuals/visual-base-impl.h>
 #include <dali-toolkit/internal/visuals/visual-url.h>
+#include <dali-toolkit/internal/visuals/animated-image/image-cache.h>
 
 namespace Dali
 {
@@ -43,29 +44,41 @@ typedef IntrusivePtr< AnimatedImageVisual > AnimatedImageVisualPtr;
 /**
  * The visual which renders an animated image
  *
- * The following property is essential
+ * One of the following properties is mandatory
  *
  * | %Property Name     | Type              |
  * |------------------- |-------------------|
  * | url                | STRING            |
+ * | urls               | ARRAY of STRING   |
+ *
+ * The remaining properties are optional
  * | pixelArea          | VECTOR4           |
  * | wrapModeU          | INTEGER OR STRING |
  * | wrapModeV          | INTEGER OR STRING |
+ * | cacheSize          | INTEGER           |
+ * | batchSize          | INTEGER           |
+ * | frameDelay         | INTEGER           |
  *
- * where pixelArea is a rectangular area.
+ * pixelArea is a rectangular area.
  * In its Vector4 value, the first two elements indicate the top-left position of the area,
  * and the last two elements are the area width and height respectively.
  * If not specified, the default value is [0.0, 0.0, 1.0, 1.0], i.e. the entire area of the image.
  *
- * where wrapModeU and wrapModeV separately decide how the texture should be sampled when the u and v coordinate exceeds the range of 0.0 to 1.0.
+ * wrapModeU and wrapModeV separately decide how the texture should be sampled when the u and v coordinate exceeds the range of 0.0 to 1.0.
  * Its value should be one of the following wrap mode:
  *   "DEFAULT"
  *   "CLAMP_TO_EDGE"
  *   "REPEAT"
  *   "MIRRORED_REPEAT"
+ *
+ * cacheSize is used with multiple images - it determines how many images are kept pre-loaded
+ * batchSize is used with multiple images - it determines how many images to load on each frame
+ * frameDelay is used with multiple images - it is the number of milliseconds between each frame
  */
 
-class AnimatedImageVisual : public Visual::Base, public ConnectionTracker
+class AnimatedImageVisual : public Visual::Base,
+                            public ConnectionTracker,
+                            public ImageCache::FrameReadyObserver
 {
 
 public:
@@ -74,13 +87,23 @@ public:
    * @brief Create the animated image Visual using the image URL.
    *
    * @param[in] factoryCache A pointer pointing to the VisualFactoryCache object
-   * @param[in] imageUrl The URL to svg resource to use
+   * @param[in] imageUrl The URL to gif resource to use
    * @param[in] properties A Property::Map containing settings for this visual
    * @return A smart-pointer to the newly allocated visual.
    */
   static AnimatedImageVisualPtr New( VisualFactoryCache& factoryCache, const VisualUrl& imageUrl, const Property::Map& properties );
 
   /**
+   * @brief Create the animated image Visual using image URLs.
+   *
+   * @param[in] factoryCache A pointer pointing to the VisualFactoryCache object
+   * @param[in] imageUrls A Property::Array containing the URLs to the image resources
+   * @param[in] properties A Property::Map containing settings for this visual
+   * @return A smart-pointer to the newly allocated visual.
+   */
+  static AnimatedImageVisualPtr New( VisualFactoryCache& factoryCache, const Property::Array& imageUrls, const Property::Map& properties );
+
+  /**
    * @brief Create the animated image visual using the image URL.
    *
    * @param[in] factoryCache A pointer pointing to the VisualFactoryCache object
@@ -125,6 +148,13 @@ protected:
   virtual void DoSetProperties( const Property::Map& propertyMap );
 
   /**
+   * Helper method to set individual values by index key.
+   * @param[in] index The index key of the value
+   * @param[in] value The value
+   */
+  void DoSetProperty( Property::Index index, const Property::Value& value );
+
+  /**
    * @copydoc Visual::Base::DoSetOnStage
    */
   virtual void DoSetOnStage( Actor& actor );
@@ -140,18 +170,50 @@ protected:
   virtual void OnSetTransform();
 
 private:
+  /**
+   * Creates the renderer for the animated image
+   */
+  void CreateRenderer();
 
   /**
-   * Load the animated image and pack the frames into atlas.
-   * @return That atlas texture.
+   * Starts the Load of the first batch of URLs
    */
-  Texture PrepareAnimatedImage();
+  void LoadFirstBatch();
 
   /**
-   * Display the next frame. It is called when the mFrameDelayTimes ticks.
+   * Adds the texture set to the renderer, and the renderer to the
+   * placement actor, and starts the frame timer
+   */
+  void StartFirstFrame( TextureSet& textureSet );
+
+  /**
+   * Prepares the texture set for displaying
+   */
+  TextureSet PrepareTextureSet();
+
+  /**
+   * Load the gif image and pack the frames into atlas.
+   * @return The atlas texture.
+   */
+  TextureSet PrepareAnimatedGifImage();
+
+  /**
+   * Set the image size from the texture set
+   */
+  void SetImageSize( TextureSet& textureSet );
+
+  /**
+   * Called when the next frame is ready.
+   */
+  void FrameReady( TextureSet textureSet );
+
+  /**
+   * Display the next frame. It is called when the mFrameDelayTimer ticks.
+   * Returns true to ensure the timer continues running.
    */
   bool DisplayNextFrame();
 
+
   // Undefined
   AnimatedImageVisual( const AnimatedImageVisual& animatedImageVisual );
 
@@ -161,15 +223,29 @@ private:
 private:
 
   Timer mFrameDelayTimer;
+  WeakHandle<Actor> mPlacementActor;
+
+  // Variables for GIF player
   Dali::Vector<Vector4> mTextureRectContainer;
   Dali::Vector<uint32_t> mFrameDelayContainer;
   Vector4 mPixelArea;
   VisualUrl mImageUrl;
+  uint32_t mCurrentFrameIndex; // Frame index into textureRects
 
+  // Variables for Multi-Image player
+  ImageCache::UrlList* mImageUrls;
+  ImageCache* mImageCache;
+  uint16_t mCacheSize;
+  uint16_t mBatchSize;
+  uint16_t mFrameDelay;
+  uint16_t mUrlIndex;
+
+  // Shared variables
   ImageDimensions mImageSize;
-  uint32_t mCurrentFrameIndex;
+
   Dali::WrapMode::Type mWrapModeU:3;
   Dali::WrapMode::Type mWrapModeV:3;
+  bool mStartFirstFrame:1;
 };
 
 } // namespace Internal
diff --git a/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp b/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp
new file mode 100644 (file)
index 0000000..d60834f
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2017 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.
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+
+FixedImageCache::FixedImageCache(
+  TextureManager& textureManager, UrlList& urlList, ImageCache::FrameReadyObserver& observer,
+  unsigned int batchSize )
+: ImageCache( textureManager, urlList, observer, batchSize ),
+  mFront(0u)
+{
+  mReadyFlags.reserve( mImageUrls.size() );
+  LoadBatch();
+}
+
+FixedImageCache::~FixedImageCache()
+{
+  for( std::size_t i = 0; i < mImageUrls.size() ; ++i )
+  {
+    mTextureManager.Remove( mImageUrls[i].mTextureId );
+  }
+}
+
+TextureSet FixedImageCache::FirstFrame()
+{
+  TextureSet textureSet = GetFrontTextureSet();
+
+  if( ! textureSet )
+  {
+    mWaitingForReadyFrame = true;
+  }
+
+  return textureSet;
+}
+
+TextureSet FixedImageCache::NextFrame()
+{
+  TextureSet textureSet;
+  ++mFront;
+  mFront %= mImageUrls.size();
+
+  if( IsFrontReady() == true )
+  {
+    textureSet = GetFrontTextureSet();
+  }
+  else
+  {
+    mWaitingForReadyFrame = true;
+  }
+
+  LoadBatch();
+
+  return textureSet;
+}
+
+bool FixedImageCache::IsFrontReady() const
+{
+  return ( mReadyFlags.size() > 0 && mReadyFlags[mFront] == true );
+}
+
+void FixedImageCache::LoadBatch()
+{
+  // Try and load up to mBatchSize images, until the cache is filled.
+  // Once the cache is filled, mUrlIndex exceeds mImageUrls size and
+  // no more images are loaded.
+  bool frontFrameReady = IsFrontReady();;
+
+  for( unsigned int i=0; i< mBatchSize && mUrlIndex < mImageUrls.size(); ++i )
+  {
+    std::string& url = mImageUrls[ mUrlIndex ].mUrl;
+
+    mReadyFlags.push_back(false);
+
+    // Note, if the image is already loaded, then UploadComplete will get called
+    // from within this method. This means it won't yet have a texture id, so we
+    // need to account for this inside the UploadComplete method using mRequestingLoad.
+    mRequestingLoad = true;
+
+    mImageUrls[ mUrlIndex ].mTextureId =
+      mTextureManager.RequestLoad( url, ImageDimensions(), FittingMode::SCALE_TO_FILL,
+                                   SamplingMode::BOX_THEN_LINEAR, TextureManager::NO_ATLAS,
+                                   this );
+    mRequestingLoad = false;
+    ++mUrlIndex;
+  }
+
+  CheckFrontFrame( frontFrameReady );
+}
+
+void FixedImageCache::SetImageFrameReady( TextureManager::TextureId textureId )
+{
+  for( std::size_t i = 0; i < mImageUrls.size() ; ++i )
+  {
+    if( mImageUrls[i].mTextureId == textureId )
+    {
+      mReadyFlags[i] = true;
+      break;
+    }
+  }
+}
+
+TextureSet FixedImageCache::GetFrontTextureSet() const
+{
+  return mTextureManager.GetTextureSet( mImageUrls[mFront].mTextureId );
+}
+
+void FixedImageCache::CheckFrontFrame( bool wasReady )
+{
+  if( mWaitingForReadyFrame && wasReady == false && IsFrontReady() )
+  {
+    mWaitingForReadyFrame = false;
+    mObserver.FrameReady( GetFrontTextureSet() );
+  }
+}
+
+void FixedImageCache::UploadComplete(
+  bool           loadSuccess,
+  int32_t        textureId,
+  TextureSet     textureSet,
+  bool           useAtlasing,
+  const Vector4& atlasRect )
+{
+  bool frontFrameReady = IsFrontReady();
+
+  if( ! mRequestingLoad )
+  {
+    SetImageFrameReady( textureId );
+
+    CheckFrontFrame( frontFrameReady );
+  }
+  else
+  {
+    // UploadComplete has been called from within RequestLoad. TextureManager must
+    // therefore already have the texture cached, so make the texture ready.
+    // (Use the last texture, as the texture id hasn't been assigned yet)
+    mReadyFlags.back() = true;
+  }
+}
+
+} //namespace Internal
+} //namespace Toolkit
+} //namespace Dali
diff --git a/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h b/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h
new file mode 100644 (file)
index 0000000..b30a11a
--- /dev/null
@@ -0,0 +1,109 @@
+#ifndef DALI_TOOLKIT_INTERNAL_FIXED_IMAGE_CACHE_H
+#define DALI_TOOLKIT_INTERNAL_FIXED_IMAGE_CACHE_H
+
+/*
+ * Copyright (c) 2017 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.
+ */
+
+// EXTERNAL INCLUDES
+#include <dali-toolkit/internal/visuals/animated-image/image-cache.h>
+#include <dali-toolkit/internal/visuals/texture-manager.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+
+class FixedImageCache : public ImageCache
+{
+public:
+  /**
+   * Constructor.
+   * @param[in] textureManager The texture manager
+   * @param[in] urlList List of urls to cache
+   * @param[in] observer FrameReady observer
+   * @param[in] batchSize The size of a batch to load
+   *
+   * This will start loading textures immediately, according to the
+   * batch and cache sizes. The cache is as large as the number of urls.
+   */
+  FixedImageCache( TextureManager&                 textureManager,
+                   UrlList&                        urlList,
+                   ImageCache::FrameReadyObserver& observer,
+                   unsigned int                    batchSize );
+
+  virtual ~FixedImageCache();
+
+  /**
+   * Get the first frame. If it's not ready, this will trigger the
+   * sending of FrameReady() when the image becomes ready.
+   */
+  virtual TextureSet FirstFrame();
+
+  /**
+   * Get the next frame. If it's not ready, this will trigger the
+   * sending of FrameReady() when the image becomes ready.
+   * This will trigger the loading of the next batch.
+   */
+  virtual TextureSet NextFrame();
+
+private:
+  /**
+   * @return true if the front frame is ready
+   */
+  bool IsFrontReady() const;
+
+  /**
+   * Load the next batch of images
+   */
+  void LoadBatch();
+
+  /**
+   * Find the matching image frame, and set it to ready
+   */
+  void SetImageFrameReady( TextureManager::TextureId textureId );
+
+  /**
+   * Get the texture set of the front frame.
+   * @return the texture set
+   */
+  TextureSet GetFrontTextureSet() const;
+
+  /**
+   * Check if the front frame has become ready - if so, inform observer
+   * @param[in] wasReady Readiness before call.
+   */
+  void CheckFrontFrame( bool wasReady );
+
+protected:
+  virtual void UploadComplete(
+    bool           loadSuccess,
+    int32_t        textureId,
+    TextureSet     textureSet,
+    bool           useAtlasing,
+    const Vector4& atlasRect );
+
+protected:
+  std::vector<bool> mReadyFlags;
+  unsigned int      mFront;
+};
+
+} //namespace Internal
+} //namespace Toolkit
+} //namespace Dali
+
+#endif // DALI_TOOLKIT_INTERNAL_FIXED_IMAGE_CACHE_H
diff --git a/dali-toolkit/internal/visuals/animated-image/image-cache.cpp b/dali-toolkit/internal/visuals/animated-image/image-cache.cpp
new file mode 100644 (file)
index 0000000..1d0db6f
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017 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.
+ */
+
+#include "image-cache.h"
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+
+ImageCache::ImageCache( TextureManager&                 textureManager,
+                        UrlList&                        urlList,
+                        ImageCache::FrameReadyObserver& observer,
+                        unsigned int                    batchSize )
+: mTextureManager( textureManager ),
+  mObserver( observer ),
+  mImageUrls( urlList ),
+  mBatchSize( batchSize ),
+  mUrlIndex(0u),
+  mWaitingForReadyFrame(false),
+  mRequestingLoad(false)
+{
+}
+
+ImageCache::~ImageCache()
+{
+}
+
+} //namespace Internal
+} //namespace Toolkit
+} //namespace Dali
diff --git a/dali-toolkit/internal/visuals/animated-image/image-cache.h b/dali-toolkit/internal/visuals/animated-image/image-cache.h
new file mode 100644 (file)
index 0000000..5061d76
--- /dev/null
@@ -0,0 +1,103 @@
+#ifndef DALI_TOOLKIT_INTERNAL_IMAGE_CACHE_H
+#define DALI_TOOLKIT_INTERNAL_IMAGE_CACHE_H
+
+/*
+ * Copyright (c) 2017 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.
+ */
+
+// EXTERNAL INCLUDES
+#include <dali-toolkit/internal/visuals/texture-upload-observer.h>
+#include <dali-toolkit/internal/visuals/texture-manager.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+
+class ImageCache : public TextureUploadObserver
+{
+public:
+  /**
+   * Observer class to inform when the next image is ready
+   */
+  class FrameReadyObserver
+  {
+  public:
+    /**
+     * Informs observer when the next texture set is ready to display
+     * @param[in] textureSet The ready texture set
+     */
+    virtual void FrameReady( TextureSet textureSet ) = 0;
+  };
+
+  struct UrlStore
+  {
+    TextureManager::TextureId mTextureId;
+    std::string mUrl;
+  };
+
+  /**
+   * List of URLs
+   */
+  typedef std::vector<UrlStore> UrlList;
+
+public:
+  /**
+   * Constructor.
+   * @param[in] textureManager The texture manager
+   * @param[in] urlList List of urls to cache
+   * @param[in] observer FrameReady observer
+   * @param[in] batchSize The size of a batch to load
+   *
+   * This will start loading textures immediately, according to the
+   * batch and cache sizes. The cache is as large as the number of urls.
+   */
+  ImageCache( TextureManager&                 textureManager,
+              UrlList&                        urlList,
+              ImageCache::FrameReadyObserver& observer,
+              unsigned int                    batchSize );
+
+  virtual ~ImageCache();
+
+  /**
+   * Get the first frame. If it's not ready, this will trigger the
+   * sending of FrameReady() when the image becomes ready.
+   */
+  virtual TextureSet FirstFrame() = 0;
+
+  /**
+   * Get the next frame. If it's not ready, this will trigger the
+   * sending of FrameReady() when the image becomes ready.
+   * This will trigger the loading of the next batch.
+   */
+  virtual TextureSet NextFrame() = 0;
+
+protected:
+  TextureManager&        mTextureManager;
+  FrameReadyObserver&    mObserver;
+  std::vector<UrlStore>& mImageUrls;
+  unsigned int           mBatchSize;
+  unsigned int           mUrlIndex;
+  bool                   mWaitingForReadyFrame:1;
+  bool                   mRequestingLoad:1;
+};
+
+} //namespace Internal
+} //namespace Toolkit
+} //namespace Dali
+
+#endif // DALI_TOOLKIT_INTERNAL_IMAGE_CACHE_H
diff --git a/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.cpp b/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.cpp
new file mode 100644 (file)
index 0000000..8abe059
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2017 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.
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h>
+
+// INTERNAL HEADERS
+#include <dali/integration-api/debug.h>
+
+// EXTERNAL HEADERS
+
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_ANIMATED_IMAGE");
+
+#define LOG_CACHE                                                       \
+  {                                                                     \
+    std::ostringstream oss;                                             \
+    oss<<"Size:"<<mQueue.Count()<<" [ ";                                \
+    for(std::size_t _i=0; _i<mQueue.Count(); ++_i)                      \
+    {                                                                   \
+      oss<<_i<<                                                         \
+        "={ tex:"<<mImageUrls[mQueue[_i].mUrlIndex].mTextureId<<        \
+        " urlId:"<<mQueue[_i].mUrlIndex<<                               \
+        " rdy:"<<(mQueue[_i].mReady?"T":"F")<< "}, ";                   \
+    }                                                                   \
+    oss<<" ]"<<std::endl;                                               \
+    DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"%s",oss.str().c_str()); \
+  }
+
+#else
+  #define LOG_CACHE
+#endif
+}
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+
+RollingImageCache::RollingImageCache(
+  TextureManager& textureManager, UrlList& urlList, ImageCache::FrameReadyObserver& observer,
+  uint16_t cacheSize, uint16_t batchSize )
+: ImageCache( textureManager, urlList, observer, batchSize ),
+  mQueue( cacheSize )
+{
+  LoadBatch();
+}
+
+RollingImageCache::~RollingImageCache()
+{
+  while( !mQueue.IsEmpty() )
+  {
+    ImageFrame imageFrame = mQueue.PopFront();
+    mTextureManager.Remove( mImageUrls[ imageFrame.mUrlIndex ].mTextureId );
+  }
+}
+
+TextureSet RollingImageCache::FirstFrame()
+{
+  TextureSet textureSet = GetFrontTextureSet();
+
+  if( ! textureSet )
+  {
+    mWaitingForReadyFrame = true;
+  }
+
+  return textureSet;
+}
+
+TextureSet RollingImageCache::NextFrame()
+{
+  TextureSet textureSet;
+
+  ImageFrame imageFrame = mQueue.PopFront();
+  mTextureManager.Remove( mImageUrls[ imageFrame.mUrlIndex ].mTextureId );
+  mImageUrls[ imageFrame.mUrlIndex ].mTextureId = TextureManager::INVALID_TEXTURE_ID;
+
+  if( IsFrontReady() == true )
+  {
+    textureSet = GetFrontTextureSet();
+  }
+  else
+  {
+    mWaitingForReadyFrame = true;
+  }
+
+  LoadBatch();
+
+  return textureSet;
+}
+
+bool RollingImageCache::IsFrontReady() const
+{
+  return ( !mQueue.IsEmpty() && mQueue.Front().mReady );
+}
+
+void RollingImageCache::LoadBatch()
+{
+  // Try and load up to mBatchSize images, until the cache is filled.
+  // Once the cache is filled, as frames progress, the old frame is
+  // cleared, but not erased, and another image is loaded
+  bool frontFrameReady = IsFrontReady();;
+
+  for( unsigned int i=0; i< mBatchSize && !mQueue.IsFull(); ++i )
+  {
+    ImageFrame imageFrame;
+
+    std::string& url = mImageUrls[ mUrlIndex ].mUrl;
+    imageFrame.mUrlIndex = mUrlIndex;
+    imageFrame.mReady = false;
+
+    ++mUrlIndex;
+    mUrlIndex %= mImageUrls.size();
+
+    mQueue.PushBack( imageFrame );
+
+    // Note, if the image is already loaded, then UploadComplete will get called
+    // from within this method. This means it won't yet have a texture id, so we
+    // need to account for this inside the UploadComplete method using mRequestingLoad.
+    mRequestingLoad = true;
+
+    mImageUrls[ imageFrame.mUrlIndex ].mTextureId =
+      mTextureManager.RequestLoad( url, ImageDimensions(), FittingMode::SCALE_TO_FILL,
+                                   SamplingMode::BOX_THEN_LINEAR, TextureManager::NO_ATLAS,
+                                   this );
+    mRequestingLoad = false;
+  }
+
+  CheckFrontFrame( frontFrameReady );
+}
+
+void RollingImageCache::SetImageFrameReady( TextureManager::TextureId textureId )
+{
+  for( std::size_t i = 0; i < mQueue.Count() ; ++i )
+  {
+    if( GetCachedTextureId(i) == textureId )
+    {
+      mQueue[i].mReady = true;
+      break;
+    }
+  }
+}
+
+TextureSet RollingImageCache::GetFrontTextureSet() const
+{
+  TextureManager::TextureId textureId = GetCachedTextureId( 0 );
+  return mTextureManager.GetTextureSet( textureId );
+}
+
+TextureManager::TextureId RollingImageCache::GetCachedTextureId( int index ) const
+{
+  return mImageUrls[ mQueue[ index ].mUrlIndex ].mTextureId;
+}
+
+void RollingImageCache::CheckFrontFrame( bool wasReady )
+{
+  if( mWaitingForReadyFrame && wasReady == false && IsFrontReady() )
+  {
+    mWaitingForReadyFrame = false;
+    mObserver.FrameReady( GetFrontTextureSet() );
+  }
+}
+
+void RollingImageCache::UploadComplete(
+  bool           loadSuccess,
+  int32_t        textureId,
+  TextureSet     textureSet,
+  bool           useAtlasing,
+  const Vector4& atlasRect )
+{
+  DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::UploadComplete(textureId:%d) start\n", textureId);
+  LOG_CACHE;
+
+  bool frontFrameReady = IsFrontReady();
+
+  if( ! mRequestingLoad )
+  {
+    SetImageFrameReady( textureId );
+
+    CheckFrontFrame( frontFrameReady );
+  }
+  else
+  {
+    // UploadComplete has been called from within RequestLoad. TextureManager must
+    // therefore already have the texture cached, so make the texture ready.
+    // (Use the last texture, as the texture id hasn't been assigned yet)
+    mQueue.Back().mReady = true;
+  }
+
+  LOG_CACHE;
+}
+
+} //namespace Internal
+} //namespace Toolkit
+} //namespace Dali
diff --git a/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h b/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h
new file mode 100644 (file)
index 0000000..6f5607c
--- /dev/null
@@ -0,0 +1,133 @@
+#ifndef DALI_TOOLKIT_INTERNAL_ROLLING_IMAGE_CACHE_H
+#define DALI_TOOLKIT_INTERNAL_ROLLING_IMAGE_CACHE_H
+
+/*
+ * Copyright (c) 2017 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.
+ */
+
+// EXTERNAL INCLUDES
+
+#include <dali/devel-api/common/circular-queue.h>
+#include <dali-toolkit/internal/visuals/animated-image/image-cache.h>
+#include <dali-toolkit/internal/visuals/texture-manager.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+
+/**
+ * Class to manage a rolling cache of images, where the cache size
+ * is smaller than the total number of images.
+ */
+class RollingImageCache : public ImageCache
+{
+public:
+  /**
+   * Constructor.
+   * @param[in] textureManager The texture manager
+   * @param[in] urlList List of urls to cache
+   * @param[in] observer FrameReady observer
+   * @param[in] cacheSize The size of the cache
+   * @param[in] batchSize The size of a batch to load
+   *
+   * This will start loading textures immediately, according to the
+   * batch and cache sizes.
+   */
+  RollingImageCache( TextureManager&                 textureManager,
+                     UrlList&                        urlList,
+                     ImageCache::FrameReadyObserver& observer,
+                     uint16_t                        cacheSize,
+                     uint16_t                        batchSize );
+
+  /**
+   * Destructor
+   */
+  virtual ~RollingImageCache();
+
+  /**
+   * Get the first frame. If it's not ready, this will trigger the
+   * sending of FrameReady() when the image becomes ready.
+   */
+  virtual TextureSet FirstFrame();
+
+  /**
+   * Get the next frame. If it's not ready, this will trigger the
+   * sending of FrameReady() when the image becomes ready.
+   * This will trigger the loading of the next batch.
+   */
+  virtual TextureSet NextFrame();
+
+private:
+  /**
+   * @return true if the front frame is ready
+   */
+  bool IsFrontReady() const;
+
+  /**
+   * Load the next batch of images
+   */
+  void LoadBatch();
+
+  /**
+   * Find the matching image frame, and set it to ready
+   */
+  void SetImageFrameReady( TextureManager::TextureId textureId );
+
+  /**
+   * Get the texture set of the front frame.
+   * @return the texture set
+   */
+  TextureSet GetFrontTextureSet() const;
+
+  /**
+   * Get the texture id of the given index
+   */
+  TextureManager::TextureId GetCachedTextureId( int index ) const;
+
+  /**
+   * Check if the front frame has become ready - if so, inform observer
+   * @param[in] wasReady Readiness before call.
+   */
+  void CheckFrontFrame( bool wasReady );
+
+protected:
+  virtual void UploadComplete(
+    bool           loadSuccess,
+    int32_t        textureId,
+    TextureSet     textureSet,
+    bool           useAtlasing,
+    const Vector4& atlasRect );
+
+private:
+  /**
+   * Secondary class to hold readiness and index into url
+   */
+  struct ImageFrame
+  {
+    unsigned int mUrlIndex;
+    bool mReady;
+  };
+
+  CircularQueue<ImageFrame> mQueue;
+};
+
+} // namespace Internal
+} // namespace Toolkit
+} // namespace Dali
+
+#endif
index d050442..7ce2f23 100644 (file)
@@ -962,7 +962,7 @@ void ImageVisual::UploadCompleted()
 }
 
 // From Texture Manager
-void ImageVisual::UploadComplete( bool loadingSuccess, TextureSet textureSet, bool usingAtlas, const Vector4& atlasRectangle )
+void ImageVisual::UploadComplete( bool loadingSuccess, int32_t textureId, TextureSet textureSet, bool usingAtlas, const Vector4& atlasRectangle )
 {
   Actor actor = mPlacementActor.GetHandle();
   if( actor )
index 31dc79f..ce03e6f 100644 (file)
@@ -239,7 +239,7 @@ public:
    * To avoid rendering garbage pixels, renderer should be added to actor after the resources are ready.
    * This callback is the place to add the renderer as it would be called once the loading is finished.
    */
-  virtual void UploadComplete( bool success, TextureSet textureSet, bool usingAtlas, const Vector4& atlasRectangle );
+  virtual void UploadComplete( bool success, int32_t textureId, TextureSet textureSet, bool usingAtlas, const Vector4& atlasRectangle );
 
 private:
 
index 7334b48..b125a9e 100644 (file)
@@ -169,9 +169,8 @@ TextureManager::TextureId TextureManager::RequestLoadInternal(
       {
         // The Texture has already loaded. The other observers have already been notified.
         // We need to send a "late" loaded notification for this observer.
-        observer->UploadComplete( true,
-                                  textureInfo.textureSet, textureInfo.useAtlas,
-                                  textureInfo.atlasRect );
+        observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
+                                  textureInfo.useAtlas, textureInfo.atlasRect );
       }
       break;
     }
@@ -201,7 +200,6 @@ void TextureManager::Remove( const TextureManager::TextureId textureId )
   {
     TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
 
-
     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
                    textureId, textureInfoIndex,
                    textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
@@ -483,8 +481,8 @@ void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
 {
   TextureId textureId = textureInfo.textureId;
 
-  // If there is an observer: Notify the load is complete, whether successful or not:
-  // And erase it from the list
+  // If there is an observer: Notify the load is complete, whether successful or not,
+  // and erase it from the list
   unsigned int observerCount = textureInfo.observerList.Count();
   TextureInfo* info = &textureInfo;
 
@@ -492,26 +490,29 @@ void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
   {
     TextureUploadObserver* observer = info->observerList[0];
 
-    // During UploadComplete() a Control ResourceReady() signal is emitted
-    // During that signal the app may add remove /add Textures (e.g. via ImageViews).
-    // At this point no more observers can be added to the observerList, because  textureInfo.loadState = UPLOADED
-    // However it is possible for observers to be removed, hence we check the observer list count every iteration
-
-    // Also the reference to the textureInfo struct can become invalidated, because new load requests can modify
-    // the mTextureInfoContainer list (e.g. if more requests are pushed_back it can cause the list to be resized
-    // invalidating the reference to the TextureInfo ).
-    observer->UploadComplete( success, info->textureSet, info->useAtlas, info->atlasRect );
+    // During UploadComplete() a Control ResourceReady() signal is emitted.
+    // During that signal the app may add remove /add Textures (e.g. via
+    // ImageViews).  At this point no more observers can be added to the
+    // observerList, because textureInfo.loadState = UPLOADED. However it is
+    // possible for observers to be removed, hence we check the observer list
+    // count every iteration.
+
+    // The reference to the textureInfo struct can also become invalidated,
+    // because new load requests can modify the mTextureInfoContainer list
+    // (e.g. if more requests are pushed back it can cause the list to be
+    // resized invalidating the reference to the TextureInfo ).
+    observer->UploadComplete( success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect );
     observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
 
-    // regrab the textureInfo from the container as it may have been invalidated, if textures have been removed
-    // or added during the ResourceReady() signal emission (from UploadComplete() )
-    int textureInfoIndex = GetCacheIndexFromId( textureId );
+    // Get the textureInfo from the container again as it may have been
+    // invalidated,
 
+    int textureInfoIndex = GetCacheIndexFromId( textureId );
     if( textureInfoIndex == INVALID_CACHE_INDEX)
     {
-      // texture has been removed
-      return;
+      return; // texture has been removed - can stop.
     }
+
     info = &mTextureInfoContainer[ textureInfoIndex ];
     observerCount = info->observerList.Count();
     if ( observerCount > 0 )
@@ -524,15 +525,12 @@ void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
           info->observerList.Erase( j );
           observerCount--;
           break;
-         }
+        }
       }
     }
-
   }
-
 }
 
-
 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
 {
   return mCurrentTextureId++;
index c300274..e5c09fe 100644 (file)
@@ -57,11 +57,12 @@ public:
    * This should be overridden by the deriving class.
    *
    * @param[in] loadSuccess True if the texture load was successful (i.e. the resource is available). If false, then the resource failed to load. In future, this will automatically upload a "broken" image.
+   * @param[in] textureId   The textureId of the loaded texture in the TextureManager
    * @param[in] textureSet  The TextureSet containing the Texture
    * @param[in] useAtlasing True if atlasing was used (note: this may be different to what was requested)
    * @param[in] atlasRect   If using atlasing, this is the rectangle within the atlas to use.
    */
-  virtual void UploadComplete( bool loadSuccess, TextureSet textureSet, bool useAtlasing, const Vector4& atlasRect ) = 0;
+  virtual void UploadComplete( bool loadSuccess, int32_t textureId, TextureSet textureSet, bool useAtlasing, const Vector4& atlasRect ) = 0;
 
   /**
    * @brief Returns the destruction signal.
index 8a5d245..469ead5 100644 (file)
@@ -119,31 +119,42 @@ Toolkit::Visual::Base VisualFactory::CreateVisual( const Property::Map& property
     {
       Property::Value* imageURLValue = propertyMap.Find( Toolkit::ImageVisual::Property::URL, IMAGE_URL_NAME );
       std::string imageUrl;
-      if( imageURLValue && imageURLValue->Get( imageUrl ) )
+      if( imageURLValue )
       {
-        VisualUrl visualUrl( imageUrl );
-
-        switch( visualUrl.GetType() )
+        if( imageURLValue->Get( imageUrl ) )
         {
-          case VisualUrl::N_PATCH:
-          {
-            visualPtr = NPatchVisual::New( *( mFactoryCache.Get() ), visualUrl, propertyMap );
-            break;
-          }
-          case VisualUrl::SVG:
-          {
-            visualPtr = SvgVisual::New( *( mFactoryCache.Get() ), visualUrl, propertyMap );
-            break;
-          }
-          case VisualUrl::GIF:
+          VisualUrl visualUrl( imageUrl );
+
+          switch( visualUrl.GetType() )
           {
-            visualPtr = AnimatedImageVisual::New( *( mFactoryCache.Get() ), visualUrl, propertyMap );
-            break;
+            case VisualUrl::N_PATCH:
+            {
+              visualPtr = NPatchVisual::New( *( mFactoryCache.Get() ), visualUrl, propertyMap );
+              break;
+            }
+            case VisualUrl::SVG:
+            {
+              visualPtr = SvgVisual::New( *( mFactoryCache.Get() ), visualUrl, propertyMap );
+              break;
+            }
+            case VisualUrl::GIF:
+            {
+              visualPtr = AnimatedImageVisual::New( *( mFactoryCache.Get() ), visualUrl, propertyMap );
+              break;
+            }
+            case VisualUrl::REGULAR_IMAGE:
+            {
+              visualPtr = ImageVisual::New( *( mFactoryCache.Get() ), visualUrl, propertyMap );
+              break;
+            }
           }
-          case VisualUrl::REGULAR_IMAGE:
+        }
+        else
+        {
+          Property::Array* array = imageURLValue->GetArray();
+          if( array )
           {
-            visualPtr = ImageVisual::New( *( mFactoryCache.Get() ), visualUrl, propertyMap );
-            break;
+            visualPtr = AnimatedImageVisual::New( *( mFactoryCache.Get() ), *array, propertyMap );
           }
         }
       }
@@ -200,9 +211,20 @@ Toolkit::Visual::Base VisualFactory::CreateVisual( const Property::Map& property
     {
       Property::Value* imageURLValue = propertyMap.Find( Toolkit::ImageVisual::Property::URL, IMAGE_URL_NAME );
       std::string imageUrl;
-      if( imageURLValue && imageURLValue->Get( imageUrl ) )
+      if( imageURLValue )
       {
-        visualPtr = AnimatedImageVisual::New( *( mFactoryCache.Get() ), imageUrl, propertyMap );
+        if( imageURLValue->Get( imageUrl ) )
+        {
+          visualPtr = AnimatedImageVisual::New( *( mFactoryCache.Get() ), imageUrl, propertyMap );
+        }
+        else
+        {
+          Property::Array* array = imageURLValue->GetArray();
+          if( array )
+          {
+            visualPtr = AnimatedImageVisual::New( *( mFactoryCache.Get() ), *array, propertyMap );
+          }
+        }
       }
       break;
     }
index 1fc3272..81af2a0 100644 (file)
@@ -80,6 +80,10 @@ const char * const IMAGE_WRAP_MODE_U("wrapModeU");
 const char * const IMAGE_WRAP_MODE_V("wrapModeV");
 const char * const IMAGE_BORDER( "border" );
 const char * const PIXEL_ALIGNED_UNIFORM_NAME( "uPixelAligned" );
+const char * const ANIMATED_IMAGE_URLS_NAME("urls");
+const char * const BATCH_SIZE_NAME("batchSize");
+const char * const CACHE_SIZE_NAME("cacheSize");
+const char * const FRAME_DELAY_NAME("frameDelay");
 
 // Text visual
 const char * const TEXT_PROPERTY( "text" );
index d57733c..3bbb95e 100644 (file)
@@ -67,7 +67,10 @@ extern const char * const IMAGE_WRAP_MODE_U;
 extern const char * const IMAGE_WRAP_MODE_V;
 extern const char * const IMAGE_BORDER;
 extern const char * const PIXEL_ALIGNED_UNIFORM_NAME;
-
+extern const char * const ANIMATED_IMAGE_URLS_NAME;
+extern const char * const BATCH_SIZE_NAME;
+extern const char * const CACHE_SIZE_NAME;
+extern const char * const FRAME_DELAY_NAME;
 
 // Text visual
 extern const char * const TEXT_PROPERTY;
index a17ee6c..f8bd1d7 100644 (file)
@@ -31,7 +31,7 @@ namespace Toolkit
 
 const unsigned int TOOLKIT_MAJOR_VERSION = 1;
 const unsigned int TOOLKIT_MINOR_VERSION = 2;
-const unsigned int TOOLKIT_MICRO_VERSION = 48;
+const unsigned int TOOLKIT_MICRO_VERSION = 49;
 const char * const TOOLKIT_BUILD_DATE    = __DATE__ " " __TIME__;
 
 #ifdef DEBUG_ENABLED
index edd94cc..44d5692 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_IMAGE_VISUAL_PROPERTIES_H
 
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 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.
@@ -54,7 +54,8 @@ enum
 {
   /**
    * @brief The URL of the image.
-   * @details Name "url", type Property::STRING.
+   * @details Name "url", type Property::STRING or Property::ARRAY of Property::STRING
+   * @note The array form is used for generating animated image visuals.
    * @SINCE_1_1.45
    * @note Mandatory.
    */
index ccb4e51..01db7c7 100644 (file)
@@ -1,6 +1,6 @@
 Name:       dali-toolkit
 Summary:    The OpenGLES Canvas Core Library Toolkit
-Version:    1.2.48
+Version:    1.2.49
 Release:    1
 Group:      System/Libraries
 License:    Apache-2.0 and BSD-3-Clause and MIT