From: Adeel Kazmi Date: Thu, 9 Jul 2020 16:33:20 +0000 (+0000) Subject: Merge "Support Asynchronous Loading of Animated Image" into devel/master X-Git-Tag: dali_1.9.20~2 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=3a2dfe800fe4ec8214f42b28b3851ea8b8ffc72b;hp=b2e0875240ab94aeafce0f5a7d05f84a562184b7 Merge "Support Asynchronous Loading of Animated Image" into devel/master --- diff --git a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp index bad5ea0..59be4f2 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp @@ -158,6 +158,105 @@ int UtcDaliAnimatedImageVisualGetPropertyMap02(void) END_TEST; } +int UtcDaliAnimatedImageVisualGetPropertyMap03(void) +{ + ToolkitTestApplication application; + tet_infoline( "UtcDaliAnimatedImageVisualGetPropertyMap" ); + + // request AnimatedImageVisual with a property map + VisualFactory factory = VisualFactory::Get(); + Visual::Base animatedImageVisual = factory.CreateVisual( + Property::Map() + .Add( Toolkit::Visual::Property::TYPE, Visual::ANIMATED_IMAGE ) + .Add( ImageVisual::Property::URL, TEST_GIF_FILE_NAME ) + .Add( ImageVisual::Property::BATCH_SIZE, 1 ) + .Add( ImageVisual::Property::CACHE_SIZE, 1 ) + .Add( ImageVisual::Property::SYNCHRONOUS_LOADING, false )); + + Property::Map resultMap; + animatedImageVisual.CreatePropertyMap( resultMap ); + + // check the property values from the returned map from a visual + Property::Value* value = resultMap.Find( Toolkit::Visual::Property::TYPE, Property::INTEGER ); + DALI_TEST_CHECK( value ); + DALI_TEST_CHECK( value->Get() == Visual::ANIMATED_IMAGE ); + + value = resultMap.Find( ImageVisual::Property::URL, Property::STRING ); + DALI_TEST_CHECK( value ); + DALI_TEST_CHECK( value->Get() == TEST_GIF_FILE_NAME ); + + value = resultMap.Find( ImageVisual::Property::BATCH_SIZE, Property::INTEGER ); + DALI_TEST_CHECK( value ); + DALI_TEST_CHECK( value->Get() == 2 ); + + value = resultMap.Find( ImageVisual::Property::CACHE_SIZE, Property::INTEGER ); + DALI_TEST_CHECK( value ); + DALI_TEST_CHECK( value->Get() == 2 ); + + END_TEST; +} + + +int UtcDaliAnimatedImageVisualSynchronousLoading(void) +{ + ToolkitTestApplication application; + TestGlAbstraction& gl = application.GetGlAbstraction(); + + { + Property::Map propertyMap; + propertyMap.Insert(Visual::Property::TYPE, Visual::ANIMATED_IMAGE ); + propertyMap.Insert(ImageVisual::Property::URL, TEST_GIF_FILE_NAME ); + propertyMap.Insert( ImageVisual::Property::BATCH_SIZE, 2); + propertyMap.Insert( ImageVisual::Property::CACHE_SIZE, 2); + propertyMap.Insert( ImageVisual::Property::FRAME_DELAY, 20); + propertyMap.Insert( ImageVisual::Property::SYNCHRONOUS_LOADING, true); + + VisualFactory factory = VisualFactory::Get(); + Visual::Base visual = factory.CreateVisual( propertyMap ); + + DummyControl dummyControl = DummyControl::New(true); + Impl::DummyControl& dummyImpl = static_cast(dummyControl.GetImplementation()); + dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual ); + + dummyControl.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); + Stage::GetCurrent().Add( dummyControl ); + + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + + application.SendNotification(); + application.Render(20); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( Test::GetTimerCount(), 1, TEST_LOCATION ); + DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 2, TEST_LOCATION ); + + DevelControl::DoAction( dummyControl, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedImageVisual::Action::JUMP_TO, 3 ); + + application.SendNotification(); + application.Render(20); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 3, 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 UtcDaliAnimatedImageVisualJumpToAction(void) { @@ -318,6 +417,12 @@ int UtcDaliAnimatedImageVisualAnimatedImage01(void) dummyControl.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); Stage::GetCurrent().Add( dummyControl ); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION ); + application.SendNotification(); application.Render(20); @@ -331,6 +436,11 @@ int UtcDaliAnimatedImageVisualAnimatedImage01(void) Test::EmitGlobalTimerSignal(); application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION ); + + application.SendNotification(); application.Render(20); DALI_TEST_EQUALS( gl.GetLastGenTextureId(), 4, TEST_LOCATION ); @@ -452,8 +562,8 @@ int UtcDaliAnimatedImageVisualMultiImage02(void) Property::Map propertyMap; propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE ); propertyMap.Insert( ImageVisual::Property::URL, Property::Value(urls) ); - propertyMap.Insert( ImageVisual::Property::BATCH_SIZE, 0); - propertyMap.Insert( ImageVisual::Property::CACHE_SIZE, 0); + propertyMap.Insert( ImageVisual::Property::BATCH_SIZE, 2); + propertyMap.Insert( ImageVisual::Property::CACHE_SIZE, 2); propertyMap.Insert( ImageVisual::Property::FRAME_DELAY, 100); VisualFactory factory = VisualFactory::Get(); @@ -749,9 +859,15 @@ void TestLoopCount( ToolkitTestApplication &application, DummyControl &dummyCont textureTrace.Enable(true); Stage::GetCurrent().Add( dummyControl ); + application.SendNotification(); application.Render(16); + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_INNER_LOCATION( location ) ); + + application.SendNotification(); + application.Render(); + tet_infoline( "Test that a timer has been created" ); DALI_TEST_EQUALS( Test::GetTimerCount(), 1, TEST_INNER_LOCATION( location ) ); @@ -767,10 +883,15 @@ void TestLoopCount( ToolkitTestApplication &application, DummyControl &dummyCont Test::EmitGlobalTimerSignal(); application.SendNotification(); application.Render(16); - DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 1, TEST_INNER_LOCATION( location ) ); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_INNER_LOCATION( location ) ); + + application.SendNotification(); + application.Render(); + DALI_TEST_EQUALS( gl.GetNumGeneratedTextures(), 2, TEST_INNER_LOCATION( location ) ); DALI_TEST_EQUALS( Test::AreTimersRunning(), true, TEST_INNER_LOCATION( location ) ); } - tet_printf( "\nTest Loop %u \n", i ); + tet_printf( "Test Loop %u \n\n", i + 1u ); } tet_printf( "Test that after %u loops, and we have no frame. Timer should stop \n", loopCount ); @@ -808,6 +929,12 @@ int UtcDaliAnimatedImageVisualLoopCount(void) TestLoopCount( application, dummyControl, 4, 0, TEST_LOCATION ); + dummyImpl.UnregisterVisual( DummyControl::Property::TEST_VISUAL ); + animatedImageVisual.Reset(); + + application.SendNotification(); + application.Render(16); + // Test with no (1) loop count. Request AnimatedImageVisual with a property map animatedImageVisual = factory.CreateVisual( Property::Map() @@ -822,6 +949,12 @@ int UtcDaliAnimatedImageVisualLoopCount(void) TestLoopCount( application, dummyControl, 4, 1, TEST_LOCATION ); + dummyImpl.UnregisterVisual( DummyControl::Property::TEST_VISUAL ); + animatedImageVisual.Reset(); + + application.SendNotification(); + application.Render(16); + // Test with no (100) loop count. Request AnimatedImageVisual with a property map animatedImageVisual = factory.CreateVisual( Property::Map() @@ -836,7 +969,6 @@ int UtcDaliAnimatedImageVisualLoopCount(void) TestLoopCount( application, dummyControl, 4, 100, TEST_LOCATION ); } - END_TEST; } @@ -871,12 +1003,22 @@ int UtcDaliAnimatedImageVisualPlayback(void) application.SendNotification(); application.Render(16); + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + tet_infoline( "Test that a timer has been created" ); DALI_TEST_EQUALS( Test::GetTimerCount(), 1, TEST_LOCATION ); Test::EmitGlobalTimerSignal(); application.SendNotification(); application.Render(16); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); DALI_TEST_EQUALS( Test::AreTimersRunning(), true, TEST_LOCATION ); Property::Map attributes; @@ -892,6 +1034,11 @@ int UtcDaliAnimatedImageVisualPlayback(void) Test::EmitGlobalTimerSignal(); application.SendNotification(); application.Render(16); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); DALI_TEST_EQUALS( Test::AreTimersRunning(), true, TEST_LOCATION ); tet_infoline( "Test Stop action. Timer should stop after Stop action" ); @@ -906,6 +1053,11 @@ int UtcDaliAnimatedImageVisualPlayback(void) Test::EmitGlobalTimerSignal(); application.SendNotification(); application.Render(16); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); DALI_TEST_EQUALS( Test::AreTimersRunning(), true, TEST_LOCATION ); dummyControl.Unparent(); diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp index d7aa177..7ece9d3 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp @@ -424,6 +424,11 @@ int UtcDaliImageViewPixelArea(void) // loading started application.SendNotification(); application.Render(16); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); DALI_TEST_CHECK( gifView.GetRendererCount() == 1u ); const Vector4 fullTextureRect( 0.f, 0.f, 1.f, 1.f ); @@ -861,6 +866,11 @@ int UtcDaliImageViewCheckResourceReady(void) application.SendNotification(); application.Render(16); + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + DALI_TEST_EQUALS( imageView.IsResourceReady(), true, TEST_LOCATION ); DALI_TEST_EQUALS( gResourceReadySignalFired, true, TEST_LOCATION ); diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp index 78792d9..e816bfc 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp @@ -1098,7 +1098,7 @@ int UtcDaliImageVisualTextureCancelRemoteLoad(void) int UtcDaliImageVisualTextureCancelAsyncLoad(void) { ToolkitTestApplication application; - tet_infoline( "Load image asynchronosly, cancel loading, then load again" ); + tet_infoline( "Load image asynchronously, cancel loading, then load again" ); VisualFactory factory = VisualFactory::Get(); DALI_TEST_CHECK( factory ); diff --git a/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp b/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp index c78d090..bad210c 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp @@ -2048,6 +2048,11 @@ int UtcDaliVisualFactoryGetAnimatedImageVisual1(void) application.SendNotification(); application.Render(); + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + // renderer is added to actor DALI_TEST_CHECK( actor.GetRendererCount() == 1u ); Renderer renderer = actor.GetRendererAt( 0u ); @@ -2061,14 +2066,23 @@ int UtcDaliVisualFactoryGetAnimatedImageVisual1(void) timer.MockEmitSignal(); application.SendNotification(); application.Render(); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); DALI_TEST_EQUALS( textureTrace.FindMethod("GenTextures"), true, TEST_LOCATION ); textureTrace.Reset(); - // Force the timer used by the animatedImageVisual to tick, timer.MockEmitSignal(); application.SendNotification(); application.Render(); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); DALI_TEST_EQUALS( textureTrace.FindMethod("GenTextures"), true, TEST_LOCATION ); textureTrace.Reset(); @@ -2076,6 +2090,11 @@ int UtcDaliVisualFactoryGetAnimatedImageVisual1(void) timer.MockEmitSignal(); application.SendNotification(); application.Render(); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); DALI_TEST_EQUALS( textureTrace.FindMethod("GenTextures"), true, TEST_LOCATION ); textureTrace.Reset(); @@ -2117,6 +2136,11 @@ int UtcDaliVisualFactoryGetAnimatedImageVisual2(void) application.SendNotification(); application.Render(); + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + DALI_TEST_CHECK( actor.GetRendererCount() == 1u ); DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); diff --git a/dali-toolkit/devel-api/image-loader/async-image-loader-devel.cpp b/dali-toolkit/devel-api/image-loader/async-image-loader-devel.cpp index 5fc985f..e9b7744 100644 --- a/dali-toolkit/devel-api/image-loader/async-image-loader-devel.cpp +++ b/dali-toolkit/devel-api/image-loader/async-image-loader-devel.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * Copyright (c) 2020 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. @@ -24,6 +24,13 @@ namespace Toolkit namespace DevelAsyncImageLoader { +uint32_t LoadAnimatedImage( AsyncImageLoader asyncImageLoader, + Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex ) +{ + return GetImplementation( asyncImageLoader ).LoadAnimatedImage( animatedImageLoading, frameIndex ); +} + uint32_t Load( AsyncImageLoader asyncImageLoader, const std::string& url, ImageDimensions dimensions, diff --git a/dali-toolkit/devel-api/image-loader/async-image-loader-devel.h b/dali-toolkit/devel-api/image-loader/async-image-loader-devel.h index 99ca154..a6732df 100644 --- a/dali-toolkit/devel-api/image-loader/async-image-loader-devel.h +++ b/dali-toolkit/devel-api/image-loader/async-image-loader-devel.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_DEVEL_API_IMAGE_LOADER_ASYNC_IMAGE_LOADER_DEVEL_H /* - * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * Copyright (c) 2020 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. @@ -17,8 +17,12 @@ * limitations under the License. */ +// EXTERNAL HEADER #include #include +#include + +// INTERNAL HEADER #include namespace Dali @@ -40,6 +44,19 @@ enum class PreMultiplyOnLoad }; /** + * @brief Starts an animated image loading task. + * @REMARK_INTERNET + * @REMARK_STORAGE + * @param[in] asyncImageLoader The ayncImageLoader + * @param[in] animatedImageLoading The AnimatedImageLoading to load animated image + * @param[in] frameIndex The frame index of a frame to be loaded frame + * @return The loading task id + */ +DALI_TOOLKIT_API uint32_t LoadAnimatedImage( AsyncImageLoader asyncImageLoader, + Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex ); + +/** * @brief Starts an image loading task. * @REMARK_INTERNET * @REMARK_STORAGE diff --git a/dali-toolkit/internal/image-loader/async-image-loader-impl.cpp b/dali-toolkit/internal/image-loader/async-image-loader-impl.cpp index 6fe8eba..912a441 100644 --- a/dali-toolkit/internal/image-loader/async-image-loader-impl.cpp +++ b/dali-toolkit/internal/image-loader/async-image-loader-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * Copyright (c) 2020 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. @@ -49,6 +49,19 @@ IntrusivePtr AsyncImageLoader::New() return internal; } +uint32_t AsyncImageLoader::LoadAnimatedImage( Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex ) +{ + if( !mIsLoadThreadStarted ) + { + mLoadThread.Start(); + mIsLoadThreadStarted = true; + } + mLoadThread.AddTask( new LoadingTask( ++mLoadTaskId, animatedImageLoading, frameIndex ) ); + + return mLoadTaskId; +} + uint32_t AsyncImageLoader::Load( const VisualUrl& url, ImageDimensions dimensions, FittingMode::Type fittingMode, diff --git a/dali-toolkit/internal/image-loader/async-image-loader-impl.h b/dali-toolkit/internal/image-loader/async-image-loader-impl.h index a97088d..e49b146 100644 --- a/dali-toolkit/internal/image-loader/async-image-loader-impl.h +++ b/dali-toolkit/internal/image-loader/async-image-loader-impl.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_ASYNC_IMAGE_LOADER_IMPL_H /* - * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * Copyright (c) 2020 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. @@ -50,6 +50,12 @@ public: static IntrusivePtr New(); /** + * @copydoc Toolkit::AsyncImageLoader::LoadAnimatedImage( Dali::AnimatedImageLoading animatedImageLoading, uint32_t frameIndex ) + */ + uint32_t LoadAnimatedImage( Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex ); + + /** * @copydoc Toolkit::AsyncImageLoader::Load( const std::string&, ImageDimensions, FittingMode::Type, SamplingMode::Type, bool , DevelAsyncImageLoader::PreMultiplyOnLoad ) */ uint32_t Load( const VisualUrl& url, diff --git a/dali-toolkit/internal/image-loader/image-load-thread.cpp b/dali-toolkit/internal/image-loader/image-load-thread.cpp index 179734a..dae742d 100644 --- a/dali-toolkit/internal/image-loader/image-load-thread.cpp +++ b/dali-toolkit/internal/image-loader/image-load-thread.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * Copyright (c) 2020 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. @@ -33,6 +33,24 @@ namespace Toolkit namespace Internal { +LoadingTask::LoadingTask( uint32_t id, Dali::AnimatedImageLoading animatedImageLoading, uint32_t frameIndex ) +: pixelBuffer(), + url(), + id( id ), + dimensions(), + fittingMode(), + samplingMode(), + orientationCorrection(), + preMultiplyOnLoad( DevelAsyncImageLoader::PreMultiplyOnLoad::OFF ), + isMaskTask( false ), + maskPixelBuffer(), + contentScale( 1.0f ), + cropToMask( false ), + animatedImageLoading( animatedImageLoading ), + frameIndex( frameIndex ) +{ +} + LoadingTask::LoadingTask( uint32_t id, const VisualUrl& url, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, bool orientationCorrection, DevelAsyncImageLoader::PreMultiplyOnLoad preMultiplyOnLoad ) : pixelBuffer(), @@ -46,7 +64,9 @@ LoadingTask::LoadingTask( uint32_t id, const VisualUrl& url, ImageDimensions dim isMaskTask( false ), maskPixelBuffer(), contentScale( 1.0f ), - cropToMask( false ) + cropToMask( false ), + animatedImageLoading(), + frameIndex( 0u ) { } @@ -63,13 +83,19 @@ LoadingTask::LoadingTask( uint32_t id, Devel::PixelBuffer pixelBuffer, Devel::Pi isMaskTask( true ), maskPixelBuffer( maskPixelBuffer ), contentScale( contentScale ), - cropToMask( cropToMask ) + cropToMask( cropToMask ), + animatedImageLoading(), + frameIndex( 0u ) { } void LoadingTask::Load() -{ - if( url.IsLocalResource() ) +{; + if( animatedImageLoading ) + { + pixelBuffer = animatedImageLoading.LoadFrame( frameIndex ); + } + else if( url.IsLocalResource() ) { pixelBuffer = Dali::LoadImageFromFile( url.GetUrl(), dimensions, fittingMode, samplingMode, orientationCorrection ); } @@ -140,7 +166,6 @@ void ImageLoadThread::Run() void ImageLoadThread::AddTask( LoadingTask* task ) { bool wasEmpty = false; - { // Lock while adding task to the queue ConditionalWait::ScopedLock lock( mConditionalWait ); diff --git a/dali-toolkit/internal/image-loader/image-load-thread.h b/dali-toolkit/internal/image-loader/image-load-thread.h index 779541a..b435611 100644 --- a/dali-toolkit/internal/image-loader/image-load-thread.h +++ b/dali-toolkit/internal/image-loader/image-load-thread.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_IMAGE_LOAD_THREAD_H /* - * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * Copyright (c) 2020 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. @@ -47,6 +47,16 @@ struct LoadingTask /** * Constructor. * @param [in] id of the task + * @param [in] animatedImageLoading The AnimatedImageLoading to load animated image + * @param [in] frameIndex The frame index of a frame to be loaded frame + */ + LoadingTask( uint32_t id, + Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex ); + + /** + * Constructor. + * @param [in] id of the task * @param [in] url The URL of the image file to load. * @param [in] size The width and height to fit the loaded image to, 0.0 means whole image * @param [in] fittingMode The method used to fit the shape of the image before loading to the shape defined by the size parameter. @@ -60,7 +70,7 @@ struct LoadingTask FittingMode::Type fittingMode, SamplingMode::Type samplingMode, bool orientationCorrection, - DevelAsyncImageLoader::PreMultiplyOnLoad preMultiplyOnLoad); + DevelAsyncImageLoader::PreMultiplyOnLoad preMultiplyOnLoad ); /** * Constructor. @@ -72,11 +82,11 @@ struct LoadingTask * @param [in] preMultiplyOnLoad ON if the image's color should be multiplied by it's alpha. Set to OFF if there is no alpha. */ LoadingTask( uint32_t id, - Devel::PixelBuffer pixelBuffer, - Devel::PixelBuffer maskPixelBuffer, - float contentScale, - bool cropToMask, - DevelAsyncImageLoader::PreMultiplyOnLoad preMultiplyOnLoad); + Devel::PixelBuffer pixelBuffer, + Devel::PixelBuffer maskPixelBuffer, + float contentScale, + bool cropToMask, + DevelAsyncImageLoader::PreMultiplyOnLoad preMultiplyOnLoad ); /** * Load the image @@ -117,6 +127,8 @@ public: Devel::PixelBuffer maskPixelBuffer; ///< pixelBuffer of mask image float contentScale; ///< The factor to scale the content bool cropToMask; ///< Whether to crop the content to the mask size + Dali::AnimatedImageLoading animatedImageLoading; + uint32_t frameIndex; }; diff --git a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp index fdde260..6b7adf3 100755 --- a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp +++ b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp @@ -177,8 +177,8 @@ AnimatedImageVisual::AnimatedImageVisual( VisualFactoryCache& factoryCache, Imag mCurrentFrameIndex( 0 ), mImageUrls( NULL ), mImageCache( NULL ), - mCacheSize( 1 ), - mBatchSize( 1 ), + mCacheSize( 2 ), + mBatchSize( 2 ), mFrameDelay( 100 ), mLoopCount( LOOP_FOREVER ), mCurrentLoopIndex( 0 ), @@ -221,6 +221,9 @@ void AnimatedImageVisual::DoCreatePropertyMap( Property::Map& map ) const { map.Clear(); + bool sync = IsSynchronousLoadingRequired(); + map.Insert( Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING, sync ); + map.Insert( Toolkit::Visual::Property::TYPE, Toolkit::Visual::ANIMATED_IMAGE ); if( mImageUrl.IsValid() ) @@ -402,7 +405,14 @@ void AnimatedImageVisual::DoSetProperty( Property::Index index, int batchSize; if( value.Get( batchSize ) ) { - mBatchSize = batchSize; + if( batchSize < 2 ) + { + DALI_LOG_ERROR( "The minimum value of batch size is 2." ); + } + else + { + mBatchSize = batchSize; + } } break; } @@ -412,7 +422,14 @@ void AnimatedImageVisual::DoSetProperty( Property::Index index, int cacheSize; if( value.Get( cacheSize ) ) { - mCacheSize = cacheSize; + if( cacheSize < 2 ) + { + DALI_LOG_ERROR( "The minimum value of cache size is 2." ); + } + else + { + mCacheSize = cacheSize; + } } break; } @@ -446,6 +463,21 @@ void AnimatedImageVisual::DoSetProperty( Property::Index index, } break; } + + case Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING: + { + bool sync = false; + value.Get( sync ); + if( sync ) + { + mImpl->mFlags |= Impl::IS_SYNCHRONOUS_RESOURCE_LOADING; + } + else + { + mImpl->mFlags &= ~Impl::IS_SYNCHRONOUS_RESOURCE_LOADING; + } + break; + } } } @@ -543,7 +575,7 @@ void AnimatedImageVisual::LoadFirstBatch() if( mAnimatedImageLoading ) { - mImageCache = new RollingAnimatedImageCache( textureManager, mAnimatedImageLoading, mFrameCount, *this, cacheSize, batchSize ); + mImageCache = new RollingAnimatedImageCache( textureManager, mAnimatedImageLoading, mFrameCount, *this, cacheSize, batchSize, IsSynchronousLoadingRequired() ); } else if( mImageUrls ) { @@ -607,14 +639,16 @@ TextureSet AnimatedImageVisual::PrepareTextureSet() { TextureSet textureSet; if (mImageCache) + { textureSet = mImageCache->FirstFrame(); + } if( textureSet ) { SetImageSize( textureSet ); } else { - DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"ResourceReady(ResourceStatus::FAILED)\n"); + DALI_LOG_INFO( gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::FAILED)\n" ); ResourceReady( Toolkit::Visual::ResourceStatus::FAILED ); } @@ -644,7 +678,7 @@ void AnimatedImageVisual::FrameReady( TextureSet textureSet ) } else { - if(mImpl->mRenderer) + if( mImpl->mRenderer ) { mImpl->mRenderer.SetTextures( textureSet ); } diff --git a/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.cpp b/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.cpp index c421678..3992b08 100644 --- a/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.cpp +++ b/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.cpp @@ -60,15 +60,17 @@ namespace Internal RollingAnimatedImageCache::RollingAnimatedImageCache( TextureManager& textureManager, AnimatedImageLoading& animatedImageLoading, uint32_t frameCount, ImageCache::FrameReadyObserver& observer, - uint16_t cacheSize, uint16_t batchSize ) + uint16_t cacheSize, uint16_t batchSize, bool isSynchronousLoading ) : ImageCache( textureManager, observer, batchSize ), mAnimatedImageLoading( animatedImageLoading ), mFrameCount( frameCount ), mFrameIndex( 0 ), - mCacheSize( cacheSize ), - mQueue( cacheSize ) + mQueue( cacheSize ), + mIsSynchronousLoading( isSynchronousLoading ), + mOnLoading( false ) { mImageUrls.resize( mFrameCount ); + mIntervals.assign( mFrameCount, 0 ); LoadBatch(); } @@ -76,10 +78,10 @@ RollingAnimatedImageCache::~RollingAnimatedImageCache() { if( mTextureManagerAlive ) { - while( IsFrontReady() ) + while( !mQueue.IsEmpty() ) { ImageFrame imageFrame = mQueue.PopFront(); - Dali::Toolkit::TextureManager::RemoveTexture( mImageUrls[ imageFrame.mFrameNumber ].mUrl ); + mTextureManager.Remove( mImageUrls[ imageFrame.mFrameNumber ].mTextureId, this ); } } } @@ -87,29 +89,59 @@ RollingAnimatedImageCache::~RollingAnimatedImageCache() TextureSet RollingAnimatedImageCache::Frame( uint32_t frameIndex ) { bool popExist = false; - while( IsFrontReady() && mQueue.Front().mFrameNumber != frameIndex ) + while( !mQueue.IsEmpty() && mQueue.Front().mFrameNumber != frameIndex ) { ImageFrame imageFrame = mQueue.PopFront(); - Dali::Toolkit::TextureManager::RemoveTexture( mImageUrls[ imageFrame.mFrameNumber ].mUrl ); + mTextureManager.Remove( mImageUrls[ imageFrame.mFrameNumber ].mTextureId, this ); mImageUrls[ imageFrame.mFrameNumber ].mTextureId = TextureManager::INVALID_TEXTURE_ID; popExist = true; } - if( popExist || mImageUrls[ frameIndex ].mTextureId == TextureManager::INVALID_TEXTURE_ID ) + + TextureSet textureSet; + // If we need to load new frame that are not stored in queue. + // Load the frame synchronously. + if( mIsSynchronousLoading && mQueue.IsEmpty() ) + { + bool synchronousLoading = true; + textureSet = mTextureManager.LoadAnimatedImageTexture( mAnimatedImageLoading, frameIndex, SamplingMode::BOX_THEN_LINEAR, + synchronousLoading, mImageUrls[ frameIndex ].mTextureId, Dali::WrapMode::Type::DEFAULT, + Dali::WrapMode::Type::DEFAULT, this ); + mFrameIndex = ( frameIndex + 1 ) % mFrameCount; + } + + if( popExist || mQueue.IsEmpty() ) { // If the frame of frameIndex was already loaded, load batch from the last frame of queue - if( IsFrontReady() ) + if( !mQueue.IsEmpty() ) { mFrameIndex = ( mQueue.Back().mFrameNumber + 1 ) % mFrameCount; } - // If the queue is empty, load batch from the frame of frameIndex else { - mFrameIndex = frameIndex; + // If the request is for the first frame or a jumped frame(JUMP_TO) remove current waiting queue. + mLoadWaitingQueue.clear(); + // If the queue is empty, and the frame of frameIndex is not loaded synchronously. load batch from the frame of frameIndex + if( !textureSet ) + { + mFrameIndex = frameIndex; + } } LoadBatch(); } - return GetFrontTextureSet(); + if( !textureSet ) + { + if( IsFrontReady() == true ) + { + textureSet = GetFrontTextureSet(); + } + else + { + mWaitingForReadyFrame = true; + } + } + + return textureSet; } TextureSet RollingAnimatedImageCache::FirstFrame() @@ -119,13 +151,24 @@ TextureSet RollingAnimatedImageCache::FirstFrame() uint32_t RollingAnimatedImageCache::GetFrameInterval( uint32_t frameIndex ) { - Frame( frameIndex ); return mAnimatedImageLoading.GetFrameInterval( frameIndex ); } bool RollingAnimatedImageCache::IsFrontReady() const { - return ( !mQueue.IsEmpty() ); + return ( !mQueue.IsEmpty() && mQueue.Front().mReady ); +} + +void RollingAnimatedImageCache::RequestFrameLoading( uint32_t frameIndex ) +{ + mRequestingLoad = true; + + bool synchronousLoading = false; + mTextureManager.LoadAnimatedImageTexture( mAnimatedImageLoading, frameIndex, SamplingMode::BOX_THEN_LINEAR, + synchronousLoading, mImageUrls[ frameIndex ].mTextureId, Dali::WrapMode::Type::DEFAULT, + Dali::WrapMode::Type::DEFAULT, this ); + + mRequestingLoad = false; } void RollingAnimatedImageCache::LoadBatch() @@ -134,61 +177,46 @@ void RollingAnimatedImageCache::LoadBatch() // Once the cache is filled, as frames progress, the old frame is // removed, and another frame is loaded - std::vector pixelDataList; - - // Get the smallest number of frames we need to load - int batchSize = std::min( std::size_t(mBatchSize), mCacheSize - mQueue.Count() ); - DALI_LOG_INFO( gAnimImgLogFilter, Debug::Concise, "RollingAnimatedImageCache::LoadBatch() mFrameIndex:%d batchSize:%d\n", mFrameIndex, batchSize ); - if( mAnimatedImageLoading.LoadNextNFrames( mFrameIndex, batchSize, pixelDataList) ) + bool frontFrameReady = IsFrontReady(); + for( unsigned int i=0; i< mBatchSize && !mQueue.IsFull(); ++i ) { - unsigned int pixelDataListCount = pixelDataList.size(); + ImageFrame imageFrame; + imageFrame.mFrameNumber = mFrameIndex; + imageFrame.mReady = false; - for( unsigned int i = 0; i < pixelDataListCount && !mQueue.IsFull(); ++i ) + mQueue.PushBack( imageFrame ); + + if( !mOnLoading ) { - ImageFrame imageFrame; - - // create the texture for uploading the pixel data - Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, - pixelDataList[i].GetPixelFormat(), - pixelDataList[i].GetWidth(), - pixelDataList[i].GetHeight() ); - - texture.Upload( pixelDataList[i] ); - - mImageUrls[ mUrlIndex ].mUrl = Dali::Toolkit::TextureManager::AddTexture(texture); - imageFrame.mFrameNumber = mUrlIndex; - - ++mUrlIndex; - mUrlIndex %= mImageUrls.size(); - - mQueue.PushBack( imageFrame ); - - bool synchronousLoading = false; - bool atlasingStatus = false; - bool loadingStatus = false; - TextureManager::MaskingDataPointer maskInfo = nullptr; - AtlasUploadObserver* atlasObserver = nullptr; - ImageAtlasManagerPtr imageAtlasManager = nullptr; - Vector4 textureRect; - Dali::ImageDimensions textureRectSize; - auto preMultiply = TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY; - - mTextureManager.LoadTexture( - mImageUrls[ imageFrame.mFrameNumber ].mUrl, ImageDimensions(), FittingMode::SCALE_TO_FILL, - SamplingMode::BOX_THEN_LINEAR, maskInfo, - synchronousLoading, mImageUrls[ imageFrame.mFrameNumber ].mTextureId, textureRect, textureRectSize, - atlasingStatus, loadingStatus, Dali::WrapMode::Type::DEFAULT, - Dali::WrapMode::Type::DEFAULT, NULL, - atlasObserver, imageAtlasManager, ENABLE_ORIENTATION_CORRECTION, TextureManager::ReloadPolicy::CACHED, preMultiply ); + mOnLoading = true; + RequestFrameLoading( mFrameIndex ); + } + else + { + mLoadWaitingQueue.push_back( mFrameIndex ); } - mFrameIndex += batchSize; + mFrameIndex++; mFrameIndex %= mFrameCount; } + CheckFrontFrame( frontFrameReady ); + LOG_CACHE; } +void RollingAnimatedImageCache::SetImageFrameReady( TextureManager::TextureId textureId ) +{ + for( std::size_t i = 0; i < mQueue.Count() ; ++i ) + { + if( GetCachedTextureId( i ) == textureId ) + { + mQueue[i].mReady = true; + break; + } + } +} + TextureSet RollingAnimatedImageCache::GetFrontTextureSet() const { DALI_LOG_INFO( gAnimImgLogFilter, Debug::Concise, "RollingAnimatedImageCache::GetFrontTextureSet() FrameNumber:%d\n", mQueue[ 0 ].mFrameNumber ); @@ -202,6 +230,68 @@ TextureManager::TextureId RollingAnimatedImageCache::GetCachedTextureId( int ind return mImageUrls[ mQueue[ index ].mFrameNumber ].mTextureId; } +void RollingAnimatedImageCache::CheckFrontFrame( bool wasReady ) +{ + if( mWaitingForReadyFrame && wasReady == false && IsFrontReady() ) + { + mWaitingForReadyFrame = false; + mObserver.FrameReady( GetFrontTextureSet() ); + } +} + +void RollingAnimatedImageCache::UploadComplete( + bool loadSuccess, + int32_t textureId, + TextureSet textureSet, + bool useAtlasing, + const Vector4& atlasRect, + bool preMultiplied ) +{ + 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; + } + + mOnLoading = false; + // The frames of a single animated image can not be loaded parallelly. + // Therefore, a frame is now loading, other orders are waiting. + // And, after the frame is loaded, requests load of next order. + if( !mLoadWaitingQueue.empty() ) + { + uint32_t loadingIndex = mLoadWaitingQueue.front(); + mLoadWaitingQueue.erase( mLoadWaitingQueue.begin() ); + mOnLoading = true; + RequestFrameLoading( loadingIndex ); + } + + LOG_CACHE; +} + +void RollingAnimatedImageCache::LoadComplete( + bool loadSuccess, + Devel::PixelBuffer pixelBuffer, + const VisualUrl& url, + bool preMultiplied ) +{ + // LoadComplete is called if this TextureUploadObserver requested to load + // an image that will be returned as a type of PixelBuffer by using a method + // TextureManager::LoadPixelBuffer. +} + } //namespace Internal } //namespace Toolkit } //namespace Dali diff --git a/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.h b/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.h index 503f65e..8281c32 100644 --- a/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.h +++ b/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.h @@ -37,7 +37,7 @@ namespace Internal * Frames are always ready, so the observer.FrameReady callback is never triggered; * the FirstFrame and NextFrame APIs will always return a texture. */ -class RollingAnimatedImageCache : public ImageCache +class RollingAnimatedImageCache : public ImageCache, public TextureUploadObserver { public: /** @@ -48,16 +48,18 @@ public: * @param[in] observer FrameReady observer * @param[in] cacheSize The size of the cache * @param[in] batchSize The size of a batch to load + * @param[in] isSynchronousLoading The flag to define whether to load first frame synchronously * * This will start loading textures immediately, according to the * batch and cache sizes. */ RollingAnimatedImageCache( TextureManager& textureManager, - AnimatedImageLoading& animatedImageLoader, - uint32_t frameCount, - ImageCache::FrameReadyObserver& observer, - uint16_t cacheSize, - uint16_t batchSize ); + AnimatedImageLoading& animatedImageLoader, + uint32_t frameCount, + ImageCache::FrameReadyObserver& observer, + uint16_t cacheSize, + uint16_t batchSize, + bool isSynchronousLoading ); /** * Destructor @@ -88,11 +90,21 @@ private: bool IsFrontReady() const; /** + * Request to Load a frame + */ + void RequestFrameLoading( uint32_t frameIndex ); + + /** * 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 */ @@ -103,6 +115,27 @@ private: */ 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: + void UploadComplete( + bool loadSuccess, + int32_t textureId, + TextureSet textureSet, + bool useAtlasing, + const Vector4& atlasRect, + bool preMultiplied ) override; + + void LoadComplete( + bool loadSuccess, + Devel::PixelBuffer pixelBuffer, + const VisualUrl& url, + bool preMultiplied ) override; + private: /** * Secondary class to hold readiness and index into url @@ -110,14 +143,18 @@ private: struct ImageFrame { unsigned int mFrameNumber = 0u; + bool mReady = false; }; - Dali::AnimatedImageLoading& mAnimatedImageLoading; - uint32_t mFrameCount; - int mFrameIndex; - std::vector mImageUrls; - uint16_t mCacheSize; - CircularQueue mQueue; + Dali::AnimatedImageLoading mAnimatedImageLoading; + uint32_t mFrameCount; + int mFrameIndex; + std::vector mImageUrls; + std::vector mIntervals; + std::vector mLoadWaitingQueue; + CircularQueue mQueue; + bool mIsSynchronousLoading; + bool mOnLoading; }; } // namespace Internal diff --git a/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp b/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp index 6ba1a99..147f88c 100644 --- a/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp +++ b/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp @@ -431,6 +431,8 @@ void NPatchVisual::OnSetTransform() void NPatchVisual::DoCreatePropertyMap( Property::Map& map ) const { map.Clear(); + bool sync = IsSynchronousLoadingRequired(); + map.Insert( Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING, sync ); map.Insert( Toolkit::Visual::Property::TYPE, Toolkit::Visual::N_PATCH ); map.Insert( Toolkit::ImageVisual::Property::URL, mImageUrl.GetUrl() ); map.Insert( Toolkit::ImageVisual::Property::BORDER_ONLY, mBorderOnly ); diff --git a/dali-toolkit/internal/visuals/texture-manager-impl.cpp b/dali-toolkit/internal/visuals/texture-manager-impl.cpp index 5c694d0..e23290b 100644 --- a/dali-toolkit/internal/visuals/texture-manager-impl.cpp +++ b/dali-toolkit/internal/visuals/texture-manager-impl.cpp @@ -143,6 +143,71 @@ TextureManager::~TextureManager() } } +TextureSet TextureManager::LoadAnimatedImageTexture( + Dali::AnimatedImageLoading animatedImageLoading, uint32_t frameIndex, Dali::SamplingMode::Type samplingMode, + bool synchronousLoading, TextureManager::TextureId& textureId, Dali::WrapMode::Type wrapModeU, Dali::WrapMode::Type wrapModeV, TextureUploadObserver* textureObserver ) +{ + TextureSet textureSet; + + if( synchronousLoading ) + { + Devel::PixelBuffer pixelBuffer; + if( animatedImageLoading ) + { + pixelBuffer = animatedImageLoading.LoadFrame( frameIndex ); + } + if( !pixelBuffer ) + { + // use broken image + pixelBuffer = LoadImageFromFile( mBrokenImageUrl ); + PixelData pixelData; + if( pixelBuffer ) + { + pixelData = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer + } + Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), + pixelData.GetWidth(), pixelData.GetHeight() ); + texture.Upload( pixelData ); + textureSet = TextureSet::New(); + textureSet.SetTexture( 0u, texture ); + } + else + { + PixelData pixelData = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer + if( !textureSet ) + { + Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), + pixelData.GetWidth(), pixelData.GetHeight() ); + texture.Upload( pixelData ); + textureSet = TextureSet::New(); + textureSet.SetTexture( 0u, texture ); + } + } + } + else + { + auto preMultiply = TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY; + textureId = RequestLoadInternal( animatedImageLoading.GetUrl(), INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL, + SamplingMode::BOX_THEN_LINEAR, TextureManager::NO_ATLAS, false, UPLOAD_TO_TEXTURE, textureObserver, + true, TextureManager::ReloadPolicy::CACHED, preMultiply, animatedImageLoading, frameIndex ); + TextureManager::LoadState loadState = GetTextureStateInternal( textureId ); + if( loadState == TextureManager::UPLOADED ) + { + // UploadComplete has already been called - keep the same texture set + textureSet = GetTextureSet( textureId ); + } + } + + if( textureSet ) + { + Sampler sampler = Sampler::New(); + sampler.SetWrapMode( wrapModeU, wrapModeV ); + textureSet.SetSampler( 0u, sampler ); + } + + return textureSet; +} + Devel::PixelBuffer TextureManager::LoadPixelBuffer( const VisualUrl& url, Dali::ImageDimensions desiredSize, Dali::FittingMode::Type fittingMode, Dali::SamplingMode::Type samplingMode, bool synchronousLoading, TextureUploadObserver* textureObserver, bool orientationCorrection, TextureManager::MultiplyOnLoad& preMultiplyOnLoad ) { @@ -163,7 +228,7 @@ Devel::PixelBuffer TextureManager::LoadPixelBuffer( { RequestLoadInternal( url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, TextureManager::NO_ATLAS, false, RETURN_PIXEL_BUFFER, textureObserver, orientationCorrection, TextureManager::ReloadPolicy::FORCED, - preMultiplyOnLoad ); + preMultiplyOnLoad, Dali::AnimatedImageLoading(), 0u ); } return pixelBuffer; @@ -335,7 +400,7 @@ TextureManager::TextureId TextureManager::RequestLoad( { return RequestLoadInternal( url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas, false, UPLOAD_TO_TEXTURE, observer, orientationCorrection, reloadPolicy, - preMultiplyOnLoad ); + preMultiplyOnLoad, Dali::AnimatedImageLoading(), 0u ); } TextureManager::TextureId TextureManager::RequestLoad( @@ -354,7 +419,7 @@ TextureManager::TextureId TextureManager::RequestLoad( { return RequestLoadInternal( url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas, cropToMask, UPLOAD_TO_TEXTURE, observer, orientationCorrection, reloadPolicy, - preMultiplyOnLoad ); + preMultiplyOnLoad, Dali::AnimatedImageLoading(), 0u ); } TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl ) @@ -363,7 +428,7 @@ TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& mask auto preMultiply = TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY; return RequestLoadInternal( maskUrl, INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, false, KEEP_PIXEL_BUFFER, NULL, true, - TextureManager::ReloadPolicy::CACHED, preMultiply ); + TextureManager::ReloadPolicy::CACHED, preMultiply, Dali::AnimatedImageLoading(), 0u ); } TextureManager::TextureId TextureManager::RequestLoadInternal( @@ -379,16 +444,19 @@ TextureManager::TextureId TextureManager::RequestLoadInternal( TextureUploadObserver* observer, bool orientationCorrection, TextureManager::ReloadPolicy reloadPolicy, - TextureManager::MultiplyOnLoad& preMultiplyOnLoad ) + TextureManager::MultiplyOnLoad& preMultiplyOnLoad, + Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex ) { // First check if the requested Texture is cached. + bool isAnimatedImage = ( animatedImageLoading ) ? true : false; const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, - maskTextureId ); + maskTextureId, storageType, isAnimatedImage, frameIndex ); TextureManager::TextureId textureId = INVALID_TEXTURE_ID; // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision. int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, - maskTextureId, preMultiplyOnLoad, storageType ); + maskTextureId, preMultiplyOnLoad, storageType, isAnimatedImage, frameIndex ); // Check if the requested Texture exists in the cache. if( cacheIndex != INVALID_CACHE_INDEX ) @@ -416,7 +484,7 @@ TextureManager::TextureId TextureManager::RequestLoadInternal( mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(), desiredSize, contentScale, fittingMode, samplingMode, false, cropToMask, useAtlas, textureHash, orientationCorrection, - preMultiply ) ); + preMultiply, animatedImageLoading, frameIndex ) ); cacheIndex = mTextureInfoContainer.size() - 1u; DALI_LOG_INFO( gTextureManagerLogFilter, Debug::General, "TextureManager::RequestLoad( url=%s observer=%p ) New texture, cacheIndex:%d, textureId=%d\n", @@ -675,7 +743,6 @@ TextureSet TextureManager::RemoveExternalTexture( const std::string& url ) return TextureSet(); } - void TextureManager::AddObserver( TextureManager::LifecycleObserver& observer ) { // make sure an observer doesn't observe the same object twice @@ -763,10 +830,17 @@ void TextureManager::LoadTexture( TextureInfo& textureInfo, TextureUploadObserve auto premultiplyOnLoad = ( textureInfo.preMultiplyOnLoad && textureInfo.maskTextureId == INVALID_TEXTURE_ID ) ? DevelAsyncImageLoader::PreMultiplyOnLoad::ON : DevelAsyncImageLoader::PreMultiplyOnLoad::OFF; DALI_ASSERT_ALWAYS(loadingHelperIt != loadersContainer.End()); - loadingHelperIt->Load(textureInfo.textureId, textureInfo.url, - textureInfo.desiredSize, textureInfo.fittingMode, - textureInfo.samplingMode, textureInfo.orientationCorrection, - premultiplyOnLoad ); + if( textureInfo.animatedImageLoading ) + { + loadingHelperIt->LoadAnimatedImage( textureInfo.textureId, textureInfo.animatedImageLoading, textureInfo.frameIndex ); + } + else + { + loadingHelperIt->Load(textureInfo.textureId, textureInfo.url, + textureInfo.desiredSize, textureInfo.fittingMode, + textureInfo.samplingMode, textureInfo.orientationCorrection, + premultiplyOnLoad ); + } } ObserveTexture( textureInfo, observer ); } @@ -1078,7 +1152,10 @@ TextureManager::TextureHash TextureManager::GenerateHash( const FittingMode::Type fittingMode, const Dali::SamplingMode::Type samplingMode, const UseAtlas useAtlas, - TextureId maskTextureId ) + TextureId maskTextureId, + StorageType storageType, + bool isAnimationImage, + uint32_t frameIndex ) { std::string hashTarget( url ); const size_t urlLength = hashTarget.length(); @@ -1099,8 +1176,8 @@ TextureManager::TextureHash TextureManager::GenerateHash( *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff; // Bit-pack the FittingMode, SamplingMode and atlasing. - // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit - *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas; + // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit, storageType=2bits + *hashTargetPtr = ( fittingMode << 6u ) | ( samplingMode << 3 ) | ( useAtlas << 2 ) | storageType; } else { @@ -1123,6 +1200,19 @@ TextureManager::TextureHash TextureManager::GenerateHash( } } + if( isAnimationImage ) + { + auto textureIdIndex = hashTarget.length(); + hashTarget.resize( hashTarget.length() + sizeof( uint32_t ) ); + char* hashTargetPtr = &( hashTarget[ textureIdIndex ] ); + + for( size_t byteIter = 0; byteIter < sizeof( uint32_t ); ++byteIter ) + { + *hashTargetPtr++ = frameIndex & 0xff; + frameIndex >>= 8u; + } + } + if( maskTextureId != INVALID_TEXTURE_ID ) { auto textureIdIndex = hashTarget.length(); @@ -1150,7 +1240,9 @@ int TextureManager::FindCachedTexture( const bool useAtlas, TextureId maskTextureId, TextureManager::MultiplyOnLoad preMultiplyOnLoad, - StorageType storageType ) + StorageType storageType, + bool isAnimatedImage, + uint32_t frameIndex ) { // Default to an invalid ID, in case we do not find a match. int cacheIndex = INVALID_CACHE_INDEX; @@ -1171,7 +1263,9 @@ int TextureManager::FindCachedTexture( ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) || ( fittingMode == textureInfo.fittingMode && samplingMode == textureInfo.samplingMode ) ) && - ( storageType == textureInfo.storageType ) ) + ( storageType == textureInfo.storageType ) && + ( isAnimatedImage == ( ( textureInfo.animatedImageLoading ) ? true : false ) ) && + ( frameIndex == textureInfo.frameIndex ) ) { // 1. If preMultiplyOnLoad is MULTIPLY_ON_LOAD, then textureInfo.preMultiplyOnLoad should be true. The premultiplication result can be different. // 2. If preMultiplyOnLoad is LOAD_WITHOUT_MULTIPLY, then textureInfo.preMultiplied should be false. @@ -1217,27 +1311,36 @@ TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(TextureManager& textureMa { } -void TextureManager::AsyncLoadingHelper::Load(TextureId textureId, - const VisualUrl& url, - ImageDimensions desiredSize, - FittingMode::Type fittingMode, - SamplingMode::Type samplingMode, - bool orientationCorrection, - DevelAsyncImageLoader::PreMultiplyOnLoad preMultiplyOnLoad) +void TextureManager::AsyncLoadingHelper::LoadAnimatedImage( TextureId textureId, + Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex ) +{ + mLoadingInfoContainer.push_back( AsyncLoadingInfo( textureId ) ); + auto id = DevelAsyncImageLoader::LoadAnimatedImage( mLoader, animatedImageLoading, frameIndex ); + mLoadingInfoContainer.back().loadId = id; +} + +void TextureManager::AsyncLoadingHelper::Load( TextureId textureId, + const VisualUrl& url, + ImageDimensions desiredSize, + FittingMode::Type fittingMode, + SamplingMode::Type samplingMode, + bool orientationCorrection, + DevelAsyncImageLoader::PreMultiplyOnLoad preMultiplyOnLoad ) { - mLoadingInfoContainer.push_back(AsyncLoadingInfo(textureId)); + mLoadingInfoContainer.push_back( AsyncLoadingInfo( textureId ) ); auto id = DevelAsyncImageLoader::Load( mLoader, url.GetUrl(), desiredSize, fittingMode, samplingMode, orientationCorrection, preMultiplyOnLoad ); mLoadingInfoContainer.back().loadId = id; } -void TextureManager::AsyncLoadingHelper::ApplyMask( TextureId textureId, - Devel::PixelBuffer pixelBuffer, - Devel::PixelBuffer maskPixelBuffer, - float contentScale, - bool cropToMask, +void TextureManager::AsyncLoadingHelper::ApplyMask( TextureId textureId, + Devel::PixelBuffer pixelBuffer, + Devel::PixelBuffer maskPixelBuffer, + float contentScale, + bool cropToMask, DevelAsyncImageLoader::PreMultiplyOnLoad preMultiplyOnLoad ) { - mLoadingInfoContainer.push_back(AsyncLoadingInfo(textureId)); + mLoadingInfoContainer.push_back( AsyncLoadingInfo( textureId ) ); auto id = DevelAsyncImageLoader::ApplyMask( mLoader, pixelBuffer, maskPixelBuffer, contentScale, cropToMask, preMultiplyOnLoad ); mLoadingInfoContainer.back().loadId = id; } diff --git a/dali-toolkit/internal/visuals/texture-manager-impl.h b/dali-toolkit/internal/visuals/texture-manager-impl.h index cc5c0e4..c3cd015 100755 --- a/dali-toolkit/internal/visuals/texture-manager-impl.h +++ b/dali-toolkit/internal/visuals/texture-manager-impl.h @@ -164,6 +164,33 @@ public: // TextureManager Main API: /** + * @brief Requests an frame of animated image load. + * + * The parameters are used to specify how the animated image is loaded. + * The observer has the LoadComplete method called when the load is ready. + * + * @param[in] animatedImageLoading The AnimatedImageLoading that contain the animated image information + * @param[in] frameIndex The frame index to load. + * @param[in] samplingMode The SamplingMode to use + * @param[in] synchronousLoading true if the frame should be loaded synchronously + * @param[out] textureId The textureId of the frame + * @param[in] wrapModeU Horizontal Wrap mode + * @param[in] wrapModeV Vertical Wrap mode + * @param[in] textureObserver The client object should inherit from this and provide the "UploadCompleted" virtual. + * This is called when an image load completes (or fails). + * + * @return The texture set containing the frame of animated image, or empty if still loading. + */ + + TextureSet LoadAnimatedImageTexture( Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex, + Dali::SamplingMode::Type samplingMode, + bool synchronousLoading, + TextureManager::TextureId& textureId, + Dali::WrapMode::Type wrapModeU, Dali::WrapMode::Type wrapModeV, + TextureUploadObserver* textureObserver ); + + /** * @brief Requests an image load of the given URL to get PixelBuffer. * * The parameters are used to specify how the image is loaded. @@ -431,6 +458,8 @@ private: * @param[in] reloadPolicy Forces a reload of the texture even if already cached * @param[in] preMultiplyOnLoad True if the image color should be multiplied by it's alpha. Set to false if * there is no alpha + * @param[in] animatedImageLoading The AnimatedImageLoading to load animated image + * @param[in] frameIndex The frame index of a frame to be loaded frame * @return A TextureId to use as a handle to reference this Texture */ TextureId RequestLoadInternal( @@ -446,7 +475,9 @@ private: TextureUploadObserver* observer, bool orientationCorrection, TextureManager::ReloadPolicy reloadPolicy, - MultiplyOnLoad& preMultiplyOnLoad ); + MultiplyOnLoad& preMultiplyOnLoad, + Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex ); /** * @brief Get the current state of a texture @@ -477,7 +508,9 @@ private: UseAtlas useAtlas, TextureManager::TextureHash hash, bool orientationCorrection, - bool preMultiplyOnLoad ) + bool preMultiplyOnLoad, + Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex ) : url( url ), desiredSize( desiredSize ), useSize( desiredSize ), @@ -491,6 +524,8 @@ private: fittingMode( fittingMode ), samplingMode( samplingMode ), storageType( UPLOAD_TO_TEXTURE ), + animatedImageLoading( animatedImageLoading ), + frameIndex( frameIndex ), loadSynchronously( loadSynchronously ), useAtlas( useAtlas ), cropToMask( cropToMask ), @@ -522,6 +557,8 @@ private: FittingMode::Type fittingMode:3; ///< The requested FittingMode Dali::SamplingMode::Type samplingMode:3; ///< The requested SamplingMode StorageType storageType:2; ///< CPU storage / GPU upload; + Dali::AnimatedImageLoading animatedImageLoading; ///< AnimatedImageLoading that contains animated image information. + uint32_t frameIndex; ///< frame index that be loaded, in case of animated image bool loadSynchronously:1; ///< True if synchronous loading was requested UseAtlas useAtlas:2; ///< USE_ATLAS if an atlas was requested. ///< This is updated to false if atlas is not used @@ -685,18 +722,20 @@ private: * Only applies size, fitting mode andsampling mode if the size is specified. * Only applies maskTextureId if it isn't INVALID_TEXTURE_ID * Always applies useAtlas. - * @param[in] url The URL of the image to load - * @param[in] size The image size - * @param[in] fittingMode The FittingMode to use - * @param[in] samplingMode The SamplingMode to use - * @param[in] useAtlas True if atlased - * @param[in] maskTextureId The masking texture id (or INVALID_TEXTURE_ID) - * @return A hash of the provided data for caching. + * @param[in] url The URL of the image to load + * @param[in] size The image size + * @param[in] fittingMode The FittingMode to use + * @param[in] samplingMode The SamplingMode to use + * @param[in] useAtlas True if atlased + * @param[in] maskTextureId The masking texture id (or INVALID_TEXTURE_ID) + * @param[in] isAnimatedImage The boolean value to know whether the request is for animated image or not + * @param[in] frameIndex The frame index of a frame to be loaded frame + * @return A hash of the provided data for caching. */ TextureHash GenerateHash( const std::string& url, const ImageDimensions size, const FittingMode::Type fittingMode, const Dali::SamplingMode::Type samplingMode, const UseAtlas useAtlas, - TextureId maskTextureId ); + TextureId maskTextureId, StorageType storageType, bool isAnimatedImage, uint32_t frameIndex ); /** * @brief Looks up a cached texture by its hash. @@ -710,7 +749,9 @@ private: * @param[in] maskTextureId Optional texture ID to use to mask this image * @param[in] preMultiplyOnLoad if the image's color should be multiplied by it's alpha. Set to OFF if there is no alpha. * @param[in] storageType Whether the pixel data is stored in the cache, returned with PixelBuffer or uploaded to the GPU - * @return A TextureId of a cached Texture if found. Or INVALID_TEXTURE_ID if not found. + * @param[in] isAnimatedImage The boolean value to know whether the request is for animated image or not + * @param[in] frameIndex The frame index of a frame to be loaded frame + * @return A TextureId of a cached Texture if found. Or INVALID_TEXTURE_ID if not found. */ TextureManager::TextureId FindCachedTexture( const TextureManager::TextureHash hash, @@ -721,7 +762,9 @@ private: const bool useAtlas, TextureId maskTextureId, MultiplyOnLoad preMultiplyOnLoad, - StorageType storageType ); + StorageType storageType, + bool isAnimatedImage, + uint32_t frameIndex ); private: @@ -738,6 +781,16 @@ private: AsyncLoadingHelper(TextureManager& textureManager); /** + * @brief Load a new frame of animated image + * @param[in] textureId TextureId to reference the texture that will be loaded + * @param[in] animatedImageLoading The AnimatedImageLoading to load animated image + * @param[in] frameIndex The frame index of a frame to be loaded frame + */ + void LoadAnimatedImage( TextureId textureId, + Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex); + + /** * @brief Load a new texture. * @param[in] textureId TextureId to reference the texture that will be loaded * @param[in] url The URL of the image to load diff --git a/dali-toolkit/public-api/visuals/image-visual-properties.h b/dali-toolkit/public-api/visuals/image-visual-properties.h index 0f5d58b..c28b059 100644 --- a/dali-toolkit/public-api/visuals/image-visual-properties.h +++ b/dali-toolkit/public-api/visuals/image-visual-properties.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_IMAGE_VISUAL_PROPERTIES_H /* - * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * Copyright (c) 2020 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. @@ -184,19 +184,21 @@ enum /** * @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 + * before starting to play. Default value: 2 * @SINCE_1_2.60 * @note Optional. + * @note Minimum supported value is 2. */ BATCH_SIZE, /** * @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 + * cached ahead during playback. Default value: 2 * * @SINCE_1_2.60 * @note Optional. + * @note Minimum supported value is 2. * @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