Fix Coverity issues
[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, Visual::FittingMode::FIT_KEEP_ASPECT_RATIO ),
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   mActionStatus( DevelAnimatedImageVisual::Action::PLAY ),
183   mStartFirstFrame(false)
184 {}
185
186 AnimatedImageVisual::~AnimatedImageVisual()
187 {
188   delete mImageCache;
189   delete mImageUrls;
190 }
191
192 void AnimatedImageVisual::GetNaturalSize( Vector2& naturalSize )
193 {
194   if( mImageSize.GetWidth() == 0 &&  mImageSize.GetHeight() == 0)
195   {
196     if( mImageUrl.IsValid() )
197     {
198       mImageSize = mGifLoading->GetImageSize();
199     }
200     else if( mImageUrls && mImageUrls->size() > 0 )
201     {
202       mImageSize = Dali::GetClosestImageSize( (*mImageUrls)[0].mUrl );
203     }
204   }
205
206   naturalSize.width = mImageSize.GetWidth();
207   naturalSize.height = mImageSize.GetHeight();
208 }
209
210 void AnimatedImageVisual::DoCreatePropertyMap( Property::Map& map ) const
211 {
212   map.Clear();
213
214   map.Insert( Toolkit::Visual::Property::TYPE, Toolkit::Visual::ANIMATED_IMAGE );
215
216   if( mImageUrl.IsValid() )
217   {
218     map.Insert( Toolkit::ImageVisual::Property::URL, mImageUrl.GetUrl() );
219   }
220   if( mImageUrls != NULL && ! mImageUrls->empty() )
221   {
222     Property::Array urls;
223     for( unsigned int i=0; i<mImageUrls->size(); ++i)
224     {
225       urls.Add( (*mImageUrls)[i].mUrl );
226     }
227     Property::Value value( const_cast<Property::Array&>(urls) );
228     map.Insert( Toolkit::ImageVisual::Property::URL, value );
229   }
230
231   map.Insert( Toolkit::ImageVisual::Property::PIXEL_AREA, mPixelArea );
232   map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_U, mWrapModeU );
233   map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_V, mWrapModeV );
234
235   map.Insert( Toolkit::ImageVisual::Property::BATCH_SIZE, static_cast<int>(mBatchSize) );
236   map.Insert( Toolkit::ImageVisual::Property::CACHE_SIZE, static_cast<int>(mCacheSize) );
237   map.Insert( Toolkit::ImageVisual::Property::FRAME_DELAY, static_cast<int>(mFrameDelay) );
238   map.Insert( Toolkit::DevelImageVisual::Property::LOOP_COUNT, static_cast<int>(mLoopCount) );
239 }
240
241 void AnimatedImageVisual::DoCreateInstancePropertyMap( Property::Map& map ) const
242 {
243   // Do nothing
244 }
245
246 void AnimatedImageVisual::OnDoAction( const Dali::Property::Index actionId, const Dali::Property::Value& attributes )
247 {
248   // Check if action is valid for this visual type and perform action if possible
249
250   switch ( actionId )
251   {
252     case DevelAnimatedImageVisual::Action::PAUSE:
253     {
254       // Pause will be executed on next timer tick
255       mActionStatus = DevelAnimatedImageVisual::Action::PAUSE;
256       break;
257     }
258     case DevelAnimatedImageVisual::Action::PLAY:
259     {
260       if( IsOnStage() && mActionStatus != DevelAnimatedImageVisual::Action::PLAY )
261       {
262         mFrameDelayTimer.Start();
263       }
264       mActionStatus = DevelAnimatedImageVisual::Action::PLAY;
265       break;
266     }
267     case DevelAnimatedImageVisual::Action::STOP:
268     {
269       // STOP reset functionality will actually be done in a future change
270       // Stop will be executed on next timer tick
271       mCurrentFrameIndex = 0;
272       mActionStatus = DevelAnimatedImageVisual::Action::STOP;
273       break;
274     }
275   }
276 }
277
278 void AnimatedImageVisual::DoSetProperties( const Property::Map& propertyMap )
279 {
280   // url[s] already passed in from constructor
281
282   for( Property::Map::SizeType iter = 0; iter < propertyMap.Count(); ++iter )
283   {
284     KeyValuePair keyValue = propertyMap.GetKeyValue( iter );
285     if( keyValue.first.type == Property::Key::INDEX )
286     {
287       DoSetProperty( keyValue.first.indexKey, keyValue.second );
288     }
289     else
290     {
291       if( keyValue.first == PIXEL_AREA_UNIFORM_NAME )
292       {
293         DoSetProperty( Toolkit::ImageVisual::Property::PIXEL_AREA, keyValue.second );
294       }
295       else if( keyValue.first == IMAGE_WRAP_MODE_U )
296       {
297         DoSetProperty( Toolkit::ImageVisual::Property::WRAP_MODE_U, keyValue.second );
298       }
299       else if( keyValue.first == IMAGE_WRAP_MODE_V )
300       {
301         DoSetProperty( Toolkit::ImageVisual::Property::WRAP_MODE_V, keyValue.second );
302       }
303       else if( keyValue.first == BATCH_SIZE_NAME )
304       {
305         DoSetProperty( Toolkit::ImageVisual::Property::BATCH_SIZE, keyValue.second );
306       }
307       else if( keyValue.first == CACHE_SIZE_NAME )
308       {
309         DoSetProperty( Toolkit::ImageVisual::Property::CACHE_SIZE, keyValue.second );
310       }
311       else if( keyValue.first == FRAME_DELAY_NAME )
312       {
313         DoSetProperty( Toolkit::ImageVisual::Property::FRAME_DELAY, keyValue.second );
314       }
315       else if( keyValue.first == LOOP_COUNT_NAME )
316       {
317         DoSetProperty( Toolkit::DevelImageVisual::Property::LOOP_COUNT, keyValue.second );
318       }
319     }
320   }
321 }
322
323 void AnimatedImageVisual::DoSetProperty( Property::Index index,
324                                          const Property::Value& value )
325 {
326   switch(index)
327   {
328     case Toolkit::ImageVisual::Property::PIXEL_AREA:
329     {
330       value.Get( mPixelArea );
331       break;
332     }
333     case Toolkit::ImageVisual::Property::WRAP_MODE_U:
334     {
335       int wrapMode = 0;
336       if(Scripting::GetEnumerationProperty( value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode ))
337       {
338         mWrapModeU = Dali::WrapMode::Type(wrapMode);
339       }
340       else
341       {
342         mWrapModeU = Dali::WrapMode::Type::DEFAULT;
343       }
344       break;
345     }
346     case Toolkit::ImageVisual::Property::WRAP_MODE_V:
347     {
348       int wrapMode = 0;
349       if(Scripting::GetEnumerationProperty( value, WRAP_MODE_TABLE, WRAP_MODE_TABLE_COUNT, wrapMode ))
350       {
351         mWrapModeV = Dali::WrapMode::Type(wrapMode);
352       }
353       else
354       {
355         mWrapModeV = Dali::WrapMode::Type::DEFAULT;
356       }
357       break;
358     }
359
360     case Toolkit::ImageVisual::Property::BATCH_SIZE:
361     {
362       int batchSize;
363       if( value.Get( batchSize ) )
364       {
365         mBatchSize = batchSize;
366       }
367       break;
368     }
369
370     case Toolkit::ImageVisual::Property::CACHE_SIZE:
371     {
372       int cacheSize;
373       if( value.Get( cacheSize ) )
374       {
375         mCacheSize = cacheSize;
376       }
377       break;
378     }
379
380     case Toolkit::ImageVisual::Property::FRAME_DELAY:
381     {
382       int frameDelay;
383       if( value.Get( frameDelay ) )
384       {
385         mFrameDelay = frameDelay;
386       }
387       break;
388     }
389
390     case Toolkit::DevelImageVisual::Property::LOOP_COUNT:
391     {
392       int loopCount;
393       if( value.Get( loopCount ) )
394       {
395         mLoopCount = loopCount;
396       }
397       break;
398     }
399   }
400 }
401
402 void AnimatedImageVisual::DoSetOnStage( Actor& actor )
403 {
404   mPlacementActor = actor;
405   TextureSet textureSet = PrepareTextureSet();
406   CreateRenderer(); // Always create a renderer when on stage
407
408   if( textureSet ) // if the image loading is successful
409   {
410     StartFirstFrame( textureSet );
411   }
412   else
413   {
414     mStartFirstFrame = true;
415   }
416 }
417
418 void AnimatedImageVisual::DoSetOffStage( Actor& actor )
419 {
420   DALI_ASSERT_DEBUG( (bool)mImpl->mRenderer && "There should always be a renderer whilst on stage");
421
422   if( mFrameDelayTimer )
423   {
424     mFrameDelayTimer.Stop();
425     mFrameDelayTimer.Reset();
426   }
427
428   actor.RemoveRenderer( mImpl->mRenderer );
429   mImpl->mRenderer.Reset();
430   mPlacementActor.Reset();
431 }
432
433 void AnimatedImageVisual::OnSetTransform()
434 {
435   if( mImpl->mRenderer )
436   {
437     mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT );
438   }
439 }
440
441 void AnimatedImageVisual::CreateRenderer()
442 {
443   bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE;
444   bool atlasing = false;
445   Shader shader = ImageVisual::GetImageShader( mFactoryCache, atlasing, defaultWrapMode );
446
447   Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
448
449   mImpl->mRenderer = Renderer::New( geometry, shader );
450
451   // Register transform properties
452   mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT );
453
454   if( !defaultWrapMode ) // custom wrap mode
455   {
456     Vector2 wrapMode(mWrapModeU-WrapMode::CLAMP_TO_EDGE, mWrapModeV-WrapMode::CLAMP_TO_EDGE);
457     wrapMode.Clamp( Vector2::ZERO, Vector2( 2.f, 2.f ) );
458     mImpl->mRenderer.RegisterProperty( WRAP_MODE_UNIFORM_NAME, wrapMode );
459   }
460
461   if( mPixelArea != FULL_TEXTURE_RECT )
462   {
463     mImpl->mRenderer.RegisterProperty( PIXEL_AREA_UNIFORM_NAME, mPixelArea );
464   }
465
466   mCurrentFrameIndex = 0;
467 }
468
469 void AnimatedImageVisual::LoadFirstBatch()
470 {
471   // Ensure the batch size and cache size are no bigger than the number of URLs,
472   // and that the cache is at least as big as the batch size.
473   uint16_t numUrls = 0;
474   uint16_t batchSize = 1;
475   uint16_t cacheSize = 1;
476
477   if( mImageUrls )
478   {
479     numUrls = mImageUrls->size();
480   }
481   else
482   {
483     numUrls = mFrameCount;
484   }
485
486   batchSize = std::min( mBatchSize, numUrls );
487   cacheSize = std::min( std::max( batchSize, mCacheSize ), numUrls );
488
489   DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::LoadFirstBatch()  batchSize:%d  cacheSize:%d\n", batchSize, cacheSize);
490
491   mUrlIndex = 0;
492   TextureManager& textureManager = mFactoryCache.GetTextureManager();
493
494   if( mGifLoading != nullptr )
495   {
496     mImageCache = new RollingGifImageCache( textureManager, *mGifLoading, mFrameCount, *this, cacheSize, batchSize );
497   }
498   else if( mImageUrls )
499   {
500     if( batchSize > 0 && cacheSize > 0 )
501     {
502       if( cacheSize < numUrls )
503       {
504         mImageCache = new RollingImageCache( textureManager, *mImageUrls, *this, cacheSize, batchSize );
505       }
506       else
507       {
508         mImageCache = new FixedImageCache( textureManager, *mImageUrls, *this, batchSize );
509       }
510     }
511     else
512     {
513       mImageCache = new RollingImageCache( textureManager, *mImageUrls, *this, 1, 1 );
514     }
515   }
516
517   if (!mImageCache)
518   {
519     DALI_LOG_ERROR("mImageCache is null");
520   }
521 }
522
523 void AnimatedImageVisual::StartFirstFrame( TextureSet& textureSet )
524 {
525   DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::StartFirstFrame()\n");
526
527   mStartFirstFrame = false;
528   mImpl->mRenderer.SetTextures( textureSet );
529   Actor actor = mPlacementActor.GetHandle();
530   if( actor )
531   {
532     actor.AddRenderer( mImpl->mRenderer );
533     mPlacementActor.Reset();
534   }
535
536   mCurrentFrameIndex = 0;
537
538   if( mFrameCount > 1 )
539   {
540     int frameDelay = mFrameDelay; // from URL array
541     if( mFrameDelayContainer.Count() > 0 ) // from GIF
542     {
543       frameDelay = mFrameDelayContainer[0];
544     }
545
546     mFrameDelayTimer = Timer::New( frameDelay );
547     mFrameDelayTimer.TickSignal().Connect( this, &AnimatedImageVisual::DisplayNextFrame );
548     mFrameDelayTimer.Start();
549   }
550   DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"ResourceReady(ResourceStatus::READY)\n");
551   ResourceReady( Toolkit::Visual::ResourceStatus::READY );
552 }
553
554 TextureSet AnimatedImageVisual::PrepareTextureSet()
555 {
556   TextureSet textureSet;
557   if (mImageCache)
558     textureSet = mImageCache->FirstFrame();
559   if( textureSet )
560   {
561     SetImageSize( textureSet );
562   }
563   else
564   {
565     DALI_LOG_INFO(gAnimImgLogFilter,Debug::Concise,"ResourceReady(ResourceStatus::FAILED)\n");
566     ResourceReady( Toolkit::Visual::ResourceStatus::FAILED );
567   }
568
569   return textureSet;
570 }
571
572 void AnimatedImageVisual::SetImageSize( TextureSet& textureSet )
573 {
574   if( textureSet )
575   {
576     Texture texture = textureSet.GetTexture( 0 );
577     if( texture )
578     {
579       mImageSize.SetWidth( texture.GetWidth() );
580       mImageSize.SetHeight( texture.GetHeight() );
581     }
582   }
583 }
584
585 void AnimatedImageVisual::FrameReady( TextureSet textureSet )
586 {
587   SetImageSize( textureSet );
588
589   if( mStartFirstFrame )
590   {
591     StartFirstFrame( textureSet );
592   }
593   else
594   {
595     mImpl->mRenderer.SetTextures( textureSet );
596   }
597 }
598
599 bool AnimatedImageVisual::DisplayNextFrame()
600 {
601   if( mActionStatus == DevelAnimatedImageVisual::Action::STOP || mActionStatus == DevelAnimatedImageVisual::Action::PAUSE )
602   {
603     return false;
604   }
605   if( mFrameCount > 1 )
606   {
607     // Wrap the frame index
608     ++mCurrentFrameIndex;
609
610     if( mLoopCount < 0 || mCurrentLoopIndex <= mLoopCount)
611     {
612       mCurrentFrameIndex %= mFrameCount;
613       if( mCurrentFrameIndex == 0 )
614       {
615         ++mCurrentLoopIndex;
616       }
617     }
618     else
619     {
620       // This will stop timer
621       return false;
622     }
623   }
624   DALI_LOG_INFO( gAnimImgLogFilter,Debug::Concise,"AnimatedImageVisual::DisplayNextFrame(this:%p) FrameCount:%d\n", this, mCurrentFrameIndex);
625
626   if( mFrameDelayContainer.Count() > 0 )
627   {
628     unsigned int delay = mFrameDelayContainer[mCurrentFrameIndex];
629
630     if( mFrameDelayTimer.GetInterval() != delay )
631     {
632       mFrameDelayTimer.SetInterval( delay );
633     }
634   }
635
636   TextureSet textureSet;
637   if( mImageCache )
638   {
639     textureSet = mImageCache->NextFrame();
640     if( textureSet )
641     {
642       SetImageSize( textureSet );
643       mImpl->mRenderer.SetTextures( textureSet );
644     }
645   }
646
647   // Keep timer ticking
648   return true;
649 }
650
651
652 } // namespace Internal
653
654 } // namespace Toolkit
655
656 } // namespace Dali