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