X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Fvisuals%2Fanimated-image%2Fanimated-image-visual.cpp;h=ec58860b84eccf3e0724124eeb900646d7da928e;hp=0ba78331f546c8623061d95d3de37cf944663d64;hb=ba1c6fea08ea39ca92356ae4b39a952919398e7e;hpb=95bc87993608242c8eaff49551ae85b4b78c4de7 diff --git a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp index 0ba7833..a334e2f 100644 --- a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp +++ b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2022 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. @@ -19,238 +19,979 @@ #include "animated-image-visual.h" // EXTERNAL INCLUDES -#include +#include +#include +#include // INTERNAL INCLUDES -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include -#include -#include -#include +#include +#include namespace Dali { - namespace Toolkit { - namespace Internal { - namespace { +const int CUSTOM_PROPERTY_COUNT(10); // ltr, wrap, pixel area, crop to mask, mask texture ratio + border/corner + +// stop behavior +DALI_ENUM_TO_STRING_TABLE_BEGIN(STOP_BEHAVIOR) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::DevelImageVisual::StopBehavior, CURRENT_FRAME) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::DevelImageVisual::StopBehavior, FIRST_FRAME) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::DevelImageVisual::StopBehavior, LAST_FRAME) +DALI_ENUM_TO_STRING_TABLE_END(STOP_BEHAVIOR) + // wrap modes -DALI_ENUM_TO_STRING_TABLE_BEGIN( WRAP_MODE ) -DALI_ENUM_TO_STRING_WITH_SCOPE( Dali::WrapMode, DEFAULT ) -DALI_ENUM_TO_STRING_WITH_SCOPE( Dali::WrapMode, CLAMP_TO_EDGE ) -DALI_ENUM_TO_STRING_WITH_SCOPE( Dali::WrapMode, REPEAT ) -DALI_ENUM_TO_STRING_WITH_SCOPE( Dali::WrapMode, MIRRORED_REPEAT ) -DALI_ENUM_TO_STRING_TABLE_END( WRAP_MODE ) +DALI_ENUM_TO_STRING_TABLE_BEGIN(WRAP_MODE) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::WrapMode, DEFAULT) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::WrapMode, CLAMP_TO_EDGE) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::WrapMode, REPEAT) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::WrapMode, MIRRORED_REPEAT) +DALI_ENUM_TO_STRING_TABLE_END(WRAP_MODE) + +// load policies +DALI_ENUM_TO_STRING_TABLE_BEGIN(LOAD_POLICY) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::LoadPolicy, IMMEDIATE) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::LoadPolicy, ATTACHED) +DALI_ENUM_TO_STRING_TABLE_END(LOAD_POLICY) + +// release policies +DALI_ENUM_TO_STRING_TABLE_BEGIN(RELEASE_POLICY) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::ReleasePolicy, DETACHED) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::ReleasePolicy, DESTROYED) + DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::ImageVisual::ReleasePolicy, NEVER) +DALI_ENUM_TO_STRING_TABLE_END(RELEASE_POLICY) + +static constexpr uint32_t SINGLE_IMAGE_COUNT = 1u; +static constexpr uint32_t FIRST_FRAME_INDEX = 0u; +static constexpr uint16_t MINIMUM_CACHESIZE = 1; +static constexpr Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); +static constexpr auto LOOP_FOREVER = -1; +static constexpr auto FIRST_LOOP = 0u; + +constexpr uint32_t TEXTURE_COUNT_FOR_GPU_ALPHA_MASK = 2u; + +#if defined(DEBUG_ENABLED) +Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_ANIMATED_IMAGE"); +#endif +} // namespace + +/** + * Multi-image Flow of execution + * + * | New + * | DoSetProperties() + * | OnInitialize() + * | CreateImageCache() + * | + * | DoSetOnScene() + * | PrepareTextureSet() + * | cache->FirstFrame() + * | + * | FrameReady(textureSet) + * | StartFirstFrame: + * | actor.AddRenderer + * | start timer + * | mRenderer.SetTextures(textureSet) + * | + * | Timer ticks + * | DisplayNextFrame() + * | if front frame is ready, + * | mRenderer.SetTextures( front frame's texture ) + * | else + * | Waiting for frame ready. + * | + * | FrameReady(textureSet) + * | mRenderer.SetTextures(textureSet) + * V + * Time + */ -const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); +AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory, const VisualUrl& imageUrl, const Property::Map& properties) +{ + AnimatedImageVisualPtr visual(new AnimatedImageVisual(factoryCache, shaderFactory)); + visual->InitializeAnimatedImage(imageUrl); + visual->SetProperties(properties); + + visual->Initialize(); + return visual; } -AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const VisualUrl& imageUrl, const Property::Map& properties ) +AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory, const Property::Array& imageUrls, const Property::Map& properties) { - AnimatedImageVisual* visual = new AnimatedImageVisual( factoryCache ); - visual->mImageUrl = imageUrl; - visual->SetProperties( properties ); + AnimatedImageVisualPtr visual(new AnimatedImageVisual(factoryCache, shaderFactory)); + visual->mImageUrls = new ImageCache::UrlList(); + visual->mImageUrls->reserve(imageUrls.Count()); + + for(unsigned int i = 0; i < imageUrls.Count(); ++i) + { + ImageCache::UrlStore urlStore; + urlStore.mTextureId = TextureManager::INVALID_TEXTURE_ID; + urlStore.mUrl = imageUrls[i].Get(); + visual->mImageUrls->push_back(urlStore); + } + visual->mFrameCount = imageUrls.Count(); + visual->SetProperties(properties); + + visual->Initialize(); return visual; } -AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const VisualUrl& imageUrl ) +AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory, const VisualUrl& imageUrl) { - AnimatedImageVisual* visual = new AnimatedImageVisual( factoryCache ); - visual->mImageUrl = imageUrl; + AnimatedImageVisualPtr visual(new AnimatedImageVisual(factoryCache, shaderFactory)); + visual->InitializeAnimatedImage(imageUrl); + + visual->Initialize(); return visual; } -AnimatedImageVisual::AnimatedImageVisual( VisualFactoryCache& factoryCache ) -: Visual::Base( factoryCache ), +void AnimatedImageVisual::InitializeAnimatedImage(const VisualUrl& imageUrl) +{ + mImageUrl = imageUrl; + mAnimatedImageLoading = AnimatedImageLoading::New(imageUrl.GetUrl(), imageUrl.IsLocalResource()); +} + +void AnimatedImageVisual::CreateImageCache() +{ + DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::CreateImageCache() batchSize:%d cacheSize:%d\n", mBatchSize, mCacheSize); + + TextureManager& textureManager = mFactoryCache.GetTextureManager(); + + if(mAnimatedImageLoading) + { + mImageCache = new RollingAnimatedImageCache(textureManager, mAnimatedImageLoading, mMaskingData, *this, mCacheSize, mBatchSize, IsSynchronousLoadingRequired(),mFactoryCache.GetPreMultiplyOnLoad()); + } + else if(mImageUrls) + { + // Ensure the batch size and cache size are no bigger than the number of URLs, + // and that the cache is at least as big as the batch size. + uint16_t numUrls = mImageUrls->size(); + uint16_t batchSize = std::max(std::min(mBatchSize, numUrls), MINIMUM_CACHESIZE); + uint16_t cacheSize = std::max(std::min(std::max(batchSize, mCacheSize), numUrls), MINIMUM_CACHESIZE); + if(cacheSize < numUrls) + { + mImageCache = new RollingImageCache(textureManager, *mImageUrls, mMaskingData, *this, cacheSize, batchSize, mFrameDelay); + } + else + { + mImageCache = new FixedImageCache(textureManager, *mImageUrls, mMaskingData, *this, batchSize, mFrameDelay); + } + } + + if(!mImageCache) + { + DALI_LOG_ERROR("mImageCache is null\n"); + } +} + +AnimatedImageVisual::AnimatedImageVisual(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory) +: Visual::Base(factoryCache, Visual::FittingMode::FILL, Toolkit::Visual::ANIMATED_IMAGE), mFrameDelayTimer(), - mPixelArea( FULL_TEXTURE_RECT ), + mPlacementActor(), + mImageVisualShaderFactory(shaderFactory), + mPixelArea(FULL_TEXTURE_RECT), mImageUrl(), + mAnimatedImageLoading(), + mFrameIndexForJumpTo(0), + mCurrentFrameIndex(FIRST_FRAME_INDEX), + mImageUrls(NULL), + mImageCache(NULL), + mCacheSize(2), + mBatchSize(2), + mFrameDelay(100), + mLoopCount(LOOP_FOREVER), + mCurrentLoopIndex(FIRST_LOOP), + mLoadPolicy(Toolkit::ImageVisual::LoadPolicy::ATTACHED), + mReleasePolicy(Toolkit::ImageVisual::ReleasePolicy::DETACHED), + mMaskingData(), + mFrameCount(0), mImageSize(), - mCurrentFrameIndex( 0 ), - mWrapModeU( WrapMode::DEFAULT ), - mWrapModeV( WrapMode::DEFAULT ) -{} + mActionStatus(DevelAnimatedImageVisual::Action::PLAY), + mWrapModeU(WrapMode::DEFAULT), + mWrapModeV(WrapMode::DEFAULT), + mStopBehavior(DevelImageVisual::StopBehavior::CURRENT_FRAME), + mStartFirstFrame(false), + mIsJumpTo(false) +{ + EnablePreMultipliedAlpha(mFactoryCache.GetPreMultiplyOnLoad()); +} AnimatedImageVisual::~AnimatedImageVisual() { + // AnimatedImageVisual destroyed so remove texture unless ReleasePolicy is set to never release + // If this is animated image, clear cache. Else if this is single frame image, this is affected be release policy. + if(mFrameCount > SINGLE_IMAGE_COUNT || mReleasePolicy != Toolkit::ImageVisual::ReleasePolicy::NEVER) + { + mImageCache->ClearCache(); + } + delete mImageCache; + delete mImageUrls; } -void AnimatedImageVisual::GetNaturalSize( Vector2& naturalSize ) +void AnimatedImageVisual::GetNaturalSize(Vector2& naturalSize) { - if( mImageSize.GetWidth() == 0 && mImageSize.GetHeight() == 0) + if(mImageSize.GetWidth() == 0 && mImageSize.GetHeight() == 0) { - mImageSize = Dali::GetGifImageSize( mImageUrl.GetUrl() ); + if(mMaskingData && mMaskingData->mAlphaMaskUrl.IsValid() && + mMaskingData->mCropToMask) + { + ImageDimensions dimensions = Dali::GetClosestImageSize(mMaskingData->mAlphaMaskUrl.GetUrl()); + if(dimensions != ImageDimensions(0, 0)) + { + mImageSize = dimensions; + naturalSize.x = dimensions.GetWidth(); + naturalSize.y = dimensions.GetHeight(); + return; + } + } + + if(mImageUrl.IsValid()) + { + mImageSize = mAnimatedImageLoading.GetImageSize(); + } + else if(mImageUrls && mImageUrls->size() > 0) + { + mImageSize = Dali::GetClosestImageSize((*mImageUrls)[0].mUrl.GetUrl()); + } } - naturalSize.width = mImageSize.GetWidth(); + naturalSize.width = mImageSize.GetWidth(); naturalSize.height = mImageSize.GetHeight(); } -void AnimatedImageVisual::DoCreatePropertyMap( Property::Map& map ) const +void AnimatedImageVisual::DoCreatePropertyMap(Property::Map& map) const { map.Clear(); - map.Insert( Toolkit::DevelVisual::Property::TYPE, Toolkit::DevelVisual::ANIMATED_IMAGE ); + bool sync = IsSynchronousLoadingRequired(); + map.Insert(Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING, sync); + + map.Insert(Toolkit::Visual::Property::TYPE, Toolkit::Visual::ANIMATED_IMAGE); - if( mImageUrl.IsValid() ) + if(mImageUrl.IsValid()) { - map.Insert( Toolkit::ImageVisual::Property::URL, mImageUrl.GetUrl() ); + map.Insert(Toolkit::ImageVisual::Property::URL, mImageUrl.GetUrl()); + } + if(mImageUrls != NULL && !mImageUrls->empty()) + { + Property::Array urls; + for(unsigned int i = 0; i < mImageUrls->size(); ++i) + { + urls.Add((*mImageUrls)[i].mUrl.GetUrl()); + } + Property::Value value(const_cast(urls)); + map.Insert(Toolkit::ImageVisual::Property::URL, value); + } + + map.Insert(Toolkit::ImageVisual::Property::PIXEL_AREA, mPixelArea); + map.Insert(Toolkit::ImageVisual::Property::WRAP_MODE_U, mWrapModeU); + map.Insert(Toolkit::ImageVisual::Property::WRAP_MODE_V, mWrapModeV); + + map.Insert(Toolkit::ImageVisual::Property::BATCH_SIZE, static_cast(mBatchSize)); + map.Insert(Toolkit::ImageVisual::Property::CACHE_SIZE, static_cast(mCacheSize)); + map.Insert(Toolkit::ImageVisual::Property::FRAME_DELAY, static_cast(mFrameDelay)); + map.Insert(Toolkit::DevelImageVisual::Property::LOOP_COUNT, static_cast(mLoopCount)); + map.Insert(Toolkit::DevelImageVisual::Property::CURRENT_FRAME_NUMBER, (mImageCache) ? static_cast(mImageCache->GetCurrentFrameIndex()) : -1); + map.Insert(Toolkit::DevelImageVisual::Property::TOTAL_FRAME_NUMBER, (mImageCache) ? static_cast((mAnimatedImageLoading) ? mAnimatedImageLoading.GetImageCount() : mImageCache->GetTotalFrameCount()) : -1); + + map.Insert(Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR, mStopBehavior); + + if(mMaskingData != nullptr) + { + map.Insert(Toolkit::ImageVisual::Property::ALPHA_MASK_URL, mMaskingData->mAlphaMaskUrl.GetUrl()); + map.Insert(Toolkit::ImageVisual::Property::MASK_CONTENT_SCALE, mMaskingData->mContentScaleFactor); + map.Insert(Toolkit::ImageVisual::Property::CROP_TO_MASK, mMaskingData->mCropToMask); + map.Insert(Toolkit::DevelImageVisual::Property::MASKING_TYPE, mMaskingData->mPreappliedMasking ? DevelImageVisual::MaskingType::MASKING_ON_LOADING : DevelImageVisual::MaskingType::MASKING_ON_RENDERING); } - map.Insert( Toolkit::ImageVisual::Property::PIXEL_AREA, mPixelArea ); - map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_U, mWrapModeU ); - map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_V, mWrapModeV ); + map.Insert(Toolkit::ImageVisual::Property::LOAD_POLICY, mLoadPolicy); + map.Insert(Toolkit::ImageVisual::Property::RELEASE_POLICY, mReleasePolicy); } -void AnimatedImageVisual::DoCreateInstancePropertyMap( Property::Map& map ) const +void AnimatedImageVisual::DoCreateInstancePropertyMap(Property::Map& map) const { // Do nothing } -void AnimatedImageVisual::DoSetProperties( const Property::Map& propertyMap ) +void AnimatedImageVisual::OnDoAction(const Dali::Property::Index actionId, const Dali::Property::Value& attributes) { - // url already passed in from constructor - - Property::Value* pixelAreaValue = propertyMap.Find( Toolkit::ImageVisual::Property::PIXEL_AREA, PIXEL_AREA_UNIFORM_NAME ); - if( pixelAreaValue ) + // Make not set any action when the resource status is already failed. + if(mImpl->mResourceStatus == Toolkit::Visual::ResourceStatus::FAILED) { - pixelAreaValue->Get( mPixelArea ); + return; } - Property::Value* wrapModeValueU = propertyMap.Find( Toolkit::ImageVisual::Property::WRAP_MODE_U, IMAGE_WRAP_MODE_U ); - if( wrapModeValueU ) + // Check if action is valid for this visual type and perform action if possible + switch(actionId) { - int value; - Scripting::GetEnumerationProperty( *wrapModeValueU, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, value ); - mWrapModeU = Dali::WrapMode::Type( value ); + case DevelAnimatedImageVisual::Action::PAUSE: + { + // Pause will be executed on next timer tick + mActionStatus = DevelAnimatedImageVisual::Action::PAUSE; + break; + } + case DevelAnimatedImageVisual::Action::PLAY: + { + if(mFrameDelayTimer && IsOnScene() && mActionStatus != DevelAnimatedImageVisual::Action::PLAY) + { + mFrameDelayTimer.Start(); + } + mActionStatus = DevelAnimatedImageVisual::Action::PLAY; + break; + } + case DevelAnimatedImageVisual::Action::STOP: + { + // STOP reset functionality will actually be done in a future change + // Stop will be executed on next timer tick + mActionStatus = DevelAnimatedImageVisual::Action::STOP; + mCurrentLoopIndex = FIRST_LOOP; + if(IsOnScene()) + { + DisplayNextFrame(); + } + break; + } + case DevelAnimatedImageVisual::Action::JUMP_TO: + { + int32_t frameNumber; + if(attributes.Get(frameNumber)) + { + if(frameNumber < 0 || frameNumber >= static_cast(mFrameCount)) + { + DALI_LOG_ERROR("Invalid frame index used.\n"); + } + else + { + mIsJumpTo = true; + mFrameIndexForJumpTo = frameNumber; + if(IsOnScene()) + { + DisplayNextFrame(); + } + } + } + break; + } } +} - Property::Value* wrapModeValueV = propertyMap.Find( Toolkit::ImageVisual::Property::WRAP_MODE_V, IMAGE_WRAP_MODE_V ); - if( wrapModeValueV ) +void AnimatedImageVisual::DoSetProperties(const Property::Map& propertyMap) +{ + // url[s] already passed in from constructor + for(Property::Map::SizeType iter = 0; iter < propertyMap.Count(); ++iter) + { + KeyValuePair keyValue = propertyMap.GetKeyValue(iter); + if(keyValue.first.type == Property::Key::INDEX) + { + DoSetProperty(keyValue.first.indexKey, keyValue.second); + } + else + { + if(keyValue.first == PIXEL_AREA_UNIFORM_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::PIXEL_AREA, keyValue.second); + } + else if(keyValue.first == IMAGE_WRAP_MODE_U) + { + DoSetProperty(Toolkit::ImageVisual::Property::WRAP_MODE_U, keyValue.second); + } + else if(keyValue.first == IMAGE_WRAP_MODE_V) + { + DoSetProperty(Toolkit::ImageVisual::Property::WRAP_MODE_V, keyValue.second); + } + else if(keyValue.first == BATCH_SIZE_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::BATCH_SIZE, keyValue.second); + } + else if(keyValue.first == CACHE_SIZE_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::CACHE_SIZE, keyValue.second); + } + else if(keyValue.first == FRAME_DELAY_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::FRAME_DELAY, keyValue.second); + } + else if(keyValue.first == LOOP_COUNT_NAME) + { + DoSetProperty(Toolkit::DevelImageVisual::Property::LOOP_COUNT, keyValue.second); + } + else if(keyValue.first == STOP_BEHAVIOR_NAME) + { + DoSetProperty(Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR, keyValue.second); + } + else if(keyValue.first == ALPHA_MASK_URL) + { + DoSetProperty(Toolkit::ImageVisual::Property::ALPHA_MASK_URL, keyValue.second); + } + else if(keyValue.first == MASK_CONTENT_SCALE_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::MASK_CONTENT_SCALE, keyValue.second); + } + else if(keyValue.first == CROP_TO_MASK_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::CROP_TO_MASK, keyValue.second); + } + else if(keyValue.first == MASKING_TYPE_NAME) + { + DoSetProperty(Toolkit::DevelImageVisual::Property::MASKING_TYPE, keyValue.second); + } + else if(keyValue.first == LOAD_POLICY_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::LOAD_POLICY, keyValue.second); + } + else if(keyValue.first == RELEASE_POLICY_NAME) + { + DoSetProperty(Toolkit::ImageVisual::Property::RELEASE_POLICY, keyValue.second); + } + else if(keyValue.first == SYNCHRONOUS_LOADING) + { + DoSetProperty(Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING, keyValue.second); + } + } + } + // Load image immediately if LOAD_POLICY requires it + if(mLoadPolicy == Toolkit::ImageVisual::LoadPolicy::IMMEDIATE) { - int value; - Scripting::GetEnumerationProperty( *wrapModeValueV, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, value ); - mWrapModeV = Dali::WrapMode::Type( value ); + PrepareTextureSet(); } } -void AnimatedImageVisual::DoSetOnStage( Actor& actor ) +void AnimatedImageVisual::DoSetProperty(Property::Index index, + const Property::Value& value) { - Texture texture = PrepareAnimatedImage(); - if( texture ) // if the image loading is successful + switch(index) { - bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE; - Shader shader = ImageVisual::GetImageShader( mFactoryCache, true, defaultWrapMode ); + case Toolkit::ImageVisual::Property::PIXEL_AREA: + { + value.Get(mPixelArea); + break; + } + case Toolkit::ImageVisual::Property::WRAP_MODE_U: + { + int wrapMode = 0; + if(Scripting::GetEnumerationProperty(value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode)) + { + mWrapModeU = Dali::WrapMode::Type(wrapMode); + } + else + { + mWrapModeU = Dali::WrapMode::Type::DEFAULT; + } + break; + } + case Toolkit::ImageVisual::Property::WRAP_MODE_V: + { + int wrapMode = 0; + if(Scripting::GetEnumerationProperty(value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode)) + { + mWrapModeV = Dali::WrapMode::Type(wrapMode); + } + else + { + mWrapModeV = Dali::WrapMode::Type::DEFAULT; + } + break; + } - Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY ); + case Toolkit::ImageVisual::Property::BATCH_SIZE: + { + int batchSize; + if(value.Get(batchSize)) + { + if(batchSize < 2) + { + DALI_LOG_ERROR("The minimum value of batch size is 2."); + } + else + { + mBatchSize = batchSize; + } + } + break; + } - TextureSet textureSet = TextureSet::New(); - textureSet.SetTexture( 0u, texture ); + case Toolkit::ImageVisual::Property::CACHE_SIZE: + { + int cacheSize; + if(value.Get(cacheSize)) + { + if(cacheSize < 2) + { + DALI_LOG_ERROR("The minimum value of cache size is 2."); + } + else + { + mCacheSize = cacheSize; + } + } + break; + } - mImpl->mRenderer = Renderer::New( geometry, shader ); - mImpl->mRenderer.SetTextures( textureSet ); + case Toolkit::ImageVisual::Property::FRAME_DELAY: + { + int frameDelay; + if(value.Get(frameDelay)) + { + mFrameDelay = frameDelay; + if(mImageCache) + { + mImageCache->SetInterval(static_cast(mFrameDelay)); + } + } + break; + } - // Register transform properties - mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT ); + case Toolkit::DevelImageVisual::Property::LOOP_COUNT: + { + int loopCount; + if(value.Get(loopCount)) + { + mLoopCount = loopCount; + } + break; + } - if( !defaultWrapMode ) // custom wrap mode + case Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR: { - Vector2 wrapMode(mWrapModeU-WrapMode::CLAMP_TO_EDGE, mWrapModeV-WrapMode::CLAMP_TO_EDGE); - wrapMode.Clamp( Vector2::ZERO, Vector2( 2.f, 2.f ) ); - mImpl->mRenderer.RegisterProperty( WRAP_MODE_UNIFORM_NAME, wrapMode ); + int32_t stopBehavior = mStopBehavior; + if(Scripting::GetEnumerationProperty(value, STOP_BEHAVIOR_TABLE, STOP_BEHAVIOR_TABLE_COUNT, stopBehavior)) + { + mStopBehavior = DevelImageVisual::StopBehavior::Type(stopBehavior); + } + break; } - if( mPixelArea != FULL_TEXTURE_RECT ) + case Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING: { - mImpl->mRenderer.RegisterProperty( PIXEL_AREA_UNIFORM_NAME, mPixelArea ); + bool sync = false; + value.Get(sync); + if(sync) + { + mImpl->mFlags |= Visual::Base::Impl::IS_SYNCHRONOUS_RESOURCE_LOADING; + } + else + { + mImpl->mFlags &= ~Visual::Base::Impl::IS_SYNCHRONOUS_RESOURCE_LOADING; + } + break; } - mCurrentFrameIndex = 0; - mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, mTextureRectContainer[mCurrentFrameIndex] ); - if( mFrameDelayContainer.Count() > 1 ) + case Toolkit::ImageVisual::Property::ALPHA_MASK_URL: { - mFrameDelayTimer = Timer::New( mFrameDelayContainer[0] ); - mFrameDelayTimer.TickSignal().Connect( this, &AnimatedImageVisual::DisplayNextFrame ); - mFrameDelayTimer.Start(); + std::string alphaUrl = ""; + if(value.Get(alphaUrl)) + { + AllocateMaskData(); + mMaskingData->mAlphaMaskUrl = alphaUrl; + } + break; + } + + case Toolkit::ImageVisual::Property::MASK_CONTENT_SCALE: + { + float scale = 1.0f; + if(value.Get(scale)) + { + AllocateMaskData(); + mMaskingData->mContentScaleFactor = scale; + } + break; + } + + case Toolkit::ImageVisual::Property::CROP_TO_MASK: + { + bool crop = false; + if(value.Get(crop)) + { + AllocateMaskData(); + mMaskingData->mCropToMask = crop; + } + break; + } + + case Toolkit::DevelImageVisual::Property::MASKING_TYPE: + { + int maskingType = 0; + if(value.Get(maskingType)) + { + AllocateMaskData(); + mMaskingData->mPreappliedMasking = Toolkit::DevelImageVisual::MaskingType::Type(maskingType) == Toolkit::DevelImageVisual::MaskingType::MASKING_ON_LOADING ? true : false; + } + break; } - actor.AddRenderer( mImpl->mRenderer ); + case Toolkit::ImageVisual::Property::RELEASE_POLICY: + { + int releasePolicy = 0; + Scripting::GetEnumerationProperty(value, RELEASE_POLICY_TABLE, RELEASE_POLICY_TABLE_COUNT, releasePolicy); + mReleasePolicy = Toolkit::ImageVisual::ReleasePolicy::Type(releasePolicy); + break; + } - // Animated Image loaded and ready to display - ResourceReady(); + case Toolkit::ImageVisual::Property::LOAD_POLICY: + { + int loadPolicy = 0; + Scripting::GetEnumerationProperty(value, LOAD_POLICY_TABLE, LOAD_POLICY_TABLE_COUNT, loadPolicy); + mLoadPolicy = Toolkit::ImageVisual::LoadPolicy::Type(loadPolicy); + break; + } } } -void AnimatedImageVisual::DoSetOffStage( Actor& actor ) +void AnimatedImageVisual::DoSetOnScene(Actor& actor) { - if( !mImpl->mRenderer ) - { - return; - } + mStartFirstFrame = true; + mPlacementActor = actor; + PrepareTextureSet(); +} + +void AnimatedImageVisual::DoSetOffScene(Actor& actor) +{ + DALI_ASSERT_DEBUG((bool)mImpl->mRenderer && "There should always be a renderer whilst on stage"); - if( mFrameDelayTimer ) + if(mFrameDelayTimer) { mFrameDelayTimer.Stop(); mFrameDelayTimer.Reset(); } - mTextureRectContainer.Clear(); - mFrameDelayContainer.Clear(); + actor.RemoveRenderer(mImpl->mRenderer); + if(mReleasePolicy == Toolkit::ImageVisual::ReleasePolicy::DETACHED) + { + mImageCache->ClearCache(); // If INVALID_TEXTURE_ID then removal will be attempted on atlas + mImpl->mResourceStatus = Toolkit::Visual::ResourceStatus::PREPARING; + + TextureSet textureSet = TextureSet::New(); + mImpl->mRenderer.SetTextures(textureSet); + } - actor.RemoveRenderer( mImpl->mRenderer ); - mImpl->mRenderer.Reset(); + mPlacementActor.Reset(); + mStartFirstFrame = false; + mCurrentFrameIndex = FIRST_FRAME_INDEX; + mCurrentLoopIndex = FIRST_LOOP; } void AnimatedImageVisual::OnSetTransform() { - if( mImpl->mRenderer ) + if(mImpl->mRenderer) + { + mImpl->mTransform.SetUniforms(mImpl->mRenderer, Direction::LEFT_TO_RIGHT); + } +} + +void AnimatedImageVisual::UpdateShader() +{ + if(mImpl->mRenderer) + { + Shader shader = GenerateShader(); + mImpl->mRenderer.SetShader(shader); + } +} + +Shader AnimatedImageVisual::GenerateShader() const +{ + bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE; + bool requiredAlphaMaskingOnRendering = (mMaskingData && !mMaskingData->mMaskImageLoadingFailed) ? !mMaskingData->mPreappliedMasking : false; + Shader shader; + shader = mImageVisualShaderFactory.GetShader( + mFactoryCache, + ImageVisualShaderFeature::FeatureBuilder() + .ApplyDefaultTextureWrapMode(defaultWrapMode) + .EnableRoundedCorner(IsRoundedCornerRequired()) + .EnableBorderline(IsBorderlineRequired()) + .EnableAlphaMaskingOnRendering(requiredAlphaMaskingOnRendering)); + return shader; +} + +void AnimatedImageVisual::OnInitialize() +{ + CreateImageCache(); + + bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE; + Shader shader = GenerateShader(); + + Geometry geometry = mFactoryCache.GetGeometry(VisualFactoryCache::QUAD_GEOMETRY); + + mImpl->mRenderer = VisualRenderer::New(geometry, shader); + mImpl->mRenderer.ReserveCustomProperties(CUSTOM_PROPERTY_COUNT); + + // Register transform properties + mImpl->mTransform.SetUniforms(mImpl->mRenderer, Direction::LEFT_TO_RIGHT); + + if(!defaultWrapMode) // custom wrap mode + { + Vector2 wrapMode(mWrapModeU - WrapMode::CLAMP_TO_EDGE, mWrapModeV - WrapMode::CLAMP_TO_EDGE); + wrapMode.Clamp(Vector2::ZERO, Vector2(2.f, 2.f)); + mImpl->mRenderer.RegisterProperty(WRAP_MODE_UNIFORM_NAME, wrapMode); + } + + if(mPixelArea != FULL_TEXTURE_RECT) + { + mImpl->mRenderer.RegisterProperty(PIXEL_AREA_UNIFORM_NAME, mPixelArea); + } + + if(mMaskingData) + { + mImpl->mRenderer.RegisterProperty(CROP_TO_MASK_NAME, static_cast(mMaskingData->mCropToMask)); + } + + // Enable PreMultipliedAlpha if it need premultiplied + auto preMultiplyOnLoad = IsPreMultipliedAlphaEnabled() && !mImpl->mCustomShader + ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD + : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY; + EnablePreMultipliedAlpha(preMultiplyOnLoad == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD); +} + +void AnimatedImageVisual::StartFirstFrame(TextureSet& textureSet, uint32_t firstInterval) +{ + DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::StartFirstFrame()\n"); + + mStartFirstFrame = false; + if(mImpl->mRenderer) + { + mImpl->mRenderer.SetTextures(textureSet); + CheckMaskTexture(); + + Actor actor = mPlacementActor.GetHandle(); + if(actor) + { + actor.AddRenderer(mImpl->mRenderer); + mPlacementActor.Reset(); + } + } + + if(mImpl->mResourceStatus != Toolkit::Visual::ResourceStatus::FAILED) { - mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT ); + if(mFrameCount > SINGLE_IMAGE_COUNT) + { + mFrameDelayTimer = Timer::New(firstInterval); + mFrameDelayTimer.TickSignal().Connect(this, &AnimatedImageVisual::DisplayNextFrame); + mFrameDelayTimer.Start(); + } + + DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::READY)\n"); + ResourceReady(Toolkit::Visual::ResourceStatus::READY); } + + mCurrentFrameIndex = FIRST_FRAME_INDEX; } -Texture AnimatedImageVisual::PrepareAnimatedImage() +void AnimatedImageVisual::PrepareTextureSet() { - // load from image file - std::vector pixelDataList; + TextureSet textureSet; + if(mImageCache) + { + textureSet = mImageCache->FirstFrame(); + } + + // Check whether synchronous loading is true or false for the first frame. + if(textureSet) + { + SetImageSize(textureSet); + } +} - if( mImageUrl.IsLocal() ) +void AnimatedImageVisual::SetImageSize(TextureSet& textureSet) +{ + if(textureSet) { - if( Dali::LoadAnimatedGifFromFile( mImageUrl.GetUrl().c_str() , pixelDataList, mFrameDelayContainer ) ) + Texture texture = textureSet.GetTexture(0); + if(texture) { - mImageSize.SetWidth( pixelDataList[0].GetWidth() ); - mImageSize.SetHeight( pixelDataList[0].GetHeight() ); + mImageSize.SetWidth(texture.GetWidth()); + mImageSize.SetHeight(texture.GetHeight()); + } - return Toolkit::ImageAtlas::PackToAtlas( pixelDataList, mTextureRectContainer ); + if(textureSet.GetTextureCount() > 1u && mMaskingData && mMaskingData->mCropToMask) + { + Texture maskTexture = textureSet.GetTexture(1); + if(maskTexture) + { + mImageSize.SetWidth(std::min(static_cast(mImageSize.GetWidth() * mMaskingData->mContentScaleFactor), maskTexture.GetWidth())); + mImageSize.SetHeight(std::min(static_cast(mImageSize.GetHeight() * mMaskingData->mContentScaleFactor), maskTexture.GetHeight())); + + float textureWidth = std::max(static_cast(texture.GetWidth() * mMaskingData->mContentScaleFactor), Dali::Math::MACHINE_EPSILON_1); + float textureHeight = std::max(static_cast(texture.GetHeight() * mMaskingData->mContentScaleFactor), Dali::Math::MACHINE_EPSILON_1); + Vector2 textureRatio(std::min(static_cast(maskTexture.GetWidth()), textureWidth) / textureWidth, + std::min(static_cast(maskTexture.GetHeight()), textureHeight) / textureHeight); + mImpl->mRenderer.RegisterProperty(MASK_TEXTURE_RATIO_NAME, textureRatio); + } } } +} + +void AnimatedImageVisual::FrameReady(TextureSet textureSet, uint32_t interval) +{ + // When image visual requested to load new frame to mImageCache and it is failed. + if(!mImageCache || !textureSet) + { + textureSet = SetLoadingFailed(); + } + SetImageSize(textureSet); - return Texture(); + if(mStartFirstFrame) + { + mFrameCount = mImageCache->GetTotalFrameCount(); + StartFirstFrame(textureSet, interval); + } + else + { + if(mImpl->mRenderer) + { + if(mFrameDelayTimer && interval > 0u) + { + mFrameDelayTimer.SetInterval(interval); + } + mImpl->mRenderer.SetTextures(textureSet); + CheckMaskTexture(); + } + } } bool AnimatedImageVisual::DisplayNextFrame() { - mCurrentFrameIndex = (mCurrentFrameIndex+1) % mFrameDelayContainer.Count(); - mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, mTextureRectContainer[mCurrentFrameIndex] ); - if( mFrameDelayTimer.GetInterval() != mFrameDelayContainer[mCurrentFrameIndex] ) + TextureSet textureSet; + bool continueTimer = false; + + if(mImageCache) + { + uint32_t frameIndex = mImageCache->GetCurrentFrameIndex(); + + if(mIsJumpTo) + { + mIsJumpTo = false; + frameIndex = mFrameIndexForJumpTo; + } + else if(mActionStatus == DevelAnimatedImageVisual::Action::PAUSE) + { + return false; + } + else if(mActionStatus == DevelAnimatedImageVisual::Action::STOP) + { + mCurrentLoopIndex = FIRST_LOOP; + if(mStopBehavior == DevelImageVisual::StopBehavior::FIRST_FRAME) + { + frameIndex = FIRST_FRAME_INDEX; + } + else if(mStopBehavior == DevelImageVisual::StopBehavior::LAST_FRAME) + { + frameIndex = mFrameCount - 1; + } + else + { + return false; // Do not draw already rendered scene twice. + } + } + else + { + if(mFrameCount > SINGLE_IMAGE_COUNT) + { + frameIndex++; + if(frameIndex >= mFrameCount) + { + frameIndex = FIRST_FRAME_INDEX; + ++mCurrentLoopIndex; + } + + if(mLoopCount >= 0 && mCurrentLoopIndex >= mLoopCount) + { + // This will stop timer + mActionStatus = DevelAnimatedImageVisual::Action::STOP; + return DisplayNextFrame(); + } + } + } + + DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::DisplayNextFrame(this:%p) CurrentFrameIndex:%d\n", this, frameIndex); + + textureSet = mImageCache->Frame(frameIndex); + + if(textureSet) + { + SetImageSize(textureSet); + if(mImpl->mRenderer) + { + mImpl->mRenderer.SetTextures(textureSet); + CheckMaskTexture(); + } + mFrameDelayTimer.SetInterval(mImageCache->GetFrameInterval(frameIndex)); + } + + mCurrentFrameIndex = frameIndex; + continueTimer = (mActionStatus == DevelAnimatedImageVisual::Action::PLAY && textureSet) ? true : false; + } + + return continueTimer; +} + +TextureSet AnimatedImageVisual::SetLoadingFailed() +{ + DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::FAILED)\n"); + ResourceReady(Toolkit::Visual::ResourceStatus::FAILED); + + Actor actor = mPlacementActor.GetHandle(); + Vector2 imageSize = Vector2::ZERO; + if(actor) { - mFrameDelayTimer.SetInterval( mFrameDelayContainer[mCurrentFrameIndex] ); + imageSize = actor.GetProperty(Actor::Property::SIZE).Get(); + } + mFactoryCache.UpdateBrokenImageRenderer(mImpl->mRenderer, imageSize); + TextureSet textureSet = mImpl->mRenderer.GetTextures(); + + if(mFrameDelayTimer) + { + mFrameDelayTimer.Stop(); + mFrameDelayTimer.Reset(); } - return true; + SetImageSize(textureSet); + + return textureSet; } +void AnimatedImageVisual::AllocateMaskData() +{ + if(!mMaskingData) + { + mMaskingData.reset(new TextureManager::MaskingData()); + } +} + +void AnimatedImageVisual::CheckMaskTexture() +{ + if(mMaskingData && !mMaskingData->mPreappliedMasking) + { + bool maskLoadFailed = true; + TextureSet textures = mImpl->mRenderer.GetTextures(); + if(textures && textures.GetTextureCount() >= TEXTURE_COUNT_FOR_GPU_ALPHA_MASK) + { + maskLoadFailed = false; + } + if(mMaskingData->mMaskImageLoadingFailed != maskLoadFailed) + { + mMaskingData->mMaskImageLoadingFailed = maskLoadFailed; + UpdateShader(); + } + } +} } // namespace Internal