Support Asynchronous Loading of Animated Image
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / gif-loading.cpp
1 /*
2  * Copyright (c) 2020 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  * Copyright notice for the EFL:
17  * Copyright (C) EFL developers (see AUTHORS)
18  */
19
20 // CLASS HEADER
21 #include <dali/internal/imaging/common/gif-loading.h>
22
23 // EXTERNAL INCLUDES
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <gif_lib.h>
29 #include <cstring>
30 #include <dali/integration-api/debug.h>
31 #include <dali/public-api/images/pixel-data.h>
32 #include <dali/internal/imaging/common/file-download.h>
33 #include <dali/internal/system/common/file-reader.h>
34
35 #define IMG_TOO_BIG( w, h )                                                        \
36   ( ( static_cast<unsigned long long>(w) * static_cast<unsigned long long>(h) ) >= \
37     ( (1ULL << (29 * (sizeof(void *) / 4))) - 2048) )
38
39 #define LOADERR( x )                              \
40   do {                                            \
41     DALI_LOG_ERROR( x );                          \
42     goto on_error;                                \
43   } while ( 0 )
44
45 namespace Dali
46 {
47
48 namespace Internal
49 {
50
51 namespace Adaptor
52 {
53
54 namespace
55 {
56 #if defined(DEBUG_ENABLED)
57 Debug::Filter *gGifLoadingLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_GIF_LOADING" );
58 #endif
59
60 const int IMG_MAX_SIZE = 65000;
61 constexpr size_t MAXIMUM_DOWNLOAD_IMAGE_SIZE  = 50 * 1024 * 1024;
62
63 #if GIFLIB_MAJOR < 5
64 const int DISPOSE_BACKGROUND = 2;       /* Set area too background color */
65 const int DISPOSE_PREVIOUS = 3;         /* Restore to previous content */
66 #endif
67
68 struct FrameInfo
69 {
70   FrameInfo()
71   : x( 0 ),
72     y( 0 ),
73     w( 0 ),
74     h( 0 ),
75     delay( 0 ),
76     transparent( -1 ),
77     dispose( DISPOSE_BACKGROUND ),
78     interlace( 0 )
79   {
80   }
81
82   int x, y, w, h;
83   unsigned short delay; // delay time in 1/100ths of a sec
84   short transparent : 10; // -1 == not, anything else == index
85   short dispose : 6; // 0, 1, 2, 3 (others invalid)
86   short interlace : 1; // interlaced or not
87 };
88
89 struct ImageFrame
90 {
91   ImageFrame()
92   : index( 0 ),
93     data( nullptr ),
94     info(),
95     loaded( false )
96   {
97   }
98
99   ~ImageFrame()
100   {
101   }
102
103   int       index;
104   uint32_t  *data;     /* frame decoding data */
105   FrameInfo  info;     /* special image type info */
106   bool loaded : 1;
107 };
108
109 struct GifAnimationData
110 {
111   GifAnimationData()
112   : frames( ),
113     frameCount( 0 ),
114     loopCount( 0 ),
115     currentFrame( 0 ),
116     animated( false )
117   {
118   }
119
120   std::vector<ImageFrame> frames;
121   int frameCount;
122   int loopCount;
123   int currentFrame;
124   bool animated;
125 };
126
127 struct LoaderInfo
128 {
129   LoaderInfo()
130   : gif( nullptr ) ,
131     imageNumber ( 0 )
132   {
133   }
134
135   struct FileData
136   {
137     FileData()
138     : fileName( nullptr ),
139       globalMap ( nullptr ),
140       length( 0 ),
141       isLocalResource( true )
142     {
143     }
144
145     const char *fileName;  /**< The absolute path of the file. */
146     unsigned char *globalMap ;      /**< A pointer to the entire contents of the file that have been mapped with mmap(2). */
147     long long length;  /**< The length of the file in bytes. */
148     bool isLocalResource; /**< The flag whether the file is a local resource */
149   };
150
151   struct FileInfo
152   {
153     FileInfo()
154     : map( nullptr ),
155       position( 0 ),
156       length( 0 )
157     {
158     }
159
160     unsigned char *map;
161     int position, length; // yes - gif uses ints for file sizes.
162   };
163
164   FileData fileData;
165   GifAnimationData animated;
166   GifFileType *gif;
167   int imageNumber;
168   FileInfo fileInfo;
169 };
170
171 struct ImageProperties
172 {
173   ImageProperties()
174   : w( 0 ),
175     h( 0 ),
176     alpha( 0 )
177   {
178   }
179
180   unsigned int w;
181   unsigned int h;
182   bool alpha;
183 };
184
185 /**
186  * @brief This combines R, G, B and Alpha values into a single 32-bit (ABGR) value.
187  *
188  * @param[in] animated A structure containing GIF animation data
189  * @param[in] index Frame index to be searched in GIF
190  * @return A pointer to the ImageFrame.
191  */
192 inline int CombinePixelABGR( int a, int r, int g, int b )
193 {
194   return ( ((a) << 24) + ((b) << 16) + ((g) << 8) + (r) );
195 }
196
197 inline int PixelLookup( ColorMapObject *colorMap, int index )
198 {
199   return CombinePixelABGR( 0xFF, colorMap->Colors[index].Red, colorMap->Colors[index].Green, colorMap->Colors[index].Blue );
200 }
201
202 /**
203  * @brief Brute force find frame index - gifs are normally small so ok for now.
204  *
205  * @param[in] animated A structure containing GIF animation data
206  * @param[in] index Frame index to be searched in GIF
207  * @return A pointer to the ImageFrame.
208  */
209 ImageFrame *FindFrame( const GifAnimationData &animated, int index )
210 {
211   for( auto &&elem : animated.frames )
212   {
213     if( elem.index == index )
214     {
215       return const_cast<ImageFrame *>( &elem );
216     }
217   }
218   return nullptr;
219 }
220
221 /**
222  * @brief Fill in an image with a specific rgba color value.
223  *
224  * @param[in] data A pointer pointing to an image data
225  * @param[in] row A int containing the number of rows in an image
226  * @param[in] val A uint32_t containing rgba color value
227  * @param[in] x X-coordinate used an offset to calculate pixel position
228  * @param[in] y Y-coordinate used an offset to calculate pixel position
229  * @param[in] width Width of the image
230  * @param[in] height Height of the image
231  */
232 void FillImage( uint32_t *data, int row, uint32_t val, int x, int y, int width, int height )
233 {
234   int xAxis, yAxis;
235   uint32_t *pixelPosition;
236
237   for( yAxis = 0; yAxis < height; yAxis++ )
238   {
239     pixelPosition = data + ((y + yAxis) * row) + x;
240     for( xAxis = 0; xAxis < width; xAxis++ )
241     {
242       *pixelPosition = val;
243       pixelPosition++;
244     }
245   }
246 }
247
248 /**
249  * @brief Fill a rgba data pixle blob with a frame color (bg or trans)
250  *
251  * @param[in] data A pointer pointing to an image data
252  * @param[in] row A int containing the number of rows in an image
253  * @param[in] gif A pointer pointing to GIF File Type
254  * @param[in] frameInfo A pointer pointing to Frame Information data
255  * @param[in] x X-coordinate used an offset to calculate pixel position
256  * @param[in] y Y-coordinate used an offset to calculate pixel position
257  * @param[in] width Width of the image
258  * @param[in] height Height of the image
259  */
260 void FillFrame( uint32_t *data, int row, GifFileType *gif, FrameInfo *frameInfo, int x, int y, int w, int h )
261 {
262   // solid color fill for pre frame region
263   if( frameInfo->transparent < 0 )
264   {
265     ColorMapObject *colorMap;
266     int backGroundColor;
267
268     // work out color to use from colorMap
269     if( gif->Image.ColorMap )
270     {
271       colorMap = gif->Image.ColorMap;
272     }
273     else
274     {
275       colorMap = gif->SColorMap;
276     }
277     backGroundColor = gif->SBackGroundColor;
278     // and do the fill
279     FillImage( data, row,
280               CombinePixelABGR( 0xff, colorMap->Colors[backGroundColor].Red,
281                                 colorMap->Colors[backGroundColor].Green,
282                                 colorMap->Colors[backGroundColor].Blue ),
283               x, y, w, h );
284   }
285   // fill in region with 0 (transparent)
286   else
287   {
288     FillImage( data, row, 0, x, y, w, h );
289   }
290 }
291
292 /**
293  * @brief Store common fields from gif file info into frame info
294  *
295  * @param[in] gif A pointer pointing to GIF File Type
296  * @param[in] frameInfo A pointer pointing to Frame Information data
297  */
298 void StoreFrameInfo( GifFileType *gif, FrameInfo *frameInfo )
299 {
300   frameInfo->x = gif->Image.Left;
301   frameInfo->y = gif->Image.Top;
302   frameInfo->w = gif->Image.Width;
303   frameInfo->h = gif->Image.Height;
304   frameInfo->interlace = gif->Image.Interlace;
305 }
306
307 /**
308  * @brief Check if image fills "screen space" and if so, if it is transparent
309  * at all then the image could be transparent - OR if image doesnt fill,
310  * then it could be trasnparent (full coverage of screen). Some gifs will
311  * be recognized as solid here for faster rendering, but not all.
312  *
313  * @param[out] full A boolean to show whether image is transparent or not
314  * @param[in] frameInfo A pointer pointing to Frame Information data
315  * @param[in] width Width of the image
316  * @param[in] height Height of the image
317  */
318 void CheckTransparency( bool &full, FrameInfo *frameInfo, int width, int height )
319 {
320   if( ( frameInfo->x == 0 ) && ( frameInfo->y == 0 ) &&
321       ( frameInfo->w == width ) && ( frameInfo->h == height ) )
322   {
323     if( frameInfo->transparent >= 0 )
324     {
325       full = false;
326     }
327   }
328   else
329   {
330     full = false;
331   }
332 }
333
334 /**
335  * @brief Fix coords and work out an x and y inset in orig data if out of image bounds.
336  */
337 void ClipCoordinates( int imageWidth, int imageHeight, int *xin, int *yin, int x0, int y0, int w0, int h0, int *x, int *y, int *w, int *h )
338 {
339   if( x0 < 0 )
340   {
341     w0 += x0;
342     *xin = -x0;
343     x0 = 0;
344   }
345   if( (x0 + w0) > imageWidth )
346   {
347     w0 = imageWidth - x0;
348   }
349   if( y0 < 0 )
350   {
351     h0 += y0;
352     *yin = -y0;
353     y0 = 0;
354   }
355   if( (y0 + h0) > imageHeight )
356   {
357     h0 = imageHeight - y0;
358   }
359   *x = x0;
360   *y = y0;
361   *w = w0;
362   *h = h0;
363 }
364
365 /**
366  * @brief Flush out rgba frame images to save memory but skip current,
367  * previous and lastPreservedFrame frames (needed for dispose mode DISPOSE_PREVIOUS)
368  *
369  * @param[in] animated A structure containing GIF animation data
370  * @param[in] width Width of the image
371  * @param[in] height Height of the image
372  * @param[in] thisframe The current frame
373  * @param[in] prevframe The previous frame
374  * @param[in] lastPreservedFrame The last preserved frame
375  */
376 void FlushFrames( GifAnimationData &animated, int width, int height, ImageFrame *thisframe, ImageFrame *prevframe, ImageFrame *lastPreservedFrame )
377 {
378   DALI_LOG_INFO( gGifLoadingLogFilter, Debug::Concise, "FlushFrames() START \n" );
379
380   // target is the amount of memory we want to be under for stored frames
381   int total = 0, target = 512 * 1024;
382
383   // total up the amount of memory used by stored frames for this image
384   for( auto &&frame : animated.frames )
385   {
386     if( frame.data )
387     {
388       total++;
389     }
390   }
391   total *= ( width * height * sizeof( uint32_t ) );
392
393   DALI_LOG_INFO( gGifLoadingLogFilter, Debug::Concise, "Total used frame size: %d\n", total );
394
395   // If we use more than target (512k) for frames - flush
396   if( total > target )
397   {
398     // Clean frames (except current and previous) until below target
399     for( auto &&frame : animated.frames )
400     {
401       if( (frame.index != thisframe->index) && (!prevframe || frame.index != prevframe->index) &&
402           (!lastPreservedFrame || frame.index != lastPreservedFrame->index) )
403       {
404         if( frame.data != nullptr )
405         {
406           delete[] frame.data;
407           frame.data = nullptr;
408
409           // subtract memory used and if below target - stop flush
410           total -= ( width * height * sizeof( uint32_t ) );
411           if( total < target )
412           {
413             break;
414           }
415         }
416       }
417     }
418   }
419
420   DALI_LOG_INFO( gGifLoadingLogFilter, Debug::Concise, "FlushFrames() END \n" );
421 }
422
423 /**
424  * @brief allocate frame and frame info and append to list and store fields.
425  *
426  * @param[in] animated A structure containing GIF animation data
427  * @param[in] transparent Transparent index of the new frame
428  * @param[in] dispose Dispose mode of new frame
429  * @param[in] delay The frame delay of new frame
430  * @param[in] index The index of new frame
431  */
432 FrameInfo *NewFrame( GifAnimationData &animated, int transparent, int dispose, int delay, int index )
433 {
434   ImageFrame frame;
435
436   // record transparent index to be used or -1 if none
437   // for this SPECIFIC frame
438   frame.info.transparent = transparent;
439   // record dispose mode (3 bits)
440   frame.info.dispose = dispose;
441   // record delay (2 bytes so max 65546 /100 sec)
442   frame.info.delay = delay;
443   // record the index number we are at
444   frame.index = index;
445   // that frame is stored AT image/screen size
446
447   animated.frames.push_back( frame );
448
449   DALI_LOG_INFO( gGifLoadingLogFilter, Debug::Concise, "NewFrame: animated.frames.size() = %d\n", animated.frames.size() );
450
451   return &( animated.frames.back().info );
452 }
453
454 /**
455  * @brief Copy data from gif file into buffer.
456  *
457  * @param[in] gifFileType A pointer pointing to GIF File Type
458  * @param[out] buffer A pointer to buffer containing GIF raw data
459  * @param[in] len The length in bytes to be copied
460  * @return The data length of the image in bytes
461  */
462 int FileRead( GifFileType *gifFileType, GifByteType *buffer, int length )
463 {
464   LoaderInfo::FileInfo *fi = reinterpret_cast<LoaderInfo::FileInfo *>( gifFileType->UserData );
465
466   if( fi->position >= fi->length )
467   {
468     return 0; // if at or past end - no
469   }
470   if( (fi->position + length) >= fi->length )
471   {
472     length = fi->length - fi->position;
473   }
474   memcpy( buffer, fi->map + fi->position, length );
475   fi->position += length;
476   return length;
477 }
478
479 /**
480  * @brief Decode a gif image into rows then expand to 32bit into the destination
481  * data pointer.
482  */
483 bool DecodeImage( GifFileType *gif, uint32_t *data, int rowpix, int xin, int yin,
484                   int transparent, int x, int y, int w, int h, bool fill )
485 {
486   int intoffset[] = {0, 4, 2, 1};
487   int intjump[] = {8, 8, 4, 2};
488   int i, xx, yy, pix, gifW, gifH;
489   GifRowType *rows = NULL;
490   bool ret = false;
491   ColorMapObject *colorMap;
492   uint32_t *p;
493
494   // what we need is image size.
495   SavedImage *sp;
496   sp = &gif->SavedImages[ gif->ImageCount - 1 ];
497   if( !sp )
498   {
499     goto on_error;
500   }
501
502   gifW = sp->ImageDesc.Width;
503   gifH = sp->ImageDesc.Height;
504
505   if( ( gifW < w ) || ( gifH < h ) )
506   {
507     DALI_ASSERT_DEBUG( false && "Dimensions are bigger than the Gif image size");
508     goto on_error;
509   }
510
511   // build a blob of memory to have pointers to rows of pixels
512   // AND store the decoded gif pixels (1 byte per pixel) as welll
513   rows = static_cast<GifRowType *>(malloc( (gifH * sizeof(GifRowType) ) + ( gifW * gifH * sizeof(GifPixelType) )));
514   if( !rows )
515   {
516     goto on_error;
517   }
518
519   // fill in the pointers at the start
520   for( yy = 0; yy < gifH; yy++ )
521   {
522     rows[yy] = reinterpret_cast<unsigned char *>(rows) + (gifH * sizeof(GifRowType)) + (yy * gifW * sizeof(GifPixelType));
523   }
524
525   // if gif is interlaced, walk interlace pattern and decode into rows
526   if( gif->Image.Interlace )
527   {
528     for( i = 0; i < 4; i++ )
529     {
530       for( yy = intoffset[i]; yy < gifH; yy += intjump[i] )
531       {
532         if( DGifGetLine( gif, rows[yy], gifW ) != GIF_OK )
533         {
534           goto on_error;
535         }
536       }
537     }
538   }
539   // normal top to bottom - decode into rows
540   else
541   {
542     for( yy = 0; yy < gifH; yy++ )
543     {
544       if( DGifGetLine( gif, rows[yy], gifW ) != GIF_OK )
545       {
546         goto on_error;
547       }
548     }
549   }
550
551   // work out what colormap to use
552   if( gif->Image.ColorMap )
553   {
554     colorMap = gif->Image.ColorMap;
555   }
556   else
557   {
558     colorMap = gif->SColorMap;
559   }
560
561   // if we need to deal with transparent pixels at all...
562   if( transparent >= 0 )
563   {
564     // if we are told to FILL (overwrite with transparency kept)
565     if( fill )
566     {
567       for( yy = 0; yy < h; yy++ )
568       {
569         p = data + ((y + yy) * rowpix) + x;
570         for( xx = 0; xx < w; xx++ )
571         {
572           pix = rows[yin + yy][xin + xx];
573           if( pix != transparent )
574           {
575             *p = PixelLookup( colorMap, pix );
576           }
577           else
578           {
579             *p = 0;
580           }
581           p++;
582         }
583       }
584     }
585     // paste on top with transparent pixels untouched
586     else
587     {
588       for( yy = 0; yy < h; yy++ )
589       {
590         p = data + ((y + yy) * rowpix) + x;
591         for( xx = 0; xx < w; xx++ )
592         {
593           pix = rows[yin + yy][xin + xx];
594           if( pix != transparent )
595           {
596             *p = PixelLookup( colorMap, pix );
597           }
598           p++;
599         }
600       }
601     }
602   }
603   else
604   {
605     // walk pixels without worring about transparency at all
606     for( yy = 0; yy < h; yy++ )
607     {
608       p = data + ((y + yy) * rowpix) + x;
609       for( xx = 0; xx < w; xx++ )
610       {
611         pix = rows[yin + yy][xin + xx];
612         *p = PixelLookup( colorMap, pix );
613         p++;
614       }
615     }
616   }
617   ret = true;
618
619 on_error:
620   if( rows )
621   {
622     free( rows );
623   }
624   return ret;
625 }
626
627 /**
628  * @brief Reader header from the gif file and populates structures accordingly.
629  *
630  * @param[in] loaderInfo A LoaderInfo structure containing file descriptor and other data about GIF.
631  * @param[out] prop A ImageProperties structure for storing information about GIF data.
632  * @param[out] error Error code
633  * @return The true or false whether reading was successful or not.
634  */
635 bool ReadHeader( LoaderInfo &loaderInfo,
636                  ImageProperties &prop, //output struct
637                  int *error )
638 {
639   GifAnimationData &animated = loaderInfo.animated;
640   LoaderInfo::FileData &fileData = loaderInfo.fileData;
641   bool ret = false;
642   LoaderInfo::FileInfo fileInfo;
643   GifRecordType rec;
644   GifFileType *gif = NULL;
645   // it is possible which gif file have error midle of frames,
646   // in that case we should play gif file until meet error frame.
647   int imageNumber = 0;
648   int loopCount = -1;
649   FrameInfo *frameInfo = NULL;
650   bool full = true;
651
652   if( fileData.isLocalResource )
653   {
654     Internal::Platform::FileReader fileReader( fileData.fileName );
655     FILE *fp = fileReader.GetFile();
656     if( fp == NULL )
657     {
658       return false;
659     }
660
661     if( fseek( fp, 0, SEEK_END ) <= -1 )
662     {
663       return false;
664     }
665
666     fileData.length = ftell( fp );
667     if( fileData.length <= -1 )
668     {
669       return false;
670     }
671
672     if( ( ! fseek( fp, 0, SEEK_SET ) ) )
673     {
674       fileData.globalMap = reinterpret_cast<GifByteType*>( malloc(sizeof( GifByteType ) * fileData.length ) );
675       fileData.length = fread( fileData.globalMap, sizeof( GifByteType ), fileData.length, fp);
676       fileInfo.map = fileData.globalMap;
677     }
678     else
679     {
680       return false;
681     }
682   }
683   else
684   {
685     // remote file
686     bool succeeded;
687     Dali::Vector<uint8_t> dataBuffer;
688     size_t dataSize;
689
690     succeeded = TizenPlatform::Network::DownloadRemoteFileIntoMemory( fileData.fileName, dataBuffer, dataSize,
691                                                                       MAXIMUM_DOWNLOAD_IMAGE_SIZE );
692     if( succeeded )
693     {
694       size_t blobSize = dataBuffer.Size();
695       if( blobSize > 0U )
696       {
697         // Open a file handle on the memory buffer:
698         Dali::Internal::Platform::FileReader fileReader( dataBuffer, blobSize );
699         FILE * const fp = fileReader.GetFile();
700         if ( NULL != fp )
701         {
702           if( ( ! fseek( fp, 0, SEEK_SET ) ) )
703           {
704             fileData.globalMap = reinterpret_cast<GifByteType*>( malloc(sizeof( GifByteType ) * blobSize ) );
705             fileData.length = fread( fileData.globalMap, sizeof( GifByteType ), blobSize, fp);
706             fileInfo.map = fileData.globalMap;
707           }
708           else
709           {
710             DALI_LOG_ERROR( "Error seeking within file\n" );
711           }
712         }
713         else
714         {
715           DALI_LOG_ERROR( "Error reading file\n" );
716         }
717       }
718     }
719   }
720
721   if( !fileInfo.map )
722   {
723     LOADERR("LOAD_ERROR_CORRUPT_FILE");
724   }
725   fileInfo.length = fileData.length;
726   fileInfo.position = 0;
727
728 // actually ask libgif to open the file
729 #if GIFLIB_MAJOR >= 5
730   gif = DGifOpen( &fileInfo, FileRead, NULL );
731 #else
732   gif = DGifOpen( &fileInfo, FileRead );
733 #endif
734
735   if (!gif)
736   {
737     LOADERR("LOAD_ERROR_UNKNOWN_FORMAT");
738   }
739
740   // get the gif "screen size" (the actual image size)
741   prop.w = gif->SWidth;
742   prop.h = gif->SHeight;
743
744   // if size is invalid - abort here
745   if( (prop.w < 1) || (prop.h < 1) || (prop.w > IMG_MAX_SIZE) || (prop.h > IMG_MAX_SIZE) || IMG_TOO_BIG(prop.w, prop.h) )
746   {
747     if( IMG_TOO_BIG(prop.w, prop.h) )
748     {
749       LOADERR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
750     }
751     LOADERR("LOAD_ERROR_GENERIC");
752   }
753   // walk through gif records in file to figure out info
754   do
755   {
756     if( DGifGetRecordType(gif, &rec) == GIF_ERROR )
757     {
758       // if we have a gif that ends part way through a sequence
759       // (or animation) consider it valid and just break - no error
760       if( imageNumber > 1 )
761       {
762         break;
763       }
764       LOADERR("LOAD_ERROR_UNKNOWN_FORMAT");
765     }
766
767     // get image description section
768     if( rec == IMAGE_DESC_RECORD_TYPE )
769     {
770       int img_code;
771       GifByteType *img;
772
773       // get image desc
774       if( DGifGetImageDesc(gif) == GIF_ERROR )
775       {
776         LOADERR("LOAD_ERROR_UNKNOWN_FORMAT");
777       }
778       // skip decoding and just walk image to next
779       if( DGifGetCode(gif, &img_code, &img) == GIF_ERROR )
780       {
781         LOADERR("LOAD_ERROR_UNKNOWN_FORMAT");
782       }
783       // skip till next...
784       while( img )
785       {
786         img = NULL;
787         DGifGetCodeNext( gif, &img );
788       }
789       // store geometry in the last frame info data
790       if( frameInfo )
791       {
792         StoreFrameInfo( gif, frameInfo );
793         CheckTransparency( full, frameInfo, prop.w, prop.h );
794       }
795       // or if we dont have a frameInfo entry - create one even for stills
796       else
797       {
798         // allocate and save frame with field data
799         frameInfo = NewFrame( animated, -1, 0, 0, imageNumber + 1 );
800         if (!frameInfo)
801         {
802           LOADERR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
803         }
804         // store geometry info from gif image
805         StoreFrameInfo( gif, frameInfo );
806         // check for transparency/alpha
807         CheckTransparency( full, frameInfo, prop.w, prop.h );
808       }
809       imageNumber++;
810     }
811     // we have an extension code block - for animated gifs for sure
812     else if( rec == EXTENSION_RECORD_TYPE )
813     {
814       int ext_code;
815       GifByteType *ext;
816
817       ext = NULL;
818       // get the first extension entry
819       DGifGetExtension( gif, &ext_code, &ext );
820       while( ext )
821       {
822         // graphic control extension - for animated gif data
823         // and transparent index + flag
824         if( ext_code == 0xf9 )
825         {
826           // create frame and store it in image
827           int transparencyIndex = (ext[1] & 1) ? ext[4] : -1;
828           int disposeMode = (ext[1] >> 2) & 0x7;
829           int delay = (int(ext[3]) << 8) | int(ext[2]);
830           frameInfo = NewFrame(animated, transparencyIndex, disposeMode, delay, imageNumber + 1);
831           if( !frameInfo )
832           {
833             LOADERR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
834           }
835         }
836         // netscape extension indicating loop count.
837         else if( ext_code == 0xff ) /* application extension */
838         {
839           if( !strncmp(reinterpret_cast<char *>(&ext[1]), "NETSCAPE2.0", 11) ||
840               !strncmp(reinterpret_cast<char *>(&ext[1]), "ANIMEXTS1.0", 11) )
841           {
842             ext = NULL;
843             DGifGetExtensionNext( gif, &ext );
844             if( ext[1] == 0x01 )
845             {
846               loopCount = (int(ext[3]) << 8) | int(ext[2]);
847               if( loopCount > 0 )
848               {
849                 loopCount++;
850               }
851             }
852           }
853         }
854         // and continue onto the next extension entry
855         ext = NULL;
856         DGifGetExtensionNext( gif, &ext );
857       }
858     }
859   } while( rec != TERMINATE_RECORD_TYPE );
860
861   // if the gif main says we have more than one image or our image counting
862   // says so, then this image is animated - indicate this
863   if( (gif->ImageCount > 1) || (imageNumber > 1) )
864   {
865     animated.animated = 1;
866     animated.loopCount = loopCount;
867   }
868   animated.frameCount = std::min( gif->ImageCount, imageNumber );
869
870   if( !full )
871   {
872     prop.alpha = 1;
873   }
874
875   animated.currentFrame = 1;
876
877   // no errors in header scan etc. so set err and return value
878   *error = 0;
879   ret = true;
880
881 on_error: // jump here on any errors to clean up
882 #if (GIFLIB_MAJOR > 5) || ((GIFLIB_MAJOR == 5) && (GIFLIB_MINOR >= 1))
883   if( gif )
884   {
885     DGifCloseFile( gif, NULL );
886   }
887 #else
888   if( gif )
889   {
890     DGifCloseFile( gif );
891   }
892 #endif
893
894   return ret;
895 }
896
897 /**
898  * @brief Reader next frame of the gif file and populates structures accordingly.
899  *
900  * @param[in] loaderInfo A LoaderInfo structure containing file descriptor and other data about GIF.
901  * @param[in/out] prop A ImageProperties structure containing information about gif data.
902  * @param[out] pixels A pointer to buffer which will contain all pixel data of the frame on return.
903  * @param[out] error Error code
904  * @return The true or false whether reading was successful or not.
905  */
906 bool ReadNextFrame( LoaderInfo &loaderInfo, ImageProperties &prop, //  use for w and h
907                     unsigned char *pixels, int *error )
908 {
909   GifAnimationData &animated = loaderInfo.animated;
910   LoaderInfo::FileData &fileData = loaderInfo.fileData;
911   bool ret = false;
912   GifRecordType rec;
913   GifFileType *gif = NULL;
914   int index = 0, imageNumber = 0;
915   FrameInfo *frameInfo;
916   ImageFrame *frame = NULL;
917   ImageFrame *lastPreservedFrame = NULL;
918
919   index = animated.currentFrame;
920
921   // if index is invalid for animated image - error out
922   if ((animated.animated) && ((index <= 0) || (index > animated.frameCount)))
923   {
924     LOADERR("LOAD_ERROR_GENERIC");
925   }
926
927   // find the given frame index
928   frame = FindFrame( animated, index );
929   if( frame )
930   {
931     if( (frame->loaded) && (frame->data) )
932     {
933       // frame is already there and decoded - jump to end
934       goto on_ok;
935     }
936   }
937   else
938   {
939     LOADERR("LOAD_ERROR_CORRUPT_FILE");
940   }
941
942 open_file:
943   // actually ask libgif to open the file
944   gif = loaderInfo.gif;
945   if( !gif )
946   {
947     loaderInfo.fileInfo.map = fileData.globalMap ;
948     if( !loaderInfo.fileInfo.map )
949     {
950       LOADERR("LOAD_ERROR_CORRUPT_FILE");
951     }
952     loaderInfo.fileInfo.length = fileData.length;
953     loaderInfo.fileInfo.position = 0;
954
955 #if GIFLIB_MAJOR >= 5
956     gif = DGifOpen( &( loaderInfo.fileInfo ), FileRead, NULL );
957 #else
958     gif = DGifOpen( &( loaderInfo.fileInfo ), FileRead );
959 #endif
960     // if gif open failed... get out of here
961     if( !gif )
962     {
963       LOADERR("LOAD_ERROR_UNKNOWN_FORMAT");
964     }
965     loaderInfo.gif = gif;
966     loaderInfo.imageNumber = 1;
967   }
968
969   // if we want to go backwards, we likely need/want to re-decode from the
970   // start as we have nothing to build on
971   if( (index > 0) && (index < loaderInfo.imageNumber) && (animated.animated) )
972   {
973 #if (GIFLIB_MAJOR > 5) || ((GIFLIB_MAJOR == 5) && (GIFLIB_MINOR >= 1))
974     if( loaderInfo.gif )
975       DGifCloseFile( loaderInfo.gif, NULL );
976 #else
977     if( loaderInfo.gif )
978       DGifCloseFile( loaderInfo.gif );
979 #endif
980     loaderInfo.gif = NULL;
981     loaderInfo.imageNumber = 0;
982     goto open_file;
983   }
984
985   // our current position is the previous frame we decoded from the file
986   imageNumber = loaderInfo.imageNumber;
987
988   // walk through gif records in file to figure out info
989   do
990   {
991     if( DGifGetRecordType( gif, &rec ) == GIF_ERROR )
992     {
993       LOADERR("LOAD_ERROR_UNKNOWN_FORMAT");
994     }
995
996     if( rec == EXTENSION_RECORD_TYPE )
997     {
998       int ext_code;
999       GifByteType *ext = NULL;
1000       DGifGetExtension( gif, &ext_code, &ext );
1001
1002       while( ext )
1003       {
1004         ext = NULL;
1005         DGifGetExtensionNext( gif, &ext );
1006       }
1007     }
1008     // get image description section
1009     else if( rec == IMAGE_DESC_RECORD_TYPE )
1010     {
1011       int xin = 0, yin = 0, x = 0, y = 0, w = 0, h = 0;
1012       int img_code;
1013       GifByteType *img;
1014       ImageFrame *previousFrame = NULL;
1015       ImageFrame *thisFrame = NULL;
1016
1017       // get image desc
1018       if( DGifGetImageDesc(gif) == GIF_ERROR )
1019       {
1020         LOADERR("LOAD_ERROR_UNKNOWN_FORMAT");
1021       }
1022
1023       // get the previous frame entry AND the current one to fill in
1024       previousFrame = FindFrame(animated, imageNumber - 1);
1025       thisFrame = FindFrame(animated, imageNumber);
1026
1027       // if we have a frame AND we're animated AND we have no data...
1028       if( (thisFrame) && (!thisFrame->data) && (animated.animated) )
1029       {
1030         bool first = false;
1031
1032         // allocate it
1033         thisFrame->data = new uint32_t[prop.w * prop.h];
1034
1035         if( !thisFrame->data )
1036         {
1037           LOADERR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
1038         }
1039
1040         // if we have no prior frame OR prior frame data... empty
1041         if( (!previousFrame) || (!previousFrame->data) )
1042         {
1043           first = true;
1044           frameInfo = &(thisFrame->info);
1045           memset( thisFrame->data, 0, prop.w * prop.h * sizeof(uint32_t) );
1046         }
1047         // we have a prior frame to copy data from...
1048         else
1049         {
1050           frameInfo = &( previousFrame->info );
1051
1052           // fix coords of sub image in case it goes out...
1053           ClipCoordinates( prop.w, prop.h, &xin, &yin,
1054                            frameInfo->x, frameInfo->y, frameInfo->w, frameInfo->h,
1055                            &x, &y, &w, &h );
1056
1057           // if dispose mode is not restore - then copy pre frame
1058           if( frameInfo->dispose != DISPOSE_PREVIOUS )
1059           {
1060             memcpy( thisFrame->data, previousFrame->data, prop.w * prop.h * sizeof(uint32_t) );
1061           }
1062
1063           // if dispose mode is "background" then fill with bg
1064           if( frameInfo->dispose == DISPOSE_BACKGROUND )
1065           {
1066             FillFrame( thisFrame->data, prop.w, gif, frameInfo, x, y, w, h );
1067           }
1068           else if( frameInfo->dispose == DISPOSE_PREVIOUS ) // GIF_DISPOSE_RESTORE
1069           {
1070             int prevIndex = 2;
1071             do
1072             {
1073               // Find last preserved frame.
1074               lastPreservedFrame = FindFrame( animated, imageNumber - prevIndex );
1075               if( ! lastPreservedFrame )
1076               {
1077                 LOADERR( "LOAD_ERROR_LAST_PRESERVED_FRAME_NOT_FOUND" );
1078               }
1079               prevIndex++;
1080             } while( lastPreservedFrame && lastPreservedFrame->info.dispose == DISPOSE_PREVIOUS );
1081
1082             if ( lastPreservedFrame )
1083             {
1084               memcpy( thisFrame->data, lastPreservedFrame->data, prop.w * prop.h * sizeof(uint32_t) );
1085             }
1086           }
1087         }
1088         // now draw this frame on top
1089         frameInfo = &( thisFrame->info );
1090         ClipCoordinates( prop.w, prop.h, &xin, &yin,
1091                          frameInfo->x, frameInfo->y, frameInfo->w, frameInfo->h,
1092                          &x, &y, &w, &h );
1093         if( !DecodeImage( gif, thisFrame->data, prop.w,
1094                           xin, yin, frameInfo->transparent,
1095                           x, y, w, h, first) )
1096         {
1097           LOADERR("LOAD_ERROR_CORRUPT_FILE");
1098         }
1099
1100         // mark as loaded and done
1101         thisFrame->loaded = true;
1102
1103         FlushFrames( animated, prop.w, prop.h, thisFrame, previousFrame, lastPreservedFrame );
1104       }
1105       // if we have a frame BUT the image is not animated. different
1106       // path
1107       else if( (thisFrame) && (!thisFrame->data) && (!animated.animated) )
1108       {
1109         // if we don't have the data decoded yet - decode it
1110         if( (!thisFrame->loaded) || (!thisFrame->data) )
1111         {
1112           // use frame info but we WONT allocate frame pixels
1113           frameInfo = &( thisFrame->info );
1114           ClipCoordinates( prop.w, prop.h, &xin, &yin,
1115                            frameInfo->x, frameInfo->y, frameInfo->w, frameInfo->h,
1116                            &x, &y, &w, &h );
1117
1118           // clear out all pixels
1119           FillFrame( reinterpret_cast<uint32_t *>(pixels), prop.w, gif, frameInfo, 0, 0, prop.w, prop.h );
1120
1121           // and decode the gif with overwriting
1122           if( !DecodeImage( gif, reinterpret_cast<uint32_t *>(pixels), prop.w,
1123                             xin, yin, frameInfo->transparent, x, y, w, h, true) )
1124           {
1125             LOADERR("LOAD_ERROR_CORRUPT_FILE");
1126           }
1127
1128           // mark as loaded and done
1129           thisFrame->loaded = true;
1130         }
1131         // flush mem we don't need (at expense of decode cpu)
1132       }
1133       else
1134       {
1135         // skip decoding and just walk image to next
1136         if( DGifGetCode( gif, &img_code, &img ) == GIF_ERROR )
1137         {
1138           LOADERR("LOAD_ERROR_UNKNOWN_FORMAT");
1139         }
1140
1141         while( img )
1142         {
1143           img = NULL;
1144           DGifGetCodeNext( gif, &img );
1145         }
1146       }
1147
1148       imageNumber++;
1149       // if we found the image we wanted - get out of here
1150       if( imageNumber > index )
1151       {
1152         break;
1153       }
1154     }
1155   } while( rec != TERMINATE_RECORD_TYPE );
1156
1157   // if we are at the end of the animation or not animated, close file
1158   loaderInfo.imageNumber = imageNumber;
1159   if( (animated.frameCount <= 1) || (rec == TERMINATE_RECORD_TYPE) )
1160   {
1161 #if (GIFLIB_MAJOR > 5) || ((GIFLIB_MAJOR == 5) && (GIFLIB_MINOR >= 1))
1162     if( loaderInfo.gif )
1163     {
1164       DGifCloseFile( loaderInfo.gif, NULL );
1165     }
1166 #else
1167     if( loaderInfo.gif )
1168     {
1169       DGifCloseFile( loaderInfo.gif );
1170     }
1171 #endif
1172
1173     loaderInfo.gif = NULL;
1174     loaderInfo.imageNumber = 0;
1175   }
1176
1177 on_ok:
1178   // no errors in header scan etc. so set err and return value
1179   *error = 0;
1180   ret = true;
1181
1182   // if it was an animated image we need to copy the data to the
1183   // pixels for the image from the frame holding the data
1184   if( animated.animated && frame->data )
1185   {
1186     memcpy( pixels, frame->data, prop.w * prop.h * sizeof( uint32_t ) );
1187   }
1188
1189 on_error: // jump here on any errors to clean up
1190   return ret;
1191 }
1192
1193 } // unnamed namespace
1194
1195 struct GifLoading::Impl
1196 {
1197 public:
1198   Impl( const std::string& url, bool isLocalResource )
1199   : mUrl( url )
1200   {
1201     loaderInfo.gif = nullptr;
1202     int error;
1203     loaderInfo.fileData.fileName = mUrl.c_str();
1204     loaderInfo.fileData.isLocalResource = isLocalResource;
1205
1206     ReadHeader( loaderInfo, imageProperties, &error );
1207   }
1208
1209   // Moveable but not copyable
1210
1211   Impl( const Impl& ) = delete;
1212   Impl& operator=( const Impl& ) = delete;
1213   Impl( Impl&& ) = default;
1214   Impl& operator=( Impl&& ) = default;
1215
1216   ~Impl()
1217   {
1218     if( loaderInfo.fileData.globalMap  )
1219     {
1220       free( loaderInfo.fileData.globalMap );
1221       loaderInfo.fileData.globalMap  = nullptr;
1222     }
1223
1224     // Delete all image frames
1225     for( auto &&frame : loaderInfo.animated.frames )
1226     {
1227       if( frame.data != nullptr )
1228       {
1229         // De-allocate memory of the frame data.
1230         delete[] frame.data;
1231         frame.data = nullptr;
1232       }
1233     }
1234   }
1235
1236   std::string mUrl;
1237   LoaderInfo loaderInfo;
1238   ImageProperties imageProperties;
1239 };
1240
1241 AnimatedImageLoadingPtr GifLoading::New( const std::string &url, bool isLocalResource )
1242 {
1243   return AnimatedImageLoadingPtr( new GifLoading( url, isLocalResource ) );
1244 }
1245
1246 GifLoading::GifLoading( const std::string &url, bool isLocalResource )
1247 : mImpl( new GifLoading::Impl( url, isLocalResource ) )
1248 {
1249 }
1250
1251
1252 GifLoading::~GifLoading()
1253 {
1254   delete mImpl;
1255 }
1256
1257 bool GifLoading::LoadNextNFrames( uint32_t frameStartIndex, int count, std::vector<Dali::PixelData> &pixelData )
1258 {
1259   int error;
1260   bool ret = false;
1261
1262   const int bufferSize = mImpl->imageProperties.w * mImpl->imageProperties.h * sizeof( uint32_t );
1263
1264   DALI_LOG_INFO( gGifLoadingLogFilter, Debug::Concise, "LoadNextNFrames( frameStartIndex:%d, count:%d )\n", frameStartIndex, count );
1265
1266   for( int i = 0; i < count; ++i )
1267   {
1268     auto pixelBuffer = new unsigned char[ bufferSize ];
1269
1270     mImpl->loaderInfo.animated.currentFrame = 1 + ( (frameStartIndex + i) % mImpl->loaderInfo.animated.frameCount );
1271
1272     if( ReadNextFrame( mImpl->loaderInfo, mImpl->imageProperties, pixelBuffer, &error ) )
1273     {
1274       if( pixelBuffer )
1275       {
1276         pixelData.push_back( Dali::PixelData::New( pixelBuffer, bufferSize,
1277                                                    mImpl->imageProperties.w, mImpl->imageProperties.h,
1278                                                    Dali::Pixel::RGBA8888, Dali::PixelData::DELETE_ARRAY) );
1279         ret = true;
1280       }
1281     }
1282   }
1283
1284   return ret;
1285 }
1286
1287 Dali::Devel::PixelBuffer GifLoading::LoadFrame( uint32_t frameIndex )
1288 {
1289   int error;
1290   Dali::Devel::PixelBuffer pixelBuffer;
1291
1292   DALI_LOG_INFO( gGifLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex );
1293
1294   pixelBuffer = Dali::Devel::PixelBuffer::New( mImpl->imageProperties.w, mImpl->imageProperties.h, Dali::Pixel::RGBA8888 );
1295
1296   mImpl->loaderInfo.animated.currentFrame = 1 + ( frameIndex % mImpl->loaderInfo.animated.frameCount );
1297   ReadNextFrame( mImpl->loaderInfo, mImpl->imageProperties, pixelBuffer.GetBuffer(), &error );
1298
1299   if( error != 0 )
1300   {
1301     pixelBuffer = Dali::Devel::PixelBuffer();
1302   }
1303   return pixelBuffer;
1304 }
1305
1306 ImageDimensions GifLoading::GetImageSize() const
1307 {
1308   return ImageDimensions( mImpl->imageProperties.w, mImpl->imageProperties.h );
1309 }
1310
1311 uint32_t GifLoading::GetImageCount() const
1312 {
1313   return mImpl->loaderInfo.animated.frameCount;
1314 }
1315
1316 uint32_t GifLoading::GetFrameInterval( uint32_t frameIndex ) const
1317 {
1318   return mImpl->loaderInfo.animated.frames[frameIndex].info.delay * 10;
1319 }
1320
1321 std::string GifLoading::GetUrl() const
1322 {
1323   return mImpl->mUrl;
1324 }
1325
1326 } // namespace Adaptor
1327
1328 } // namespace Internal
1329
1330 } // namespace Dali