From 21d324d6d7eaae6fec8af7ce8c4490bcb5557624 Mon Sep 17 00:00:00 2001 From: seungho Date: Wed, 1 Dec 2021 17:20:05 +0900 Subject: [PATCH] [Tizen] Refactoring Animated image visual - Animated image file will be opened when the visual is on scene. - Cache first frame only to cache single frame image as like image visual. - Pause timer when next frame is not cached yet. And resume it after the frame is ready. - Load policy and release policy is now supported. Change-Id: I86548fc0d1952a70959f710a7005eb1d4b66624b Signed-off-by: seungho --- .../animated-image/animated-image-visual.cpp | 299 +++++++++++---------- .../visuals/animated-image/animated-image-visual.h | 45 ++-- .../visuals/animated-image/fixed-image-cache.cpp | 138 +++++----- .../visuals/animated-image/fixed-image-cache.h | 69 ++--- .../visuals/animated-image/image-cache.cpp | 11 +- .../internal/visuals/animated-image/image-cache.h | 65 +++-- .../rolling-animated-image-cache.cpp | 223 ++++++++------- .../animated-image/rolling-animated-image-cache.h | 97 ++++--- .../visuals/animated-image/rolling-image-cache.cpp | 171 +++++------- .../visuals/animated-image/rolling-image-cache.h | 75 +++--- .../internal/visuals/texture-manager-impl.cpp | 83 ++++-- .../internal/visuals/texture-manager-impl.h | 45 ++-- .../internal/visuals/texture-upload-observer.h | 27 +- 13 files changed, 750 insertions(+), 598 deletions(-) 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 da7fb09..cc90d5d 100644 --- a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp +++ b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp @@ -61,8 +61,25 @@ DALI_ENUM_TO_STRING_TABLE_BEGIN(WRAP_MODE) DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::WrapMode, MIRRORED_REPEAT) DALI_ENUM_TO_STRING_TABLE_END(WRAP_MODE) -const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); -constexpr auto LOOP_FOREVER = -1; +// load policies +DALI_ENUM_TO_STRING_TABLE_BEGIN(LOAD_POLICY) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::LoadPolicy, IMMEDIATE) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::LoadPolicy, ATTACHED) +DALI_ENUM_TO_STRING_TABLE_END(LOAD_POLICY) + +// release policies +DALI_ENUM_TO_STRING_TABLE_BEGIN(RELEASE_POLICY) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::ReleasePolicy, DETACHED) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::ReleasePolicy, DESTROYED) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::ReleasePolicy, NEVER) +DALI_ENUM_TO_STRING_TABLE_END(RELEASE_POLICY) + +static constexpr uint32_t SINGLE_IMAGE_COUNT = 1u; +static constexpr uint32_t FIRST_FRAME_INDEX = 0u; +static constexpr uint16_t MINIMUM_CACHESIZE = 1; +static constexpr Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); +static constexpr auto LOOP_FOREVER = -1; +static constexpr auto FIRST_LOOP = 0u; #if defined(DEBUG_ENABLED) Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_ANIMATED_IMAGE"); @@ -74,9 +91,7 @@ Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, " * * | New * | DoSetProperties() - * | LoadFirstBatch() * | new cache - * | cache->LoadBatch() * | * | DoSetOnScene() * | PrepareTextureSet() @@ -85,7 +100,7 @@ Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, " * | StartFirstFrame() * | * | FrameReady(textureSet) - * | start first frame: + * | StartFirstFrame: * | actor.AddRenderer * | start timer * | mRenderer.SetTextures(textureSet) @@ -95,8 +110,7 @@ Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, " * | if front frame is ready, * | mRenderer.SetTextures( front frame's texture ) * | else - * | mWaitingForTexture=true - * | cache->LoadBatch() + * | Waiting for frame ready. * | * | FrameReady(textureSet) * | mRenderer.SetTextures(textureSet) @@ -110,11 +124,6 @@ AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache visual->InitializeAnimatedImage(imageUrl); visual->SetProperties(properties); - if(visual->mFrameCount > 0) - { - visual->LoadFirstBatch(); - } - visual->Initialize(); return visual; @@ -136,11 +145,6 @@ AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache visual->mFrameCount = imageUrls.Count(); visual->SetProperties(properties); - if(visual->mFrameCount > 0) - { - visual->LoadFirstBatch(); - } - visual->Initialize(); return visual; @@ -151,11 +155,6 @@ AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache AnimatedImageVisualPtr visual(new AnimatedImageVisual(factoryCache, shaderFactory)); visual->InitializeAnimatedImage(imageUrl); - if(visual->mFrameCount > 0) - { - visual->LoadFirstBatch(); - } - visual->Initialize(); return visual; @@ -163,9 +162,41 @@ AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache void AnimatedImageVisual::InitializeAnimatedImage(const VisualUrl& imageUrl) { - mImageUrl = imageUrl; + mImageUrl = imageUrl; mAnimatedImageLoading = AnimatedImageLoading::New(imageUrl.GetUrl(), imageUrl.IsLocalResource()); - mFrameCount = mAnimatedImageLoading.GetImageCount(); +} + +void AnimatedImageVisual::CreateImageCache() +{ + DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::CreateImageCache() batchSize:%d cacheSize:%d\n", mBatchSize, mCacheSize); + + TextureManager& textureManager = mFactoryCache.GetTextureManager(); + + if(mAnimatedImageLoading) + { + mImageCache = new RollingAnimatedImageCache(textureManager, mAnimatedImageLoading, *this, mCacheSize, mBatchSize, IsSynchronousLoadingRequired()); + } + else if(mImageUrls) + { + // 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::max(std::min(mBatchSize, numUrls), MINIMUM_CACHESIZE); + uint16_t cacheSize = std::max(std::min(std::max(batchSize, mCacheSize), numUrls), MINIMUM_CACHESIZE); + if(cacheSize < numUrls) + { + mImageCache = new RollingImageCache(textureManager, *mImageUrls, *this, cacheSize, batchSize, mFrameDelay); + } + else + { + mImageCache = new FixedImageCache(textureManager, *mImageUrls, *this, batchSize, mFrameDelay); + } + } + + if(!mImageCache) + { + DALI_LOG_ERROR("mImageCache is null\n"); + } } AnimatedImageVisual::AnimatedImageVisual(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory) @@ -177,14 +208,16 @@ AnimatedImageVisual::AnimatedImageVisual(VisualFactoryCache& factoryCache, Image mImageUrl(), mAnimatedImageLoading(), mFrameIndexForJumpTo(0), + mCurrentFrameIndex(FIRST_FRAME_INDEX), mImageUrls(NULL), mImageCache(NULL), mCacheSize(2), mBatchSize(2), mFrameDelay(100), mLoopCount(LOOP_FOREVER), - mCurrentLoopIndex(0), - mUrlIndex(0), + mCurrentLoopIndex(FIRST_LOOP), + mLoadPolicy(Toolkit::ImageVisual::LoadPolicy::ATTACHED), + mReleasePolicy(Toolkit::ImageVisual::ReleasePolicy::DETACHED), mFrameCount(0), mImageSize(), mActionStatus(DevelAnimatedImageVisual::Action::PLAY), @@ -198,6 +231,12 @@ AnimatedImageVisual::AnimatedImageVisual(VisualFactoryCache& factoryCache, Image AnimatedImageVisual::~AnimatedImageVisual() { + // AnimatedImageVisual destroyed so remove texture unless ReleasePolicy is set to never release + // If this is animated image, clear cache. Else if this is single frame image, this is affected be release policy. + if(mFrameCount > SINGLE_IMAGE_COUNT || mReleasePolicy != Toolkit::ImageVisual::ReleasePolicy::NEVER) + { + mImageCache->ClearCache(); + } delete mImageCache; delete mImageUrls; } @@ -208,7 +247,14 @@ void AnimatedImageVisual::GetNaturalSize(Vector2& naturalSize) { if(mImageUrl.IsValid()) { - mImageSize = mAnimatedImageLoading.GetImageSize(); + if(mAnimatedImageLoading.HasLoadingSucceeded()) + { + mImageSize = mAnimatedImageLoading.GetImageSize(); + } + else + { + mImageSize = Dali::GetClosestImageSize(mImageUrl.GetUrl()); + } } else if(mImageUrls && mImageUrls->size() > 0) { @@ -256,6 +302,9 @@ void AnimatedImageVisual::DoCreatePropertyMap(Property::Map& map) const map.Insert(Toolkit::DevelImageVisual::Property::TOTAL_FRAME_NUMBER, (mImageCache) ? static_cast(mImageCache->GetTotalFrameCount()) : -1); map.Insert(Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR, mStopBehavior); + + map.Insert(Toolkit::ImageVisual::Property::LOAD_POLICY, mLoadPolicy); + map.Insert(Toolkit::ImageVisual::Property::RELEASE_POLICY, mReleasePolicy); } void AnimatedImageVisual::DoCreateInstancePropertyMap(Property::Map& map) const @@ -294,6 +343,7 @@ void AnimatedImageVisual::OnDoAction(const Dali::Property::Index actionId, const // STOP reset functionality will actually be done in a future change // Stop will be executed on next timer tick mActionStatus = DevelAnimatedImageVisual::Action::STOP; + mCurrentLoopIndex = FIRST_LOOP; if(IsOnScene()) { DisplayNextFrame(); @@ -368,8 +418,25 @@ void AnimatedImageVisual::DoSetProperties(const Property::Map& propertyMap) { DoSetProperty(Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR, keyValue.second); } + else if(keyValue.first == LOAD_POLICY_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::LOAD_POLICY, keyValue.second); + } + else if(keyValue.first == RELEASE_POLICY_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::RELEASE_POLICY, keyValue.second); + } + else if(keyValue.first == SYNCHRONOUS_LOADING) + { + DoSetProperty(Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING, keyValue.second); + } } } + // Load image immediately if LOAD_POLICY requires it + if(mLoadPolicy == Toolkit::ImageVisual::LoadPolicy::IMMEDIATE) + { + PrepareTextureSet(); + } } void AnimatedImageVisual::DoSetProperty(Property::Index index, @@ -449,6 +516,10 @@ void AnimatedImageVisual::DoSetProperty(Property::Index index, if(value.Get(frameDelay)) { mFrameDelay = frameDelay; + if(mImageCache) + { + mImageCache->SetInterval(static_cast(mFrameDelay)); + } } break; } @@ -487,28 +558,42 @@ void AnimatedImageVisual::DoSetProperty(Property::Index index, } break; } + + case Toolkit::ImageVisual::Property::RELEASE_POLICY: + { + int releasePolicy = 0; + Scripting::GetEnumerationProperty(value, RELEASE_POLICY_TABLE, RELEASE_POLICY_TABLE_COUNT, releasePolicy); + mReleasePolicy = Toolkit::ImageVisual::ReleasePolicy::Type(releasePolicy); + break; + } + + case Toolkit::ImageVisual::Property::LOAD_POLICY: + { + int loadPolicy = 0; + Scripting::GetEnumerationProperty(value, LOAD_POLICY_TABLE, LOAD_POLICY_TABLE_COUNT, loadPolicy); + mLoadPolicy = Toolkit::ImageVisual::LoadPolicy::Type(loadPolicy); + break; + } } } void AnimatedImageVisual::DoSetOnScene(Actor& actor) { + mStartFirstFrame = true; mPlacementActor = actor; TextureSet textureSet = PrepareTextureSet(); // Loading animated image file is failed. - if(!mImageCache || - (mAnimatedImageLoading && !mAnimatedImageLoading.HasLoadingSucceeded())) + if(!mImageCache || mImageCache->GetLoadState() == TextureManager::LoadState::LOAD_FAILED) { textureSet = SetLoadingFailed(); } - if(textureSet) // if the image loading is successful + // If textureSet is prepared and first frame still not started, + // make first frame start. + if(textureSet && mStartFirstFrame) { - StartFirstFrame(textureSet); - } - else - { - mStartFirstFrame = true; + StartFirstFrame(textureSet, mImageCache->GetFrameInterval(FIRST_FRAME_INDEX)); } } @@ -523,8 +608,19 @@ void AnimatedImageVisual::DoSetOffScene(Actor& actor) } actor.RemoveRenderer(mImpl->mRenderer); + if(mReleasePolicy == Toolkit::ImageVisual::ReleasePolicy::DETACHED) + { + mImageCache->ClearCache(); // If INVALID_TEXTURE_ID then removal will be attempted on atlas + mImpl->mResourceStatus = Toolkit::Visual::ResourceStatus::PREPARING; + + TextureSet textureSet = TextureSet::New(); + mImpl->mRenderer.SetTextures(textureSet); + } + mPlacementActor.Reset(); mStartFirstFrame = false; + mCurrentFrameIndex = FIRST_FRAME_INDEX; + mCurrentLoopIndex = FIRST_LOOP; } void AnimatedImageVisual::OnSetTransform() @@ -546,6 +642,8 @@ void AnimatedImageVisual::UpdateShader() void AnimatedImageVisual::OnInitialize() { + CreateImageCache(); + bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE; Shader shader = GenerateShader(); @@ -569,61 +667,7 @@ void AnimatedImageVisual::OnInitialize() } } -void AnimatedImageVisual::LoadFirstBatch() -{ - // 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 = 0; - uint16_t batchSize = 1; - uint16_t cacheSize = 1; - - if(mImageUrls) - { - numUrls = mImageUrls->size(); - } - else - { - numUrls = mFrameCount; - } - - batchSize = std::min(mBatchSize, numUrls); - cacheSize = std::min(std::max(batchSize, mCacheSize), numUrls); - - DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::LoadFirstBatch() batchSize:%d cacheSize:%d\n", batchSize, cacheSize); - - mUrlIndex = 0; - TextureManager& textureManager = mFactoryCache.GetTextureManager(); - - if(mAnimatedImageLoading) - { - mImageCache = new RollingAnimatedImageCache(textureManager, mAnimatedImageLoading, mFrameCount, *this, cacheSize, batchSize, IsSynchronousLoadingRequired()); - } - else if(mImageUrls) - { - 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); - } - } - - if(!mImageCache) - { - DALI_LOG_ERROR("mImageCache is null\n"); - } -} - -void AnimatedImageVisual::StartFirstFrame(TextureSet& textureSet) +void AnimatedImageVisual::StartFirstFrame(TextureSet& textureSet, uint32_t firstInterval) { DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::StartFirstFrame()\n"); @@ -640,23 +684,20 @@ void AnimatedImageVisual::StartFirstFrame(TextureSet& textureSet) } } - if(mFrameCount > 1) + if(mImpl->mResourceStatus != Toolkit::Visual::ResourceStatus::FAILED) { - int frameDelay = mImageCache->GetFrameInterval(0); - if(frameDelay == 0u) + if(mFrameCount > SINGLE_IMAGE_COUNT) { - frameDelay = mFrameDelay; // from URL array + mFrameDelayTimer = Timer::New(firstInterval); + mFrameDelayTimer.TickSignal().Connect(this, &AnimatedImageVisual::DisplayNextFrame); + mFrameDelayTimer.Start(); } - mFrameDelayTimer = Timer::New(frameDelay); - mFrameDelayTimer.TickSignal().Connect(this, &AnimatedImageVisual::DisplayNextFrame); - mFrameDelayTimer.Start(); - } - if(mImpl->mResourceStatus != Toolkit::Visual::ResourceStatus::FAILED) - { DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::READY)\n"); ResourceReady(Toolkit::Visual::ResourceStatus::READY); } + + mCurrentFrameIndex = FIRST_FRAME_INDEX; } TextureSet AnimatedImageVisual::PrepareTextureSet() @@ -667,6 +708,7 @@ TextureSet AnimatedImageVisual::PrepareTextureSet() textureSet = mImageCache->FirstFrame(); } + // Check whether synchronous loading is true or false for the first frame. if(textureSet) { SetImageSize(textureSet); @@ -688,24 +730,28 @@ void AnimatedImageVisual::SetImageSize(TextureSet& textureSet) } } -void AnimatedImageVisual::FrameReady(TextureSet textureSet) +void AnimatedImageVisual::FrameReady(TextureSet textureSet, uint32_t interval) { // When image visual requested to load new frame to mImageCache and it is failed. if(!textureSet) { textureSet = SetLoadingFailed(); } - SetImageSize(textureSet); if(mStartFirstFrame) { - StartFirstFrame(textureSet); + mFrameCount = mImageCache->GetTotalFrameCount(); + StartFirstFrame(textureSet, interval); } else { if(mImpl->mRenderer) { + if(mFrameDelayTimer && interval > 0u) + { + mFrameDelayTimer.SetInterval(interval); + } mImpl->mRenderer.SetTextures(textureSet); } } @@ -715,10 +761,8 @@ bool AnimatedImageVisual::DisplayNextFrame() { TextureSet textureSet; bool continueTimer = false; - if(mImageCache) { - bool nextFrame = false; uint32_t frameIndex = mImageCache->GetCurrentFrameIndex(); if(mIsJumpTo) @@ -732,10 +776,10 @@ bool AnimatedImageVisual::DisplayNextFrame() } else if(mActionStatus == DevelAnimatedImageVisual::Action::STOP) { - frameIndex = 0; + mCurrentLoopIndex = FIRST_LOOP; if(mStopBehavior == DevelImageVisual::StopBehavior::FIRST_FRAME) { - frameIndex = 0; + frameIndex = FIRST_FRAME_INDEX; } else if(mStopBehavior == DevelImageVisual::StopBehavior::LAST_FRAME) { @@ -748,13 +792,12 @@ bool AnimatedImageVisual::DisplayNextFrame() } else { - if(mFrameCount > 1) + if(mFrameCount > SINGLE_IMAGE_COUNT) { - nextFrame = true; frameIndex++; if(frameIndex >= mFrameCount) { - frameIndex %= mFrameCount; + frameIndex = FIRST_FRAME_INDEX; ++mCurrentLoopIndex; } @@ -765,38 +808,24 @@ bool AnimatedImageVisual::DisplayNextFrame() return DisplayNextFrame(); } } - - unsigned int delay = mImageCache->GetFrameInterval(frameIndex); - if(delay > 0u) - { - if(mFrameDelayTimer.GetInterval() != delay) - { - mFrameDelayTimer.SetInterval(delay); - } - } } DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::DisplayNextFrame(this:%p) CurrentFrameIndex:%d\n", this, frameIndex); - if(nextFrame) - { - textureSet = mImageCache->NextFrame(); - } - else - { - textureSet = mImageCache->Frame(frameIndex); - } - - continueTimer = (mActionStatus == DevelAnimatedImageVisual::Action::PLAY) ? true : false; - } + textureSet = mImageCache->Frame(frameIndex); - if(textureSet) - { - SetImageSize(textureSet); - if(mImpl->mRenderer) + if(textureSet) { - mImpl->mRenderer.SetTextures(textureSet); + SetImageSize(textureSet); + if(mImpl->mRenderer) + { + mImpl->mRenderer.SetTextures(textureSet); + } + mFrameDelayTimer.SetInterval(mImageCache->GetFrameInterval(frameIndex)); } + + mCurrentFrameIndex = frameIndex; + continueTimer = (mActionStatus == DevelAnimatedImageVisual::Action::PLAY && textureSet) ? true : false; } return continueTimer; diff --git a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.h b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.h index 1d21356..6679ef2 100644 --- a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.h +++ b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.h @@ -193,54 +193,57 @@ protected: private: /** - * Creates the renderer for the animated image + * @brief Creates the renderer for the animated image */ void CreateRenderer(); /** - * Starts the Load of the first batch of URLs - */ - void LoadFirstBatch(); - - /** - * Adds the texture set to the renderer, and the renderer to the + * @brief Adds the texture set to the renderer, and the renderer to the * placement actor, and starts the frame timer - * @param[in] textureSet The texture set to apply + * @param[in] textureSet The texture set to apply + * @param[in] firstInterval frame interval for the first frame. */ - void StartFirstFrame(TextureSet& textureSet); + void StartFirstFrame(TextureSet& textureSet, uint32_t firstInterval); /** - * Prepares the texture set for displaying + * @brief Prepares the texture set for displaying + * @return Prepared texture set if it is loaded. */ TextureSet PrepareTextureSet(); /** - * Set the image size from the texture set + * @brief Set the image size from the texture set * @param[in] textureSet The texture set to get the size from */ void SetImageSize(TextureSet& textureSet); /** - * Called when the next frame is ready. + * @brief Called when the next frame is ready. * @param[in] textureSet the texture set to apply + * @param[in] interval interval for the frame */ - void FrameReady(TextureSet textureSet) override; + void FrameReady(TextureSet textureSet, uint32_t interval) override; /** - * Display the next frame. It is called when the mFrameDelayTimer ticks. - * Returns true to ensure the timer continues running. + * @brief Display the next frame. It is called when the mFrameDelayTimer ticks. + * @return true to ensure the timer continues running. */ bool DisplayNextFrame(); /** - * Initialize the animated image variables. + * @brief Initialize the animated image variables. * @param[in] imageUrl The url of the animated image */ void InitializeAnimatedImage(const VisualUrl& imageUrl); /** - * Set the state of loading fail of an image or a frame. - * Returns TextureSet of broken image. + * @brief Create image cache for animated image or image array. + */ + void CreateImageCache(); + + /** + * @brief Set the state of loading fail of an image or a frame. + * @return TextureSet of broken image. */ TextureSet SetLoadingFailed(); @@ -260,6 +263,7 @@ private: VisualUrl mImageUrl; Dali::AnimatedImageLoading mAnimatedImageLoading; // Only needed for animated image uint32_t mFrameIndexForJumpTo; // Frame index into textureRects + uint32_t mCurrentFrameIndex; // Variables for Multi-Image player ImageCache::UrlList* mImageUrls; @@ -269,7 +273,10 @@ private: uint16_t mFrameDelay; int16_t mLoopCount; int16_t mCurrentLoopIndex; - uint16_t mUrlIndex; + + // Variables for image visual policy. + Dali::Toolkit::ImageVisual::LoadPolicy::Type mLoadPolicy; + Dali::Toolkit::ImageVisual::ReleasePolicy::Type mReleasePolicy; // Shared variables uint32_t mFrameCount; // Number of frames diff --git a/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp b/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp index 4593bc7..c90e86e 100644 --- a/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp +++ b/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp @@ -20,6 +20,9 @@ // INTERNAL HEADERS #include // For ImageAtlasManagerPtr +// EXTERNAL HEADERS +#include + namespace Dali { namespace Toolkit @@ -28,33 +31,30 @@ namespace Internal { namespace { -const bool ENABLE_ORIENTATION_CORRECTION(true); +static constexpr bool ENABLE_ORIENTATION_CORRECTION(true); + +static constexpr uint32_t FIRST_FRAME_INDEX = 0u; } // namespace FixedImageCache::FixedImageCache( - TextureManager& textureManager, UrlList& urlList, ImageCache::FrameReadyObserver& observer, unsigned int batchSize) -: ImageCache(textureManager, observer, batchSize), + TextureManager& textureManager, UrlList& urlList, ImageCache::FrameReadyObserver& observer, uint32_t batchSize, uint32_t interval) +: ImageCache(textureManager, observer, batchSize, interval), mImageUrls(urlList), - mFront(0u) + mFront(FIRST_FRAME_INDEX) { + mLoadStates.assign(mImageUrls.size(), TextureManager::LoadState::NOT_STARTED); mReadyFlags.reserve(mImageUrls.size()); - LoadBatch(); } FixedImageCache::~FixedImageCache() { - if(mTextureManagerAlive) - { - for(std::size_t i = 0; i < mImageUrls.size(); ++i) - { - mTextureManager.Remove(mImageUrls[i].mTextureId, this); - } - } + ClearCache(); } TextureSet FixedImageCache::Frame(uint32_t frameIndex) { - while(frameIndex > mFront) + while(frameIndex > mFront || mReadyFlags.empty() || + (frameIndex == FIRST_FRAME_INDEX && mFront != FIRST_FRAME_INDEX)) { ++mFront; if(mFront >= mImageUrls.size()) @@ -67,40 +67,24 @@ TextureSet FixedImageCache::Frame(uint32_t frameIndex) mFront = frameIndex; TextureSet textureSet; - if(IsFrontReady() == true) + if(IsFrontReady()) { textureSet = GetFrontTextureSet(); } - else - { - mWaitingForReadyFrame = true; - } return textureSet; } TextureSet FixedImageCache::FirstFrame() { - TextureSet textureSet = GetFrontTextureSet(); - - if(!textureSet) - { - mWaitingForReadyFrame = true; - } - - return textureSet; -} - -TextureSet FixedImageCache::NextFrame() -{ - TextureSet textureSet = Frame((mFront + 1) % mImageUrls.size()); + TextureSet textureSet = Frame(FIRST_FRAME_INDEX); return textureSet; } uint32_t FixedImageCache::GetFrameInterval(uint32_t frameIndex) const { - return 0u; + return mInterval; } int32_t FixedImageCache::GetCurrentFrameIndex() const @@ -113,6 +97,11 @@ int32_t FixedImageCache::GetTotalFrameCount() const return mImageUrls.size(); } +TextureManager::LoadState FixedImageCache::GetLoadState() const +{ + return mLoadStates[mFront]; +} + bool FixedImageCache::IsFrontReady() const { return (mReadyFlags.size() > 0 && mReadyFlags[mFront] == true); @@ -121,13 +110,11 @@ bool FixedImageCache::IsFrontReady() const 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) + // Once the cache is filled, no more images are loaded. + for(unsigned int i = 0; i < mBatchSize && mReadyFlags.size() < mImageUrls.size(); ++i) { - std::string& url = mImageUrls[mUrlIndex].mUrl; + uint32_t frameIndex = mReadyFlags.size(); + std::string& url = mImageUrls[frameIndex].mUrl; mReadyFlags.push_back(false); @@ -146,26 +133,44 @@ void FixedImageCache::LoadBatch() Dali::ImageDimensions textureRectSize; auto preMultiply = TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY; - mTextureManager.LoadTexture( - url, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::BOX_THEN_LINEAR, maskInfo, synchronousLoading, mImageUrls[mUrlIndex].mTextureId, textureRect, textureRectSize, atlasingStatus, loadingStatus, Dali::WrapMode::Type::DEFAULT, Dali::WrapMode::Type::DEFAULT, this, atlasObserver, imageAtlasManager, ENABLE_ORIENTATION_CORRECTION, TextureManager::ReloadPolicy::CACHED, preMultiply); + TextureSet textureSet = mTextureManager.LoadTexture( + url, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::BOX_THEN_LINEAR, maskInfo, synchronousLoading, mImageUrls[frameIndex].mTextureId, textureRect, textureRectSize, atlasingStatus, loadingStatus, Dali::WrapMode::Type::DEFAULT, Dali::WrapMode::Type::DEFAULT, this, atlasObserver, imageAtlasManager, ENABLE_ORIENTATION_CORRECTION, TextureManager::ReloadPolicy::CACHED, preMultiply); - if(loadingStatus == false) // not loading, means it's already ready. + // If textureSet is returned but loadingState is false than load state is LOAD_FINISHED. (Notification is not comming yet.) + // If textureSet is null and the request is synchronous, load state is LOAD_FAILED. + // If textureSet is null but the request is asynchronous, the frame is still loading so load state is LOADING. + mLoadStates[frameIndex] = TextureManager::LoadState::LOADING; + if(textureSet) + { + if(!loadingStatus) + { + SetImageFrameReady(mImageUrls[frameIndex].mTextureId, true); + } + } + else if(synchronousLoading) { - SetImageFrameReady(mImageUrls[mUrlIndex].mTextureId); + // Synchronous loading is failed + mLoadStates[frameIndex] = TextureManager::LoadState::LOAD_FAILED; } + mRequestingLoad = false; - ++mUrlIndex; } - - CheckFrontFrame(frontFrameReady); } -void FixedImageCache::SetImageFrameReady(TextureManager::TextureId textureId) +void FixedImageCache::SetImageFrameReady(TextureManager::TextureId textureId, bool loadSuccess) { for(std::size_t i = 0; i < mImageUrls.size(); ++i) { if(mImageUrls[i].mTextureId == textureId) { + if(loadSuccess) + { + mLoadStates[i] = TextureManager::LoadState::LOAD_FINISHED; + } + else + { + mLoadStates[i] = TextureManager::LoadState::LOAD_FAILED; + } mReadyFlags[i] = true; break; } @@ -179,11 +184,24 @@ TextureSet FixedImageCache::GetFrontTextureSet() const void FixedImageCache::CheckFrontFrame(bool wasReady) { - if(mWaitingForReadyFrame && wasReady == false && IsFrontReady()) + if(wasReady == false && IsFrontReady()) + { + mObserver.FrameReady(GetFrontTextureSet(), mInterval); + } +} + +void FixedImageCache::ClearCache() +{ + if(mTextureManagerAlive) { - mWaitingForReadyFrame = false; - mObserver.FrameReady(GetFrontTextureSet()); + for(std::size_t i = 0; i < mImageUrls.size(); ++i) + { + mTextureManager.Remove(mImageUrls[i].mTextureId, this); + mImageUrls[i].mTextureId = TextureManager::INVALID_TEXTURE_ID; + } } + mReadyFlags.clear(); + mLoadStates.assign(mImageUrls.size(), TextureManager::LoadState::NOT_STARTED); } void FixedImageCache::UploadComplete( @@ -195,31 +213,11 @@ void FixedImageCache::UploadComplete( bool preMultiplied) { bool frontFrameReady = IsFrontReady(); - + SetImageFrameReady(textureId, loadSuccess); 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; - } -} - -void FixedImageCache::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 diff --git a/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h b/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h index 63212df..93a32b6 100644 --- a/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h +++ b/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h @@ -36,6 +36,7 @@ public: * @param[in] urlList List of urls to cache * @param[in] observer FrameReady observer * @param[in] batchSize The size of a batch to load + * @param[in] interval Time interval between each frame * * This will start loading textures immediately, according to the * batch and cache sizes. The cache is as large as the number of urls. @@ -43,73 +44,86 @@ public: FixedImageCache(TextureManager& textureManager, UrlList& urlList, ImageCache::FrameReadyObserver& observer, - unsigned int batchSize); + uint32_t batchSize, + uint32_t interval); ~FixedImageCache() override; /** - * Get the Nth frame. If it's not ready, this will trigger the - * sending of FrameReady() when the image becomes ready. + * @copydoc Internal::ImageCache::Frame() */ TextureSet Frame(uint32_t frameIndex) override; /** - * Get the first frame. If it's not ready, this will trigger the - * sending of FrameReady() when the image becomes ready. + * @copydoc Internal::ImageCache::FirstFrame() */ TextureSet FirstFrame() override; /** - * Get the next frame. If it's not ready, this will trigger the - * sending of FrameReady() when the image becomes ready. - */ - TextureSet NextFrame() override; - - /** - * Get the interval of Nth frame. + * @copydoc Internal::ImageCache::GetFrameInterval() */ uint32_t GetFrameInterval(uint32_t frameIndex) const override; /** - * Get the current rendered frame index. - * If there isn't any loaded frame, returns -1. + * @copydoc Internal::ImageCache::GetCurrentFrameIndex() */ int32_t GetCurrentFrameIndex() const override; /** - * Get total frame count of the animated image file. + * @copydoc Internal::ImageCache::GetTotalFrameCount() */ int32_t GetTotalFrameCount() const override; + /** + * @copydoc Internal::ImageCache::GetLoadState() + */ + TextureManager::LoadState GetLoadState() const override; + + /** + * @copydoc Internal::ImageCache::ClearCache() + */ + void ClearCache() override; + private: /** + * @brief Check whether the front frame is ready or not. + * * @return true if the front frame is ready */ bool IsFrontReady() const; /** - * Load the next batch of images + * @brief Load the next batch of images */ void LoadBatch(); /** - * Find the matching image frame, and set it to ready + * @brief Find the matching image frame, and set it to ready + * + * @param[in] textureId texture id to be marked as ready. + * @param[in] loadSuccess true if the frame loading is succeeded. */ - void SetImageFrameReady(TextureManager::TextureId textureId); + void SetImageFrameReady(TextureManager::TextureId textureId, bool loadSuccess); /** - * Get the texture set of the front frame. - * @return the texture set + * @brief Get the texture set of the front frame. + * + * @return the texture set of the front of Cache. */ TextureSet GetFrontTextureSet() const; /** - * Check if the front frame has become ready - if so, inform observer + * @brief Check if the front frame has become ready - if so, inform observer + * * @param[in] wasReady Readiness before call. */ void CheckFrontFrame(bool wasReady); protected: + + /** + * @copydoc Toolkit::TextureUploadObserver::UploadComplete() + */ void UploadComplete( bool loadSuccess, int32_t textureId, @@ -118,16 +132,11 @@ protected: const Vector4& atlasRect, bool premultiplied) override; - void LoadComplete( - bool loadSuccess, - Devel::PixelBuffer pixelBuffer, - const VisualUrl& url, - bool preMultiplied) override; - private: - std::vector& mImageUrls; - std::vector mReadyFlags; - unsigned int mFront; + std::vector& mImageUrls; + std::vector mReadyFlags; + std::vector mLoadStates; + uint32_t mFront; }; } //namespace Internal diff --git a/dali-toolkit/internal/visuals/animated-image/image-cache.cpp b/dali-toolkit/internal/visuals/animated-image/image-cache.cpp index cccb085..dcd0d40 100644 --- a/dali-toolkit/internal/visuals/animated-image/image-cache.cpp +++ b/dali-toolkit/internal/visuals/animated-image/image-cache.cpp @@ -24,12 +24,12 @@ namespace Internal { ImageCache::ImageCache(TextureManager& textureManager, ImageCache::FrameReadyObserver& observer, - unsigned int batchSize) + uint32_t batchSize, + uint32_t interval) : mTextureManager(textureManager), mObserver(observer), mBatchSize(batchSize), - mUrlIndex(0u), - mWaitingForReadyFrame(false), + mInterval(interval), mRequestingLoad(false), mTextureManagerAlive(true) { @@ -49,6 +49,11 @@ void ImageCache::TextureManagerDestroyed() mTextureManagerAlive = false; } +void ImageCache::SetInterval(uint32_t interval) +{ + mInterval = interval; +} + } //namespace Internal } //namespace Toolkit } //namespace Dali diff --git a/dali-toolkit/internal/visuals/animated-image/image-cache.h b/dali-toolkit/internal/visuals/animated-image/image-cache.h index f21d707..432ad8c 100644 --- a/dali-toolkit/internal/visuals/animated-image/image-cache.h +++ b/dali-toolkit/internal/visuals/animated-image/image-cache.h @@ -37,10 +37,11 @@ public: { public: /** - * Informs observer when the next texture set is ready to display + * @brief Informs observer when the next texture set is ready to display * @param[in] textureSet The ready texture set + * @param[in] interval interval for the frame */ - virtual void FrameReady(TextureSet textureSet) = 0; + virtual void FrameReady(TextureSet textureSet, uint32_t interval) = 0; }; struct UrlStore @@ -56,67 +57,93 @@ public: public: /** - * Constructor. + * @brief 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 + * @param[in] interval Time interval between each frame * * 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, ImageCache::FrameReadyObserver& observer, - unsigned int batchSize); + uint32_t batchSize, + uint32_t interval); virtual ~ImageCache(); /** - * Get the first frame. If it's not ready, this will trigger the + * @brief Get the first frame. If it's not ready, this will trigger the * sending of FrameReady() when the image becomes ready. + * + * @return TextureSet of the first frame. */ 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. - */ - virtual TextureSet NextFrame() = 0; - - /** - * Get the Nth frame. If it's not ready, this will trigger the + * @brief Get the Nth frame. If it's not ready, this will trigger the * sending of FrameReady() when the image becomes ready. + * + * @param[in] frameIndex required frame index to be returned. + * @return TextureSet of the frame index. */ virtual TextureSet Frame(uint32_t frameIndex) = 0; /** - * Get the interval of Nth frame. + * @brief Get the interval of Nth frame. + * + * @param[in] frameIndex frame index to get frame interval. + * @return Time interval in millisecond between frames of frameIndex and frameIndex + 1. */ virtual uint32_t GetFrameInterval(uint32_t frameIndex) const = 0; /** - * Get the current rendered frame index. + * @brief Get the current rendered frame index. * If there isn't any loaded frame, returns -1. + * + * @return Frame index of currently showing frame. */ virtual int32_t GetCurrentFrameIndex() const = 0; /** - * Get total frame count of the animated image file. + * @brief Get total frame count of the animated image file. + * + * @return Total frame count of the animated image file. */ virtual int32_t GetTotalFrameCount() const = 0; + /** + * @brief Get Loading state of the animated image file. + * + * @return LoadState of currently requested frame. + */ + virtual TextureManager::LoadState GetLoadState() const = 0; + + /** + * @brief Clears animated image cache and remove loaded textures. + */ + virtual void ClearCache() = 0; + + /** + * @brief Set default interval between each frame. + * + * @param[in] interval time interval in millisecond to be used as default interval. + */ + virtual void SetInterval(uint32_t interval); + private: /** - * Called before the texture manager is destroyed. + * @brief Called before the texture manager is destroyed. */ void TextureManagerDestroyed() final; protected: TextureManager& mTextureManager; FrameReadyObserver& mObserver; - unsigned int mBatchSize; - unsigned int mUrlIndex; - bool mWaitingForReadyFrame : 1; + uint32_t mBatchSize; + uint32_t mInterval; bool mRequestingLoad : 1; bool mTextureManagerAlive : 1; }; 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 eba9341..f8f8310 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 @@ -17,8 +17,6 @@ // CLASS HEADER #include "rolling-animated-image-cache.h" -// EXTERNAL HEADERS - // INTERNAL HEADERS #include #include // For ImageAtlasManagerPtr @@ -45,7 +43,7 @@ Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, " #define LOG_CACHE #endif -const bool ENABLE_ORIENTATION_CORRECTION(true); +static constexpr bool ENABLE_ORIENTATION_CORRECTION(true); } // namespace @@ -55,32 +53,31 @@ namespace Toolkit { namespace Internal { +namespace +{ +static constexpr uint32_t SINGLE_IMAGE_COUNT = 1u; +static constexpr uint32_t FIRST_FRAME_INDEX = 0u; +} // namespace + RollingAnimatedImageCache::RollingAnimatedImageCache( - TextureManager& textureManager, AnimatedImageLoading& animatedImageLoading, uint32_t frameCount, ImageCache::FrameReadyObserver& observer, uint16_t cacheSize, uint16_t batchSize, bool isSynchronousLoading) -: ImageCache(textureManager, observer, batchSize), + TextureManager& textureManager, AnimatedImageLoading& animatedImageLoading, ImageCache::FrameReadyObserver& observer, uint16_t cacheSize, uint16_t batchSize, bool isSynchronousLoading) +: ImageCache(textureManager, observer, batchSize, 0u), mAnimatedImageLoading(animatedImageLoading), - mFrameCount(frameCount), - mFrameIndex(0), + mFrameCount(SINGLE_IMAGE_COUNT), + mFrameIndex(FIRST_FRAME_INDEX), mCacheSize(cacheSize), mQueue(cacheSize), - mIsSynchronousLoading(isSynchronousLoading), - mOnLoading(false) + mLoadState(TextureManager::LoadState::NOT_STARTED), + mIsSynchronousLoading(isSynchronousLoading) { mImageUrls.resize(mFrameCount); mIntervals.assign(mFrameCount, 0); - LoadBatch(); } RollingAnimatedImageCache::~RollingAnimatedImageCache() { - if(mTextureManagerAlive) - { - while(!mQueue.IsEmpty()) - { - ImageFrame imageFrame = mQueue.PopFront(); - mTextureManager.Remove(mImageUrls[imageFrame.mFrameNumber].mTextureId, this); - } - } + ClearCache(); + mAnimatedImageLoading.Reset(); } TextureSet RollingAnimatedImageCache::Frame(uint32_t frameIndex) @@ -95,13 +92,13 @@ TextureSet RollingAnimatedImageCache::Frame(uint32_t frameIndex) } TextureSet textureSet; + uint32_t batchFrameIndex = frameIndex; // 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; + textureSet = RequestFrameLoading(frameIndex, frameIndex == FIRST_FRAME_INDEX, true); + batchFrameIndex = (frameIndex + 1) % mFrameCount; } if(popExist || mQueue.IsEmpty()) @@ -111,25 +108,24 @@ TextureSet RollingAnimatedImageCache::Frame(uint32_t frameIndex) { if(!mLoadWaitingQueue.empty()) { - mFrameIndex = (mLoadWaitingQueue.back() + 1) % mFrameCount; + batchFrameIndex = (mLoadWaitingQueue.back() + 1) % mFrameCount; } else { - mFrameIndex = (mQueue.Back().mFrameNumber + 1) % mFrameCount; + batchFrameIndex = (mQueue.Back().mFrameNumber + 1) % mFrameCount; } } else { - mOnLoading = false; // 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; + batchFrameIndex = frameIndex; } } - LoadBatch(); + LoadBatch(batchFrameIndex); } if(!textureSet) @@ -138,10 +134,6 @@ TextureSet RollingAnimatedImageCache::Frame(uint32_t frameIndex) { textureSet = GetFrontTextureSet(); } - else - { - mWaitingForReadyFrame = true; - } } return textureSet; @@ -149,32 +141,17 @@ TextureSet RollingAnimatedImageCache::Frame(uint32_t frameIndex) TextureSet RollingAnimatedImageCache::FirstFrame() { - return Frame(0u); -} - -TextureSet RollingAnimatedImageCache::NextFrame() -{ - TextureSet textureSet; - if(!mQueue.IsEmpty()) - { - uint32_t frameIndex = mQueue.Front().mFrameNumber; - if(IsFrontReady()) - { - frameIndex = (frameIndex + 1) % mFrameCount; - } - textureSet = Frame(frameIndex); - } - else - { - DALI_LOG_ERROR("Cache is empty."); - } - + TextureSet textureSet = Frame(FIRST_FRAME_INDEX); return textureSet; } uint32_t RollingAnimatedImageCache::GetFrameInterval(uint32_t frameIndex) const { - return mAnimatedImageLoading.GetFrameInterval(frameIndex); + if(frameIndex >= mIntervals.size()) + { + return 0u; + } + return mIntervals[frameIndex]; } int32_t RollingAnimatedImageCache::GetCurrentFrameIndex() const @@ -191,12 +168,17 @@ int32_t RollingAnimatedImageCache::GetTotalFrameCount() const return mFrameCount; } +TextureManager::LoadState RollingAnimatedImageCache::GetLoadState() const +{ + return mLoadState; +} + bool RollingAnimatedImageCache::IsFrontReady() const { return (!mQueue.IsEmpty() && mQueue.Front().mReady); } -void RollingAnimatedImageCache::RequestFrameLoading(uint32_t frameIndex) +TextureSet RollingAnimatedImageCache::RequestFrameLoading(uint32_t frameIndex, bool useCache, bool synchronousLoading) { ImageFrame imageFrame; imageFrame.mFrameNumber = frameIndex; @@ -204,39 +186,59 @@ void RollingAnimatedImageCache::RequestFrameLoading(uint32_t frameIndex) mQueue.PushBack(imageFrame); - 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); + bool loadingStatus = false; + TextureSet textureSet = mTextureManager.LoadAnimatedImageTexture(mAnimatedImageLoading, + frameIndex, + loadingStatus, + mImageUrls[frameIndex].mTextureId, + SamplingMode::BOX_THEN_LINEAR, + Dali::WrapMode::Type::DEFAULT, + Dali::WrapMode::Type::DEFAULT, + synchronousLoading, + useCache, + this); + + // If textureSet is returned but loadingState is false than load state is LOAD_FINISHED. (Notification is not comming yet.) + // If textureSet is null and the request is synchronous, load state is LOAD_FAILED. + // If textureSet is null but the request is asynchronous, the frame is still loading so load state is LOADING. + mLoadState = TextureManager::LoadState::LOADING; + if(textureSet) + { + if(!loadingStatus) + { + mLoadState = TextureManager::LoadState::LOAD_FINISHED; + } + } + else if(synchronousLoading) + { + // Synchronous loading is failed + mLoadState = TextureManager::LoadState::LOAD_FAILED; + } - mRequestingLoad = false; + return textureSet; } -void RollingAnimatedImageCache::LoadBatch() +void RollingAnimatedImageCache::LoadBatch(uint32_t frameIndex) { // Try and load up to mBatchSize images, until the cache is filled. // Once the cache is filled, as frames progress, the old frame is // removed, and another frame is loaded - - bool frontFrameReady = IsFrontReady(); - for(unsigned int i = 0; i < mBatchSize && mQueue.Count() + mLoadWaitingQueue.size() < static_cast(mCacheSize) && !mQueue.IsFull(); ++i) + uint32_t minimumSize = std::min(mCacheSize, mFrameCount); + for(uint32_t i = 0; i < mBatchSize && (mQueue.Count() + mLoadWaitingQueue.size()) < minimumSize; ++i) { - if(!mOnLoading) + if(mLoadState != TextureManager::LoadState::LOADING) { - mOnLoading = true; - RequestFrameLoading(mFrameIndex); + RequestFrameLoading(frameIndex, frameIndex == FIRST_FRAME_INDEX, false); } else { - mLoadWaitingQueue.push_back(mFrameIndex); + mLoadWaitingQueue.push_back(frameIndex); } - mFrameIndex++; - mFrameIndex %= mFrameCount; + frameIndex++; + frameIndex %= mFrameCount; } - CheckFrontFrame(frontFrameReady); - LOG_CACHE; } @@ -265,43 +267,53 @@ TextureManager::TextureId RollingAnimatedImageCache::GetCachedTextureId(int inde return mImageUrls[mQueue[index].mFrameNumber].mTextureId; } -void RollingAnimatedImageCache::CheckFrontFrame(bool wasReady) +void RollingAnimatedImageCache::ClearCache() { - if(mWaitingForReadyFrame && wasReady == false && IsFrontReady()) + while(mTextureManagerAlive && !mQueue.IsEmpty()) { - mWaitingForReadyFrame = false; - mObserver.FrameReady(GetFrontTextureSet()); + ImageFrame imageFrame = mQueue.PopFront(); + mTextureManager.Remove(mImageUrls[imageFrame.mFrameNumber].mTextureId, this); + mImageUrls[imageFrame.mFrameNumber].mTextureId = TextureManager::INVALID_TEXTURE_ID; } + + mLoadWaitingQueue.clear(); + mLoadState = TextureManager::LoadState::NOT_STARTED; } -void RollingAnimatedImageCache::UploadComplete( - bool loadSuccess, - int32_t textureId, - TextureSet textureSet, - bool useAtlasing, - const Vector4& atlasRect, - bool preMultiplied) +void RollingAnimatedImageCache::AnimatedImageUploadComplete(bool loadSuccess, int32_t textureId, uint32_t frameCount, uint32_t interval) { DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::UploadComplete(textureId:%d) start\n", textureId); LOG_CACHE; - bool frontFrameReady = IsFrontReady(); - - if(!mRequestingLoad) + // Reset size of Queue according to the real frame count. + if(mFrameCount != frameCount) { - SetImageFrameReady(textureId); + mFrameCount = frameCount; + mImageUrls.resize(mFrameCount); + mIntervals.assign(mFrameCount, 0u); + } - CheckFrontFrame(frontFrameReady); + if(loadSuccess) + { + mLoadState = TextureManager::LoadState::LOAD_FINISHED; } 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; + mLoadState = TextureManager::LoadState::LOAD_FAILED; + } + + bool frontFrameReady = IsFrontReady(); + // Because only one frame is on loading and the others are in mLoadWaitingQueue, + // mQueue.Back() is always the frame currently loaded. + mQueue.Back().mReady = true; + mIntervals[mQueue.Back().mFrameNumber] = interval; + // Check whether currently loaded frame is front of queue or not. + // If it is, notify frame ready to observer. + if(frontFrameReady == false && IsFrontReady()) + { + mObserver.FrameReady(GetFrontTextureSet(), interval); } - 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. @@ -309,24 +321,27 @@ void RollingAnimatedImageCache::UploadComplete( { uint32_t loadingIndex = mLoadWaitingQueue.front(); mLoadWaitingQueue.erase(mLoadWaitingQueue.begin()); - mOnLoading = true; - RequestFrameLoading(loadingIndex); + RequestFrameLoading(loadingIndex, loadingIndex == FIRST_FRAME_INDEX, false); + } + else if(mQueue.Count() == 1u && frameCount > SINGLE_IMAGE_COUNT) + { + // There is only an image in queue and no waiting queue. + // Request to load batch once again. + uint32_t batchFrameIndex = 0u; + if(!mLoadWaitingQueue.empty()) + { + batchFrameIndex = (mLoadWaitingQueue.back() + 1) % mFrameCount; + } + else + { + batchFrameIndex = (mQueue.Back().mFrameNumber + 1) % mFrameCount; + } + LoadBatch(batchFrameIndex); } 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 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 b1b4afc..6a06717 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 @@ -40,10 +40,9 @@ class RollingAnimatedImageCache : public ImageCache, public TextureUploadObserve { public: /** - * Constructor. + * @brief Constructor. * @param[in] textureManager The texture manager * @param[in] animatedImageLoader The loaded animated image - * @param[in] frameCount The number of frames in the animated image * @param[in] observer FrameReady observer * @param[in] cacheSize The size of the cache * @param[in] batchSize The size of a batch to load @@ -54,124 +53,124 @@ public: */ RollingAnimatedImageCache(TextureManager& textureManager, AnimatedImageLoading& animatedImageLoader, - uint32_t frameCount, ImageCache::FrameReadyObserver& observer, uint16_t cacheSize, uint16_t batchSize, bool isSynchronousLoading); /** - * Destructor + * @brief Destructor */ ~RollingAnimatedImageCache() override; /** - * Get the Nth frame. If it's not ready, this will trigger the - * sending of FrameReady() when the image becomes ready. + * @copydoc Internal::ImageCache::Frame() */ TextureSet Frame(uint32_t frameIndex) override; /** - * Get the first frame. If it's not ready, this will trigger the - * sending of FrameReady() when the image becomes ready. + * @copydoc Internal::ImageCache::FirstFrame() */ TextureSet FirstFrame() override; /** - * Get the next frame. If it's not ready, this will trigger the - * sending of FrameReady() when the image becomes ready. - */ - TextureSet NextFrame() override; - - /** - * Get the interval of Nth frame. + * @copydoc Internal::ImageCache::GetFrameInterval() */ uint32_t GetFrameInterval(uint32_t frameIndex) const override; /** - * Get the current rendered frame index. - * If there isn't any loaded frame, returns -1. + * @copydoc Internal::ImageCache::GetCurrentFrameIndex() */ int32_t GetCurrentFrameIndex() const override; /** - * Get total frame count of the animated image file. + * @copydoc Internal::ImageCache::GetTotalFrameCount() */ int32_t GetTotalFrameCount() const override; + /** + * @copydoc Internal::ImageCache::GetLoadState() + */ + TextureManager::LoadState GetLoadState() const override; + + /** + * @copydoc Internal::ImageCache::ClearCache() + */ + void ClearCache() override; + private: /** + * @brief Check whether the front frame is ready or not. + * * @return true if the front frame is ready */ bool IsFrontReady() const; /** - * Request to Load a frame + * @brief Request to Load a frame + * + * @param[in] frameIndex index of frame to be loaded. + * @param[in] useCache true if this frame loading uses cache. + * @param[in] synchronousLoading true if the frame should be loaded synchronously */ - void RequestFrameLoading(uint32_t frameIndex); + TextureSet RequestFrameLoading(uint32_t frameIndex, bool useCache, bool synchronousLoading); /** - * Load the next batch of images + * @brief Load the next batch of images + * + * @param[in] frameIndex starting frame index of batch to be loaded. */ - void LoadBatch(); + void LoadBatch(uint32_t frameIndex); /** - * Find the matching image frame, and set it to ready + * @brief Find the matching image frame, and set it to ready + * + * @param[in] textureId texture id to be marked as ready. */ void SetImageFrameReady(TextureManager::TextureId textureId); /** - * Get the texture set of the front frame. - * @return the texture set + * @brief Get the texture set of the front frame. + * + * @return the texture set of the front of Cache. */ TextureSet GetFrontTextureSet() const; /** - * Get the texture id of the given index + * @brief Get the texture id of the given index + * + * @param[in] index index of the queue. */ TextureManager::TextureId GetCachedTextureId(int index) const; +protected: + /** - * Check if the front frame has become ready - if so, inform observer - * @param[in] wasReady Readiness before call. + * @copydoc Toolkit::TextureUploadObserver::AnimatedImageUploadComplete() */ - 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; + void AnimatedImageUploadComplete(bool loadSuccess, int32_t textureId, uint32_t frameCount, uint32_t interval) override; private: + /** * Secondary class to hold readiness and index into url */ struct ImageFrame { - unsigned int mFrameNumber = 0u; - bool mReady = false; + uint32_t mFrameNumber = 0u; + bool mReady = false; }; Dali::AnimatedImageLoading mAnimatedImageLoading; uint32_t mFrameCount; - int mFrameIndex; - int mCacheSize; + uint32_t mFrameIndex; + uint32_t mCacheSize; std::vector mImageUrls; std::vector mIntervals; std::vector mLoadWaitingQueue; CircularQueue mQueue; + TextureManager::LoadState mLoadState; ///< The texture loading state bool mIsSynchronousLoading; - bool mOnLoading; }; } // namespace Internal diff --git a/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.cpp b/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.cpp index 9bcf8e4..c776605 100644 --- a/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.cpp +++ b/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.cpp @@ -44,7 +44,9 @@ Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, " #define LOG_CACHE #endif -const bool ENABLE_ORIENTATION_CORRECTION(true); +static constexpr bool ENABLE_ORIENTATION_CORRECTION(true); + +static constexpr uint32_t FIRST_FRAME_INDEX = 0u; } // namespace @@ -55,57 +57,39 @@ namespace Toolkit namespace Internal { RollingImageCache::RollingImageCache( - TextureManager& textureManager, UrlList& urlList, ImageCache::FrameReadyObserver& observer, uint16_t cacheSize, uint16_t batchSize) -: ImageCache(textureManager, observer, batchSize), + TextureManager& textureManager, UrlList& urlList, ImageCache::FrameReadyObserver& observer, uint16_t cacheSize, uint16_t batchSize, uint32_t interval) +: ImageCache(textureManager, observer, batchSize, interval), mImageUrls(urlList), mQueue(cacheSize) { - LoadBatch(); + mLoadStates.assign(mImageUrls.size(), TextureManager::LoadState::NOT_STARTED); } RollingImageCache::~RollingImageCache() { - if(mTextureManagerAlive) - { - while(!mQueue.IsEmpty()) - { - ImageFrame imageFrame = mQueue.PopFront(); - mTextureManager.Remove(mImageUrls[imageFrame.mUrlIndex].mTextureId, this); - } - } + ClearCache(); } TextureSet RollingImageCache::Frame(uint32_t frameIndex) { - // If a frame of frameIndex is not loaded, clear the queue and remove all loaded textures. - if(mImageUrls[frameIndex].mTextureId == TextureManager::INVALID_TEXTURE_ID) + // Pop frames until the frame of frameIndex become front frame. + bool popExist = false; + while(!mQueue.IsEmpty() && mQueue.Front().mUrlIndex != frameIndex) { - mUrlIndex = frameIndex; - while(!mQueue.IsEmpty()) - { - ImageFrame imageFrame = mQueue.PopFront(); - mTextureManager.Remove(mImageUrls[imageFrame.mUrlIndex].mTextureId, this); - mImageUrls[imageFrame.mUrlIndex].mTextureId = TextureManager::INVALID_TEXTURE_ID; - } - LoadBatch(); + ImageFrame imageFrame = mQueue.PopFront(); + mTextureManager.Remove(mImageUrls[imageFrame.mUrlIndex].mTextureId, this); + mImageUrls[imageFrame.mUrlIndex].mTextureId = TextureManager::INVALID_TEXTURE_ID; + popExist = true; } - // If the frame is already loaded, remove previous frames of the frame in the queue - // and load new frames amount of removed frames. - else + if(popExist || mQueue.IsEmpty()) { - bool popExist = false; - while(!mQueue.IsEmpty() && mQueue.Front().mUrlIndex != frameIndex) - { - ImageFrame imageFrame = mQueue.PopFront(); - mTextureManager.Remove(mImageUrls[imageFrame.mUrlIndex].mTextureId, this); - mImageUrls[imageFrame.mUrlIndex].mTextureId = TextureManager::INVALID_TEXTURE_ID; - popExist = true; - } - if(popExist) + uint32_t batchFrameIndex = frameIndex; + // If the frame of frameIndex was already loaded, load batch from the last frame of queue + if(!mQueue.IsEmpty()) { - mUrlIndex = (mQueue.Back().mUrlIndex + 1) % mImageUrls.size(); - LoadBatch(); + batchFrameIndex = (mQueue.Back().mUrlIndex + 1) % mImageUrls.size(); } + LoadBatch(batchFrameIndex); } TextureSet textureSet; @@ -113,42 +97,19 @@ TextureSet RollingImageCache::Frame(uint32_t frameIndex) { textureSet = GetFrontTextureSet(); } - else - { - mWaitingForReadyFrame = true; - } return textureSet; } TextureSet RollingImageCache::FirstFrame() { - return Frame(0u); -} - -TextureSet RollingImageCache::NextFrame() -{ - TextureSet textureSet; - if(!mQueue.IsEmpty()) - { - uint32_t frameIndex = mQueue.Front().mUrlIndex; - if(IsFrontReady()) - { - frameIndex = (frameIndex + 1) % mImageUrls.size(); - } - textureSet = Frame(frameIndex); - } - else - { - DALI_LOG_ERROR("Cache is empty."); - } - + TextureSet textureSet = Frame(FIRST_FRAME_INDEX); return textureSet; } uint32_t RollingImageCache::GetFrameInterval(uint32_t frameIndex) const { - return 0u; + return mInterval; } int32_t RollingImageCache::GetCurrentFrameIndex() const @@ -165,29 +126,29 @@ int32_t RollingImageCache::GetTotalFrameCount() const return mImageUrls.size(); } +TextureManager::LoadState RollingImageCache::GetLoadState() const +{ + return mLoadStates[mQueue.Front().mUrlIndex]; +} + bool RollingImageCache::IsFrontReady() const { return (!mQueue.IsEmpty() && mQueue.Front().mReady); } -void RollingImageCache::LoadBatch() +void RollingImageCache::LoadBatch(uint32_t frameIndex) { // 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; + std::string& url = mImageUrls[frameIndex].mUrl; + imageFrame.mUrlIndex = frameIndex; imageFrame.mReady = false; - ++mUrlIndex; - mUrlIndex %= mImageUrls.size(); - mQueue.PushBack(imageFrame); // Note, if the image is already loaded, then UploadComplete will get called @@ -205,21 +166,47 @@ void RollingImageCache::LoadBatch() Dali::ImageDimensions textureRectSize; auto preMultiply = TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY; - mTextureManager.LoadTexture( + TextureSet textureSet = mTextureManager.LoadTexture( url, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::BOX_THEN_LINEAR, maskInfo, synchronousLoading, mImageUrls[imageFrame.mUrlIndex].mTextureId, textureRect, textureRectSize, atlasingStatus, loadingStatus, Dali::WrapMode::Type::DEFAULT, Dali::WrapMode::Type::DEFAULT, this, atlasObserver, imageAtlasManager, ENABLE_ORIENTATION_CORRECTION, TextureManager::ReloadPolicy::CACHED, preMultiply); + // If textureSet is returned but loadingState is false than load state is LOAD_FINISHED. (Notification is not comming yet.) + // If textureSet is null and the request is synchronous, load state is LOAD_FAILED. + // If textureSet is null but the request is asynchronous, the frame is still loading so load state is LOADING. + mLoadStates[frameIndex] = TextureManager::LoadState::LOADING; + if(textureSet) + { + if(!loadingStatus) + { + mLoadStates[frameIndex] = TextureManager::LoadState::LOAD_FINISHED; + } + } + else if(synchronousLoading) + { + // Synchronous loading is failed + mLoadStates[frameIndex] = TextureManager::LoadState::LOAD_FAILED; + } + mRequestingLoad = false; - } - CheckFrontFrame(frontFrameReady); + ++frameIndex; + frameIndex %= mImageUrls.size(); + } } -void RollingImageCache::SetImageFrameReady(TextureManager::TextureId textureId) +void RollingImageCache::SetImageFrameReady(TextureManager::TextureId textureId, bool loadSuccess) { for(std::size_t i = 0; i < mQueue.Count(); ++i) { if(GetCachedTextureId(i) == textureId) { + if(loadSuccess) + { + mLoadStates[i] = TextureManager::LoadState::LOAD_FINISHED; + } + else + { + mLoadStates[i] = TextureManager::LoadState::LOAD_FAILED; + } mQueue[i].mReady = true; break; } @@ -239,13 +226,23 @@ TextureManager::TextureId RollingImageCache::GetCachedTextureId(int index) const void RollingImageCache::CheckFrontFrame(bool wasReady) { - if(mWaitingForReadyFrame && wasReady == false && IsFrontReady()) + if(wasReady == false && IsFrontReady()) { - mWaitingForReadyFrame = false; - mObserver.FrameReady(GetFrontTextureSet()); + mObserver.FrameReady(GetFrontTextureSet(), mInterval); } } +void RollingImageCache::ClearCache() +{ + while(mTextureManagerAlive && !mQueue.IsEmpty()) + { + ImageFrame imageFrame = mQueue.PopFront(); + mTextureManager.Remove(mImageUrls[imageFrame.mUrlIndex].mTextureId, this); + mImageUrls[imageFrame.mUrlIndex].mTextureId = TextureManager::INVALID_TEXTURE_ID; + } + mLoadStates.assign(mImageUrls.size(), TextureManager::LoadState::NOT_STARTED); +} + void RollingImageCache::UploadComplete( bool loadSuccess, int32_t textureId, @@ -258,35 +255,15 @@ void RollingImageCache::UploadComplete( LOG_CACHE; bool frontFrameReady = IsFrontReady(); - + SetImageFrameReady(textureId, loadSuccess); 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; } -void RollingImageCache::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-image-cache.h b/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h index 1fcbe88..6f168df 100644 --- a/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h +++ b/dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h @@ -43,6 +43,7 @@ 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] interval Time interval between each frame * * This will start loading textures immediately, according to the * batch and cache sizes. @@ -51,7 +52,8 @@ public: UrlList& urlList, ImageCache::FrameReadyObserver& observer, uint16_t cacheSize, - uint16_t batchSize); + uint16_t batchSize, + uint32_t interval); /** * Destructor @@ -59,73 +61,89 @@ public: ~RollingImageCache() override; /** - * Get the Nth frame. If it's not ready, this will trigger the - * sending of FrameReady() when the image becomes ready. + * @copydoc Internal::ImageCache::Frame() */ TextureSet Frame(uint32_t frameIndex) override; /** - * Get the first frame. If it's not ready, this will trigger the - * sending of FrameReady() when the image becomes ready. + * @copydoc Internal::ImageCache::FirstFrame() */ TextureSet FirstFrame() override; /** - * Get the next frame. If it's not ready, this will trigger the - * sending of FrameReady() when the image becomes ready. - */ - TextureSet NextFrame() override; - - /** - * Get the interval of Nth frame. + * @copydoc Internal::ImageCache::GetFrameInterval() */ uint32_t GetFrameInterval(uint32_t frameIndex) const override; /** - * Get the current rendered frame index. - * If there isn't any loaded frame, returns -1. + * @copydoc Internal::ImageCache::GetCurrentFrameIndex() */ int32_t GetCurrentFrameIndex() const override; /** - * Get total frame count of the animated image file. + * @copydoc Internal::ImageCache::GetTotalFrameCount() */ int32_t GetTotalFrameCount() const override; + /** + * @copydoc Internal::ImageCache::GetLoadState() + */ + TextureManager::LoadState GetLoadState() const override; + + /** + * @copydoc Internal::ImageCache::ClearCache() + */ + void ClearCache() override; + private: /** + * @brief Check whether the front frame is ready or not. + * * @return true if the front frame is ready */ bool IsFrontReady() const; /** - * Load the next batch of images + * @brief Load the next batch of images + * + * @param[in] frameIndex starting frame index of batch to be loaded. */ - void LoadBatch(); + void LoadBatch(uint32_t frameIndex); /** - * Find the matching image frame, and set it to ready + * @brief Find the matching image frame, and set it to ready + * + * @param[in] textureId texture id to be marked as ready. + * @param[in] loadSuccess true if the frame loading is succeeded. */ - void SetImageFrameReady(TextureManager::TextureId textureId); + void SetImageFrameReady(TextureManager::TextureId textureId, bool loadSuccess); /** - * Get the texture set of the front frame. - * @return the texture set + * @brief Get the texture set of the front frame. + * + * @return the texture set of the front of Cache. */ TextureSet GetFrontTextureSet() const; /** - * Get the texture id of the given index + * @brief Get the texture id of the given index + * + * @param[in] index index of the queue. */ TextureManager::TextureId GetCachedTextureId(int index) const; /** - * Check if the front frame has become ready - if so, inform observer + * @brief Check if the front frame has become ready - if so, inform observer + * * @param[in] wasReady Readiness before call. */ void CheckFrontFrame(bool wasReady); protected: + + /** + * @copydoc Toolkit::TextureUploadObserver::UploadComplete() + */ void UploadComplete( bool loadSuccess, int32_t textureId, @@ -134,12 +152,6 @@ protected: 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 @@ -150,8 +162,9 @@ private: bool mReady = false; }; - std::vector& mImageUrls; - CircularQueue mQueue; + std::vector& mImageUrls; + std::vector mLoadStates; + CircularQueue mQueue; }; } // namespace Internal diff --git a/dali-toolkit/internal/visuals/texture-manager-impl.cpp b/dali-toolkit/internal/visuals/texture-manager-impl.cpp index 093954e..b1f18eb 100644 --- a/dali-toolkit/internal/visuals/texture-manager-impl.cpp +++ b/dali-toolkit/internal/visuals/texture-manager-impl.cpp @@ -38,9 +38,9 @@ namespace { -constexpr auto INITIAL_CACHE_NUMBER = size_t{0u}; -constexpr auto DEFAULT_NUMBER_OF_LOCAL_LOADER_THREADS = size_t{4u}; -constexpr auto DEFAULT_NUMBER_OF_REMOTE_LOADER_THREADS = size_t{8u}; +constexpr auto INITIAL_CACHE_NUMBER = size_t{0u}; +constexpr auto DEFAULT_NUMBER_OF_LOCAL_LOADER_THREADS = size_t{4u}; +constexpr auto DEFAULT_NUMBER_OF_REMOTE_LOADER_THREADS = size_t{8u}; constexpr auto NUMBER_OF_LOCAL_LOADER_THREADS_ENV = "DALI_TEXTURE_LOCAL_THREADS"; constexpr auto NUMBER_OF_REMOTE_LOADER_THREADS_ENV = "DALI_TEXTURE_REMOTE_THREADS"; @@ -142,11 +142,19 @@ 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 TextureManager::LoadAnimatedImageTexture(Dali::AnimatedImageLoading animatedImageLoading, + uint32_t frameIndex, + bool& loadingStatus, + TextureManager::TextureId& textureId, + Dali::SamplingMode::Type samplingMode, + Dali::WrapMode::Type wrapModeU, + Dali::WrapMode::Type wrapModeV, + bool synchronousLoading, + bool useCache, + TextureUploadObserver* textureObserver) { + loadingStatus = false; TextureSet textureSet; - if(synchronousLoading) { Devel::PixelBuffer pixelBuffer; @@ -172,14 +180,24 @@ TextureSet TextureManager::LoadAnimatedImageTexture( } else { + loadingStatus = true; 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, StorageType::UPLOAD_TO_TEXTURE, textureObserver, true, TextureManager::ReloadPolicy::CACHED, preMultiply, animatedImageLoading, frameIndex); + textureId = RequestLoadInternal(animatedImageLoading.GetUrl(), INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::BOX_THEN_LINEAR, TextureManager::NO_ATLAS, false, StorageType::UPLOAD_TO_TEXTURE, textureObserver, true, TextureManager::ReloadPolicy::CACHED, preMultiply, animatedImageLoading, frameIndex, useCache); TextureManager::LoadState loadState = GetTextureStateInternal(textureId); if(loadState == TextureManager::LoadState::UPLOADED) { // UploadComplete has already been called - keep the same texture set textureSet = GetTextureSet(textureId); } + + // If we are loading the texture, or waiting for the ready signal handler to complete, inform + // caller that they need to wait. + loadingStatus = (loadState == TextureManager::LoadState::LOADING || + loadState == TextureManager::LoadState::WAITING_FOR_MASK || + loadState == TextureManager::LoadState::MASK_APPLYING || + loadState == TextureManager::LoadState::MASK_APPLIED || + loadState == TextureManager::LoadState::NOT_STARTED || + mQueueLoadFlag); } if(textureSet) @@ -220,7 +238,7 @@ Devel::PixelBuffer TextureManager::LoadPixelBuffer( } else { - RequestLoadInternal(url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, TextureManager::NO_ATLAS, false, StorageType::RETURN_PIXEL_BUFFER, textureObserver, orientationCorrection, TextureManager::ReloadPolicy::FORCED, preMultiplyOnLoad, Dali::AnimatedImageLoading(), 0u); + RequestLoadInternal(url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, TextureManager::NO_ATLAS, false, StorageType::RETURN_PIXEL_BUFFER, textureObserver, orientationCorrection, TextureManager::ReloadPolicy::FORCED, preMultiplyOnLoad, Dali::AnimatedImageLoading(), 0u, false); } return pixelBuffer; @@ -382,7 +400,7 @@ TextureManager::TextureId TextureManager::RequestLoad( TextureManager::ReloadPolicy reloadPolicy, TextureManager::MultiplyOnLoad& preMultiplyOnLoad) { - return RequestLoadInternal(url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas, false, StorageType::UPLOAD_TO_TEXTURE, observer, orientationCorrection, reloadPolicy, preMultiplyOnLoad, Dali::AnimatedImageLoading(), 0u); + return RequestLoadInternal(url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas, false, StorageType::UPLOAD_TO_TEXTURE, observer, orientationCorrection, reloadPolicy, preMultiplyOnLoad, Dali::AnimatedImageLoading(), 0u, true); } TextureManager::TextureId TextureManager::RequestLoad( @@ -399,14 +417,14 @@ TextureManager::TextureId TextureManager::RequestLoad( TextureManager::ReloadPolicy reloadPolicy, TextureManager::MultiplyOnLoad& preMultiplyOnLoad) { - return RequestLoadInternal(url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas, cropToMask, StorageType::UPLOAD_TO_TEXTURE, observer, orientationCorrection, reloadPolicy, preMultiplyOnLoad, Dali::AnimatedImageLoading(), 0u); + return RequestLoadInternal(url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas, cropToMask, StorageType::UPLOAD_TO_TEXTURE, observer, orientationCorrection, reloadPolicy, preMultiplyOnLoad, Dali::AnimatedImageLoading(), 0u, true); } TextureManager::TextureId TextureManager::RequestMaskLoad(const VisualUrl& maskUrl) { // Use the normal load procedure to get the alpha 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, StorageType::KEEP_PIXEL_BUFFER, NULL, true, TextureManager::ReloadPolicy::CACHED, preMultiply, Dali::AnimatedImageLoading(), 0u); + return RequestLoadInternal(maskUrl, INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, false, StorageType::KEEP_PIXEL_BUFFER, NULL, true, TextureManager::ReloadPolicy::CACHED, preMultiply, Dali::AnimatedImageLoading(), 0u, true); } TextureManager::TextureId TextureManager::RequestLoadInternal( @@ -424,19 +442,18 @@ TextureManager::TextureId TextureManager::RequestLoadInternal( TextureManager::ReloadPolicy reloadPolicy, TextureManager::MultiplyOnLoad& preMultiplyOnLoad, Dali::AnimatedImageLoading animatedImageLoading, - uint32_t frameIndex) + uint32_t frameIndex, + bool useCache) { // First check if the requested Texture is cached. - bool isAnimatedImage = (animatedImageLoading) ? true : false; - TextureHash textureHash = INITIAL_CACHE_NUMBER; int cacheIndex = INVALID_CACHE_INDEX; - if(storageType != StorageType::RETURN_PIXEL_BUFFER && !isAnimatedImage) + if(storageType != StorageType::RETURN_PIXEL_BUFFER && useCache) { textureHash = GenerateHash(url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId); // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision. - cacheIndex = FindCachedTexture(textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId, preMultiplyOnLoad); + cacheIndex = FindCachedTexture(textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId, preMultiplyOnLoad, (animatedImageLoading) ? true : false); } TextureManager::TextureId textureId = INVALID_TEXTURE_ID; @@ -574,7 +591,6 @@ void TextureManager::Remove(const TextureManager::TextureId textureId, TextureUp if(textureInfoIndex != INVALID_INDEX) { TextureInfo& textureInfo(mTextureInfoContainer[textureInfoIndex]); - DALI_LOG_INFO(gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) url:%s\n cacheIdx:%d loadState:%s reference count = %d\n", textureId, textureInfo.url.GetUrl().c_str(), textureInfoIndex, GET_LOAD_STATE_STRING(textureInfo.loadState), textureInfo.referenceCount); // Decrement the reference count and check if this is the last user of this Texture. @@ -913,7 +929,14 @@ void TextureManager::LoadOrQueueTexture(TextureInfo& textureInfo, TextureUploadO { // 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.textureId, textureInfo.textureSet, textureInfo.useAtlas, textureInfo.atlasRect, textureInfo.preMultiplied); + if(textureInfo.isAnimatedImageFormat) + { + observer->AnimatedImageUploadComplete(true, textureInfo.textureId, textureInfo.frameCount, textureInfo.frameInterval); + } + else + { + observer->UploadComplete(true, textureInfo.textureId, textureInfo.textureSet, textureInfo.useAtlas, textureInfo.atlasRect, textureInfo.preMultiplied); + } } break; } @@ -975,7 +998,14 @@ void TextureManager::ProcessQueuedTextures() TextureInfo& textureInfo(mTextureInfoContainer[cacheIndex]); if(textureInfo.loadState == LoadState::UPLOADED) { - element.mObserver->UploadComplete(true, textureInfo.textureId, textureInfo.textureSet, textureInfo.useAtlas, textureInfo.atlasRect, textureInfo.preMultiplied); + if(textureInfo.isAnimatedImageFormat) + { + element.mObserver->AnimatedImageUploadComplete(true, textureInfo.textureId, textureInfo.frameCount, textureInfo.frameInterval); + } + else + { + element.mObserver->UploadComplete(true, textureInfo.textureId, textureInfo.textureSet, textureInfo.useAtlas, textureInfo.atlasRect, textureInfo.preMultiplied); + } } else if(textureInfo.loadState == LoadState::LOAD_FINISHED && textureInfo.storageType == StorageType::RETURN_PIXEL_BUFFER) { @@ -1192,6 +1222,13 @@ void TextureManager::NotifyObservers(TextureInfo& textureInfo, bool success) // and erase it from the list TextureInfo* info = &textureInfo; + if(info->animatedImageLoading) + { + info->frameCount = info->animatedImageLoading.GetImageCount(); + info->frameInterval = info->animatedImageLoading.GetFrameInterval(info->frameIndex); + info->animatedImageLoading.Reset(); + } + mQueueLoadFlag = true; while(info->observerList.Count()) @@ -1218,6 +1255,10 @@ void TextureManager::NotifyObservers(TextureInfo& textureInfo, bool success) { observer->LoadComplete(success, info->pixelBuffer, info->url, info->preMultiplied); } + else if(info->isAnimatedImageFormat) + { + observer->AnimatedImageUploadComplete(success, info->textureId, info->frameCount, info->frameInterval); + } else { observer->UploadComplete(success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect, info->preMultiplied); @@ -1338,7 +1379,8 @@ int TextureManager::FindCachedTexture( const Dali::SamplingMode::Type samplingMode, const bool useAtlas, TextureId maskTextureId, - TextureManager::MultiplyOnLoad preMultiplyOnLoad) + TextureManager::MultiplyOnLoad preMultiplyOnLoad, + bool isAnimatedImage) { // Default to an invalid ID, in case we do not find a match. int cacheIndex = INVALID_CACHE_INDEX; @@ -1356,6 +1398,7 @@ int TextureManager::FindCachedTexture( (useAtlas == textureInfo.useAtlas) && (maskTextureId == textureInfo.maskTextureId) && (size == textureInfo.desiredSize) && + (isAnimatedImage == textureInfo.isAnimatedImageFormat) && ((size.GetWidth() == 0 && size.GetHeight() == 0) || (fittingMode == textureInfo.fittingMode && samplingMode == textureInfo.samplingMode))) diff --git a/dali-toolkit/internal/visuals/texture-manager-impl.h b/dali-toolkit/internal/visuals/texture-manager-impl.h index 48eea78..4fddf49 100644 --- a/dali-toolkit/internal/visuals/texture-manager-impl.h +++ b/dali-toolkit/internal/visuals/texture-manager-impl.h @@ -165,26 +165,30 @@ public: * 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). + * @param[in] animatedImageLoading The AnimatedImageLoading that contain the animated image information + * @param[in] frameIndex The frame index to load. + * @param[out] loadingStatus The loading status of the texture + * @param[out] textureId The textureId of the frame + * @param[in] samplingMode The SamplingMode to use + * @param[in] wrapModeU Horizontal Wrap mode + * @param[in] wrapModeV Vertical Wrap mode + * @param[in] synchronousLoading true if the frame should be loaded synchronously + * @param[in] useCache true if this frame loading uses cache. + * @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. + * @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, + bool& loadingStatus, TextureManager::TextureId& textureId, + Dali::SamplingMode::Type samplingMode, Dali::WrapMode::Type wrapModeU, Dali::WrapMode::Type wrapModeV, + bool synchronousLoading, + bool useCache, TextureUploadObserver* textureObserver); /** @@ -509,7 +513,8 @@ private: TextureManager::ReloadPolicy reloadPolicy, MultiplyOnLoad& preMultiplyOnLoad, Dali::AnimatedImageLoading animatedImageLoading, - uint32_t frameIndex); + uint32_t frameIndex, + bool useCache); /** * @brief Get the current state of a texture @@ -558,6 +563,8 @@ private: storageType(StorageType::UPLOAD_TO_TEXTURE), animatedImageLoading(animatedImageLoading), frameIndex(frameIndex), + frameCount(0u), + frameInterval(0u), loadSynchronously(loadSynchronously), useAtlas(useAtlas), cropToMask(cropToMask), @@ -565,6 +572,7 @@ private: preMultiplyOnLoad(preMultiplyOnLoad), preMultiplied(false) { + isAnimatedImageFormat = (animatedImageLoading) ? true : false; } /** @@ -590,7 +598,9 @@ private: Dali::SamplingMode::Type samplingMode : 3; ///< The requested SamplingMode StorageType storageType; ///< 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 + uint32_t frameIndex; ///< Frame index that be loaded, in case of animated image + uint32_t frameCount; ///< Total frame count of input animated image. If this variable is not 0, this textureInfo is for animated image file format. + uint32_t frameInterval; ///< Time interval between this frame and next frame 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 @@ -598,6 +608,7 @@ private: bool orientationCorrection : 1; ///< true if the image should be rotated to match exif orientation data bool preMultiplyOnLoad : 1; ///< true if the image's color should be multiplied by it's alpha bool preMultiplied : 1; ///< true if the image's color was multiplied by it's alpha + bool isAnimatedImageFormat : 1; ///< true if the image is requested from animated image visual. }; /** @@ -781,7 +792,8 @@ private: * @param[in] samplingMode The SamplingMode to use * @param[in] useAtlas True if atlased * @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] preMultiplyOnLoad If the image's color should be multiplied by it's alpha. Set to OFF if there is no alpha. + * @param[in] isAnimatedImage True if the texture is from animated image. * @return A TextureId of a cached Texture if found. Or INVALID_TEXTURE_ID if not found. */ TextureManager::TextureId FindCachedTexture( @@ -792,7 +804,8 @@ private: const Dali::SamplingMode::Type samplingMode, const bool useAtlas, TextureId maskTextureId, - MultiplyOnLoad preMultiplyOnLoad); + MultiplyOnLoad preMultiplyOnLoad, + bool isAnimatedImage); private: /** diff --git a/dali-toolkit/internal/visuals/texture-upload-observer.h b/dali-toolkit/internal/visuals/texture-upload-observer.h index d6657a2..b22b4df 100644 --- a/dali-toolkit/internal/visuals/texture-upload-observer.h +++ b/dali-toolkit/internal/visuals/texture-upload-observer.h @@ -21,6 +21,7 @@ // EXTERNAL INCLUDES #include #include +#include // INTERNAL INCLUDES #include @@ -28,7 +29,6 @@ namespace Dali { -class TextureSet; namespace Toolkit { @@ -54,7 +54,7 @@ public: virtual ~TextureUploadObserver(); /** - * The action to be taken once the async load has finished and the upload to GPU is completed. + * @brief The action to be taken once the async load has finished and the upload to GPU is completed. * 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. @@ -64,10 +64,25 @@ public: * @param[in] atlasRect If using atlasing, this is the rectangle within the atlas to use. * @param[in] preMultiplied True if the image had pre-multiplied alpha applied */ - virtual void UploadComplete(bool loadSuccess, int32_t textureId, TextureSet textureSet, bool useAtlasing, const Vector4& atlasRect, bool preMultiplied) = 0; + virtual void UploadComplete(bool loadSuccess, int32_t textureId, TextureSet textureSet, bool useAtlasing, const Vector4& atlasRect, bool preMultiplied) + { + } /** - * The action to be taken once the async load has finished. + * @brief The action to be taken once the async load of animated image has finished and the upload to GPU is completed. + * 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] frameCount The frameCount of the animated image + * @param[in] interval Time interval between currently loaded frame and next frame. + */ + virtual void AnimatedImageUploadComplete(bool loadSuccess, int32_t textureId, uint32_t frameCount, uint32_t interval) + { + } + + /** + * @brief The action to be taken once the async load has finished. * This should be overridden by the deriving class. * * @param[in] loadSuccess True if the image load was successful (i.e. the resource is available). If false, then the resource failed to load. @@ -75,7 +90,9 @@ public: * @param[in] url The url address of the loaded image. * @param[in] preMultiplied True if the image had pre-multiplied alpha applied */ - virtual void LoadComplete(bool loadSuccess, Devel::PixelBuffer pixelBuffer, const Internal::VisualUrl& url, bool preMultiplied) = 0; + virtual void LoadComplete(bool loadSuccess, Devel::PixelBuffer pixelBuffer, const Internal::VisualUrl& url, bool preMultiplied) + { + } /** * @brief Returns the destruction signal. -- 2.7.4