Make broken alpha mask image show image 68/273368/7
authorEunki, Hong <eunkiki.hong@samsung.com>
Tue, 5 Apr 2022 03:32:45 +0000 (12:32 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Wed, 13 Apr 2022 03:46:10 +0000 (12:46 +0900)
Previous code only assume that ALPHA_MASK_URL load success.
When we use alpha mask image invalid, the result become strange ;
Most of image view not showing anything, but somtimes 1~2 image show broken.

This patch make when we use that maskTextureId is load failed case.

Change-Id: Ifd157d8b3a74c3a813d13ed3072c945d8ae8cda8
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali-toolkit-internal/utc-Dali-TextureManager.cpp
automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp
dali-toolkit/internal/texture-manager/texture-manager-impl.cpp

index 6ec8617..e8088a3 100644 (file)
@@ -591,6 +591,158 @@ int UtcTextureManagerUseInvalidMask(void)
   END_TEST;
 }
 
+int UtcTextureManagerUseInvalidMaskAndMaskLoadedFirst(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("UtcTextureManagerUseInvalidMask when normal image loaded first, and mask image loaded first");
+  tet_infoline("Try to check PostLoad works well");
+
+  TextureManager textureManager; // Create new texture manager
+
+  TestObserver                       observer;
+  std::string                        filename(TEST_IMAGE_FILE_NAME);
+  std::string                        maskname("invalid.png");
+  TextureManager::MaskingDataPointer maskInfo = nullptr;
+  maskInfo.reset(new TextureManager::MaskingData());
+  maskInfo->mAlphaMaskUrl       = maskname;
+  maskInfo->mAlphaMaskId        = TextureManager::INVALID_TEXTURE_ID;
+  maskInfo->mCropToMask         = true;
+  maskInfo->mContentScaleFactor = 1.0f;
+
+  auto                          textureId(TextureManager::INVALID_TEXTURE_ID);
+  Vector4                       atlasRect(0.f, 0.f, 1.f, 1.f);
+  Dali::ImageDimensions         atlasRectSize(0, 0);
+  bool                          synchronousLoading(false);
+  bool                          atlasingStatus(false);
+  bool                          loadingStatus(false);
+  auto                          preMultiply         = TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
+  ImageAtlasManagerPtr          atlasManager        = nullptr;
+  Toolkit::AtlasUploadObserver* atlasUploadObserver = nullptr;
+
+  textureManager.LoadTexture(
+    filename,
+    ImageDimensions(),
+    FittingMode::SCALE_TO_FILL,
+    SamplingMode::BOX_THEN_LINEAR,
+    maskInfo,
+    synchronousLoading,
+    textureId,
+    atlasRect,
+    atlasRectSize,
+    atlasingStatus,
+    loadingStatus,
+    WrapMode::DEFAULT,
+    WrapMode::DEFAULT,
+    &observer,
+    atlasUploadObserver,
+    atlasManager,
+    true,
+    TextureManager::ReloadPolicy::CACHED,
+    preMultiply);
+
+  DALI_TEST_EQUALS(observer.mLoaded, false, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer.mObserverCalled, false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(observer.mLoaded, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer.mObserverCalled, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer.mCompleteType, TestObserver::CompleteType::UPLOAD_COMPLETE, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcTextureManagerUseInvalidMaskAndMaskLoadedLater(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("UtcTextureManagerUseInvalidMask when normal image loaded first, and mask image loaded later");
+  tet_infoline("Try to check CheckForWaitingTexture called");
+
+  TextureManager textureManager; // Create new texture manager
+
+  TestObserver                       observer;
+  std::string                        filename(TEST_IMAGE_FILE_NAME);
+  std::string                        maskname("invalid.png");
+  TextureManager::MaskingDataPointer maskInfo = nullptr;
+  maskInfo.reset(new TextureManager::MaskingData());
+  maskInfo->mAlphaMaskUrl       = maskname;
+  maskInfo->mAlphaMaskId        = TextureManager::INVALID_TEXTURE_ID;
+  maskInfo->mCropToMask         = true;
+  maskInfo->mContentScaleFactor = 1.0f;
+
+  auto                          textureId(TextureManager::INVALID_TEXTURE_ID);
+  Vector4                       atlasRect(0.f, 0.f, 1.f, 1.f);
+  Dali::ImageDimensions         atlasRectSize(0, 0);
+  bool                          synchronousLoading(false);
+  bool                          atlasingStatus(false);
+  bool                          loadingStatus(false);
+  auto                          preMultiply         = TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
+  ImageAtlasManagerPtr          atlasManager        = nullptr;
+  Toolkit::AtlasUploadObserver* atlasUploadObserver = nullptr;
+
+  textureManager.LoadTexture(
+    filename,
+    ImageDimensions(),
+    FittingMode::SCALE_TO_FILL,
+    SamplingMode::BOX_THEN_LINEAR,
+    maskInfo,
+    synchronousLoading,
+    textureId,
+    atlasRect,
+    atlasRectSize,
+    atlasingStatus,
+    loadingStatus,
+    WrapMode::DEFAULT,
+    WrapMode::DEFAULT,
+    &observer,
+    atlasUploadObserver,
+    atlasManager,
+    true,
+    TextureManager::ReloadPolicy::CACHED,
+    preMultiply);
+
+  DALI_TEST_EQUALS(observer.mLoaded, false, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer.mObserverCalled, false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  // CAPTION : HARD-CODING for coverage. If you are a good boy, Do not follow this code.
+  {
+    Dali::Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(
+      filename,
+      ImageDimensions(),
+      FittingMode::SCALE_TO_FILL,
+      SamplingMode::BOX_THEN_LINEAR,
+      true, ///< synchronousLoading
+      nullptr,
+      true, ///< orientationCorrection
+      preMultiply);
+
+    textureManager.AsyncLoadComplete(textureId, pixelBuffer);
+    textureManager.AsyncLoadComplete(maskInfo->mAlphaMaskId, Dali::Devel::PixelBuffer());
+    textureManager.Remove(maskInfo->mAlphaMaskId, nullptr);
+    textureManager.Remove(textureId, &observer);
+  }
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(observer.mLoaded, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer.mObserverCalled, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer.mCompleteType, TestObserver::CompleteType::UPLOAD_COMPLETE, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
+
+  END_TEST;
+}
+
 int UtcTextureManagerSynchronousLoadingFail(void)
 {
   ToolkitTestApplication application;
index 823c2c2..ddd45ba 100644 (file)
@@ -3128,6 +3128,12 @@ void OnResourceReadySignal03(Control control)
   gResourceReadySignalCounter++;
 }
 
+void OnSimpleResourceReadySignal(Control control)
+{
+  // simply increate counter
+  gResourceReadySignalCounter++;
+}
+
 } // namespace
 
 int UtcDaliImageViewSetImageOnResourceReadySignal01(void)
@@ -3228,3 +3234,105 @@ int UtcDaliImageViewSetImageOnResourceReadySignal03(void)
 
   END_TEST;
 }
+
+int UtcDaliImageViewOnResourceReadySignalWithBrokenAlphaMask01(void)
+{
+  tet_infoline("Test signal handler when image / mask image is broken.");
+
+  ToolkitTestApplication application;
+
+  auto TestResourceReadyUrl = [&application](int eventTriggerCount, bool isSynchronous, const std::string& url, const std::string& mask, const char* location) {
+    gResourceReadySignalCounter = 0;
+
+    Property::Map map;
+    map[Toolkit::ImageVisual::Property::URL] = url;
+    if(!mask.empty())
+    {
+      map[Toolkit::ImageVisual::Property::ALPHA_MASK_URL] = mask;
+    }
+    map[Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING] = isSynchronous;
+
+    ImageView imageView                            = ImageView::New();
+    imageView[Toolkit::ImageView::Property::IMAGE] = map;
+    imageView[Actor::Property::SIZE]               = Vector2(100.0f, 200.0f);
+    imageView.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal);
+
+    application.GetScene().Add(imageView);
+    application.SendNotification();
+    application.Render();
+
+    if(!isSynchronous)
+    {
+      // Wait for loading
+      DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(eventTriggerCount), true, location);
+    }
+    tet_printf("test %s [sync:%d] signal fired\n", url.c_str(), isSynchronous ? 1 : 0);
+    DALI_TEST_EQUALS(gResourceReadySignalCounter, 1, location);
+
+    imageView.Unparent();
+  };
+
+  for(int synchronous = 0; synchronous <= 1; synchronous++)
+  {
+    tet_printf("Test normal case (sync:%d)\n", synchronous);
+    TestResourceReadyUrl(1, synchronous, gImage_600_RGB, "", TEST_LOCATION);
+    TestResourceReadyUrl(3, synchronous, gImage_600_RGB, gImage_34_RGBA, TEST_LOCATION); // 3 event trigger required : 2 image load + 1 apply mask
+
+    tet_printf("Test broken image case (sync:%d)\n", synchronous);
+    TestResourceReadyUrl(1, synchronous, "invalid.jpg", "", TEST_LOCATION);
+    TestResourceReadyUrl(2, synchronous, "invalid.jpg", gImage_34_RGBA, TEST_LOCATION);
+
+    tet_printf("Test broken mask image case (sync:%d)\n", synchronous);
+    TestResourceReadyUrl(2, synchronous, gImage_600_RGB, "invalid.png", TEST_LOCATION);
+
+    tet_printf("Test broken both image, mask image case (sync:%d)\n", synchronous);
+    TestResourceReadyUrl(2, synchronous, "invalid.jpg", "invalid.png", TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliImageViewOnResourceReadySignalWithBrokenAlphaMask02(void)
+{
+  tet_infoline("Test signal handler when image try to use cached-and-broken mask image.");
+
+  ToolkitTestApplication application;
+
+  gResourceReadySignalCounter = 0;
+
+  auto TestBrokenMaskResourceReadyUrl = [&application](const std::string& url, const char* location) {
+    Property::Map map;
+    map[Toolkit::ImageVisual::Property::URL] = url;
+    // Use invalid mask url
+    map[Toolkit::ImageVisual::Property::ALPHA_MASK_URL] = "invalid.png";
+
+    ImageView imageView                            = ImageView::New();
+    imageView[Toolkit::ImageView::Property::IMAGE] = map;
+    imageView[Actor::Property::SIZE]               = Vector2(100.0f, 200.0f);
+    imageView.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal);
+
+    application.GetScene().Add(imageView);
+    application.SendNotification();
+    application.Render();
+
+    // Don't unparent imageView, for keep the cache.
+  };
+
+  // Use more than 4 images (The number of LocalImageLoadThread)
+  const std::vector<std::string> testUrlList = {gImage_34_RGBA, gImage_600_RGB, "invalid.jpg" /* invalid url */, TEST_IMAGE_1, TEST_IMAGE_2, TEST_BROKEN_IMAGE_DEFAULT};
+
+  int expectResourceReadySignalCounter = 0;
+
+  for(auto& url : testUrlList)
+  {
+    TestBrokenMaskResourceReadyUrl(url, TEST_LOCATION);
+    expectResourceReadySignalCounter++;
+  }
+
+  // Remain 1 signal due to we use #URL + 1 mask image.
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(expectResourceReadySignalCounter + 1), true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(gResourceReadySignalCounter, expectResourceReadySignalCounter, TEST_LOCATION);
+
+  END_TEST;
+}
\ No newline at end of file
index 2890e6a..ff81b22 100644 (file)
@@ -629,6 +629,10 @@ TextureManager::TextureId TextureManager::RequestLoadInternal(
             {
               pixelBuffer.ApplyMask(maskPixelBuffer, contentScale, cropToMask);
             }
+            else
+            {
+              DALI_LOG_ERROR("Mask image cached invalid pixel buffer!\n");
+            }
           }
           else
           {
@@ -883,6 +887,13 @@ void TextureManager::PostLoad(TextureManager::TextureInfo& textureInfo, Devel::P
             // Send New Task to Thread
             ApplyMask(textureInfo, textureInfo.maskTextureId);
           }
+          else // maskLoadState == LoadState::LOAD_FAILED
+          {
+            // Url texture load success, But alpha mask texture load failed. Run as normal image upload.
+            DALI_LOG_ERROR("Alpha mask image loading failed! Image will not be masked\n");
+            UploadTexture(pixelBuffer, textureInfo);
+            NotifyObservers(textureInfo, true);
+          }
         }
       }
       else
@@ -911,8 +922,16 @@ void TextureManager::PostLoad(TextureManager::TextureInfo& textureInfo, Devel::P
   else
   {
     textureInfo.loadState = LoadState::LOAD_FAILED;
-    CheckForWaitingTexture(textureInfo);
-    NotifyObservers(textureInfo, false);
+    if(textureInfo.storageType != StorageType::KEEP_PIXEL_BUFFER)
+    {
+      NotifyObservers(textureInfo, false);
+    }
+    else // if(textureInfo.storageType == StorageType::KEEP_PIXEL_BUFFER) // image mask case
+    {
+      // Check if there was another texture waiting for this load to complete
+      // (e.g. if this was an image mask, and its load is on a different thread)
+      CheckForWaitingTexture(textureInfo);
+    }
   }
 }
 
@@ -922,6 +941,8 @@ void TextureManager::CheckForWaitingTexture(TextureManager::TextureInfo& maskTex
   // maskTextureId:
   const TextureCacheIndex size = static_cast<TextureCacheIndex>(mTextureCacheManager.size());
 
+  const bool maskLoadSuccess = maskTextureInfo.loadState == LoadState::LOAD_FINISHED ? true : false;
+
   for(TextureCacheIndex cacheIndex = 0; cacheIndex < size; ++cacheIndex)
   {
     if(mTextureCacheManager[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
@@ -929,16 +950,17 @@ void TextureManager::CheckForWaitingTexture(TextureManager::TextureInfo& maskTex
     {
       TextureInfo& textureInfo(mTextureCacheManager[cacheIndex]);
 
-      if(maskTextureInfo.loadState == LoadState::LOAD_FINISHED)
+      if(maskLoadSuccess)
       {
         // Send New Task to Thread
         ApplyMask(textureInfo, maskTextureInfo.textureId);
       }
       else
       {
-        textureInfo.pixelBuffer.Reset();
-        textureInfo.loadState = LoadState::LOAD_FAILED;
-        NotifyObservers(textureInfo, false);
+        // Url texture load success, But alpha mask texture load failed. Run as normal image upload.
+        DALI_LOG_ERROR("Alpha mask image loading failed! Image will not be masked\n");
+        UploadTexture(textureInfo.pixelBuffer, textureInfo);
+        NotifyObservers(textureInfo, true);
       }
     }
   }