Updated ImageVisual::GetNaturalSize to work with loaded images
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / visuals / image / image-visual.cpp
index 5c8a004..dcb222d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 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.
 
 // EXTERNAL HEADERS
 #include <cstring> // for strlen()
+#include <dali/public-api/actors/layer.h>
+#include <dali/public-api/common/stage.h>
 #include <dali/public-api/images/resource-image.h>
 #include <dali/public-api/images/native-image.h>
 #include <dali/devel-api/images/texture-set-image.h>
-#include <dali/devel-api/adaptor-framework/bitmap-loader.h>
 #include <dali/devel-api/adaptor-framework/image-loading.h>
 #include <dali/devel-api/scripting/enum-helper.h>
 #include <dali/devel-api/scripting/scripting.h>
@@ -33,6 +34,7 @@
 #include <dali-toolkit/public-api/visuals/image-visual-properties.h>
 #include <dali-toolkit/devel-api/visuals/image-visual-properties-devel.h>
 #include <dali-toolkit/devel-api/visuals/visual-properties-devel.h>
+#include <dali-toolkit/internal/visuals/texture-manager.h>
 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
 #include <dali-toolkit/internal/visuals/visual-factory-impl.h>
 #include <dali-toolkit/internal/visuals/visual-factory-cache.h>
@@ -60,6 +62,7 @@ const char * const IMAGE_DESIRED_WIDTH( "desiredWidth" );
 const char * const IMAGE_DESIRED_HEIGHT( "desiredHeight" );
 const char * const SYNCHRONOUS_LOADING( "synchronousLoading" );
 const char * const IMAGE_ATLASING("atlasing");
+const char * const ALPHA_MASK_URL("alphaMaskUrl");
 
 // fitting modes
 DALI_ENUM_TO_STRING_TABLE_BEGIN( FITTING_MODE )
@@ -93,6 +96,9 @@ const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
 
 const char* DEFAULT_SAMPLER_TYPENAME = "sampler2D";
 
+const float PIXEL_ALIGN_ON = 1.0f;
+const float PIXEL_ALIGN_OFF = 0.0f;
+
 const char* VERTEX_SHADER = DALI_COMPOSE_SHADER(
   attribute mediump vec2 aPosition;\n
   uniform mediump mat4 uModelMatrix;\n
@@ -101,27 +107,28 @@ const char* VERTEX_SHADER = DALI_COMPOSE_SHADER(
   uniform mediump vec3 uSize;\n
   uniform mediump vec4 pixelArea;
   varying mediump vec2 vTexCoord;\n
+  uniform lowp float uPixelAligned;\n
   \n
-
   //Visual size and offset
   uniform mediump vec2 offset;\n
   uniform mediump vec2 size;\n
   uniform mediump vec4 offsetSizeMode;\n
   uniform mediump vec2 origin;\n
   uniform mediump vec2 anchorPoint;\n
-
+\n
   vec4 ComputeVertexPosition()\n
   {\n
     vec2 visualSize = mix(uSize.xy*size, size, offsetSizeMode.zw );\n
     vec2 visualOffset = mix( offset, offset/uSize.xy, offsetSizeMode.xy);\n
     return vec4( (aPosition + anchorPoint)*visualSize + (visualOffset + origin)*uSize.xy, 0.0, 1.0 );\n
   }\n
-
+\n
   void main()\n
   {\n
     mediump vec4 vertexPosition = uViewMatrix * uModelMatrix * ComputeVertexPosition();\n
-    vertexPosition.xy = floor ( vertexPosition.xy );\n // Pixel alignment
-    vertexPosition = uProjection *  vertexPosition;\n
+    vec4 alignedVertexPosition = vertexPosition;\n
+    alignedVertexPosition.xy = floor ( vertexPosition.xy );\n // Pixel alignment
+    vertexPosition = uProjection * mix( vertexPosition, alignedVertexPosition, uPixelAligned );\n
     vTexCoord = pixelArea.xy+pixelArea.zw*(aPosition + vec2(0.5) );\n
     gl_Position = vertexPosition;\n
   }\n
@@ -255,12 +262,15 @@ ImageVisual::ImageVisual( VisualFactoryCache& factoryCache,
   mPixelArea( FULL_TEXTURE_RECT ),
   mPlacementActor(),
   mImageUrl( imageUrl ),
+  mMaskingData( NULL ),
   mDesiredSize( size ),
+  mTextureId( TextureManager::INVALID_TEXTURE_ID ),
   mFittingMode( fittingMode ),
   mSamplingMode( samplingMode ),
   mWrapModeU( WrapMode::DEFAULT ),
   mWrapModeV( WrapMode::DEFAULT ),
-  mAttemptAtlasing( false )
+  mAttemptAtlasing( false ),
+  mTextureLoading( false )
 {
 }
 
@@ -271,17 +281,21 @@ ImageVisual::ImageVisual( VisualFactoryCache& factoryCache, const Image& image )
   mPixelArea( FULL_TEXTURE_RECT ),
   mPlacementActor(),
   mImageUrl(),
+  mMaskingData( NULL ),
   mDesiredSize(),
+  mTextureId( TextureManager::INVALID_TEXTURE_ID ),
   mFittingMode( FittingMode::DEFAULT ),
   mSamplingMode( SamplingMode::DEFAULT ),
   mWrapModeU( WrapMode::DEFAULT ),
   mWrapModeV( WrapMode::DEFAULT ),
-  mAttemptAtlasing( false )
+  mAttemptAtlasing( false ),
+  mTextureLoading( false )
 {
 }
 
 ImageVisual::~ImageVisual()
 {
+  delete mMaskingData;
 }
 
 void ImageVisual::DoSetProperties( const Property::Map& propertyMap )
@@ -332,6 +346,18 @@ void ImageVisual::DoSetProperties( const Property::Map& propertyMap )
       {
         DoSetProperty( Toolkit::DevelImageVisual::Property::ATLASING, keyValue.second );
       }
+      else if ( keyValue.first == ALPHA_MASK_URL )
+      {
+        DoSetProperty( Toolkit::DevelImageVisual::Property::ALPHA_MASK_URL, keyValue.second );
+      }
+      else if ( keyValue.first == MASK_CONTENT_SCALE_NAME )
+      {
+        DoSetProperty( Toolkit::DevelImageVisual::Property::MASK_CONTENT_SCALE, keyValue.second );
+      }
+      else if ( keyValue.first == CROP_TO_MASK_NAME )
+      {
+        DoSetProperty( Toolkit::DevelImageVisual::Property::CROP_TO_MASK, keyValue.second );
+      }
     }
   }
 
@@ -440,7 +466,51 @@ void ImageVisual::DoSetProperty( Property::Index index, const Property::Value& v
     {
       bool atlasing = false;
       mAttemptAtlasing = value.Get( atlasing );
+      break;
+    }
+
+    case Toolkit::DevelImageVisual::Property::ALPHA_MASK_URL:
+    {
+      std::string alphaUrl;
+      if( value.Get( alphaUrl ) )
+      {
+        AllocateMaskData();
+        // Immediately trigger the alpha mask loading (it may just get a cached value)
+        mMaskingData->SetImage( alphaUrl );
+      }
+      break;
     }
+
+    case Toolkit::DevelImageVisual::Property::MASK_CONTENT_SCALE:
+    {
+      float scale;
+      if( value.Get( scale ) )
+      {
+        AllocateMaskData();
+        mMaskingData->mContentScaleFactor = scale;
+      }
+      break;
+    }
+
+    case Toolkit::DevelImageVisual::Property::CROP_TO_MASK:
+    {
+      bool crop=false;
+      if( value.Get( crop ) )
+      {
+        AllocateMaskData();
+        mMaskingData->mCropToMask = crop;
+      }
+      break;
+    }
+  }
+}
+
+void ImageVisual::AllocateMaskData()
+{
+  if( mMaskingData == NULL )
+  {
+    TextureManager& textureManager = mFactoryCache.GetTextureManager();
+    mMaskingData = new MaskingData(textureManager);
   }
 }
 
@@ -458,18 +528,56 @@ void ImageVisual::GetNaturalSize( Vector2& naturalSize )
     naturalSize.y = mDesiredSize.GetHeight();
     return;
   }
-  else if( mImageUrl.IsValid() && mImageUrl.GetLocation() == VisualUrl::LOCAL )
+  else if( mImpl->mRenderer ) // Check if we have a loaded image
   {
-    ImageDimensions dimentions = Dali::GetClosestImageSize( mImageUrl.GetUrl() );
-    naturalSize.x = dimentions.GetWidth();
-    naturalSize.y = dimentions.GetHeight();
+    auto textureSet = mImpl->mRenderer.GetTextures();
+
+    if( textureSet )
+    {
+      auto texture = textureSet.GetTexture(0);
+      naturalSize.x = texture.GetWidth();
+      naturalSize.y = texture.GetHeight();
+      return;
+    }
+  }
+
+  if( mMaskingData != NULL && mMaskingData->mAlphaMaskUrl.IsValid() &&
+           mMaskingData->mCropToMask )
+  {
+    ImageDimensions dimensions = Dali::GetClosestImageSize( mMaskingData->mAlphaMaskUrl.GetUrl() );
+    if( dimensions != ImageDimensions( 0, 0 ) )
+    {
+      naturalSize.x = dimensions.GetWidth();
+      naturalSize.y = dimensions.GetHeight();
+    }
     return;
   }
+  else if( mImageUrl.IsValid() )
+  {
+    if( mImageUrl.GetLocation() == VisualUrl::LOCAL )
+    {
+      ImageDimensions dimensions = Dali::GetClosestImageSize( mImageUrl.GetUrl() );
+
+      if( dimensions != ImageDimensions( 0, 0 ) )
+      {
+        naturalSize.x = dimensions.GetWidth();
+        naturalSize.y = dimensions.GetHeight();
+      }
+      else
+      {
+        Image brokenImage = VisualFactoryCache::GetBrokenVisualImage();
+
+        naturalSize.x = brokenImage.GetWidth();
+        naturalSize.y = brokenImage.GetWidth();
+      }
+      return;
+    }
+  }
 
   naturalSize = Vector2::ZERO;
 }
 
-void ImageVisual::CreateRenderer( TextureSet& textures )
+void ImageVisual::CreateRenderer( TextureSet& textureSet )
 {
   Geometry geometry;
   Shader shader;
@@ -501,9 +609,17 @@ void ImageVisual::CreateRenderer( TextureSet& textures )
     }
   }
 
+  // Set pixel align off as default.
+  // ToDo: Pixel align causes issues such as rattling image animation.
+  // We should trun it off until issues are resolved
+  shader.RegisterProperty( PIXEL_ALIGNED_UNIFORM_NAME, PIXEL_ALIGN_OFF );
+
   mImpl->mRenderer = Renderer::New( geometry, shader );
-  DALI_ASSERT_DEBUG( textures );
-  mImpl->mRenderer.SetTextures( textures );
+  if( textureSet )
+  {
+    mImpl->mRenderer.SetTextures( textureSet );
+  }
+  // else still waiting for texture load to finish.
 
   //Register transform properties
   mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT );
@@ -573,15 +689,22 @@ void ImageVisual::LoadResourceSynchronously()
 {
   if( mImageUrl.IsValid() )
   {
-    BitmapLoader loader = BitmapLoader::New( mImageUrl.GetUrl(), mDesiredSize, mFittingMode, mSamplingMode );
-    loader.Load();
-    mPixels = loader.GetPixelData();
+    Devel::PixelBuffer pixelBuffer = LoadImageFromFile( mImageUrl.GetUrl(), mDesiredSize, mFittingMode, mSamplingMode );
+
+    if( pixelBuffer )
+    {
+      mPixels = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
+    }
+    mTextureLoading = false;
   }
 }
 
 TextureSet ImageVisual::CreateTextureSet( Vector4& textureRect, bool synchronousLoading, bool attemptAtlasing )
 {
   TextureSet textureSet;
+
+  mTextureLoading = false;
+
   textureRect = FULL_TEXTURE_RECT;
   if( synchronousLoading )
   {
@@ -615,23 +738,48 @@ TextureSet ImageVisual::CreateTextureSet( Vector4& textureRect, bool synchronous
     {
       textureSet = mFactoryCache.GetAtlasManager()->Add( textureRect, mImageUrl.GetUrl(), mDesiredSize, mFittingMode, true, this );
       mImpl->mFlags |= Impl::IS_ATLASING_APPLIED;
+      mTextureLoading = true;
     }
     if( !textureSet ) // big image, no atlasing or atlasing failed
     {
       mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED;
-      ResourceImage resourceImage = Dali::ResourceImage::New( mImageUrl.GetUrl(), mDesiredSize, mFittingMode, mSamplingMode );
-      resourceImage.LoadingFinishedSignal().Connect( this, &ImageVisual::OnImageLoaded );
-      textureSet = TextureSet::New();
-      TextureSetImage( textureSet, 0u, resourceImage );
+      TextureManager& textureManager = mFactoryCache.GetTextureManager();
+      if( mMaskingData == NULL )
+      {
+        mTextureId = textureManager.RequestLoad( mImageUrl, mDesiredSize, mFittingMode,
+                                                 mSamplingMode, TextureManager::NO_ATLAS, this );
+      }
+      else
+      {
+        mTextureId = textureManager.RequestLoad( mImageUrl,
+                                                 mMaskingData->mAlphaMaskId,
+                                                 mMaskingData->mContentScaleFactor,
+                                                 mDesiredSize,
+                                                 mFittingMode, mSamplingMode,
+                                                 TextureManager::NO_ATLAS,
+                                                 mMaskingData->mCropToMask,
+                                                 this );
+      }
+
+      TextureManager::LoadState loadState = textureManager.GetTextureState( mTextureId );
+
+      mTextureLoading = ( loadState == TextureManager::LOADING );
+
+      if( loadState == TextureManager::UPLOADED )
+      {
+        // UploadComplete has already been called - keep the same texture set
+        textureSet = textureManager.GetTextureSet(mTextureId);
+      }
     }
   }
 
-  if( !(mImpl->mFlags & Impl::IS_ATLASING_APPLIED) )
+  if( ! (mImpl->mFlags & Impl::IS_ATLASING_APPLIED) && textureSet )
   {
     Sampler sampler = Sampler::New();
     sampler.SetWrapMode(  mWrapModeU, mWrapModeV  );
     textureSet.SetSampler( 0u, sampler );
   }
+
   return textureSet;
 }
 
@@ -639,7 +787,7 @@ void ImageVisual::InitializeRenderer()
 {
   mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED;
 
-  if( !mImpl->mCustomShader && mImageUrl.GetLocation() == VisualUrl::LOCAL )
+  if( ! mImpl->mCustomShader && mImageUrl.GetLocation() == VisualUrl::LOCAL )
   {
     bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE;
 
@@ -674,7 +822,6 @@ void ImageVisual::InitializeRenderer( const Image& image )
   // don't reuse CreateTextureSet
   TextureSet textures = TextureSet::New();
 
-  // Renderer can't be shared if mImage is NativeImage
   NativeImage nativeImage = NativeImage::DownCast( image );
   if( nativeImage )
   {
@@ -690,18 +837,6 @@ void ImageVisual::InitializeRenderer( const Image& image )
   ApplyImageToSampler( image );
 }
 
-void ImageVisual::UploadCompleted()
-{
-  // Resource image is loaded. If weak handle is holding a placement actor, it is the time to add the renderer to actor.
-  Actor actor = mPlacementActor.GetHandle();
-  if( actor )
-  {
-    actor.AddRenderer( mImpl->mRenderer );
-    // reset the weak handle so that the renderer only get added to actor once
-    mPlacementActor.Reset();
-  }
-}
-
 void ImageVisual::DoSetOnStage( Actor& actor )
 {
   if( mImageUrl.IsValid() )
@@ -719,16 +854,26 @@ void ImageVisual::DoSetOnStage( Actor& actor )
   }
 
   mPlacementActor = actor;
+  // Search the Actor tree to find if Layer UI behaviour set.
+  Layer layer = actor.GetLayer();
+  if ( layer && layer.GetBehavior() == Layer::LAYER_3D )
+  {
+     // Layer 3D set, do not align pixels
+     mImpl->mRenderer.RegisterProperty( PIXEL_ALIGNED_UNIFORM_NAME, PIXEL_ALIGN_OFF );
+  }
 
   if( mPixelArea != FULL_TEXTURE_RECT )
   {
     mImpl->mRenderer.RegisterProperty( PIXEL_AREA_UNIFORM_NAME, mPixelArea );
   }
 
-  if( IsSynchronousResourceLoading() || !(mImpl->mFlags & Impl::IS_ATLASING_APPLIED) )
+  if( mTextureLoading == false )
   {
     actor.AddRenderer( mImpl->mRenderer );
     mPlacementActor.Reset();
+
+    // Image loaded and ready to display
+    ResourceReady();
   }
 }
 
@@ -740,10 +885,10 @@ void ImageVisual::DoSetOffStage( Actor& actor )
   actor.RemoveRenderer( mImpl->mRenderer);
   if( mImageUrl.IsValid() )
   {
-    RemoveFromAtlas( mImageUrl.GetUrl() );
+    RemoveTexture( mImageUrl.GetUrl() );
     mImage.Reset();
   }
-
+  mTextureLoading = false;
   mImpl->mRenderer.Reset();
   mPlacementActor.Reset();
 }
@@ -781,6 +926,12 @@ void ImageVisual::DoCreatePropertyMap( Property::Map& map ) const
   map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_V, mWrapModeV );
 
   map.Insert( Toolkit::DevelImageVisual::Property::ATLASING, mAttemptAtlasing );
+  if( mMaskingData != NULL )
+  {
+    map.Insert( Toolkit::DevelImageVisual::Property::ALPHA_MASK_URL, mMaskingData->mAlphaMaskUrl.GetUrl() );
+    map.Insert( Toolkit::DevelImageVisual::Property::MASK_CONTENT_SCALE, mMaskingData->mContentScaleFactor );
+    map.Insert( Toolkit::DevelImageVisual::Property::CROP_TO_MASK, mMaskingData->mCropToMask );
+  }
 }
 
 void ImageVisual::DoCreateInstancePropertyMap( Property::Map& map ) const
@@ -861,37 +1012,113 @@ void ImageVisual::ApplyImageToSampler( const Image& image )
   }
 }
 
-void ImageVisual::OnImageLoaded( ResourceImage image )
+// From existing atlas manager
+void ImageVisual::UploadCompleted()
 {
-  if( image.GetLoadingState() == Dali::ResourceLoadingFailed )
+  // Texture has been uploaded. If weak handle is holding a placement actor, it is the time to add the renderer to actor.
+  Actor actor = mPlacementActor.GetHandle();
+  if( actor )
+  {
+    actor.AddRenderer( mImpl->mRenderer );
+
+    // reset the weak handle so that the renderer only get added to actor once
+    mPlacementActor.Reset();
+  }
+  mTextureLoading = false;
+}
+
+// From Texture Manager
+void ImageVisual::UploadComplete( bool loadingSuccess, int32_t textureId, TextureSet textureSet, bool usingAtlas, const Vector4& atlasRectangle )
+{
+  Actor actor = mPlacementActor.GetHandle();
+  if( actor )
   {
-    Image brokenImage = VisualFactoryCache::GetBrokenVisualImage();
     if( mImpl->mRenderer )
     {
-      ApplyImageToSampler( brokenImage );
+      actor.AddRenderer( mImpl->mRenderer );
+      // reset the weak handle so that the renderer only get added to actor once
+      mPlacementActor.Reset();
+
+      if( loadingSuccess )
+      {
+        Sampler sampler = Sampler::New();
+        sampler.SetWrapMode(  mWrapModeU, mWrapModeV  );
+        textureSet.SetSampler( 0u, sampler );
+        mImpl->mRenderer.SetTextures(textureSet);
+      }
+      else
+      {
+        Image brokenImage = VisualFactoryCache::GetBrokenVisualImage();
+
+        textureSet = TextureSet::New();
+        mImpl->mRenderer.SetTextures( textureSet );
+
+        ApplyImageToSampler( brokenImage );
+      }
+      // Image loaded and ready to display
+      ResourceReady();
     }
   }
+  mTextureLoading = false;
 }
 
-void ImageVisual::RemoveFromAtlas(const std::string& url)
+void ImageVisual::RemoveTexture(const std::string& url)
 {
-  Vector4 atlasRect( 0.f, 0.f, 1.f, 1.f );
-  Property::Index index = mImpl->mRenderer.GetPropertyIndex( ATLAS_RECT_UNIFORM_NAME );
-  if( index != Property::INVALID_INDEX )
+  if( mTextureId != TextureManager::INVALID_TEXTURE_ID )
   {
-    Property::Value atlasRectValue = mImpl->mRenderer.GetProperty( index );
-    atlasRectValue.Get( atlasRect );
+    mFactoryCache.GetTextureManager().Remove( mTextureId );
+    mTextureId = TextureManager::INVALID_TEXTURE_ID;
   }
+  else
+  {
+    Vector4 atlasRect( 0.f, 0.f, 1.f, 1.f );
+    Property::Index index = mImpl->mRenderer.GetPropertyIndex( ATLAS_RECT_UNIFORM_NAME );
+    if( index != Property::INVALID_INDEX )
+    {
+      Property::Value atlasRectValue = mImpl->mRenderer.GetProperty( index );
+      atlasRectValue.Get( atlasRect );
+    }
 
-  TextureSet textureSet = mImpl->mRenderer.GetTextures();
-  mImpl->mRenderer.Reset();
+    TextureSet textureSet = mImpl->mRenderer.GetTextures();
+    mImpl->mRenderer.Reset();
+
+    if( index != Property::INVALID_INDEX )
+    {
+      mFactoryCache.GetAtlasManager()->Remove( textureSet, atlasRect );
+    }
+  }
+}
+
+
+ImageVisual::MaskingData::MaskingData( TextureManager& textureManager )
+: mTextureManager( textureManager ),
+  mAlphaMaskUrl(),
+  mAlphaMaskId( TextureManager::INVALID_TEXTURE_ID ),
+  mContentScaleFactor( 1.0f ),
+  mCropToMask( true )
+{
+}
 
-  if( index != Property::INVALID_INDEX )
+ImageVisual::MaskingData::~MaskingData()
+{
+  if( Stage::IsInstalled() )
   {
-    mFactoryCache.GetAtlasManager()->Remove( textureSet, atlasRect );
+    // TextureManager could have been deleted before the actor that contains this
+    // ImageVisual is destroyed (e.g. due to stage shutdown). Ensure the stage
+    // is still valid before accessing texture manager.
+    if( mAlphaMaskId != TextureManager::INVALID_TEXTURE_ID )
+    {
+      mTextureManager.Remove( mAlphaMaskId );
+    }
   }
 }
 
+void ImageVisual::MaskingData::SetImage( const std::string& maskUrl )
+{
+  mAlphaMaskUrl = maskUrl;
+  mAlphaMaskId = mTextureManager.RequestMaskLoad( mAlphaMaskUrl );
+}
+
 } // namespace Internal
 
 } // namespace Toolkit