From bcd234fc96035a85ab1a116c4ef8dc68ce3885cf Mon Sep 17 00:00:00 2001 From: "Eunki, Hong" Date: Wed, 24 Jan 2024 16:30:56 +0900 Subject: [PATCH] Make we use valid geometry if broken image was n-patch and reloaded 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 --- automated-tests/resources/overwritable-image.jpg | 1 + .../src/dali-toolkit/utc-Dali-ImageView.cpp | 153 +++++++++++++++++++++ .../internal/visuals/image/image-visual.cpp | 152 ++++++++++++-------- dali-toolkit/internal/visuals/image/image-visual.h | 28 ++-- 4 files changed, 270 insertions(+), 64 deletions(-) create mode 100644 automated-tests/resources/overwritable-image.jpg diff --git a/automated-tests/resources/overwritable-image.jpg b/automated-tests/resources/overwritable-image.jpg new file mode 100644 index 0000000..9977a28 --- /dev/null +++ b/automated-tests/resources/overwritable-image.jpg @@ -0,0 +1 @@ +invalid diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp index bfe5db2..60d1551 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp @@ -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 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 diff --git a/dali-toolkit/internal/visuals/image/image-visual.cpp b/dali-toolkit/internal/visuals/image/image-visual.cpp index 1d045dc..980c63a 100644 --- a/dali-toolkit/internal/visuals/image/image-visual.cpp +++ b/dali-toolkit/internal/visuals/image/image-visual.cpp @@ -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 diff --git a/dali-toolkit/internal/visuals/image/image-visual.h b/dali-toolkit/internal/visuals/image/image-visual.h index 0770f03..60a67f1 100644 --- a/dali-toolkit/internal/visuals/image/image-visual.h +++ b/dali-toolkit/internal/visuals/image/image-visual.h @@ -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 -- 2.7.4