Apply placeholder image & transition effect 79/289979/32
authorsunghyun kim <scholb.kim@samsung.com>
Thu, 16 Mar 2023 11:25:48 +0000 (20:25 +0900)
committersunghyun kim <scholb.kim@samsung.com>
Wed, 26 Apr 2023 07:50:56 +0000 (16:50 +0900)
Add the following features to the image view
1. If necessary for image loading, the placeholder is shown until finish loading
2. provide transition effect if necessary for image replacement

Change-Id: I0191cb3ed047efb9cdd4f0694e9fb6e065f045f2

automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp
dali-toolkit/devel-api/controls/control-devel.cpp
dali-toolkit/devel-api/controls/control-devel.h
dali-toolkit/internal/controls/control/control-data-impl.cpp
dali-toolkit/internal/controls/control/control-data-impl.h
dali-toolkit/internal/controls/image-view/image-view-impl.cpp
dali-toolkit/internal/controls/image-view/image-view-impl.h
dali-toolkit/internal/visuals/visual-base-data-impl.h
dali-toolkit/public-api/controls/image-view/image-view.h

index 3ca8543..1487961 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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.
@@ -4368,4 +4368,312 @@ int UtcDaliImageViewNpatchImageCacheTest02(void)
   DALI_TEST_EQUALS(textureCallStack.CountMethod("GenTextures"), 0, TEST_LOCATION);
 
   END_TEST;
-}
\ No newline at end of file
+}
+
+int UtcDaliImageViewPlaceholderImage(void)
+{
+  tet_infoline("Test imageView use placeholder image");
+
+  ToolkitTestApplication application;
+  Property::Map          map;
+  map[Toolkit::ImageVisual::Property::URL] = gImage_600_RGB;
+
+  ImageView imageView = ImageView::New();
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  imageView.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 200.0f));
+  application.GetScene().Add(imageView);
+
+  Property::Value value = imageView.GetProperty(ImageView::Property::PLACEHOLDER_IMAGE);
+  std::string     url;
+  DALI_TEST_CHECK(value.Get(url));
+  DALI_TEST_CHECK(url.empty());
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+
+  application.SendNotification();
+  application.Render();
+
+  value = imageView.GetProperty(ImageView::Property::PLACEHOLDER_IMAGE);
+  DALI_TEST_CHECK(value.Get(url));
+  DALI_TEST_CHECK(url == gImage_34_RGBA);
+
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  // Replace Image test
+  map[Toolkit::ImageVisual::Property::URL]    = TEST_IMAGE_1;
+  map[ImageView::Property::PLACEHOLDER_IMAGE] = gImage_34_RGBA;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = "";
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = TEST_IMAGE_2;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  // Replace Image test2
+  map[Toolkit::ImageVisual::Property::URL] = TEST_IMAGE_1;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = TEST_IMAGE_2;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = gImage_600_RGB;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render(900);
+
+  map[Toolkit::ImageVisual::Property::URL] = "";
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = TEST_IMAGE_1;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  END_TEST;
+}
+
+int UtcDaliImageViewTransitionEffect01(void)
+{
+  tet_infoline("Test imageView use transition effect");
+
+  ToolkitTestApplication application;
+  Property::Map          map;
+  map[Toolkit::ImageVisual::Property::URL] = gImage_600_RGB;
+  map[Toolkit::Visual::Property::OPACITY]  = 0.9f;
+
+  ImageView imageView = ImageView::New();
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  imageView.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 200.0f));
+  application.GetScene().Add(imageView);
+
+  Property::Value value = imageView.GetProperty(ImageView::Property::ENABLE_TRANSITION_EFFECT);
+  bool            transition;
+  DALI_TEST_CHECK(value.Get(transition));
+  DALI_TEST_CHECK(transition == false);
+  imageView.SetProperty(Toolkit::ImageView::Property::ENABLE_TRANSITION_EFFECT, true);
+
+  application.SendNotification();
+  application.Render();
+
+  value = imageView.GetProperty(ImageView::Property::ENABLE_TRANSITION_EFFECT);
+  DALI_TEST_CHECK(value.Get(transition));
+  DALI_TEST_CHECK(transition == true);
+
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  // Test transition effect with placeholder
+  map[Toolkit::ImageVisual::Property::URL] = TEST_IMAGE_1;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = "";
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = gImage_600_RGB;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = TEST_IMAGE_1;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  application.SendNotification();
+
+  map[Toolkit::ImageVisual::Property::URL] = "";
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = gImage_600_RGB;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, "");
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = gImage_600_RGB;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = "";
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  // Test transition effect without placeholder
+  map[Toolkit::ImageVisual::Property::URL] = TEST_IMAGE_1;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = gImage_600_RGB;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = "";
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  map[Toolkit::ImageVisual::Property::URL] = gImage_600_RGB;
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  imageView.SetImage(TEST_IMAGE_1);
+  application.SendNotification();
+  application.Render();
+
+  imageView.SetImage(gImage_600_RGB);
+  application.SendNotification();
+  application.Render(9000);
+
+  imageView.SetImage("");
+  application.SendNotification();
+  application.Render();
+
+  imageView.SetImage(TEST_IMAGE_1);
+  application.SendNotification();
+  application.Render();
+
+  // Clear all cached
+  imageView.Unparent();
+  imageView.Reset();
+
+  END_TEST;
+}
+
+int UtcDaliImageViewTransitionEffect02(void)
+{
+  tet_infoline("Test imageView use transition effect with replace image");
+
+  ToolkitTestApplication application;
+
+  Property::Map map;
+
+  ImageView imageView = ImageView::New();
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  imageView.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 200.0f));
+  application.GetScene().Add(imageView);
+
+  Property::Value value;
+  value = imageView.GetProperty(ImageView::Property::ENABLE_TRANSITION_EFFECT);
+  bool transition;
+  DALI_TEST_CHECK(value.Get(transition));
+  DALI_TEST_CHECK(transition == false);
+  imageView.SetProperty(Toolkit::ImageView::Property::ENABLE_TRANSITION_EFFECT, true);
+
+  value = imageView.GetProperty(ImageView::Property::PLACEHOLDER_IMAGE);
+  std::string url;
+  DALI_TEST_CHECK(value.Get(url));
+  DALI_TEST_CHECK(url.empty());
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  application.SendNotification();
+  application.Render();
+
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, "");
+  application.SendNotification();
+  application.Render();
+
+  imageView.SetProperty(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  application.SendNotification();
+  application.Render();
+
+  value = imageView.GetProperty(ImageView::Property::ENABLE_TRANSITION_EFFECT);
+  DALI_TEST_CHECK(value.Get(transition));
+  DALI_TEST_CHECK(transition == true);
+
+  value = imageView.GetProperty(ImageView::Property::PLACEHOLDER_IMAGE);
+  DALI_TEST_CHECK(value.Get(url));
+  DALI_TEST_CHECK(url == gImage_34_RGBA);
+
+  imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+  application.SendNotification();
+  application.Render();
+
+  // Clear all cached
+  imageView.Unparent();
+  imageView.Reset();
+
+  END_TEST;
+}
+
+int UtcDaliImageViewTransitionEffect03(void)
+{
+  tet_infoline("Test imageView use transition effect with placeholder");
+
+  ToolkitTestApplication application;
+  Property::Map          map;
+
+  ImageView imageView = ImageView::New();
+  imageView.SetImage("");
+  imageView.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 200.0f));
+  imageView.SetProperty(ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  imageView.SetProperty(ImageView::Property::ENABLE_TRANSITION_EFFECT, true);
+  application.GetScene().Add(imageView);
+
+  //DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render(16);
+
+  tet_infoline("(1)");
+  imageView.SetImage(gImage_600_RGB);
+  imageView.SetProperty(ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render(16);
+
+  Property::Value value;
+  value = imageView.GetProperty(ImageView::Property::ENABLE_TRANSITION_EFFECT);
+  bool transition;
+  DALI_TEST_CHECK(value.Get(transition));
+  DALI_TEST_CHECK(transition == true);
+
+  value = imageView.GetProperty(ImageView::Property::PLACEHOLDER_IMAGE);
+  std::string url;
+  DALI_TEST_CHECK(value.Get(url));
+  DALI_TEST_CHECK(url == gImage_34_RGBA);
+
+  imageView.SetImage("");
+  application.SendNotification();
+  application.Render(16);
+
+  imageView.SetImage(TEST_IMAGE_1);
+  imageView.SetProperty(ImageView::Property::PLACEHOLDER_IMAGE, gImage_34_RGBA);
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render(16);
+
+  // Clear all cached
+  imageView.Unparent();
+  imageView.Reset();
+
+  END_TEST;
+}
index ef1265b..59b0e80 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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.
index fe95c70..906e74b 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_CONTROL_DEVEL_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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.
index 76de885..308ce4d 100644 (file)
@@ -129,6 +129,22 @@ bool FindVisual(std::string visualName, const RegisteredVisualContainer& visuals
   return false;
 }
 
+/**
+ *  Finds visual in given array, returning true if found along with the iterator for that visual as a out parameter
+ */
+bool FindVisual(const Toolkit::Visual::Base findVisual , const RegisteredVisualContainer& visuals, RegisteredVisualContainer::Iterator& iter)
+{
+  for(iter = visuals.Begin(); iter != visuals.End(); iter++)
+  {
+    Toolkit::Visual::Base visual = (*iter)->visual;
+    if(visual && visual == findVisual)
+    {
+      return true;
+    }
+  }
+  return false;
+}
+
 void FindChangableVisuals(Dictionary<Property::Map>& stateVisualsToAdd,
                           Dictionary<Property::Map>& stateVisualsToChange,
                           DictionaryKeys&            stateVisualsToRemove)
@@ -894,6 +910,23 @@ bool Control::Impl::IsVisualEnabled(Property::Index index) const
   return false;
 }
 
+void Control::Impl::EnableReadyTransitionOverriden(Toolkit::Visual::Base& visual, bool enable)
+{
+  DALI_LOG_INFO(gLogFilter, Debug::General, "Control::EnableReadyTransitionOverriden(%p, %s)\n", visual, enable ? "T" : "F");
+
+  RegisteredVisualContainer::Iterator iter;
+  if(FindVisual(visual, mVisuals, iter))
+  {
+    if((*iter)->overideReadyTransition == enable)
+    {
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Control::EnableReadyTransitionOverriden Visual %s(%p) already %s\n", (*iter)->visual.GetName().c_str(), visual, enable ? "enabled" : "disabled");
+      return;
+    }
+
+    (*iter)->overideReadyTransition  = enable;
+  }
+}
+
 void Control::Impl::StopObservingVisual(Toolkit::Visual::Base& visual)
 {
   Internal::Visual::Base& visualImpl = Toolkit::GetImplementation(visual);
@@ -950,7 +983,10 @@ void Control::Impl::ResourceReady(Visual::Base& object)
       if(FindVisual((*registeredIter)->index, mRemoveVisuals, visualToRemoveIter))
       {
         (*registeredIter)->pending = false;
-        Toolkit::GetImplementation((*visualToRemoveIter)->visual).SetOffScene(self);
+        if(!((*visualToRemoveIter)->overideReadyTransition))
+        {
+          Toolkit::GetImplementation((*visualToRemoveIter)->visual).SetOffScene(self);
+        }
         mRemoveVisuals.Erase(visualToRemoveIter);
       }
       break;
index 421d69d..b84694d 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_CONTROL_DATA_IMPL_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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.
@@ -54,12 +54,14 @@ struct RegisteredVisual
   Toolkit::Visual::Base visual;
   bool                  enabled : 1;
   bool                  pending : 1;
+  bool                  overideReadyTransition : 1;
 
   RegisteredVisual(Property::Index aIndex, Toolkit::Visual::Base& aVisual, bool aEnabled, bool aPendingReplacement)
   : index(aIndex),
     visual(aVisual),
     enabled(aEnabled),
-    pending(aPendingReplacement)
+    pending(aPendingReplacement),
+    overideReadyTransition(false)
   {
   }
 };
@@ -193,6 +195,15 @@ public:
   bool IsVisualEnabled(Property::Index index) const;
 
   /**
+   * @brief Sets the given visual to be ready transition
+   *
+   * @param[in] control The control
+   * @param[in] visual The visual to ready transition overriden
+   * @param[in] enable flag to set enabled or disabled.
+   */
+  void EnableReadyTransitionOverriden(Toolkit::Visual::Base& visual, bool enable);
+
+  /**
    * @brief Stops observing the given visual.
    * @param[in] visual The visual to stop observing
    */
index 7fcc702..a8332dd 100644 (file)
@@ -45,6 +45,9 @@ namespace
 {
 const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
 
+constexpr float FULL_OPACITY = 1.0f;
+constexpr float LOW_OPACITY  = 0.2f;
+
 BaseHandle Create()
 {
   return Toolkit::ImageView::New();
@@ -54,7 +57,8 @@ BaseHandle Create()
 DALI_TYPE_REGISTRATION_BEGIN(Toolkit::ImageView, Toolkit::Control, Create);
 DALI_PROPERTY_REGISTRATION(Toolkit, ImageView, "image", MAP, IMAGE)
 DALI_PROPERTY_REGISTRATION(Toolkit, ImageView, "preMultipliedAlpha", BOOLEAN, PRE_MULTIPLIED_ALPHA)
-
+DALI_PROPERTY_REGISTRATION(Toolkit, ImageView, "placeholderImage", STRING, PLACEHOLDER_IMAGE)
+DALI_PROPERTY_REGISTRATION(Toolkit, ImageView, "enableTransitionEffect", BOOLEAN, ENABLE_TRANSITION_EFFECT)
 DALI_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT(Toolkit, ImageView, "pixelArea", Vector4(0.f, 0.f, 1.f, 1.f), PIXEL_AREA)
 DALI_TYPE_REGISTRATION_END()
 
@@ -65,8 +69,10 @@ using namespace Dali;
 ImageView::ImageView(ControlBehaviour additionalBehaviour)
 : Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT | additionalBehaviour)),
   mImageSize(),
+  mTransitionTargetAlpha(FULL_OPACITY),
   mImageVisualPaddingSetByTransform(false),
-  mImageViewPixelAreaSetByFittingMode(false)
+  mImageViewPixelAreaSetByFittingMode(false),
+  mTransitionEffect(false)
 {
 }
 
@@ -100,10 +106,49 @@ void ImageView::OnInitialize()
 
 void ImageView::SetImage(const Property::Map& map)
 {
+  DALI_LOG_ERROR("tscholb : SetImage(map) !!! \n");
+  if(mTransitionEffect && mVisual)
+  {
+    // Clear previous transition effect if it is playing
+    if(mPreviousVisual)
+    {
+      if(mTransitionAnimation)
+      {
+        if(mTransitionAnimation.GetState() == Animation::PLAYING)
+        {
+          mTransitionAnimation.Stop();
+          ClearTransitionAnimation();
+        }
+      }
+    }
+
+    // Enable transition effect for previous visual.
+    // This previous visual will be deleted when transition effect is done.
+    Internal::Control::Impl& controlDataImpl = Internal::Control::Impl::Get(*this);
+    controlDataImpl.EnableReadyTransitionOverriden(mVisual, true);
+    mPreviousVisual = mVisual;
+  }
+
   // Comparing a property map is too expensive so just creating a new visual
   mPropertyMap = map;
   mUrl.clear();
 
+  // keep alpha for transition effect
+  if(mTransitionEffect)
+  {
+    float            alpha      = FULL_OPACITY;
+    Property::Value* alphaValue = map.Find(Toolkit::Visual::Property::OPACITY);
+    if(alphaValue && alphaValue->Get(alpha))
+    {
+      mTransitionTargetAlpha = alpha;
+    }
+  }
+
+  if(!mVisual)
+  {
+    ShowPlaceholderImage();
+  }
+
   Toolkit::Visual::Base visual = Toolkit::VisualFactory::Get().CreateVisual(mPropertyMap);
   if(visual)
   {
@@ -135,11 +180,39 @@ void ImageView::SetImage(const Property::Map& map)
 
 void ImageView::SetImage(const std::string& url, ImageDimensions size)
 {
+  DALI_LOG_ERROR("tscholb : SetImage !!! \n");
+  if(mTransitionEffect && mVisual)
+  {
+    // Clear previous transition effect if it is playing
+    if(mPreviousVisual)
+    {
+      if(mTransitionAnimation)
+      {
+        if(mTransitionAnimation.GetState() == Animation::PLAYING)
+        {
+          mTransitionAnimation.Stop();
+          ClearTransitionAnimation();
+        }
+      }
+    }
+
+    // Enable transition effect for previous visual.
+    // This previous visual will be deleted when transition effect is done.
+    Internal::Control::Impl& controlDataImpl = Internal::Control::Impl::Get(*this);
+    controlDataImpl.EnableReadyTransitionOverriden(mVisual, true);
+    mPreviousVisual = mVisual;
+  }
+
   // Don't bother comparing if we had a visual previously, just drop old visual and create new one
   mUrl       = url;
   mImageSize = size;
   mPropertyMap.Clear();
 
+  if(!mVisual)
+  {
+    ShowPlaceholderImage();
+  }
+
   // Don't set mVisual until it is ready and shown. Getters will still use current visual.
   Toolkit::Visual::Base visual = Toolkit::VisualFactory::Get().CreateVisual(url, size);
   if(visual)
@@ -166,6 +239,12 @@ void ImageView::SetImage(const std::string& url, ImageDimensions size)
     RelayoutRequest();
   }
 
+  //tscholb DEBUG
+  Dali::Toolkit::Control handle(GetOwner());
+  auto                   check1 = handle.GetVisualResourceStatus(Toolkit::ImageView::Property::IMAGE);
+  auto                   check2 = handle.GetVisualResourceStatus(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE);
+  DALI_LOG_ERROR("tscholb : Resource status check >> %d,%d \n", check1, check2);
+
   // Signal that a Relayout may be needed
 }
 
@@ -174,6 +253,7 @@ void ImageView::ClearImageVisual()
   // Clear cached properties
   mPropertyMap.Clear();
   mUrl.clear();
+  mVisual.Reset();
 
   // Unregister the exsiting visual
   DevelControl::UnregisterVisual(*this, Toolkit::ImageView::Property::IMAGE);
@@ -207,6 +287,43 @@ void ImageView::SetDepthIndex(int depthIndex)
   }
 }
 
+void ImageView::SetPlaceholderUrl(const std::string& url)
+{
+  mPlaceholderUrl = url;
+  if(!url.empty())
+  {
+    mPlaceholderVisual.Reset();
+    CreatePlaceholderImage();
+  }
+  else
+  {
+    // Clear current placeholder image
+    Toolkit::Visual::Base visual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::PLACEHOLDER_IMAGE);
+    if(visual)
+    {
+      DevelControl::UnregisterVisual(*this, Toolkit::ImageView::Property::PLACEHOLDER_IMAGE);
+    }
+
+    mPlaceholderVisual.Reset();
+    mPlaceholderUrl = url;
+  }
+}
+
+std::string ImageView::GetPlaceholderUrl() const
+{
+  return mPlaceholderUrl;
+}
+
+void ImageView::EnableTransitionEffect(bool effectEnable)
+{
+  mTransitionEffect = effectEnable;
+}
+
+bool ImageView::IsTransitionEffectEnabled() const
+{
+  return mTransitionEffect;
+}
+
 Vector3 ImageView::GetNaturalSize()
 {
   if(mVisual)
@@ -328,6 +445,39 @@ void ImageView::OnUpdateVisualProperties(const std::vector<std::pair<Dali::Prope
 
 void ImageView::OnResourceReady(Toolkit::Control control)
 {
+  DALI_LOG_ERROR("tscholb : OnResourceReady !!! \n");
+  // In case of placeholder, we need to skip this call.
+  // TODO: In case of placeholder, it needs to be modified not to call OnResourceReady()
+  if(control.GetVisualResourceStatus(Toolkit::ImageView::Property::IMAGE) != Toolkit::Visual::ResourceStatus::READY)
+  {
+    DALI_LOG_ERROR("tscholb : OnResourceReady() is called, but SKIP \n");
+    return;
+  }
+
+  // Do transition effect if need.
+  if(mTransitionEffect)
+  {
+    // TODO: Consider about placeholder image is loaded failed
+    Toolkit::Visual::Base placeholderVisual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::PLACEHOLDER_IMAGE);
+    if(!placeholderVisual || control.GetVisualResourceStatus(Toolkit::ImageView::Property::PLACEHOLDER_IMAGE) == Toolkit::Visual::ResourceStatus::READY)
+    {
+      // when placeholder is disabled or ready placeholder and image, we need to transition effect
+      DALI_LOG_ERROR("tscholb : Call TransitionEffect \n");
+      TransitionImageWithEffect();
+    }
+    else
+    {
+      DALI_LOG_ERROR("tscholb : Call ClearTransitionAnimation \n");
+      ClearTransitionAnimation();
+    }
+  }
+  else
+  {
+    // we don't need placeholder anymore because visual is replaced. so hide placeholder.
+    DALI_LOG_ERROR("tscholb : transition effect is DISABLE \n");
+    HidePlaceholderImage();
+  }
+
   // Visual ready so update visual attached to this ImageView, following call to RelayoutRequest will use this visual.
   mVisual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::IMAGE);
   // Signal that a Relayout may be needed
@@ -467,6 +617,115 @@ void ImageView::ApplyFittingMode(Vector2 finalSize, Vector2 finalOffset, bool ze
   }
 }
 
+void ImageView::CreatePlaceholderImage()
+{
+  Property::Map propertyMap;
+  propertyMap.Insert(Toolkit::Visual::Property::TYPE, Toolkit::Visual::IMAGE);
+  propertyMap.Insert(Toolkit::ImageVisual::Property::URL, mPlaceholderUrl);
+  //propertyMap.Insert(Toolkit::ImageVisual::Property::LOAD_POLICY, Toolkit::ImageVisual::LoadPolicy::IMMEDIATE); // TODO: need to enable this property
+  propertyMap.Insert(Toolkit::ImageVisual::Property::RELEASE_POLICY, Toolkit::ImageVisual::ReleasePolicy::DESTROYED);
+  mPlaceholderVisual = Toolkit::VisualFactory::Get().CreateVisual(propertyMap);
+  if(mPlaceholderVisual)
+  {
+    mPlaceholderVisual.SetName("placeholder");
+  }
+  else
+  {
+    DevelControl::UnregisterVisual(*this, Toolkit::ImageView::Property::PLACEHOLDER_IMAGE);
+    mPlaceholderVisual.Reset();
+  }
+}
+
+void ImageView::ShowPlaceholderImage()
+{
+  if(mPlaceholderVisual)
+  {
+    DevelControl::RegisterVisual(*this, Toolkit::ImageView::Property::PLACEHOLDER_IMAGE, mPlaceholderVisual, false);
+    Actor self = Self();
+    Toolkit::GetImplementation(mPlaceholderVisual).SetOnScene(self);
+  }
+}
+
+void ImageView::HidePlaceholderImage()
+{
+  if(mPlaceholderVisual)
+  {
+    DevelControl::UnregisterVisual(*this, Toolkit::ImageView::Property::PLACEHOLDER_IMAGE);
+
+    // Hide placeholder
+    Actor self = Self();
+    Toolkit::GetImplementation(mPlaceholderVisual).SetOffScene(self);
+  }
+}
+
+void ImageView::TransitionImageWithEffect()
+{
+  Toolkit::ImageView handle = Toolkit::ImageView(GetOwner());
+
+  if(handle)
+  {
+    mTransitionAnimation = Animation::New(1.5f);
+    mTransitionAnimation.SetEndAction(Animation::EndAction::DISCARD);
+    float destinationAlpha = (mTransitionTargetAlpha > LOW_OPACITY) ? mTransitionTargetAlpha : LOW_OPACITY;
+
+    if(mPreviousVisual) // Transition previous image
+    {
+      DALI_LOG_ERROR("tscholb : mPreviousVisual !!! \n");
+      Dali::KeyFrames fadeoutKeyFrames = Dali::KeyFrames::New();
+      fadeoutKeyFrames.Add(0.0f, destinationAlpha);
+      fadeoutKeyFrames.Add(1.0f, LOW_OPACITY);
+      Internal::Visual::Base& visualImpl = Toolkit::GetImplementation(mPreviousVisual);
+      mTransitionAnimation.AnimateBetween(visualImpl.GetPropertyObject(Toolkit::Visual::Property::OPACITY), fadeoutKeyFrames);
+    }
+    else if(mPlaceholderVisual) // Transition placeholder
+    {
+      DALI_LOG_ERROR("tscholb : mPlaceholderVisual !!! \n");
+      Dali::KeyFrames fadeoutKeyFrames = Dali::KeyFrames::New();
+      fadeoutKeyFrames.Add(0.0f, destinationAlpha);
+      fadeoutKeyFrames.Add(1.0f, LOW_OPACITY);
+      Internal::Visual::Base& visualImpl = Toolkit::GetImplementation(mPlaceholderVisual);
+      mTransitionAnimation.AnimateBetween(visualImpl.GetPropertyObject(Toolkit::Visual::Property::OPACITY), fadeoutKeyFrames);
+    }
+
+    // Transition current image
+    Toolkit::Visual::Base imageVisual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::IMAGE);
+    if(imageVisual)
+    {
+      DALI_LOG_ERROR("tscholb : imageVisual !!! \n");
+      Dali::KeyFrames fadeinKeyFrames = Dali::KeyFrames::New();
+      fadeinKeyFrames.Add(0.0f, LOW_OPACITY);
+      fadeinKeyFrames.Add(1.0f, destinationAlpha);
+      mTransitionAnimation.AnimateBetween(DevelControl::GetVisualProperty(handle, Toolkit::ImageView::Property::IMAGE, Toolkit::Visual::Property::OPACITY), fadeinKeyFrames);
+    }
+
+    // Play transition animation
+    mTransitionAnimation.FinishedSignal().Connect(this, &ImageView::OnTransitionAnimationFinishedCallback);
+    mTransitionAnimation.Play();
+  }
+}
+
+void ImageView::ClearTransitionAnimation()
+{
+  // Hide placeholder
+  HidePlaceholderImage();
+
+  // Clear PreviousVisual
+  if(mPreviousVisual)
+  {
+    Actor                    self            = Self();
+    Internal::Control::Impl& controlDataImpl = Internal::Control::Impl::Get(*this);
+    controlDataImpl.EnableReadyTransitionOverriden(mVisual, false);
+    Toolkit::GetImplementation(mPreviousVisual).SetOffScene(self);
+    mPreviousVisual.Reset();
+  }
+
+  if(mTransitionAnimation)
+  {
+    mTransitionAnimation.FinishedSignal().Disconnect(this, &ImageView::OnTransitionAnimationFinishedCallback);
+    mTransitionAnimation.Clear();
+  }
+}
+
 ///////////////////////////////////////////////////////////
 //
 // Properties
@@ -546,6 +805,26 @@ void ImageView::SetProperty(BaseObject* object, Property::Index index, const Pro
         }
         break;
       }
+
+      case Toolkit::ImageView::Property::PLACEHOLDER_IMAGE:
+      {
+        std::string placeholderUrl;
+        if(value.Get(placeholderUrl))
+        {
+          impl.SetPlaceholderUrl(placeholderUrl);
+        }
+        break;
+      }
+
+      case Toolkit::ImageView::Property::ENABLE_TRANSITION_EFFECT:
+      {
+        bool transitionEffect;
+        if(value.Get(transitionEffect))
+        {
+          impl.EnableTransitionEffect(transitionEffect);
+        }
+        break;
+      }
     }
   }
 }
@@ -585,12 +864,29 @@ Property::Value ImageView::GetProperty(BaseObject* object, Property::Index prope
         value = impl.IsPreMultipliedAlphaEnabled();
         break;
       }
+
+      case Toolkit::ImageView::Property::PLACEHOLDER_IMAGE:
+      {
+        value = impl.GetPlaceholderUrl();
+        break;
+      }
+
+      case Toolkit::ImageView::Property::ENABLE_TRANSITION_EFFECT:
+      {
+        value = impl.IsTransitionEffectEnabled();
+        break;
+      }
     }
   }
 
   return value;
 }
 
+void ImageView::OnTransitionAnimationFinishedCallback(Animation& animation)
+{
+  ClearTransitionAnimation();
+}
+
 } // namespace Internal
 } // namespace Toolkit
 } // namespace Dali
index 7caf9eb..bcdd8ba 100644 (file)
@@ -118,6 +118,31 @@ public:
    */
   void SetDepthIndex(int depthIndex);
 
+  /**
+   * @brief Set the placeholder url
+   */
+  void SetPlaceholderUrl(const std::string& url);
+
+  /**
+   * @brief Get the placeholder url
+   */
+  std::string GetPlaceholderUrl() const;
+
+  /**
+   * @brief Enable the transition effect
+   */
+  void EnableTransitionEffect(bool effectEnable);
+
+  /**
+   * @brief Query whether transition effect is enabled
+   */
+  bool IsTransitionEffectEnabled() const;
+
+  /**
+   * @brief callback when animation for placeholder or previous visual transition effect is finished
+   */
+  void OnTransitionAnimationFinishedCallback(Animation& animation);
+
 private: // From Control
   /**
    * @copydoc Toolkit::Control::OnInitialize
@@ -183,6 +208,31 @@ private:
    */
   void ApplyFittingMode(Vector2 finalSize, Vector2 offset, bool zeroPadding, Property::Map& transformMap);
 
+   /**
+   * @brief Create placeholder image if it set. placeholder image is shown when image view is waiting for the image to load.
+   */
+  void CreatePlaceholderImage();
+
+  /**
+   * @brief Show placeholder image if it set. placeholder image is shown when image view is waiting for the image to load.
+   */
+  void ShowPlaceholderImage();
+
+  /**
+   * @brief Hide placeholder image if it set.
+   */
+  void HidePlaceholderImage();
+
+  /**
+   * @brief Transition image with effect when image is replaced.
+   */
+  void TransitionImageWithEffect();
+
+  /**
+   * @brief Clear the transition animation
+   */
+  void ClearTransitionAnimation();
+
 private:
   // Undefined
   ImageView(const ImageView&);
@@ -190,14 +240,20 @@ private:
 
 private:
   Toolkit::Visual::Base mVisual;
-
-  std::string     mUrl;         ///< the url for the image if the image came from a URL, empty otherwise
-  Property::Map   mPropertyMap; ///< the Property::Map if the image came from a Property::Map, empty otherwise
-  Property::Map   mShaderMap;   ///< the Property::Map if the custom shader is set, empty otherwise
-  ImageDimensions mImageSize;   ///< the image size
-
-  bool mImageVisualPaddingSetByTransform : 1;   //< Flag to indicate Padding was set using a transform.
-  bool mImageViewPixelAreaSetByFittingMode : 1; //< Flag to indicate pixel area was set by fitting Mode
+  Toolkit::Visual::Base mPreviousVisual;
+  Toolkit::Visual::Base mPlaceholderVisual;
+
+  std::string     mUrl;                                    ///< the url for the image if the image came from a URL, empty otherwise
+  std::string     mPlaceholderUrl;                         ///< the url for the placeholder image if the image came from a PLACEHOLDER_IMAGE, empty otherwise
+  Property::Map   mPropertyMap;                            ///< the Property::Map if the image came from a Property::Map, empty otherwise
+  Property::Map   mShaderMap;                              ///< the Property::Map if the custom shader is set, empty otherwise
+  ImageDimensions mImageSize;                              ///< the image size
+
+  Animation       mTransitionAnimation;                    ///< the animation for transition effect
+  float           mTransitionTargetAlpha;                  ///< Keep image's alpha value
+  bool            mImageVisualPaddingSetByTransform : 1;   ///< Flag to indicate Padding was set using a transform.
+  bool            mImageViewPixelAreaSetByFittingMode : 1; ///< Flag to indicate pixel area was set by fitting Mode
+  bool            mTransitionEffect :1;                    ///< Flag to indicate TransitionEffect is enabled
 };
 
 } // namespace Internal
index 16d7a76..e54c3ae 100644 (file)
@@ -53,10 +53,10 @@ struct Base::Impl
 
   enum Flags
   {
-    IS_ON_SCENE                     = 1,
-    IS_ATLASING_APPLIED             = 1 << 1,
-    IS_PREMULTIPLIED_ALPHA          = 1 << 2,
-    IS_SYNCHRONOUS_RESOURCE_LOADING = 1 << 3
+    IS_ON_SCENE                        = 1,
+    IS_ATLASING_APPLIED                = 1 << 1,
+    IS_PREMULTIPLIED_ALPHA             = 1 << 2,
+    IS_SYNCHRONOUS_RESOURCE_LOADING    = 1 << 3,
   };
 
   struct CustomShader
@@ -254,8 +254,8 @@ struct Base::Impl
   int                             mFlags;
   Toolkit::Visual::ResourceStatus mResourceStatus;
   const Toolkit::Visual::Type     mType;
-  bool                            mAlwaysUsingBorderline : 1;   ///< Whether we need the borderline in shader always.
-  bool                            mAlwaysUsingCornerRadius : 1; ///< Whether we need the corner radius in shader always.
+  bool                            mAlwaysUsingBorderline : 1;     ///< Whether we need the borderline in shader always.
+  bool                            mAlwaysUsingCornerRadius : 1;   ///< Whether we need the corner radius in shader always.
 };
 
 } // namespace Visual
index da342ab..6f52832 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_IMAGE_VIEW_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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.
@@ -114,6 +114,25 @@ public:
        */
       PRE_MULTIPLIED_ALPHA,
 
+      /**
+       * @brief name "placeholderImage", type string.
+       *
+       * placeholder image is shown when image view is waiting for the image to load.
+       * @SINCE_2_2.24
+       */
+      PLACEHOLDER_IMAGE,
+
+      /**
+       * @brief name "enableTransitionEffect", type Boolean
+       *
+       * This effect is a crossfade effect when the image is replaced.
+       * the default duration of the crossfade effect is 1.5 seconds.
+       * if the placeholder is enabled, the cross effect applies when the image is changed from a placeholder image to a new image.
+       * if not, the cross effect applies when a new image is shown or is changed from the previous image to a new image.
+       * @SINCE_2_2.24
+       */
+      ENABLE_TRANSITION_EFFECT,
+
       // Animatable properties
 
       /**