Loop count support for animated GIF
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / visuals / animated-image / animated-image-visual.cpp
1 /*
2  * Copyright (c) 2018 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/public-api/visuals/image-visual-properties.h>
28 #include <dali-toolkit/public-api/visuals/visual-properties.h>
29 #include <dali-toolkit/internal/visuals/visual-factory-impl.h>
30 #include <dali-toolkit/internal/visuals/visual-factory-cache.h>
31 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
32 #include <dali-toolkit/internal/visuals/visual-base-data-impl.h>
33 #include <dali-toolkit/internal/visuals/animated-image/fixed-image-cache.h>
34 #include <dali-toolkit/internal/visuals/animated-image/rolling-image-cache.h>
35 #include <dali-toolkit/internal/visuals/animated-image/rolling-gif-image-cache.h>
36 #include <dali-toolkit/internal/visuals/image/image-visual.h>
37 #include <dali-toolkit/devel-api/image-loader/image-atlas.h>
38 #include <dali-toolkit/devel-api/image-loader/texture-manager.h>
39
40 namespace Dali
41 {
42
43 namespace Toolkit
44 {
45
46 namespace Internal
47 {
48
49 namespace
50 {
51 // wrap modes
52 DALI_ENUM_TO_STRING_TABLE_BEGIN( WRAP_MODE )
53 DALI_ENUM_TO_STRING_WITH_SCOPE( Dali::WrapMode, DEFAULT )
54 DALI_ENUM_TO_STRING_WITH_SCOPE( Dali::WrapMode, CLAMP_TO_EDGE )
55 DALI_ENUM_TO_STRING_WITH_SCOPE( Dali::WrapMode, REPEAT )
56 DALI_ENUM_TO_STRING_WITH_SCOPE( Dali::WrapMode, MIRRORED_REPEAT )
57 DALI_ENUM_TO_STRING_TABLE_END( WRAP_MODE )
58
59 const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
60 constexpr auto LOOP_FOREVER = -1;
61
62 #if defined(DEBUG_ENABLED)
63 Debug::Filter* gAnimImgLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_ANIMATED_IMAGE");
64 #endif
65 }
66
67
68 /**
69  * Multi-image  Flow of execution
70  *
71  *   | New
72  *   |   DoSetProperties()
73  *   |   LoadFirstBatch()
74  *   |     new cache
75  *   |       cache->LoadBatch()
76  *   |
77  *   | DoSetOnStage()
78  *   |   PrepareTextureSet()
79  *   |     cache->FirstFrame()
80  *   |   CreateRenderer()    (Doesn't become ready until first frame loads)
81  *   |   StartFirstFrame()
82  *   |
83  *   | FrameReady(textureSet)
84  *   |   start first frame:
85  *   |     actor.AddRenderer
86  *   |     start timer
87  *   |   mRenderer.SetTextures(textureSet)
88  *   |
89  *   | Timer ticks
90  *   |   DisplayNextFrame()
91  *   |     if front frame is ready,
92  *   |       mRenderer.SetTextures( front frame's texture )
93  *   |     else
94  *   |       mWaitingForTexture=true
95  *   |     cache->LoadBatch()
96  *   |
97  *   | FrameReady(textureSet)
98  *   |   mRenderer.SetTextures(textureSet)
99  *   V
100  *  Time
101  */
102
103 AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const VisualUrl& imageUrl, const Property::Map& properties )
104 {
105   AnimatedImageVisualPtr visual( new AnimatedImageVisual( factoryCache ) );
106   visual->InitializeGif( imageUrl );
107   visual->SetProperties( properties );
108
109   if( visual->mFrameCount > 0 )
110   {
111     visual->LoadFirstBatch();
112   }
113
114   return visual;
115 }
116
117 AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const Property::Array& imageUrls, const Property::Map& properties )
118 {
119   AnimatedImageVisualPtr visual( new AnimatedImageVisual( factoryCache ) );
120   visual->mImageUrls = new ImageCache::UrlList();
121   visual->mImageUrls->reserve( imageUrls.Count() );
122
123   for( unsigned int i=0; i < imageUrls.Count(); ++i)
124   {
125     ImageCache::UrlStore urlStore;
126     urlStore.mTextureId = TextureManager::INVALID_TEXTURE_ID;
127     urlStore.mUrl = imageUrls[i].Get<std::string>();
128     visual->mImageUrls->push_back( urlStore );
129   }
130   visual->mFrameCount = imageUrls.Count();
131   visual->SetProperties( properties );
132
133   if( visual->mFrameCount > 0 )
134   {
135     visual->LoadFirstBatch();
136   }
137
138   return visual;
139 }
140
141 AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const VisualUrl& imageUrl )
142 {
143   AnimatedImageVisualPtr visual( new AnimatedImageVisual( factoryCache ) );
144   visual->InitializeGif( imageUrl );
145
146   if( visual->mFrameCount > 0 )
147   {
148     visual->LoadFirstBatch();
149   }
150
151   return visual;
152 }
153
154 void AnimatedImageVisual::InitializeGif( const VisualUrl& imageUrl )
155 {
156   mImageUrl = imageUrl;
157   mGifLoading = GifLoading::New( imageUrl.GetUrl() );
158   mFrameCount = mGifLoading->GetImageCount();
159   mGifLoading->LoadFrameDelays( mFrameDelayContainer );
160 }
161
162 AnimatedImageVisual::AnimatedImageVisual( VisualFactoryCache& factoryCache )
163 : Visual::Base( factoryCache ),
164   mFrameDelayTimer(),
165   mPlacementActor(),
166   mPixelArea( FULL_TEXTURE_RECT ),
167   mImageUrl(),
168   mGifLoading( nullptr ),
169   mCurrentFrameIndex( 0 ),
170   mImageUrls( NULL ),
171   mImageCache( NULL ),
172   mCacheSize( 1 ),
173   mBatchSize( 1 ),
174   mFrameDelay( 100 ),
175   mLoopCount( LOOP_FOREVER ),
176   mCurrentLoopIndex( 0 ),
177   mUrlIndex( 0 ),
178   mFrameCount( 0 ),
179   mImageSize(),
180   mWrapModeU( WrapMode::DEFAULT ),
181   mWrapModeV( WrapMode::DEFAULT ),
182   mStartFirstFrame(false)
183 {}
184
185 AnimatedImageVisual::~AnimatedImageVisual()
186 {
187   delete mImageCache;
188   delete mImageUrls;
189 }
190
191 void AnimatedImageVisual::GetNaturalSize( Vector2& naturalSize )
192 {
193   if( mImageSize.GetWidth() == 0 &&  mImageSize.GetHeight() == 0)
194   {
195     if( mImageUrl.IsValid() )
196     {
197       mImageSize = mGifLoading->GetImageSize();
198     }
199     else if( mImageUrls && mImageUrls->size() > 0 )
200     {
201       mImageSize = Dali::GetClosestImageSize( (*mImageUrls)[0].mUrl );
202     }
203   }
204
205   naturalSize.width = mImageSize.GetWidth();
206   naturalSize.height = mImageSize.GetHeight();
207 }
208
209 void AnimatedImageVisual::DoCreatePropertyMap( Property::Map& map ) const
210 {
211   map.Clear();
212
213   map.Insert( Toolkit::Visual::Property::TYPE, Toolkit::Visual::ANIMATED_IMAGE );
214
215   if( mImageUrl.IsValid() )
216   {
217     map.Insert( Toolkit::ImageVisual::Property::URL, mImageUrl.GetUrl() );
218   }
219   if( mImageUrls != NULL && ! mImageUrls->empty() )
220   {
221     Property::Array urls;
222     for( unsigned int i=0; i<mImageUrls->size(); ++i)
223     {
224       urls.Add( (*mImageUrls)[i].mUrl );
225     }
226     Property::Value value( const_cast<Property::Array&>(urls) );
227     map.Insert( Toolkit::ImageVisual::Property::URL, value );
228   }
229
230   map.Insert( Toolkit::ImageVisual::Property::PIXEL_AREA, mPixelArea );
231   map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_U, mWrapModeU );
232   map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_V, mWrapModeV );
233
234   map.Insert( Toolkit::ImageVisual::Property::BATCH_SIZE, static_cast<int>(mBatchSize) );
235   map.Insert( Toolkit::ImageVisual::Property::CACHE_SIZE, static_cast<int>(mCacheSize) );
236   map.Insert( Toolkit::ImageVisual::Property::FRAME_DELAY, static_cast<int>(mFrameDelay) );
237   map.Insert( Toolkit::DevelImageVisual::Property::LOOP_COUNT, static_cast<int>(mLoopCount) );
238 }
239
240 void AnimatedImageVisual::DoCreateInstancePropertyMap( Property::Map& map ) const
241 {
242   // Do nothing
243 }
244
245 void AnimatedImageVisual::DoSetProperties( const Property::Map& propertyMap )
246 {
247   // url[s] already passed in from constructor
248
249   for( Property::Map::SizeType iter = 0; iter < propertyMap.Count(); ++iter )
250   {
251     KeyValuePair keyValue = propertyMap.GetKeyValue( iter );
252     if( keyValue.first.type == Property::Key::INDEX )
253     {
254       DoSetProperty( keyValue.first.indexKey, keyValue.second );
255     }
256     else
257     {
258       if( keyValue.first == PIXEL_AREA_UNIFORM_NAME )
259       {
260         DoSetProperty( Toolkit::ImageVisual::Property::PIXEL_AREA, keyValue.second );
261       }
262       else if( keyValue.first == IMAGE_WRAP_MODE_U )
263       {
264         DoSetProperty( Toolkit::ImageVisual::Property::WRAP_MODE_U, keyValue.second );
265       }
266       else if( keyValue.first == IMAGE_WRAP_MODE_V )
267       {
268         DoSetProperty( Toolkit::ImageVisual::Property::WRAP_MODE_V, keyValue.second );
269       }
270       else if( keyValue.first == BATCH_SIZE_NAME )
271       {
272         DoSetProperty( Toolkit::ImageVisual::Property::BATCH_SIZE, keyValue.second );
273       }
274       else if( keyValue.first == CACHE_SIZE_NAME )
275       {
276         DoSetProperty( Toolkit::ImageVisual::Property::CACHE_SIZE, keyValue.second );
277       }
278       else if( keyValue.first == FRAME_DELAY_NAME )
279       {
280         DoSetProperty( Toolkit::ImageVisual::Property::FRAME_DELAY, keyValue.second );
281       }
282       else if( keyValue.first == LOOP_COUNT_NAME )
283       {
284         DoSetProperty( Toolkit::DevelImageVisual::Property::LOOP_COUNT, keyValue.second );
285       }
286     }
287   }
288 }
289
290 void AnimatedImageVisual::DoSetProperty( Property::Index index,
291                                          const Property::Value& value )
292 {
293   switch(index)
294   {
295     case Toolkit::ImageVisual::Property::PIXEL_AREA:
296     {
297       value.Get( mPixelArea );
298       break;
299     }
300     case Toolkit::ImageVisual::Property::WRAP_MODE_U:
301     {
302       int wrapMode;
303       if(Scripting::GetEnumerationProperty( value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode ))
304       {
305         mWrapModeU = Dali::WrapMode::Type(wrapMode);
306       }
307       else
308       {
309         mWrapModeU = Dali::WrapMode::Type::DEFAULT;
310       }
311       break;
312     }
313     case Toolkit::ImageVisual::Property::WRAP_MODE_V:
314     {
315       int wrapMode;
316       if(Scripting::GetEnumerationProperty( value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode ))
317       {
318         mWrapModeV = Dali::WrapMode::Type(wrapMode);
319       }
320       else
321       {
322         mWrapModeV = Dali::WrapMode::Type::DEFAULT;
323       }
324       break;
325     }
326
327     case Toolkit::ImageVisual::Property::BATCH_SIZE:
328     {
329       int batchSize;
330       if( value.Get( batchSize ) )
331       {
332         mBatchSize = batchSize;
333       }
334       break;
335     }
336
337     case Toolkit::ImageVisual::Property::CACHE_SIZE:
338     {
339       int cacheSize;
340       if( value.Get( cacheSize ) )
341       {
342         mCacheSize = cacheSize;
343       }
344       break;
345     }
346
347     case Toolkit::ImageVisual::Property::FRAME_DELAY:
348     {
349       int frameDelay;
350       if( value.Get( frameDelay ) )
351       {
352         mFrameDelay = frameDelay;
353       }
354       break;
355     }
356
357     case Toolkit::DevelImageVisual::Property::LOOP_COUNT:
358     {
359       int loopCount;
360       if( value.Get( loopCount ) )
361       {
362         mLoopCount = loopCount;
363       }
364       break;
365     }
366   }
367 }
368
369 void AnimatedImageVisual::DoSetOnStage( Actor& actor )
370 {
371   mPlacementActor = actor;
372   TextureSet textureSet = PrepareTextureSet();
373   CreateRenderer(); // Always create a renderer when on stage
374
375   if( textureSet ) // if the image loading is successful
376   {
377     StartFirstFrame( textureSet );
378   }
379   else
380   {
381     mStartFirstFrame = true;
382   }
383 }
384
385 void AnimatedImageVisual::DoSetOffStage( Actor& actor )
386 {
387   DALI_ASSERT_DEBUG( (bool)mImpl->mRenderer && "There should always be a renderer whilst on stage");
388
389   if( mFrameDelayTimer )
390   {
391     mFrameDelayTimer.Stop();
392     mFrameDelayTimer.Reset();
393   }
394
395   actor.RemoveRenderer( mImpl->mRenderer );
396   mImpl->mRenderer.Reset();
397   mPlacementActor.Reset();
398 }
399
400 void AnimatedImageVisual::OnSetTransform()
401 {
402   if( mImpl->mRenderer )
403   {
404     mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT );
405   }
406 }
407
408 void AnimatedImageVisual::CreateRenderer()
409 {
410   bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE;
411   bool atlasing = false;
412   Shader shader = ImageVisual::GetImageShader( mFactoryCache, atlasing, defaultWrapMode );
413
414   Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
415
416   mImpl->mRenderer = Renderer::New( geometry, shader );
417
418   // Register transform properties
419   mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT );
420
421   if( !defaultWrapMode ) // custom wrap mode
422   {
423     Vector2 wrapMode(mWrapModeU-WrapMode::CLAMP_TO_EDGE, mWrapModeV-WrapMode::CLAMP_TO_EDGE);
424     wrapMode.Clamp( Vector2::ZERO, Vector2( 2.f, 2.f ) );
425     mImpl->mRenderer.RegisterProperty( WRAP_MODE_UNIFORM_NAME, wrapMode );
426   }
427
428   if( mPixelArea != FULL_TEXTURE_RECT )
429   {
430     mImpl->mRenderer.RegisterProperty( PIXEL_AREA_UNIFORM_NAME, mPixelArea );
431   }
432
433   mCurrentFrameIndex = 0;
434 }
435
436 void AnimatedImageVisual::LoadFirstBatch()
437 {
438   // Ensure the batch size and cache size are no bigger than the number of URLs,
439   // and that the cache is at least as big as the batch size.
440   uint16_t numUrls = 0;
441   uint16_t batchSize = 1;
442   uint16_t cacheSize = 1;
443
444   if( mImageUrls )
445   {
446     numUrls = mImageUrls->size();
447   }
448   else
449   {
450     numUrls = mFrameCount;
451   }
452
453   batchSize = std::min( mBatchSize, numUrls );
454   cacheSize = std::min( std::max( batchSize, mCacheSize ), numUrls );
455
456   DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::LoadFirstBatch()  batchSize:%d  cacheSize:%d\n", batchSize, cacheSize);
457
458   mUrlIndex = 0;
459   TextureManager& textureManager = mFactoryCache.GetTextureManager();
460
461   if( mGifLoading != nullptr )
462   {
463     mImageCache = new RollingGifImageCache( textureManager, *mGifLoading, mFrameCount, *this, cacheSize, batchSize );
464   }
465   else if( batchSize > 0 && cacheSize > 0 )
466   {
467     if( cacheSize < numUrls )
468     {
469       mImageCache = new RollingImageCache( textureManager, *mImageUrls, *this, cacheSize, batchSize );
470     }
471     else
472     {
473       mImageCache = new FixedImageCache( textureManager, *mImageUrls, *this, batchSize );
474     }
475   }
476   else
477   {
478     mImageCache = new RollingImageCache( textureManager, *mImageUrls, *this, 1, 1 );
479   }
480   if (!mImageCache)
481   {
482     DALI_LOG_ERROR("mImageCache is null");
483   }
484 }
485
486 void AnimatedImageVisual::StartFirstFrame( TextureSet& textureSet )
487 {
488   DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::StartFirstFrame()\n");
489
490   mStartFirstFrame = false;
491   mImpl->mRenderer.SetTextures( textureSet );
492   Actor actor = mPlacementActor.GetHandle();
493   if( actor )
494   {
495     actor.AddRenderer( mImpl->mRenderer );
496     mPlacementActor.Reset();
497   }
498
499   mCurrentFrameIndex = 0;
500
501   if( mFrameCount > 1 )
502   {
503     int frameDelay = mFrameDelay; // from URL array
504     if( mFrameDelayContainer.Count() > 0 ) // from GIF
505     {
506       frameDelay = mFrameDelayContainer[0];
507     }
508
509     mFrameDelayTimer = Timer::New( frameDelay );
510     mFrameDelayTimer.TickSignal().Connect( this, &AnimatedImageVisual::DisplayNextFrame );
511     mFrameDelayTimer.Start();
512   }
513   DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"ResourceReady(ResourceStatus::READY)\n");
514   ResourceReady( Toolkit::Visual::ResourceStatus::READY );
515 }
516
517 TextureSet AnimatedImageVisual::PrepareTextureSet()
518 {
519   TextureSet textureSet;
520   if (mImageCache)
521     textureSet = mImageCache->FirstFrame();
522   if( textureSet )
523   {
524     SetImageSize( textureSet );
525   }
526   else
527   {
528     DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"ResourceReady(ResourceStatus::FAILED)\n");
529     ResourceReady( Toolkit::Visual::ResourceStatus::FAILED );
530   }
531
532   return textureSet;
533 }
534
535 void AnimatedImageVisual::SetImageSize( TextureSet& textureSet )
536 {
537   if( textureSet )
538   {
539     Texture texture = textureSet.GetTexture( 0 );
540     if( texture )
541     {
542       mImageSize.SetWidth( texture.GetWidth() );
543       mImageSize.SetHeight( texture.GetHeight() );
544     }
545   }
546 }
547
548 void AnimatedImageVisual::FrameReady( TextureSet textureSet )
549 {
550   SetImageSize( textureSet );
551
552   if( mStartFirstFrame )
553   {
554     StartFirstFrame( textureSet );
555   }
556   else
557   {
558     mImpl->mRenderer.SetTextures( textureSet );
559   }
560 }
561
562 bool AnimatedImageVisual::DisplayNextFrame()
563 {
564   if( mFrameCount > 1 )
565   {
566     // Wrap the frame index
567     ++mCurrentFrameIndex;
568
569     if( mLoopCount < 0 || mCurrentLoopIndex <= mLoopCount)
570     {
571       mCurrentFrameIndex %= mFrameCount;
572       if( mCurrentFrameIndex == 0 )
573       {
574         ++mCurrentLoopIndex;
575       }
576     }
577     else
578     {
579       // This will stop timer
580       return false;
581     }
582   }
583   DALI_LOG_INFO( gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::DisplayNextFrame(this:%p) FrameCount:%d\n", this, mCurrentFrameIndex);
584
585   if( mFrameDelayContainer.Count() > 0 )
586   {
587     unsigned int delay = mFrameDelayContainer[mCurrentFrameIndex];
588
589     if( mFrameDelayTimer.GetInterval() != delay )
590     {
591       mFrameDelayTimer.SetInterval( delay );
592     }
593   }
594
595   TextureSet textureSet;
596   if (mImageCache)
597     textureSet = mImageCache->NextFrame();
598   if( textureSet )
599   {
600     SetImageSize( textureSet );
601     mImpl->mRenderer.SetTextures( textureSet );
602   }
603
604   // Keep timer ticking
605   return true;
606 }
607
608
609 } // namespace Internal
610
611 } // namespace Toolkit
612
613 } // namespace Dali