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