Merge "Adds transition effect" into devel/master
[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   // Make not set any action when the resource status is already failed.
269   if(mImpl->mResourceStatus == Toolkit::Visual::ResourceStatus::FAILED)
270   {
271     return;
272   }
273
274   // Check if action is valid for this visual type and perform action if possible
275   switch(actionId)
276   {
277     case DevelAnimatedImageVisual::Action::PAUSE:
278     {
279       // Pause will be executed on next timer tick
280       mActionStatus = DevelAnimatedImageVisual::Action::PAUSE;
281       break;
282     }
283     case DevelAnimatedImageVisual::Action::PLAY:
284     {
285       if(mFrameDelayTimer && IsOnScene() && mActionStatus != DevelAnimatedImageVisual::Action::PLAY)
286       {
287         mFrameDelayTimer.Start();
288       }
289       mActionStatus = DevelAnimatedImageVisual::Action::PLAY;
290       break;
291     }
292     case DevelAnimatedImageVisual::Action::STOP:
293     {
294       // STOP reset functionality will actually be done in a future change
295       // Stop will be executed on next timer tick
296       mActionStatus = DevelAnimatedImageVisual::Action::STOP;
297       if(IsOnScene())
298       {
299         DisplayNextFrame();
300       }
301       break;
302     }
303     case DevelAnimatedImageVisual::Action::JUMP_TO:
304     {
305       int32_t frameNumber;
306       if(attributes.Get(frameNumber))
307       {
308         if(frameNumber < 0 || frameNumber >= static_cast<int32_t>(mFrameCount))
309         {
310           DALI_LOG_ERROR("Invalid frame index used.\n");
311         }
312         else
313         {
314           mIsJumpTo            = true;
315           mFrameIndexForJumpTo = frameNumber;
316           if(IsOnScene())
317           {
318             DisplayNextFrame();
319           }
320         }
321       }
322       break;
323     }
324   }
325 }
326
327 void AnimatedImageVisual::DoSetProperties(const Property::Map& propertyMap)
328 {
329   // url[s] already passed in from constructor
330   for(Property::Map::SizeType iter = 0; iter < propertyMap.Count(); ++iter)
331   {
332     KeyValuePair keyValue = propertyMap.GetKeyValue(iter);
333     if(keyValue.first.type == Property::Key::INDEX)
334     {
335       DoSetProperty(keyValue.first.indexKey, keyValue.second);
336     }
337     else
338     {
339       if(keyValue.first == PIXEL_AREA_UNIFORM_NAME)
340       {
341         DoSetProperty(Toolkit::ImageVisual::Property::PIXEL_AREA, keyValue.second);
342       }
343       else if(keyValue.first == IMAGE_WRAP_MODE_U)
344       {
345         DoSetProperty(Toolkit::ImageVisual::Property::WRAP_MODE_U, keyValue.second);
346       }
347       else if(keyValue.first == IMAGE_WRAP_MODE_V)
348       {
349         DoSetProperty(Toolkit::ImageVisual::Property::WRAP_MODE_V, keyValue.second);
350       }
351       else if(keyValue.first == BATCH_SIZE_NAME)
352       {
353         DoSetProperty(Toolkit::ImageVisual::Property::BATCH_SIZE, keyValue.second);
354       }
355       else if(keyValue.first == CACHE_SIZE_NAME)
356       {
357         DoSetProperty(Toolkit::ImageVisual::Property::CACHE_SIZE, keyValue.second);
358       }
359       else if(keyValue.first == FRAME_DELAY_NAME)
360       {
361         DoSetProperty(Toolkit::ImageVisual::Property::FRAME_DELAY, keyValue.second);
362       }
363       else if(keyValue.first == LOOP_COUNT_NAME)
364       {
365         DoSetProperty(Toolkit::DevelImageVisual::Property::LOOP_COUNT, keyValue.second);
366       }
367       else if(keyValue.first == STOP_BEHAVIOR_NAME)
368       {
369         DoSetProperty(Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR, keyValue.second);
370       }
371     }
372   }
373 }
374
375 void AnimatedImageVisual::DoSetProperty(Property::Index        index,
376                                         const Property::Value& value)
377 {
378   switch(index)
379   {
380     case Toolkit::ImageVisual::Property::PIXEL_AREA:
381     {
382       value.Get(mPixelArea);
383       break;
384     }
385     case Toolkit::ImageVisual::Property::WRAP_MODE_U:
386     {
387       int wrapMode = 0;
388       if(Scripting::GetEnumerationProperty(value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode))
389       {
390         mWrapModeU = Dali::WrapMode::Type(wrapMode);
391       }
392       else
393       {
394         mWrapModeU = Dali::WrapMode::Type::DEFAULT;
395       }
396       break;
397     }
398     case Toolkit::ImageVisual::Property::WRAP_MODE_V:
399     {
400       int wrapMode = 0;
401       if(Scripting::GetEnumerationProperty(value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode))
402       {
403         mWrapModeV = Dali::WrapMode::Type(wrapMode);
404       }
405       else
406       {
407         mWrapModeV = Dali::WrapMode::Type::DEFAULT;
408       }
409       break;
410     }
411
412     case Toolkit::ImageVisual::Property::BATCH_SIZE:
413     {
414       int batchSize;
415       if(value.Get(batchSize))
416       {
417         if(batchSize < 2)
418         {
419           DALI_LOG_ERROR("The minimum value of batch size is 2.");
420         }
421         else
422         {
423           mBatchSize = batchSize;
424         }
425       }
426       break;
427     }
428
429     case Toolkit::ImageVisual::Property::CACHE_SIZE:
430     {
431       int cacheSize;
432       if(value.Get(cacheSize))
433       {
434         if(cacheSize < 2)
435         {
436           DALI_LOG_ERROR("The minimum value of cache size is 2.");
437         }
438         else
439         {
440           mCacheSize = cacheSize;
441         }
442       }
443       break;
444     }
445
446     case Toolkit::ImageVisual::Property::FRAME_DELAY:
447     {
448       int frameDelay;
449       if(value.Get(frameDelay))
450       {
451         mFrameDelay = frameDelay;
452       }
453       break;
454     }
455
456     case Toolkit::DevelImageVisual::Property::LOOP_COUNT:
457     {
458       int loopCount;
459       if(value.Get(loopCount))
460       {
461         mLoopCount = loopCount;
462       }
463       break;
464     }
465
466     case Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR:
467     {
468       int32_t stopBehavior = mStopBehavior;
469       if(Scripting::GetEnumerationProperty(value, STOP_BEHAVIOR_TABLE, STOP_BEHAVIOR_TABLE_COUNT, stopBehavior))
470       {
471         mStopBehavior = DevelImageVisual::StopBehavior::Type(stopBehavior);
472       }
473       break;
474     }
475
476     case Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING:
477     {
478       bool sync = false;
479       value.Get(sync);
480       if(sync)
481       {
482         mImpl->mFlags |= Impl::IS_SYNCHRONOUS_RESOURCE_LOADING;
483       }
484       else
485       {
486         mImpl->mFlags &= ~Impl::IS_SYNCHRONOUS_RESOURCE_LOADING;
487       }
488       break;
489     }
490   }
491 }
492
493 void AnimatedImageVisual::DoSetOnScene(Actor& actor)
494 {
495   mPlacementActor       = actor;
496   TextureSet textureSet = PrepareTextureSet();
497
498   // Loading animated image file is failed.
499   if(!mImageCache ||
500      (mAnimatedImageLoading && !mAnimatedImageLoading.HasLoadingSucceeded()))
501   {
502     textureSet = SetLoadingFailed();
503   }
504
505   if(textureSet) // if the image loading is successful
506   {
507     StartFirstFrame(textureSet);
508   }
509   else
510   {
511     mStartFirstFrame = true;
512   }
513 }
514
515 void AnimatedImageVisual::DoSetOffScene(Actor& actor)
516 {
517   DALI_ASSERT_DEBUG((bool)mImpl->mRenderer && "There should always be a renderer whilst on stage");
518
519   if(mFrameDelayTimer)
520   {
521     mFrameDelayTimer.Stop();
522     mFrameDelayTimer.Reset();
523   }
524
525   actor.RemoveRenderer(mImpl->mRenderer);
526   mPlacementActor.Reset();
527   mStartFirstFrame = false;
528 }
529
530 void AnimatedImageVisual::OnSetTransform()
531 {
532   if(mImpl->mRenderer)
533   {
534     mImpl->mTransform.RegisterUniforms(mImpl->mRenderer, Direction::LEFT_TO_RIGHT);
535   }
536 }
537
538 void AnimatedImageVisual::OnInitialize()
539 {
540   bool   defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE;
541   Shader shader          = mImageVisualShaderFactory.GetShader(
542     mFactoryCache,
543     TextureAtlas::DISABLED,
544     defaultWrapMode ? DefaultTextureWrapMode::APPLY : DefaultTextureWrapMode::DO_NOT_APPLY,
545     IsRoundedCornerRequired() ? RoundedCorner::ENABLED : RoundedCorner::DISABLED,
546     IsBorderlineRequired() ? Borderline::ENABLED : Borderline::DISABLED);
547
548   Geometry geometry = mFactoryCache.GetGeometry(VisualFactoryCache::QUAD_GEOMETRY);
549
550   mImpl->mRenderer = Renderer::New(geometry, shader);
551
552   // Register transform properties
553   mImpl->mTransform.RegisterUniforms(mImpl->mRenderer, Direction::LEFT_TO_RIGHT);
554
555   if(!defaultWrapMode) // custom wrap mode
556   {
557     Vector2 wrapMode(mWrapModeU - WrapMode::CLAMP_TO_EDGE, mWrapModeV - WrapMode::CLAMP_TO_EDGE);
558     wrapMode.Clamp(Vector2::ZERO, Vector2(2.f, 2.f));
559     mImpl->mRenderer.RegisterProperty(WRAP_MODE_UNIFORM_NAME, wrapMode);
560   }
561
562   if(mPixelArea != FULL_TEXTURE_RECT)
563   {
564     mImpl->mRenderer.RegisterProperty(PIXEL_AREA_UNIFORM_NAME, mPixelArea);
565   }
566 }
567
568 void AnimatedImageVisual::LoadFirstBatch()
569 {
570   // Ensure the batch size and cache size are no bigger than the number of URLs,
571   // and that the cache is at least as big as the batch size.
572   uint16_t numUrls   = 0;
573   uint16_t batchSize = 1;
574   uint16_t cacheSize = 1;
575
576   if(mImageUrls)
577   {
578     numUrls = mImageUrls->size();
579   }
580   else
581   {
582     numUrls = mFrameCount;
583   }
584
585   batchSize = std::min(mBatchSize, numUrls);
586   cacheSize = std::min(std::max(batchSize, mCacheSize), numUrls);
587
588   DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::LoadFirstBatch()  batchSize:%d  cacheSize:%d\n", batchSize, cacheSize);
589
590   mUrlIndex                      = 0;
591   TextureManager& textureManager = mFactoryCache.GetTextureManager();
592
593   if(mAnimatedImageLoading)
594   {
595     mImageCache = new RollingAnimatedImageCache(textureManager, mAnimatedImageLoading, mFrameCount, *this, cacheSize, batchSize, IsSynchronousLoadingRequired());
596   }
597   else if(mImageUrls)
598   {
599     if(batchSize > 0 && cacheSize > 0)
600     {
601       if(cacheSize < numUrls)
602       {
603         mImageCache = new RollingImageCache(textureManager, *mImageUrls, *this, cacheSize, batchSize);
604       }
605       else
606       {
607         mImageCache = new FixedImageCache(textureManager, *mImageUrls, *this, batchSize);
608       }
609     }
610     else
611     {
612       mImageCache = new RollingImageCache(textureManager, *mImageUrls, *this, 1, 1);
613     }
614   }
615
616   if(!mImageCache)
617   {
618     DALI_LOG_ERROR("mImageCache is null\n");
619   }
620 }
621
622 void AnimatedImageVisual::StartFirstFrame(TextureSet& textureSet)
623 {
624   DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::StartFirstFrame()\n");
625
626   mStartFirstFrame = false;
627   if(mImpl->mRenderer)
628   {
629     mImpl->mRenderer.SetTextures(textureSet);
630
631     Actor actor = mPlacementActor.GetHandle();
632     if(actor)
633     {
634       actor.AddRenderer(mImpl->mRenderer);
635       mPlacementActor.Reset();
636     }
637   }
638
639   if(mFrameCount > 1)
640   {
641     int frameDelay = mImageCache->GetFrameInterval(0);
642     if(frameDelay == 0u)
643     {
644       frameDelay = mFrameDelay; // from URL array
645     }
646     mFrameDelayTimer = Timer::New(frameDelay);
647     mFrameDelayTimer.TickSignal().Connect(this, &AnimatedImageVisual::DisplayNextFrame);
648     mFrameDelayTimer.Start();
649   }
650
651   if(mImpl->mResourceStatus != Toolkit::Visual::ResourceStatus::FAILED)
652   {
653     DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::READY)\n");
654     ResourceReady(Toolkit::Visual::ResourceStatus::READY);
655   }
656 }
657
658 TextureSet AnimatedImageVisual::PrepareTextureSet()
659 {
660   TextureSet textureSet;
661   if(mImageCache)
662   {
663     textureSet = mImageCache->FirstFrame();
664   }
665
666   if(textureSet)
667   {
668     SetImageSize(textureSet);
669   }
670
671   return textureSet;
672 }
673
674 void AnimatedImageVisual::SetImageSize(TextureSet& textureSet)
675 {
676   if(textureSet)
677   {
678     Texture texture = textureSet.GetTexture(0);
679     if(texture)
680     {
681       mImageSize.SetWidth(texture.GetWidth());
682       mImageSize.SetHeight(texture.GetHeight());
683     }
684   }
685 }
686
687 void AnimatedImageVisual::FrameReady(TextureSet textureSet)
688 {
689   // When image visual requested to load new frame to mImageCache and it is failed.
690   if(!textureSet)
691   {
692     textureSet = SetLoadingFailed();
693   }
694
695   SetImageSize(textureSet);
696
697   if(mStartFirstFrame)
698   {
699     StartFirstFrame(textureSet);
700   }
701   else
702   {
703     if(mImpl->mRenderer)
704     {
705       mImpl->mRenderer.SetTextures(textureSet);
706     }
707   }
708 }
709
710 bool AnimatedImageVisual::DisplayNextFrame()
711 {
712   TextureSet textureSet;
713   bool       continueTimer = false;
714
715   if(mImageCache)
716   {
717     bool     nextFrame  = false;
718     uint32_t frameIndex = mImageCache->GetCurrentFrameIndex();
719
720     if(mIsJumpTo)
721     {
722       mIsJumpTo  = false;
723       frameIndex = mFrameIndexForJumpTo;
724     }
725     else if(mActionStatus == DevelAnimatedImageVisual::Action::PAUSE)
726     {
727       return false;
728     }
729     else if(mActionStatus == DevelAnimatedImageVisual::Action::STOP)
730     {
731       frameIndex = 0;
732       if(mStopBehavior == DevelImageVisual::StopBehavior::FIRST_FRAME)
733       {
734         frameIndex = 0;
735       }
736       else if(mStopBehavior == DevelImageVisual::StopBehavior::LAST_FRAME)
737       {
738         frameIndex = mFrameCount - 1;
739       }
740       else
741       {
742         return false; // Do not draw already rendered scene twice.
743       }
744     }
745     else
746     {
747       if(mFrameCount > 1)
748       {
749         nextFrame = true;
750         frameIndex++;
751         if(frameIndex >= mFrameCount)
752         {
753           frameIndex %= mFrameCount;
754           ++mCurrentLoopIndex;
755         }
756
757         if(mLoopCount >= 0 && mCurrentLoopIndex >= mLoopCount)
758         {
759           // This will stop timer
760           mActionStatus = DevelAnimatedImageVisual::Action::STOP;
761           return DisplayNextFrame();
762         }
763       }
764
765       unsigned int delay = mImageCache->GetFrameInterval(frameIndex);
766       if(delay > 0u)
767       {
768         if(mFrameDelayTimer.GetInterval() != delay)
769         {
770           mFrameDelayTimer.SetInterval(delay);
771         }
772       }
773     }
774
775     DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::DisplayNextFrame(this:%p) CurrentFrameIndex:%d\n", this, frameIndex);
776
777     if(nextFrame)
778     {
779       textureSet = mImageCache->NextFrame();
780     }
781     else
782     {
783       textureSet = mImageCache->Frame(frameIndex);
784     }
785
786     continueTimer = (mActionStatus == DevelAnimatedImageVisual::Action::PLAY) ? true : false;
787   }
788
789   if(textureSet)
790   {
791     SetImageSize(textureSet);
792     if(mImpl->mRenderer)
793     {
794       mImpl->mRenderer.SetTextures(textureSet);
795     }
796   }
797
798   return continueTimer;
799 }
800
801 TextureSet AnimatedImageVisual::SetLoadingFailed()
802 {
803   DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::FAILED)\n");
804   ResourceReady(Toolkit::Visual::ResourceStatus::FAILED);
805
806   TextureSet textureSet  = TextureSet::New();
807   Texture    brokenImage = mFactoryCache.GetBrokenVisualImage();
808   textureSet.SetTexture(0u, brokenImage);
809
810   if(mFrameDelayTimer)
811   {
812     mFrameDelayTimer.Stop();
813     mFrameDelayTimer.Reset();
814   }
815
816   SetImageSize(textureSet);
817
818   return textureSet;
819 }
820
821 } // namespace Internal
822
823 } // namespace Toolkit
824
825 } // namespace Dali