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