From 9bd4d38d901ae862a6817cce4775e71842f97135 Mon Sep 17 00:00:00 2001 From: "Eunki, Hong" Date: Thu, 16 May 2024 16:50:39 +0900 Subject: [PATCH] Make AnimatedImageVisual use single fixed image cache if it is not gif/webp + Fix several bugs at fixed image cache It is possible that mImageCache is NULL if url suffix is not gif or webp. In this case, mImageCache become null, so we should show broken image. But also, we 'might' need to show non-animatable image even if we use non-animatable image (like jpg), and set it to animated image visual forcibly. To resolve general cases, let we make AnimatedImageVisual with non-animatable format image just use image sequence with length 1. === Also, there was several bugs when we use fixed image cache, with cached texture manager image. Before, we don't consider full-scenario when LoadComplete callback comes during TextureManager.Load. Change-Id: I173020e42d6447ff43e56e19f25ea8e06c7bbfc1 Signed-off-by: Eunki, Hong --- .../dali-toolkit/utc-Dali-AnimatedImageVisual.cpp | 115 ++++++++++++++++++++- .../animated-image/animated-image-visual.cpp | 62 ++++++++--- .../visuals/animated-image/fixed-image-cache.cpp | 37 +++++-- 3 files changed, 186 insertions(+), 28 deletions(-) diff --git a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp index ba02bef..ecfa202 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 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. @@ -545,6 +545,114 @@ int UtcDaliAnimatedImageVisualImageLoadingFail01(void) END_TEST; } +int UtcDaliAnimatedImageVisualImageLoadingFail02(void) +{ + ToolkitTestApplication application; + + tet_infoline("Test with non-animated single image. We should show broken image than."); + + for(int isSynchronousLoading = 0; isSynchronousLoading < 2; ++isSynchronousLoading) + { + tet_printf("Test to load non-animatable image %s\n", (isSynchronousLoading == 1) ? "Synchronously" : "Asynchronously"); + + Property::Map propertyMap; + propertyMap.Insert(Visual::Property::TYPE, Visual::ANIMATED_IMAGE); + propertyMap.Insert(ImageVisual::Property::URL, "dummy"); + propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, (isSynchronousLoading == 1)); + + 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); + + DALI_TEST_EQUALS(dummyControl.GetRendererCount(), 0u, TEST_LOCATION); + + application.GetScene().Add(dummyControl); + + application.SendNotification(); + application.Render(20); + + // TODO : Since fixed-image-cache didn't support synchronous loading now, we need to wait for a while. + // We have to remove it in future! + //if(!(isSynchronousLoading == 1)) + { + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + } + + application.SendNotification(); + application.Render(20); + + // Check broken image uploaded. + DALI_TEST_EQUALS(dummyControl.GetRendererCount(), 1u, TEST_LOCATION); + + dummyControl.Unparent(); + + // Remove cached image at TextureManager. + application.SendNotification(); + application.Render(20); + } + + END_TEST; +} + +int UtcDaliAnimatedImageVisualImageLoadingFail03(void) +{ + ToolkitTestApplication application; + + tet_infoline("Test with invalid image that suffix is .gif, and AnimatedImageLoading not supported. We should show broken image than."); + + for(int isSynchronousLoading = 0; isSynchronousLoading < 2; ++isSynchronousLoading) + { + tet_printf("Test to load non-animatable image %s\n", (isSynchronousLoading == 1) ? "Synchronously" : "Asynchronously"); + + Property::Map propertyMap; + propertyMap.Insert(Visual::Property::TYPE, Visual::ANIMATED_IMAGE); + propertyMap.Insert(ImageVisual::Property::URL, "dummy.Gif"); ///< Suffix is gif so visual become AnimatedImageVisual. But AnimatedImageLoading become null. + propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, (isSynchronousLoading == 1)); + + 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); + + DALI_TEST_EQUALS(dummyControl.GetRendererCount(), 0u, TEST_LOCATION); + + application.GetScene().Add(dummyControl); + + application.SendNotification(); + application.Render(20); + + // TODO : Since fixed-image-cache didn't support synchronous loading now, we need to wait for a while. + // We have to remove it in future! + //if(!(isSynchronousLoading == 1)) + { + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + } + + application.SendNotification(); + application.Render(20); + + // Check broken image uploaded. + DALI_TEST_EQUALS(dummyControl.GetRendererCount(), 1u, TEST_LOCATION); + + dummyControl.Unparent(); + + // Remove cached image at TextureManager. + application.SendNotification(); + application.Render(20); + } + + END_TEST; +} + int UtcDaliAnimatedImageVisualSynchronousLoading(void) { ToolkitTestApplication application; @@ -775,7 +883,7 @@ int UtcDaliAnimatedImageVisualJumpToAction(void) DevelControl::DoAction(dummyControl, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedImageVisual::Action::JUMP_TO, 6); - DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(6), true, TEST_LOCATION); + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(4), true, TEST_LOCATION); DALI_TEST_EQUALS(gl.GetNumGeneratedTextures(), 4, TEST_LOCATION); dummyControl.Unparent(); @@ -1643,6 +1751,8 @@ int UtcDaliAnimatedImageVisualMultiImage05(void) END_TEST; } +namespace +{ void TestLoopCount(ToolkitTestApplication& application, DummyControl& dummyControl, uint16_t frameCount, uint16_t loopCount, const char* location) { TestGlAbstraction& gl = application.GetGlAbstraction(); @@ -1693,6 +1803,7 @@ void TestLoopCount(ToolkitTestApplication& application, DummyControl& dummyContr dummyControl.Unparent(); } +} // namespace int UtcDaliAnimatedImageVisualLoopCount(void) { 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 d8de5da..f2daa03 100644 --- a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp +++ b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp @@ -190,6 +190,22 @@ void AnimatedImageVisual::InitializeAnimatedImage(const VisualUrl& imageUrl) { mImageUrl = imageUrl; mAnimatedImageLoading = AnimatedImageLoading::New(imageUrl.GetUrl(), imageUrl.IsLocalResource()); + + // If we fail to load the animated image, we will try to load as a normal image. + if(!mAnimatedImageLoading) + { + mImageUrls = new ImageCache::UrlList(); + mImageUrls->reserve(SINGLE_IMAGE_COUNT); + + for(unsigned int i = 0; i < SINGLE_IMAGE_COUNT; ++i) + { + ImageCache::UrlStore urlStore; + urlStore.mTextureId = TextureManager::INVALID_TEXTURE_ID; + urlStore.mUrl = imageUrl; + mImageUrls->push_back(urlStore); + } + mFrameCount = SINGLE_IMAGE_COUNT; + } } void AnimatedImageVisual::CreateImageCache() @@ -219,7 +235,7 @@ void AnimatedImageVisual::CreateImageCache() } } - if(!mImageCache) + if(DALI_UNLIKELY(!mImageCache)) { DALI_LOG_ERROR("mImageCache is null\n"); } @@ -266,7 +282,10 @@ AnimatedImageVisual::~AnimatedImageVisual() // 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(); + if(DALI_LIKELY(mImageCache)) + { + mImageCache->ClearCache(); + } } delete mImageCache; delete mImageUrls; @@ -297,7 +316,7 @@ void AnimatedImageVisual::GetNaturalSize(Vector2& naturalSize) } } - if(mImageUrl.IsValid()) + if(mImageUrl.IsValid() && mAnimatedImageLoading) { mImageSize = mAnimatedImageLoading.GetImageSize(); } @@ -385,11 +404,8 @@ void AnimatedImageVisual::DoCreateInstancePropertyMap(Property::Map& map) const { map.Clear(); map.Insert(Toolkit::Visual::Property::TYPE, Toolkit::Visual::ANIMATED_IMAGE); - if(mImageUrl.IsValid()) - { - map.Insert(Toolkit::ImageVisual::Property::DESIRED_WIDTH, mDesiredSize.GetWidth()); - map.Insert(Toolkit::ImageVisual::Property::DESIRED_HEIGHT, mDesiredSize.GetHeight()); - } + map.Insert(Toolkit::ImageVisual::Property::DESIRED_WIDTH, mDesiredSize.GetWidth()); + map.Insert(Toolkit::ImageVisual::Property::DESIRED_HEIGHT, mDesiredSize.GetHeight()); } void AnimatedImageVisual::OnDoAction(const Dali::Property::Index actionId, const Dali::Property::Value& attributes) @@ -628,7 +644,7 @@ void AnimatedImageVisual::DoSetProperty(Property::Index index, if(value.Get(frameDelay)) { mFrameDelay = frameDelay; - if(mImageCache) + if(DALI_LIKELY(mImageCache)) { mImageCache->SetInterval(static_cast(mFrameDelay)); } @@ -806,7 +822,10 @@ 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 + if(DALI_LIKELY(mImageCache)) + { + mImageCache->ClearCache(); // If INVALID_TEXTURE_ID then removal will be attempted on atlas + } mImpl->mResourceStatus = Toolkit::Visual::ResourceStatus::PREPARING; TextureSet textureSet = TextureSet::New(); @@ -936,10 +955,15 @@ void AnimatedImageVisual::StartFirstFrame(TextureSet& textureSet, uint32_t first void AnimatedImageVisual::PrepareTextureSet() { TextureSet textureSet; - if(mImageCache) + if(DALI_LIKELY(mImageCache)) { textureSet = mImageCache->FirstFrame(); } + else + { + // preMultiplied should be false because broken image don't premultiply alpha on load + FrameReady(TextureSet(), 0, false); + } // Check whether synchronous loading is true or false for the first frame. if(textureSet) @@ -990,7 +1014,10 @@ void AnimatedImageVisual::FrameReady(TextureSet textureSet, uint32_t interval, b if(mStartFirstFrame) { - mFrameCount = mImageCache->GetTotalFrameCount(); + if(DALI_LIKELY(mImageCache)) + { + mFrameCount = mImageCache->GetTotalFrameCount(); + } StartFirstFrame(textureSet, interval); } else @@ -1012,7 +1039,7 @@ bool AnimatedImageVisual::DisplayNextFrame() TextureSet textureSet; bool continueTimer = false; - if(mImageCache) + if(DALI_LIKELY(mImageCache)) { uint32_t frameIndex = mImageCache->GetCurrentFrameIndex(); @@ -1094,8 +1121,13 @@ TextureSet AnimatedImageVisual::SetLoadingFailed() { imageSize = actor.GetProperty(Actor::Property::SIZE).Get(); } - mFactoryCache.UpdateBrokenImageRenderer(mImpl->mRenderer, imageSize); - TextureSet textureSet = mImpl->mRenderer.GetTextures(); + + TextureSet textureSet; + if(DALI_LIKELY(mImpl->mRenderer)) + { + mFactoryCache.UpdateBrokenImageRenderer(mImpl->mRenderer, imageSize); + textureSet = mImpl->mRenderer.GetTextures(); + } if(mFrameDelayTimer) { 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 e751f90..e51870b 100644 --- a/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp +++ b/dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp @@ -67,14 +67,22 @@ TextureSet FixedImageCache::Frame(uint32_t frameIndex) return textureSet; } - while(mReadyFlags.size() < mImageUrls.size() && - (frameIndex > mCurrentFrameIndex || mReadyFlags.empty())) + mCurrentFrameIndex = frameIndex; + + bool batchRequested = false; + + // Make ensure that current frameIndex load requested. + while(mReadyFlags.size() <= frameIndex) { - ++mCurrentFrameIndex; + batchRequested = true; LoadBatch(); } - mCurrentFrameIndex = frameIndex; + // Request batch only 1 times for this function. + if(!batchRequested && mReadyFlags.size() < mImageUrls.size()) + { + LoadBatch(); + } if(IsFrameReady(mCurrentFrameIndex) && mLoadState != TextureManager::LoadState::LOAD_FAILED) { @@ -108,7 +116,7 @@ int32_t FixedImageCache::GetTotalFrameCount() const bool FixedImageCache::IsFrameReady(uint32_t frameIndex) const { - return (mReadyFlags.size() > 0 && mReadyFlags[frameIndex] == true); + return ((mReadyFlags.size() > 0) && (mReadyFlags[frameIndex] == true)); } void FixedImageCache::LoadBatch() @@ -139,8 +147,10 @@ void FixedImageCache::LoadBatch() auto preMultiplyOnLoading = mPreMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY; - mTextureManager.LoadTexture(url, mDesiredSize, mFittingMode, mSamplingMode, mMaskingData, synchronousLoading, mImageUrls[frameIndex].mTextureId, textureRect, textureRectSize, atlasingStatus, loadingStatus, this, atlasObserver, imageAtlasManager, ENABLE_ORIENTATION_CORRECTION, TextureManager::ReloadPolicy::CACHED, preMultiplyOnLoading); - mRequestingLoad = false; + TextureManager::TextureId loadTextureId = TextureManager::INVALID_TEXTURE_ID; + mTextureManager.LoadTexture(url, mDesiredSize, mFittingMode, mSamplingMode, mMaskingData, synchronousLoading, loadTextureId, textureRect, textureRectSize, atlasingStatus, loadingStatus, this, atlasObserver, imageAtlasManager, ENABLE_ORIENTATION_CORRECTION, TextureManager::ReloadPolicy::CACHED, preMultiplyOnLoading); + mImageUrls[frameIndex].mTextureId = loadTextureId; + mRequestingLoad = false; } } @@ -180,8 +190,8 @@ void FixedImageCache::LoadComplete(bool loadSuccess, TextureInformation textureI { if(loadSuccess) { - mLoadState = TextureManager::LoadState::LOAD_FINISHED; - bool isCurrentFrameReady = IsFrameReady(mCurrentFrameIndex); + mLoadState = TextureManager::LoadState::LOAD_FINISHED; + bool wasCurrentFrameReady = IsFrameReady(mCurrentFrameIndex); if(!mRequestingLoad) { for(std::size_t i = 0; i < mImageUrls.size(); ++i) @@ -195,9 +205,14 @@ void FixedImageCache::LoadComplete(bool loadSuccess, TextureInformation textureI } else { - mReadyFlags.back() = true; + DALI_ASSERT_ALWAYS(mReadyFlags.size() > 0u && "Some FixedImageCache::LoadBatch() called mismatched!"); + size_t i = mReadyFlags.size() - 1u; + + // texture id might not setup yet. Update it now. + mImageUrls[i].mTextureId = textureInformation.textureId; + mReadyFlags[i] = true; } - MakeReady(isCurrentFrameReady, mCurrentFrameIndex, textureInformation.preMultiplied); + MakeReady(wasCurrentFrameReady, mCurrentFrameIndex, textureInformation.preMultiplied); } else { -- 2.7.4