Merge "Use broken image when animated image loading is failed." 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   bool   atlasing        = false;
542   Shader shader          = mImageVisualShaderFactory.GetShader(mFactoryCache, atlasing, defaultWrapMode, IsRoundedCornerRequired());
543
544   Geometry geometry = mFactoryCache.GetGeometry(VisualFactoryCache::QUAD_GEOMETRY);
545
546   mImpl->mRenderer = Renderer::New(geometry, shader);
547
548   // Register transform properties
549   mImpl->mTransform.RegisterUniforms(mImpl->mRenderer, Direction::LEFT_TO_RIGHT);
550
551   if(!defaultWrapMode) // custom wrap mode
552   {
553     Vector2 wrapMode(mWrapModeU - WrapMode::CLAMP_TO_EDGE, mWrapModeV - WrapMode::CLAMP_TO_EDGE);
554     wrapMode.Clamp(Vector2::ZERO, Vector2(2.f, 2.f));
555     mImpl->mRenderer.RegisterProperty(WRAP_MODE_UNIFORM_NAME, wrapMode);
556   }
557
558   if(mPixelArea != FULL_TEXTURE_RECT)
559   {
560     mImpl->mRenderer.RegisterProperty(PIXEL_AREA_UNIFORM_NAME, mPixelArea);
561   }
562 }
563
564 void AnimatedImageVisual::LoadFirstBatch()
565 {
566   // Ensure the batch size and cache size are no bigger than the number of URLs,
567   // and that the cache is at least as big as the batch size.
568   uint16_t numUrls   = 0;
569   uint16_t batchSize = 1;
570   uint16_t cacheSize = 1;
571
572   if(mImageUrls)
573   {
574     numUrls = mImageUrls->size();
575   }
576   else
577   {
578     numUrls = mFrameCount;
579   }
580
581   batchSize = std::min(mBatchSize, numUrls);
582   cacheSize = std::min(std::max(batchSize, mCacheSize), numUrls);
583
584   DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::LoadFirstBatch()  batchSize:%d  cacheSize:%d\n", batchSize, cacheSize);
585
586   mUrlIndex                      = 0;
587   TextureManager& textureManager = mFactoryCache.GetTextureManager();
588
589   if(mAnimatedImageLoading)
590   {
591     mImageCache = new RollingAnimatedImageCache(textureManager, mAnimatedImageLoading, mFrameCount, *this, cacheSize, batchSize, IsSynchronousLoadingRequired());
592   }
593   else if(mImageUrls)
594   {
595     if(batchSize > 0 && cacheSize > 0)
596     {
597       if(cacheSize < numUrls)
598       {
599         mImageCache = new RollingImageCache(textureManager, *mImageUrls, *this, cacheSize, batchSize);
600       }
601       else
602       {
603         mImageCache = new FixedImageCache(textureManager, *mImageUrls, *this, batchSize);
604       }
605     }
606     else
607     {
608       mImageCache = new RollingImageCache(textureManager, *mImageUrls, *this, 1, 1);
609     }
610   }
611
612   if(!mImageCache)
613   {
614     DALI_LOG_ERROR("mImageCache is null\n");
615   }
616 }
617
618 void AnimatedImageVisual::StartFirstFrame(TextureSet& textureSet)
619 {
620   DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::StartFirstFrame()\n");
621
622   mStartFirstFrame = false;
623   if(mImpl->mRenderer)
624   {
625     mImpl->mRenderer.SetTextures(textureSet);
626
627     Actor actor = mPlacementActor.GetHandle();
628     if(actor)
629     {
630       actor.AddRenderer(mImpl->mRenderer);
631       mPlacementActor.Reset();
632     }
633   }
634
635   if(mFrameCount > 1)
636   {
637     int frameDelay = mImageCache->GetFrameInterval(0);
638     if(frameDelay == 0u)
639     {
640       frameDelay = mFrameDelay; // from URL array
641     }
642     mFrameDelayTimer = Timer::New(frameDelay);
643     mFrameDelayTimer.TickSignal().Connect(this, &AnimatedImageVisual::DisplayNextFrame);
644     mFrameDelayTimer.Start();
645   }
646
647   if(mImpl->mResourceStatus != Toolkit::Visual::ResourceStatus::FAILED)
648   {
649     DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::READY)\n");
650     ResourceReady(Toolkit::Visual::ResourceStatus::READY);
651   }
652 }
653
654 TextureSet AnimatedImageVisual::PrepareTextureSet()
655 {
656   TextureSet textureSet;
657   if(mImageCache)
658   {
659     textureSet = mImageCache->FirstFrame();
660   }
661
662   if(textureSet)
663   {
664     SetImageSize(textureSet);
665   }
666
667   return textureSet;
668 }
669
670 void AnimatedImageVisual::SetImageSize(TextureSet& textureSet)
671 {
672   if(textureSet)
673   {
674     Texture texture = textureSet.GetTexture(0);
675     if(texture)
676     {
677       mImageSize.SetWidth(texture.GetWidth());
678       mImageSize.SetHeight(texture.GetHeight());
679     }
680   }
681 }
682
683 void AnimatedImageVisual::FrameReady(TextureSet textureSet)
684 {
685   // When image visual requested to load new frame to mImageCache and it is failed.
686   if(!textureSet)
687   {
688     textureSet = SetLoadingFailed();
689   }
690
691   SetImageSize(textureSet);
692
693   if(mStartFirstFrame)
694   {
695     StartFirstFrame(textureSet);
696   }
697   else
698   {
699     if(mImpl->mRenderer)
700     {
701       mImpl->mRenderer.SetTextures(textureSet);
702     }
703   }
704 }
705
706 bool AnimatedImageVisual::DisplayNextFrame()
707 {
708   TextureSet textureSet;
709   bool       continueTimer = false;
710
711   if(mImageCache)
712   {
713     bool     nextFrame  = false;
714     uint32_t frameIndex = mImageCache->GetCurrentFrameIndex();
715
716     if(mIsJumpTo)
717     {
718       mIsJumpTo  = false;
719       frameIndex = mFrameIndexForJumpTo;
720     }
721     else if(mActionStatus == DevelAnimatedImageVisual::Action::PAUSE)
722     {
723       return false;
724     }
725     else if(mActionStatus == DevelAnimatedImageVisual::Action::STOP)
726     {
727       frameIndex = 0;
728       if(mStopBehavior == DevelImageVisual::StopBehavior::FIRST_FRAME)
729       {
730         frameIndex = 0;
731       }
732       else if(mStopBehavior == DevelImageVisual::StopBehavior::LAST_FRAME)
733       {
734         frameIndex = mFrameCount - 1;
735       }
736       else
737       {
738         return false; // Do not draw already rendered scene twice.
739       }
740     }
741     else
742     {
743       if(mFrameCount > 1)
744       {
745         nextFrame = true;
746         frameIndex++;
747         if(frameIndex >= mFrameCount)
748         {
749           frameIndex %= mFrameCount;
750           ++mCurrentLoopIndex;
751         }
752
753         if(mLoopCount >= 0 && mCurrentLoopIndex >= mLoopCount)
754         {
755           // This will stop timer
756           mActionStatus = DevelAnimatedImageVisual::Action::STOP;
757           return DisplayNextFrame();
758         }
759       }
760
761       unsigned int delay = mImageCache->GetFrameInterval(frameIndex);
762       if(delay > 0u)
763       {
764         if(mFrameDelayTimer.GetInterval() != delay)
765         {
766           mFrameDelayTimer.SetInterval(delay);
767         }
768       }
769     }
770
771     DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::DisplayNextFrame(this:%p) CurrentFrameIndex:%d\n", this, frameIndex);
772
773     if(nextFrame)
774     {
775       textureSet = mImageCache->NextFrame();
776     }
777     else
778     {
779       textureSet = mImageCache->Frame(frameIndex);
780     }
781
782     continueTimer = (mActionStatus == DevelAnimatedImageVisual::Action::PLAY) ? true : false;
783   }
784
785   if(textureSet)
786   {
787     SetImageSize(textureSet);
788     if(mImpl->mRenderer)
789     {
790       mImpl->mRenderer.SetTextures(textureSet);
791     }
792   }
793
794   return continueTimer;
795 }
796
797 TextureSet AnimatedImageVisual::SetLoadingFailed()
798 {
799   DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "ResourceReady(ResourceStatus::FAILED)\n");
800   ResourceReady(Toolkit::Visual::ResourceStatus::FAILED);
801
802   TextureSet textureSet  = TextureSet::New();
803   Texture    brokenImage = mFactoryCache.GetBrokenVisualImage();
804   textureSet.SetTexture(0u, brokenImage);
805
806   if(mFrameDelayTimer)
807   {
808     mFrameDelayTimer.Stop();
809     mFrameDelayTimer.Reset();
810   }
811
812   SetImageSize(textureSet);
813
814   return textureSet;
815 }
816
817 } // namespace Internal
818
819 } // namespace Toolkit
820
821 } // namespace Dali