Merge branch 'devel/master' into devel/graphics
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / visuals / animated-image / animated-image-visual.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include "animated-image-visual.h"
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/adaptor-framework/image-loading.h>
23 #include <dali/integration-api/debug.h>
24 #include <memory>
25
26 // INTERNAL INCLUDES
27 #include <dali-toolkit/devel-api/image-loader/image-atlas.h>
28 #include <dali-toolkit/devel-api/image-loader/texture-manager.h>
29 #include <dali-toolkit/devel-api/visuals/image-visual-properties-devel.h>
30 #include <dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h>
31 #include <dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.h>
32 #include <dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h>
33 #include <dali-toolkit/internal/visuals/image-visual-shader-factory.h>
34 #include <dali-toolkit/internal/visuals/visual-base-data-impl.h>
35 #include <dali-toolkit/internal/visuals/visual-factory-cache.h>
36 #include <dali-toolkit/internal/visuals/visual-factory-impl.h>
37 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
38 #include <dali-toolkit/public-api/visuals/image-visual-properties.h>
39 #include <dali-toolkit/public-api/visuals/visual-properties.h>
40
41 namespace Dali
42 {
43 namespace Toolkit
44 {
45 namespace Internal
46 {
47 namespace
48 {
49 // stop behavior
50 DALI_ENUM_TO_STRING_TABLE_BEGIN(STOP_BEHAVIOR)
51   DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::DevelImageVisual::StopBehavior, CURRENT_FRAME)
52   DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::DevelImageVisual::StopBehavior, FIRST_FRAME)
53   DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::Toolkit::DevelImageVisual::StopBehavior, LAST_FRAME)
54 DALI_ENUM_TO_STRING_TABLE_END(STOP_BEHAVIOR)
55
56 // wrap modes
57 DALI_ENUM_TO_STRING_TABLE_BEGIN(WRAP_MODE)
58   DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::WrapMode, DEFAULT)
59   DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::WrapMode, CLAMP_TO_EDGE)
60   DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::WrapMode, REPEAT)
61   DALI_ENUM_TO_STRING_WITH_SCOPE(Dali::WrapMode, MIRRORED_REPEAT)
62 DALI_ENUM_TO_STRING_TABLE_END(WRAP_MODE)
63
64 const Vector4  FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
65 constexpr auto LOOP_FOREVER = -1;
66
67 #if defined(DEBUG_ENABLED)
68 Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_ANIMATED_IMAGE");
69 #endif
70 } // namespace
71
72 /**
73  * Multi-image  Flow of execution
74  *
75  *   | New
76  *   |   DoSetProperties()
77  *   |   LoadFirstBatch()
78  *   |     new cache
79  *   |       cache->LoadBatch()
80  *   |
81  *   | DoSetOnScene()
82  *   |   PrepareTextureSet()
83  *   |     cache->FirstFrame()
84  *   |   CreateRenderer()    (Doesn't become ready until first frame loads)
85  *   |   StartFirstFrame()
86  *   |
87  *   | FrameReady(textureSet)
88  *   |   start first frame:
89  *   |     actor.AddRenderer
90  *   |     start timer
91  *   |   mRenderer.SetTextures(textureSet)
92  *   |
93  *   | Timer ticks
94  *   |   DisplayNextFrame()
95  *   |     if front frame is ready,
96  *   |       mRenderer.SetTextures( front frame's texture )
97  *   |     else
98  *   |       mWaitingForTexture=true
99  *   |     cache->LoadBatch()
100  *   |
101  *   | FrameReady(textureSet)
102  *   |   mRenderer.SetTextures(textureSet)
103  *   V
104  *  Time
105  */
106
107 AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory, const VisualUrl& imageUrl, const Property::Map& properties)
108 {
109   AnimatedImageVisualPtr visual(new AnimatedImageVisual(factoryCache, shaderFactory));
110   visual->InitializeAnimatedImage(imageUrl);
111   visual->SetProperties(properties);
112
113   if(visual->mFrameCount > 0)
114   {
115     visual->LoadFirstBatch();
116   }
117
118   visual->Initialize();
119
120   return visual;
121 }
122
123 AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory, const Property::Array& imageUrls, const Property::Map& properties)
124 {
125   AnimatedImageVisualPtr visual(new AnimatedImageVisual(factoryCache, shaderFactory));
126   visual->mImageUrls = new ImageCache::UrlList();
127   visual->mImageUrls->reserve(imageUrls.Count());
128
129   for(unsigned int i = 0; i < imageUrls.Count(); ++i)
130   {
131     ImageCache::UrlStore urlStore;
132     urlStore.mTextureId = TextureManager::INVALID_TEXTURE_ID;
133     urlStore.mUrl       = imageUrls[i].Get<std::string>();
134     visual->mImageUrls->push_back(urlStore);
135   }
136   visual->mFrameCount = imageUrls.Count();
137   visual->SetProperties(properties);
138
139   if(visual->mFrameCount > 0)
140   {
141     visual->LoadFirstBatch();
142   }
143
144   visual->Initialize();
145
146   return visual;
147 }
148
149 AnimatedImageVisualPtr AnimatedImageVisual::New(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory, const VisualUrl& imageUrl)
150 {
151   AnimatedImageVisualPtr visual(new AnimatedImageVisual(factoryCache, shaderFactory));
152   visual->InitializeAnimatedImage(imageUrl);
153
154   if(visual->mFrameCount > 0)
155   {
156     visual->LoadFirstBatch();
157   }
158
159   visual->Initialize();
160
161   return visual;
162 }
163
164 void AnimatedImageVisual::InitializeAnimatedImage(const VisualUrl& imageUrl)
165 {
166   mImageUrl             = imageUrl;
167   mAnimatedImageLoading = AnimatedImageLoading::New(imageUrl.GetUrl(), imageUrl.IsLocalResource());
168   mFrameCount           = mAnimatedImageLoading.GetImageCount();
169 }
170
171 AnimatedImageVisual::AnimatedImageVisual(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory)
172 : Visual::Base(factoryCache, Visual::FittingMode::FIT_KEEP_ASPECT_RATIO, Toolkit::Visual::ANIMATED_IMAGE),
173   mFrameDelayTimer(),
174   mPlacementActor(),
175   mImageVisualShaderFactory(shaderFactory),
176   mPixelArea(FULL_TEXTURE_RECT),
177   mImageUrl(),
178   mAnimatedImageLoading(),
179   mFrameIndexForJumpTo(0),
180   mImageUrls(NULL),
181   mImageCache(NULL),
182   mCacheSize(2),
183   mBatchSize(2),
184   mFrameDelay(100),
185   mLoopCount(LOOP_FOREVER),
186   mCurrentLoopIndex(0),
187   mUrlIndex(0),
188   mFrameCount(0),
189   mImageSize(),
190   mWrapModeU(WrapMode::DEFAULT),
191   mWrapModeV(WrapMode::DEFAULT),
192   mActionStatus(DevelAnimatedImageVisual::Action::PLAY),
193   mStopBehavior(DevelImageVisual::StopBehavior::CURRENT_FRAME),
194   mStartFirstFrame(false),
195   mIsJumpTo(false)
196 {
197 }
198
199 AnimatedImageVisual::~AnimatedImageVisual()
200 {
201   delete mImageCache;
202   delete mImageUrls;
203 }
204
205 void AnimatedImageVisual::GetNaturalSize(Vector2& naturalSize)
206 {
207   if(mImageSize.GetWidth() == 0 && mImageSize.GetHeight() == 0)
208   {
209     if(mImageUrl.IsValid())
210     {
211       mImageSize = mAnimatedImageLoading.GetImageSize();
212     }
213     else if(mImageUrls && mImageUrls->size() > 0)
214     {
215       mImageSize = Dali::GetClosestImageSize((*mImageUrls)[0].mUrl);
216     }
217   }
218
219   naturalSize.width  = mImageSize.GetWidth();
220   naturalSize.height = mImageSize.GetHeight();
221 }
222
223 void AnimatedImageVisual::DoCreatePropertyMap(Property::Map& map) const
224 {
225   map.Clear();
226
227   bool sync = IsSynchronousLoadingRequired();
228   map.Insert(Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING, sync);
229
230   map.Insert(Toolkit::Visual::Property::TYPE, Toolkit::Visual::ANIMATED_IMAGE);
231
232   if(mImageUrl.IsValid())
233   {
234     map.Insert(Toolkit::ImageVisual::Property::URL, mImageUrl.GetUrl());
235   }
236   if(mImageUrls != NULL && !mImageUrls->empty())
237   {
238     Property::Array urls;
239     for(unsigned int i = 0; i < mImageUrls->size(); ++i)
240     {
241       urls.Add((*mImageUrls)[i].mUrl);
242     }
243     Property::Value value(const_cast<Property::Array&>(urls));
244     map.Insert(Toolkit::ImageVisual::Property::URL, value);
245   }
246
247   map.Insert(Toolkit::ImageVisual::Property::PIXEL_AREA, mPixelArea);
248   map.Insert(Toolkit::ImageVisual::Property::WRAP_MODE_U, mWrapModeU);
249   map.Insert(Toolkit::ImageVisual::Property::WRAP_MODE_V, mWrapModeV);
250
251   map.Insert(Toolkit::ImageVisual::Property::BATCH_SIZE, static_cast<int>(mBatchSize));
252   map.Insert(Toolkit::ImageVisual::Property::CACHE_SIZE, static_cast<int>(mCacheSize));
253   map.Insert(Toolkit::ImageVisual::Property::FRAME_DELAY, static_cast<int>(mFrameDelay));
254   map.Insert(Toolkit::DevelImageVisual::Property::LOOP_COUNT, static_cast<int>(mLoopCount));
255   map.Insert(Toolkit::DevelImageVisual::Property::CURRENT_FRAME_NUMBER, (mImageCache) ? static_cast<int32_t>(mImageCache->GetCurrentFrameIndex()) : -1);
256   map.Insert(Toolkit::DevelImageVisual::Property::TOTAL_FRAME_NUMBER, (mImageCache) ? static_cast<int32_t>(mImageCache->GetTotalFrameCount()) : -1);
257
258   map.Insert(Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR, mStopBehavior);
259 }
260
261 void AnimatedImageVisual::DoCreateInstancePropertyMap(Property::Map& map) const
262 {
263   // Do nothing
264 }
265
266 void AnimatedImageVisual::OnDoAction(const Dali::Property::Index actionId, const Dali::Property::Value& attributes)
267 {
268   // Check if action is valid for this visual type and perform action if possible
269
270   switch(actionId)
271   {
272     case DevelAnimatedImageVisual::Action::PAUSE:
273     {
274       // Pause will be executed on next timer tick
275       mActionStatus = DevelAnimatedImageVisual::Action::PAUSE;
276       break;
277     }
278     case DevelAnimatedImageVisual::Action::PLAY:
279     {
280       if(mFrameDelayTimer && IsOnScene() && mActionStatus != DevelAnimatedImageVisual::Action::PLAY)
281       {
282         mFrameDelayTimer.Start();
283       }
284       mActionStatus = DevelAnimatedImageVisual::Action::PLAY;
285       break;
286     }
287     case DevelAnimatedImageVisual::Action::STOP:
288     {
289       // STOP reset functionality will actually be done in a future change
290       // Stop will be executed on next timer tick
291       mActionStatus = DevelAnimatedImageVisual::Action::STOP;
292       if(IsOnScene())
293       {
294         DisplayNextFrame();
295       }
296       break;
297     }
298     case DevelAnimatedImageVisual::Action::JUMP_TO:
299     {
300       int32_t frameNumber;
301       if(attributes.Get(frameNumber))
302       {
303         if(frameNumber < 0 || frameNumber >= static_cast<int32_t>(mFrameCount))
304         {
305           DALI_LOG_ERROR("Invalid frame index used.\n");
306         }
307         else
308         {
309           mIsJumpTo            = true;
310           mFrameIndexForJumpTo = frameNumber;
311           if(IsOnScene())
312           {
313             DisplayNextFrame();
314           }
315         }
316       }
317       break;
318     }
319   }
320 }
321
322 void AnimatedImageVisual::DoSetProperties(const Property::Map& propertyMap)
323 {
324   // url[s] already passed in from constructor
325
326   for(Property::Map::SizeType iter = 0; iter < propertyMap.Count(); ++iter)
327   {
328     KeyValuePair keyValue = propertyMap.GetKeyValue(iter);
329     if(keyValue.first.type == Property::Key::INDEX)
330     {
331       DoSetProperty(keyValue.first.indexKey, keyValue.second);
332     }
333     else
334     {
335       if(keyValue.first == PIXEL_AREA_UNIFORM_NAME)
336       {
337         DoSetProperty(Toolkit::ImageVisual::Property::PIXEL_AREA, keyValue.second);
338       }
339       else if(keyValue.first == IMAGE_WRAP_MODE_U)
340       {
341         DoSetProperty(Toolkit::ImageVisual::Property::WRAP_MODE_U, keyValue.second);
342       }
343       else if(keyValue.first == IMAGE_WRAP_MODE_V)
344       {
345         DoSetProperty(Toolkit::ImageVisual::Property::WRAP_MODE_V, keyValue.second);
346       }
347       else if(keyValue.first == BATCH_SIZE_NAME)
348       {
349         DoSetProperty(Toolkit::ImageVisual::Property::BATCH_SIZE, keyValue.second);
350       }
351       else if(keyValue.first == CACHE_SIZE_NAME)
352       {
353         DoSetProperty(Toolkit::ImageVisual::Property::CACHE_SIZE, keyValue.second);
354       }
355       else if(keyValue.first == FRAME_DELAY_NAME)
356       {
357         DoSetProperty(Toolkit::ImageVisual::Property::FRAME_DELAY, keyValue.second);
358       }
359       else if(keyValue.first == LOOP_COUNT_NAME)
360       {
361         DoSetProperty(Toolkit::DevelImageVisual::Property::LOOP_COUNT, keyValue.second);
362       }
363       else if(keyValue.first == STOP_BEHAVIOR_NAME)
364       {
365         DoSetProperty(Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR, keyValue.second);
366       }
367     }
368   }
369 }
370
371 void AnimatedImageVisual::DoSetProperty(Property::Index        index,
372                                         const Property::Value& value)
373 {
374   switch(index)
375   {
376     case Toolkit::ImageVisual::Property::PIXEL_AREA:
377     {
378       value.Get(mPixelArea);
379       break;
380     }
381     case Toolkit::ImageVisual::Property::WRAP_MODE_U:
382     {
383       int wrapMode = 0;
384       if(Scripting::GetEnumerationProperty(value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode))
385       {
386         mWrapModeU = Dali::WrapMode::Type(wrapMode);
387       }
388       else
389       {
390         mWrapModeU = Dali::WrapMode::Type::DEFAULT;
391       }
392       break;
393     }
394     case Toolkit::ImageVisual::Property::WRAP_MODE_V:
395     {
396       int wrapMode = 0;
397       if(Scripting::GetEnumerationProperty(value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode))
398       {
399         mWrapModeV = Dali::WrapMode::Type(wrapMode);
400       }
401       else
402       {
403         mWrapModeV = Dali::WrapMode::Type::DEFAULT;
404       }
405       break;
406     }
407
408     case Toolkit::ImageVisual::Property::BATCH_SIZE:
409     {
410       int batchSize;
411       if(value.Get(batchSize))
412       {
413         if(batchSize < 2)
414         {
415           DALI_LOG_ERROR("The minimum value of batch size is 2.");
416         }
417         else
418         {
419           mBatchSize = batchSize;
420         }
421       }
422       break;
423     }
424
425     case Toolkit::ImageVisual::Property::CACHE_SIZE:
426     {
427       int cacheSize;
428       if(value.Get(cacheSize))
429       {
430         if(cacheSize < 2)
431         {
432           DALI_LOG_ERROR("The minimum value of cache size is 2.");
433         }
434         else
435         {
436           mCacheSize = cacheSize;
437         }
438       }
439       break;
440     }
441
442     case Toolkit::ImageVisual::Property::FRAME_DELAY:
443     {
444       int frameDelay;
445       if(value.Get(frameDelay))
446       {
447         mFrameDelay = frameDelay;
448       }
449       break;
450     }
451
452     case Toolkit::DevelImageVisual::Property::LOOP_COUNT:
453     {
454       int loopCount;
455       if(value.Get(loopCount))
456       {
457         mLoopCount = loopCount;
458       }
459       break;
460     }
461
462     case Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR:
463     {
464       int32_t stopBehavior = mStopBehavior;
465       if(Scripting::GetEnumerationProperty(value, STOP_BEHAVIOR_TABLE, STOP_BEHAVIOR_TABLE_COUNT, stopBehavior))
466       {
467         mStopBehavior = DevelImageVisual::StopBehavior::Type(stopBehavior);
468       }
469       break;
470     }
471
472     case Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING:
473     {
474       bool sync = false;
475       value.Get(sync);
476       if(sync)
477       {
478         mImpl->mFlags |= Impl::IS_SYNCHRONOUS_RESOURCE_LOADING;
479       }
480       else
481       {
482         mImpl->mFlags &= ~Impl::IS_SYNCHRONOUS_RESOURCE_LOADING;
483       }
484       break;
485     }
486   }
487 }
488
489 void AnimatedImageVisual::DoSetOnScene(Actor& actor)
490 {
491   mPlacementActor       = actor;
492   TextureSet textureSet = PrepareTextureSet();
493
494   if(textureSet) // if the image loading is successful
495   {
496     StartFirstFrame(textureSet);
497   }
498   else
499   {
500     mStartFirstFrame = true;
501   }
502 }
503
504 void AnimatedImageVisual::DoSetOffScene(Actor& actor)
505 {
506   DALI_ASSERT_DEBUG((bool)mImpl->mRenderer && "There should always be a renderer whilst on stage");
507
508   if(mFrameDelayTimer)
509   {
510     mFrameDelayTimer.Stop();
511     mFrameDelayTimer.Reset();
512   }
513
514   actor.RemoveRenderer(mImpl->mRenderer);
515   mPlacementActor.Reset();
516   mStartFirstFrame = false;
517 }
518
519 void AnimatedImageVisual::OnSetTransform()
520 {
521   if(mImpl->mRenderer)
522   {
523     mImpl->mTransform.RegisterUniforms(mImpl->mRenderer, Direction::LEFT_TO_RIGHT);
524   }
525 }
526
527 void AnimatedImageVisual::OnInitialize()
528 {
529   bool   defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE;
530   bool   atlasing        = false;
531   Shader shader          = mImageVisualShaderFactory.GetShader(mFactoryCache, atlasing, defaultWrapMode, IsRoundedCornerRequired());
532
533   Geometry geometry = mFactoryCache.GetGeometry(VisualFactoryCache::QUAD_GEOMETRY);
534
535   mImpl->mRenderer = Renderer::New(geometry, shader);
536
537   // Register transform properties
538   mImpl->mTransform.RegisterUniforms(mImpl->mRenderer, Direction::LEFT_TO_RIGHT);
539
540   if(!defaultWrapMode) // custom wrap mode
541   {
542     Vector2 wrapMode(mWrapModeU - WrapMode::CLAMP_TO_EDGE, mWrapModeV - WrapMode::CLAMP_TO_EDGE);
543     wrapMode.Clamp(Vector2::ZERO, Vector2(2.f, 2.f));
544     mImpl->mRenderer.RegisterProperty(WRAP_MODE_UNIFORM_NAME, wrapMode);
545   }
546
547   if(mPixelArea != FULL_TEXTURE_RECT)
548   {
549     mImpl->mRenderer.RegisterProperty(PIXEL_AREA_UNIFORM_NAME, mPixelArea);
550   }
551 }
552
553 void AnimatedImageVisual::LoadFirstBatch()
554 {
555   // Ensure the batch size and cache size are no bigger than the number of URLs,
556   // and that the cache is at least as big as the batch size.
557   uint16_t numUrls   = 0;
558   uint16_t batchSize = 1;
559   uint16_t cacheSize = 1;
560
561   if(mImageUrls)
562   {
563     numUrls = mImageUrls->size();
564   }
565   else
566   {
567     numUrls = mFrameCount;
568   }
569
570   batchSize = std::min(mBatchSize, numUrls);
571   cacheSize = std::min(std::max(batchSize, mCacheSize), numUrls);
572
573   DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::LoadFirstBatch()  batchSize:%d  cacheSize:%d\n", batchSize, cacheSize);
574
575   mUrlIndex                      = 0;
576   TextureManager& textureManager = mFactoryCache.GetTextureManager();
577
578   if(mAnimatedImageLoading)
579   {
580     mImageCache = new RollingAnimatedImageCache(textureManager, mAnimatedImageLoading, mFrameCount, *this, cacheSize, batchSize, IsSynchronousLoadingRequired());
581   }
582   else if(mImageUrls)
583   {
584     if(batchSize > 0 && cacheSize > 0)
585     {
586       if(cacheSize < numUrls)
587       {
588         mImageCache = new RollingImageCache(textureManager, *mImageUrls, *this, cacheSize, batchSize);
589       }
590       else
591       {
592         mImageCache = new FixedImageCache(textureManager, *mImageUrls, *this, batchSize);
593       }
594     }
595     else
596     {
597       mImageCache = new RollingImageCache(textureManager, *mImageUrls, *this, 1, 1);
598     }
599   }
600
601   if(!mImageCache)
602   {
603     DALI_LOG_ERROR("mImageCache is null\n");
604   }
605 }
606
607 void AnimatedImageVisual::StartFirstFrame(TextureSet& textureSet)
608 {
609   DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::StartFirstFrame()\n");
610
611   mStartFirstFrame = false;
612   if(mImpl->mRenderer)
613   {
614     mImpl->mRenderer.SetTextures(textureSet);
615
616     Actor actor = mPlacementActor.GetHandle();
617     if(actor)
618     {
619       actor.AddRenderer(mImpl->mRenderer);
620       mPlacementActor.Reset();
621     }
622   }
623
624   if(mFrameCount > 1)
625   {
626     int frameDelay = mImageCache->GetFrameInterval(0);
627     if(frameDelay == 0u)
628     {
629       frameDelay = mFrameDelay; // from URL array
630     }
631     mFrameDelayTimer = Timer::New(frameDelay);
632     mFrameDelayTimer.TickSignal().Connect(this, &AnimatedImageVisual::DisplayNextFrame);
633     mFrameDelayTimer.Start();
634   }
635   DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::READY)\n");
636   ResourceReady(Toolkit::Visual::ResourceStatus::READY);
637 }
638
639 TextureSet AnimatedImageVisual::PrepareTextureSet()
640 {
641   TextureSet textureSet;
642   if(mImageCache)
643   {
644     textureSet = mImageCache->FirstFrame();
645   }
646
647   if(textureSet)
648   {
649     SetImageSize(textureSet);
650   }
651
652   return textureSet;
653 }
654
655 void AnimatedImageVisual::SetImageSize(TextureSet& textureSet)
656 {
657   if(textureSet)
658   {
659     Texture texture = textureSet.GetTexture(0);
660     if(texture)
661     {
662       mImageSize.SetWidth(texture.GetWidth());
663       mImageSize.SetHeight(texture.GetHeight());
664     }
665   }
666 }
667
668 void AnimatedImageVisual::FrameReady(TextureSet textureSet)
669 {
670   if(textureSet)
671   {
672     SetImageSize(textureSet);
673
674     if(mStartFirstFrame)
675     {
676       StartFirstFrame(textureSet);
677     }
678     else
679     {
680       if(mImpl->mRenderer)
681       {
682         mImpl->mRenderer.SetTextures(textureSet);
683       }
684     }
685   }
686   else
687   {
688     DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::FAILED)\n");
689     ResourceReady(Toolkit::Visual::ResourceStatus::FAILED);
690   }
691 }
692
693 bool AnimatedImageVisual::DisplayNextFrame()
694 {
695   bool continueTimer = false;
696
697   if(mImageCache)
698   {
699     bool     nextFrame  = false;
700     uint32_t frameIndex = mImageCache->GetCurrentFrameIndex();
701
702     if(mIsJumpTo)
703     {
704       mIsJumpTo  = false;
705       frameIndex = mFrameIndexForJumpTo;
706     }
707     else if(mActionStatus == DevelAnimatedImageVisual::Action::PAUSE)
708     {
709       return false;
710     }
711     else if(mActionStatus == DevelAnimatedImageVisual::Action::STOP)
712     {
713       frameIndex = 0;
714       if(mStopBehavior == DevelImageVisual::StopBehavior::FIRST_FRAME)
715       {
716         frameIndex = 0;
717       }
718       else if(mStopBehavior == DevelImageVisual::StopBehavior::LAST_FRAME)
719       {
720         frameIndex = mFrameCount - 1;
721       }
722       else
723       {
724         return false; // Do not draw already rendered scene twice.
725       }
726     }
727     else
728     {
729       if(mFrameCount > 1)
730       {
731         nextFrame = true;
732         frameIndex++;
733         if(frameIndex >= mFrameCount)
734         {
735           frameIndex %= mFrameCount;
736           ++mCurrentLoopIndex;
737         }
738
739         if(mLoopCount >= 0 && mCurrentLoopIndex >= mLoopCount)
740         {
741           // This will stop timer
742           mActionStatus = DevelAnimatedImageVisual::Action::STOP;
743           return DisplayNextFrame();
744         }
745       }
746
747       unsigned int delay = mImageCache->GetFrameInterval(frameIndex);
748       if(delay > 0u)
749       {
750         if(mFrameDelayTimer.GetInterval() != delay)
751         {
752           mFrameDelayTimer.SetInterval(delay);
753         }
754       }
755     }
756
757     DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::DisplayNextFrame(this:%p) CurrentFrameIndex:%d\n", this, frameIndex);
758
759     TextureSet textureSet;
760     if(nextFrame)
761     {
762       textureSet = mImageCache->NextFrame();
763     }
764     else
765     {
766       textureSet = mImageCache->Frame(frameIndex);
767     }
768
769     if(textureSet)
770     {
771       SetImageSize(textureSet);
772       if(mImpl->mRenderer)
773       {
774         mImpl->mRenderer.SetTextures(textureSet);
775       }
776     }
777
778     continueTimer = (mActionStatus == DevelAnimatedImageVisual::Action::PLAY) ? true : false;
779   }
780
781   return continueTimer;
782 }
783
784 } // namespace Internal
785
786 } // namespace Toolkit
787
788 } // namespace Dali