Make we use valid geometry if broken image was n-patch and reloaded 42/304842/7
authorEunki, Hong <eunkiki.hong@samsung.com>
Wed, 24 Jan 2024 07:30:56 +0000 (16:30 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Thu, 25 Jan 2024 07:47:12 +0000 (16:47 +0900)
It is possible that we use broken image not matched with given visual,
and then Reload API call it, and successed.

For this case, we need to change to use valid geometry again.

Change-Id: I2df7fb8b6123361b515b8f9ac9c1fafc77f665b6
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
automated-tests/resources/overwritable-image.jpg [new file with mode: 0644]
automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp
dali-toolkit/internal/visuals/image/image-visual.cpp
dali-toolkit/internal/visuals/image/image-visual.h

diff --git a/automated-tests/resources/overwritable-image.jpg b/automated-tests/resources/overwritable-image.jpg
new file mode 100644 (file)
index 0000000..9977a28
--- /dev/null
@@ -0,0 +1 @@
+invalid
index bfe5db290032bf0cdb37bbecac5a66ae2805b047..60d155179e0abcb10e3ea9a701938e2c334e83b0 100644 (file)
@@ -85,6 +85,8 @@ const char* TEST_SVG_FILE_NAME                   = TEST_RESOURCE_DIR "/svg1.svg"
 const char* TEST_ANIMATED_VECTOR_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/insta_camera.json";
 const char* TEST_WEBP_FILE_NAME                  = TEST_RESOURCE_DIR "/dali-logo.webp";
 
+const char* TEST_OVERWRITABLE_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/overwritable-image.jpg";
+
 void TestUrl(ImageView imageView, const std::string url)
 {
   Property::Value value = imageView.GetProperty(imageView.GetPropertyIndex("image"));
@@ -94,6 +96,38 @@ void TestUrl(ImageView imageView, const std::string url)
   DALI_TEST_EQUALS(urlActual, url, TEST_LOCATION);
 }
 
+void OverwriteImage(const char* sourceFilename)
+{
+  FILE* fpOut = fopen(TEST_OVERWRITABLE_IMAGE_FILE_NAME, "wb");
+  DALI_TEST_CHECK(fpOut);
+  if(fpOut)
+  {
+    FILE* fpIn = fopen(sourceFilename, "rb");
+    if(fpIn)
+    {
+      fseek(fpIn, 0, SEEK_END);
+      size_t size = ftell(fpIn);
+
+      tet_printf("Open %s success! file size : %zu byte\n", sourceFilename, size);
+      Dali::Vector<uint8_t> data;
+      data.Resize(size);
+      fseek(fpIn, 0, SEEK_SET);
+      size_t realSize = fread(data.Begin(), sizeof(uint8_t), size, fpIn);
+      fclose(fpIn);
+      data.Resize(realSize);
+
+      // Overwrite
+      fwrite(data.Begin(), sizeof(uint8_t), size, fpOut);
+    }
+    else
+    {
+      tet_printf("Open %s failed! write invalid\n", sourceFilename);
+      fprintf(fpOut, "invalid\n");
+    }
+    fclose(fpOut);
+  }
+}
+
 } // namespace
 
 int UtcDaliImageViewNewP(void)
@@ -5475,3 +5509,122 @@ int UtcDaliImageViewTransitionEffect03(void)
 
   END_TEST;
 }
+
+int UtcDaliImageViewImageLoadFailureAndReload01(void)
+{
+  tet_infoline("Try to load invalid image first, and then reload after that image valid.");
+  ToolkitTestApplication application;
+
+  gResourceReadySignalFired = false;
+
+  // Make overwritable image invalid first.
+  OverwriteImage("");
+
+  ImageView imageView = ImageView::New(TEST_OVERWRITABLE_IMAGE_FILE_NAME);
+  imageView.SetProperty(Actor::Property::SIZE, Vector2(100.f, 100.f));
+  imageView.ResourceReadySignal().Connect(&ResourceReadySignal);
+
+  application.GetScene().Add(imageView);
+  application.SendNotification();
+  application.Render(16);
+
+  // loading started, this waits for the loader thread
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(gResourceReadySignalFired, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(imageView.IsResourceReady(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(imageView.GetVisualResourceStatus(ImageView::Property::IMAGE), Visual::ResourceStatus::FAILED, TEST_LOCATION);
+
+  gResourceReadySignalFired = false;
+
+  // Make overwritable image valid now.
+  OverwriteImage(gImage_34_RGBA);
+
+  // Reload the image
+  Property::Map attributes;
+  DevelControl::DoAction(imageView, ImageView::Property::IMAGE, DevelImageVisual::Action::RELOAD, attributes);
+  application.SendNotification();
+  application.Render(16);
+
+  // loading started, this waits for the loader thread
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(gResourceReadySignalFired, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(imageView.IsResourceReady(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(imageView.GetVisualResourceStatus(ImageView::Property::IMAGE), Visual::ResourceStatus::READY, TEST_LOCATION);
+
+  // Make overwritable image invalid end of test (for clean).
+  OverwriteImage("");
+
+  gResourceReadySignalFired = false;
+
+  END_TEST;
+}
+
+int UtcDaliImageViewImageLoadFailureAndReload02(void)
+{
+  tet_infoline("Try to load invalid image first, and then reload after that image valid.");
+  tet_infoline("This case, broken image was n-patch. So we should check whether Geometry / Shader changed after reload");
+  ToolkitTestApplication application;
+
+  Toolkit::StyleManager styleManager = Toolkit::StyleManager::Get();
+  DevelStyleManager::SetBrokenImageUrl(styleManager, DevelStyleManager::BrokenImageType::SMALL, TEST_BROKEN_IMAGE_S);
+  DevelStyleManager::SetBrokenImageUrl(styleManager, DevelStyleManager::BrokenImageType::NORMAL, TEST_BROKEN_IMAGE_M);
+  DevelStyleManager::SetBrokenImageUrl(styleManager, DevelStyleManager::BrokenImageType::LARGE, TEST_BROKEN_IMAGE_L);
+
+  gResourceReadySignalFired = false;
+
+  // Make overwritable image invalid first.
+  OverwriteImage("");
+
+  ImageView imageView = ImageView::New(TEST_OVERWRITABLE_IMAGE_FILE_NAME);
+  imageView.SetProperty(Actor::Property::SIZE, Vector2(100.f, 100.f));
+  imageView.ResourceReadySignal().Connect(&ResourceReadySignal);
+
+  application.GetScene().Add(imageView);
+  application.SendNotification();
+  application.Render(16);
+
+  // loading started, this waits for the loader thread
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(gResourceReadySignalFired, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(imageView.IsResourceReady(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(imageView.GetVisualResourceStatus(ImageView::Property::IMAGE), Visual::ResourceStatus::FAILED, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(imageView.GetRendererCount(), 1u, TEST_LOCATION);
+  Geometry brokenGeometry = imageView.GetRendererAt(0u).GetGeometry();
+  Shader   brokenShader   = imageView.GetRendererAt(0u).GetShader();
+  DALI_TEST_CHECK(brokenGeometry);
+  DALI_TEST_CHECK(brokenShader);
+
+  gResourceReadySignalFired = false;
+
+  // Make overwritable image valid now.
+  OverwriteImage(gImage_34_RGBA);
+
+  // Reload the image
+  Property::Map attributes;
+  DevelControl::DoAction(imageView, ImageView::Property::IMAGE, DevelImageVisual::Action::RELOAD, attributes);
+  application.SendNotification();
+  application.Render(16);
+
+  // loading started, this waits for the loader thread
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(gResourceReadySignalFired, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(imageView.IsResourceReady(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(imageView.GetVisualResourceStatus(ImageView::Property::IMAGE), Visual::ResourceStatus::READY, TEST_LOCATION);
+
+  // Check whether we don't use n-patch shader and geometry in this case.
+  DALI_TEST_EQUALS(imageView.GetRendererCount(), 1u, TEST_LOCATION);
+  DALI_TEST_CHECK(brokenGeometry != imageView.GetRendererAt(0u).GetGeometry());
+  DALI_TEST_CHECK(brokenShader != imageView.GetRendererAt(0u).GetShader());
+
+  // Make overwritable image invalid end of test (for clean).
+  OverwriteImage("");
+
+  gResourceReadySignalFired = false;
+
+  END_TEST;
+}
\ No newline at end of file
index 1d045dc870f1011de977ac04b344a88f36d2fc63..980c63aa5800c36b666fb1399fe92b405eedb20d 100644 (file)
@@ -534,6 +534,7 @@ void ImageVisual::GetNaturalSize(Vector2& naturalSize)
           imageSize = mPlacementActorSize;
         }
 
+        mUseBrokenImageRenderer = true;
         mFactoryCache.UpdateBrokenImageRenderer(mImpl->mRenderer, imageSize);
         Texture brokenImage = mImpl->mRenderer.GetTextures().GetTexture(0);
         naturalSize.x       = brokenImage.GetWidth();
@@ -547,27 +548,6 @@ void ImageVisual::GetNaturalSize(Vector2& naturalSize)
 
 void ImageVisual::OnInitialize()
 {
-  Geometry geometry;
-
-  // Get the geometry
-  if(mImpl->mCustomShader)
-  {
-    geometry = CreateGeometry(mFactoryCache, mImpl->mCustomShader->mGridSize);
-  }
-  else // Get any geometry associated with the texture
-  {
-    TextureManager& textureManager = mFactoryCache.GetTextureManager();
-
-    uint32_t firstElementCount{0u};
-    uint32_t secondElementCount{0u};
-    geometry = textureManager.GetRenderGeometry(mTextureId, firstElementCount, secondElementCount);
-
-    if(!firstElementCount && !secondElementCount) // Otherwise use quad
-    {
-      geometry = CreateGeometry(mFactoryCache, ImageDimensions(1, 1));
-    }
-  }
-
   // Increase reference count of External Resources :
   // EncodedImageBuffer or ExternalTextures.
   // Reference count will be decreased at destructor of the visual.
@@ -577,7 +557,9 @@ void ImageVisual::OnInitialize()
     textureManager.UseExternalResource(mImageUrl.GetUrl());
   }
 
-  Shader shader = GenerateShader();
+  // Generate geometry and shader. Note that we should check AddOn when generate geometry, due to LoadPolicy::IMMEDIATE case
+  Geometry geometry = GenerateGeometry(mTextureId, true);
+  Shader   shader   = GenerateShader();
 
   // Create the renderer
   mImpl->mRenderer = DecoratedVisualRenderer::New(geometry, shader);
@@ -777,7 +759,7 @@ void ImageVisual::InitializeRenderer()
     ComputeTextureSize();
     CheckMaskTexture();
 
-    bool needToUpdateShader = DevelTexture::IsNative(mTextures.GetTexture(0));
+    bool needToUpdateShader = DevelTexture::IsNative(mTextures.GetTexture(0)) || mUseBrokenImageRenderer;
 
     if(mTextures.GetTextureCount() == 3)
     {
@@ -793,6 +775,21 @@ void ImageVisual::InitializeRenderer()
       UpdateShader();
     }
     mTextures.Reset(); // Visual should not keep a handle to the texture after this point.
+
+    if(DALI_UNLIKELY(mUseBrokenImageRenderer))
+    {
+      // We need to re-generate geometry only if it was broken image before, and result changed after Reload.
+      auto geometry = GenerateGeometry(mTextureId, true);
+
+      // Update geometry only if we need.
+      if(geometry)
+      {
+        mImpl->mRenderer.SetGeometry(geometry);
+      }
+    }
+
+    // We don't use broken image anymore.
+    mUseBrokenImageRenderer = false;
   }
 
   if(attemptAtlasing) // the texture is packed inside atlas
@@ -1042,6 +1039,7 @@ void ImageVisual::FastLoadComplete(FastTrackLoadingTaskPtr task)
 void ImageVisual::LoadComplete(bool loadingSuccess, TextureInformation textureInformation)
 {
   Toolkit::Visual::ResourceStatus resourceStatus;
+
   if(mImpl->mRenderer)
   {
     EnablePreMultipliedAlpha(textureInformation.preMultiplied);
@@ -1062,15 +1060,22 @@ void ImageVisual::LoadComplete(bool loadingSuccess, TextureInformation textureIn
       ComputeTextureSize();
       CheckMaskTexture();
 
+      bool needToUpdateShader = mUseBrokenImageRenderer;
+
       if(textureInformation.textureSet.GetTextureCount() == 3)
       {
         if(textureInformation.textureSet.GetTexture(0).GetPixelFormat() == Pixel::L8 && textureInformation.textureSet.GetTexture(1).GetPixelFormat() == Pixel::CHROMINANCE_U && textureInformation.textureSet.GetTexture(2).GetPixelFormat() == Pixel::CHROMINANCE_V)
         {
-          mNeedYuvToRgb = true;
-          UpdateShader();
+          mNeedYuvToRgb      = true;
+          needToUpdateShader = true;
         }
       }
 
+      if(needToUpdateShader)
+      {
+        UpdateShader();
+      }
+
       if(actor)
       {
         actor.AddRenderer(mImpl->mRenderer);
@@ -1078,6 +1083,19 @@ void ImageVisual::LoadComplete(bool loadingSuccess, TextureInformation textureIn
         // reset the weak handle so that the renderer only get added to actor once
         mPlacementActor.Reset();
       }
+
+      auto geometry = GenerateGeometry(textureInformation.textureId, mUseBrokenImageRenderer);
+
+      if(DALI_UNLIKELY(geometry))
+      {
+        // Rare cases. If load successed image don't use quad geometry (i.e. Show some n-patch broken image, and call Reload(), and success)
+        // or If given texture use AddOn,
+        // then we need to make to use quad geometry and update shader agian.
+        mImpl->mRenderer.SetGeometry(geometry);
+      }
+
+      // We don't use broken image anymore.
+      mUseBrokenImageRenderer = false;
     }
   }
 
@@ -1099,36 +1117,6 @@ void ImageVisual::LoadComplete(bool loadingSuccess, TextureInformation textureIn
     mLoadState     = TextureManager::LoadState::LOAD_FAILED;
   }
 
-  // use geometry if needed
-  if(loadingSuccess)
-  {
-    uint32_t firstElementCount{0u};
-    uint32_t secondElementCount{0u};
-    auto     geometry = mFactoryCache.GetTextureManager().GetRenderGeometry(mTextureId, firstElementCount, secondElementCount);
-    if(mImpl->mRenderer && geometry)
-    {
-      mImpl->mRenderer.SetGeometry(geometry);
-      Dali::DevelRenderer::DrawCommand drawCommand{};
-      drawCommand.drawType = DevelRenderer::DrawType::INDEXED;
-
-      if(firstElementCount)
-      {
-        drawCommand.firstIndex   = 0;
-        drawCommand.elementCount = firstElementCount;
-        drawCommand.queue        = DevelRenderer::RENDER_QUEUE_OPAQUE;
-        DevelRenderer::AddDrawCommand(mImpl->mRenderer, drawCommand);
-      }
-
-      if(secondElementCount)
-      {
-        drawCommand.firstIndex   = firstElementCount;
-        drawCommand.elementCount = secondElementCount;
-        drawCommand.queue        = DevelRenderer::RENDER_QUEUE_TRANSPARENT;
-        DevelRenderer::AddDrawCommand(mImpl->mRenderer, drawCommand);
-      }
-    }
-  }
-
   // Signal to observers ( control ) that resources are ready. Must be all resources.
   ResourceReady(resourceStatus);
 }
@@ -1369,6 +1357,7 @@ void ImageVisual::ShowBrokenImage()
       }
     }
 
+    mUseBrokenImageRenderer = true;
     mFactoryCache.UpdateBrokenImageRenderer(mImpl->mRenderer, imageSize);
     if(actor)
     {
@@ -1401,6 +1390,59 @@ void ImageVisual::ResetFastTrackLoadingTask()
   }
 }
 
+Geometry ImageVisual::GenerateGeometry(TextureManager::TextureId textureId, bool createForce)
+{
+  Geometry geometry;
+  if(Stage::IsInstalled())
+  {
+    if(mImpl->mCustomShader)
+    {
+      if(createForce)
+      {
+        geometry = CreateGeometry(mFactoryCache, mImpl->mCustomShader->mGridSize);
+      }
+    }
+    else
+    {
+      uint32_t firstElementCount{0u};
+      uint32_t secondElementCount{0u};
+
+      geometry = mFactoryCache.GetTextureManager().GetRenderGeometry(textureId, firstElementCount, secondElementCount);
+      if(geometry)
+      {
+        if(mImpl->mRenderer)
+        {
+          Dali::DevelRenderer::DrawCommand drawCommand{};
+          drawCommand.drawType = DevelRenderer::DrawType::INDEXED;
+
+          if(firstElementCount)
+          {
+            drawCommand.firstIndex   = 0;
+            drawCommand.elementCount = firstElementCount;
+            drawCommand.queue        = DevelRenderer::RENDER_QUEUE_OPAQUE;
+            DevelRenderer::AddDrawCommand(mImpl->mRenderer, drawCommand);
+          }
+
+          if(secondElementCount)
+          {
+            drawCommand.firstIndex   = firstElementCount;
+            drawCommand.elementCount = secondElementCount;
+            drawCommand.queue        = DevelRenderer::RENDER_QUEUE_TRANSPARENT;
+            DevelRenderer::AddDrawCommand(mImpl->mRenderer, drawCommand);
+          }
+        }
+      }
+      else if(createForce)
+      {
+        // Create default quad geometry now
+        geometry = CreateGeometry(mFactoryCache, ImageDimensions(1, 1));
+      }
+    }
+  }
+
+  return geometry;
+}
+
 } // namespace Internal
 
 } // namespace Toolkit
index 0770f034715fb725be908423b32e0a4b13bb5670..60a67f17b59ed9306e2cea719e614fecdfbabbd9 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_INTERNAL_IMAGE_VISUAL_H
 
 /*
- * 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.
@@ -352,6 +352,15 @@ private:
    */
   void ResetFastTrackLoadingTask();
 
+  /**
+   * @brief Update geometry information and get the generated result.
+   *
+   * @param[in] textureId Id of texture. It will be used when we use AddOn.
+   * @param[in] createForce True if we need to create geometry forcely. False if we don't re-generate geometry.
+   * @return Generated geometry, or empty handle if we don't need to update geometry.
+   */
+  Geometry GenerateGeometry(TextureManager::TextureId textureId, bool createForce);
+
 private:
   Vector4                            mPixelArea;
   Property::Index                    mPixelAreaIndex;
@@ -377,14 +386,15 @@ private:
   Dali::Toolkit::ImageVisual::ReleasePolicy::Type mReleasePolicy;
   Vector4                                         mAtlasRect;
   Dali::ImageDimensions                           mAtlasRectSize;
-  TextureManager::LoadState                       mLoadState;                    ///< The texture loading state
-  bool                                            mAttemptAtlasing;              ///< If true will attempt atlasing, otherwise create unique texture
-  bool                                            mOrientationCorrection;        ///< true if the image will have it's orientation corrected.
-  bool                                            mNeedYuvToRgb{false};          ///< true if we need to convert yuv to rgb.
-  bool                                            mNeedUnifiedYuvAndRgb{false};  ///< true if we need to support both yuv and rgb.
-  bool                                            mEnableBrokenImage{true};      ///< true if enable broken image.
-  bool                                            mUseFastTrackUploading{false}; ///< True if we use fast tack feature.
-  bool                                            mRendererAdded{false};         ///< True if renderer added into actor.
+  TextureManager::LoadState                       mLoadState;                     ///< The texture loading state
+  bool                                            mAttemptAtlasing;               ///< If true will attempt atlasing, otherwise create unique texture
+  bool                                            mOrientationCorrection;         ///< true if the image will have it's orientation corrected.
+  bool                                            mNeedYuvToRgb{false};           ///< true if we need to convert yuv to rgb.
+  bool                                            mNeedUnifiedYuvAndRgb{false};   ///< true if we need to support both yuv and rgb.
+  bool                                            mEnableBrokenImage{true};       ///< true if enable broken image.
+  bool                                            mUseFastTrackUploading{false};  ///< True if we use fast tack feature.
+  bool                                            mRendererAdded{false};          ///< True if renderer added into actor.
+  bool                                            mUseBrokenImageRenderer{false}; ///< True if renderer changed as broken image.
 };
 
 } // namespace Internal