Make AnimatedImageVisual use single fixed image cache if it is not gif/webp + Fix... 17/311217/7
authorEunki, Hong <eunkiki.hong@samsung.com>
Thu, 16 May 2024 07:50:39 +0000 (16:50 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Mon, 27 May 2024 00:38:21 +0000 (09:38 +0900)
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 <eunkiki.hong@samsung.com>
automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp
dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp
dali-toolkit/internal/visuals/animated-image/fixed-image-cache.cpp

index ba02bef..ecfa202 100644 (file)
@@ -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<Impl::DummyControl&>(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<Impl::DummyControl&>(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)
 {
index d8de5da..f2daa03 100644 (file)
@@ -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<uint32_t>(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<Vector2>();
   }
-  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)
   {
index e751f90..e51870b 100644 (file)
@@ -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
   {