{
}
- 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;
# 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
/*
- * 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.
mLastBlendFuncSrcAlpha = 0;
mLastBlendFuncDstAlpha = 0;
mLastAutoTextureIdUsed = 0;
+ mNumGeneratedTextures = 0;
mLastShaderIdUsed = 0;
mLastProgramIdUsed = 0;
mLastUniformIdUsed = 0;
{
std::stringstream out;
out << GL_BLEND;
- bool blendEnabled = callStack.FindMethodAndParams("Enable", out.str());
+ bool blendEnabled = callStack.FindMethodAndParams( "Enable", out.str() );
return blendEnabled;
}
{
std::stringstream out;
out << GL_BLEND;
- bool blendEnabled = callStack.FindMethodAndParams("Disable", out.str());
+ bool blendEnabled = callStack.FindMethodAndParams( "Disable", out.str() );
return blendEnabled;
}
#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.
paramName<<"texture["<<i<<"]";
namedParams[paramName.str()] = ToString(textures[i]);
mDeletedTextureIds.push_back(textures[i]);
+ mNumGeneratedTextures--;
}
out << "]";
{
*(textures+i) = ++mLastAutoTextureIdUsed;
}
+ mNumGeneratedTextures++;
}
TraceCallStack::NamedParams namedParams;
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)
{
}
// Data for manipulating the IDs returned by GenTextures
GLuint mLastAutoTextureIdUsed;
+ GLuint mNumGeneratedTextures;
std::vector<GLuint> mNextTextureIds;
std::vector<GLuint> mDeletedTextureIds;
std::vector<GLuint> mBoundTextures;
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 );
}
}
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;
*
* Will wait for a maximum of 30s before failing the test and returning.
*/
-bool WaitForEventThreadTrigger( int triggerCount );
+bool WaitForEventThreadTrigger( int triggerCount, int timeoutInSeconds=30 );
}
} // namespace Dali
+
+namespace Test
+{
+
+int GetTimerCount()
+{
+ return Dali::Internal::Adaptor::gTimerCount;
+}
+
+void EmitGlobalTimerSignal()
+{
+ Dali::Internal::Adaptor::gTickSignal.Emit();
+}
+
+}
} // namespace Dali
+namespace Test
+{
+int GetTimerCount();
+void EmitGlobalTimerSignal();
+}
+
#endif // __DALI_TOOLKIT_TOOLKIT_TIMER_H__
--- /dev/null
+
+/*
+ * 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;
+}
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 "/";
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;
*/
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
using namespace Dali;
ImageView::ImageView()
-: Control( ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) )
+: Control( ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) ),
+ mRelayoutRequired(true)
{
}
void ImageView::OnResourceReady( Toolkit::Control control )
{
+ if( mRelayoutRequired)
+ {
+ mRelayoutRequired = false;
+ RelayoutRequest();
+ }
}
///////////////////////////////////////////////////////////
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
$(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 \
/*
- * 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>
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 );
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 );
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();
{
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
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 )
{
actor.RemoveRenderer( mImpl->mRenderer );
mImpl->mRenderer.Reset();
+ mPlacementActor.Reset();
}
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;
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;
}
*/
// 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
{
/**
* 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:
* @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
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 );
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 );
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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
}
// 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 )
* 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:
{
// 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;
}
{
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" :
{
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;
{
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 )
info->observerList.Erase( j );
observerCount--;
break;
- }
+ }
}
}
-
}
-
}
-
TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
{
return mCurrentTextureId++;
* 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.
{
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 );
}
}
}
{
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;
}
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" );
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;
#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.
{
/**
* @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.
*/