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