[dali_2.0.19] Merge branch 'devel/master'
[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\n");
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_LOG_ERROR("gifW : %d, w : %d, gifH : %d, h : %d\n", gifW, w, gifH, h);
639     DALI_ASSERT_DEBUG(false && "Dimensions are bigger than the Gif image size");
640     goto on_error;
641   }
642
643   // build a blob of memory to have pointers to rows of pixels
644   // AND store the decoded gif pixels (1 byte per pixel) as welll
645   rows = static_cast<GifRowType*>(malloc((gifH * sizeof(GifRowType)) + (gifW * gifH * sizeof(GifPixelType))));
646   if(!rows)
647   {
648     goto on_error;
649   }
650
651   // fill in the pointers at the start
652   for(yy = 0; yy < gifH; yy++)
653   {
654     rows[yy] = reinterpret_cast<unsigned char*>(rows) + (gifH * sizeof(GifRowType)) + (yy * gifW * sizeof(GifPixelType));
655   }
656
657   // if gif is interlaced, walk interlace pattern and decode into rows
658   if(gif->Image.Interlace)
659   {
660     for(i = 0; i < 4; i++)
661     {
662       for(yy = intoffset[i]; yy < gifH; yy += intjump[i])
663       {
664         if(DGifGetLine(gif, rows[yy], gifW) != GIF_OK)
665         {
666           goto on_error;
667         }
668       }
669     }
670   }
671   // normal top to bottom - decode into rows
672   else
673   {
674     for(yy = 0; yy < gifH; yy++)
675     {
676       if(DGifGetLine(gif, rows[yy], gifW) != GIF_OK)
677       {
678         goto on_error;
679       }
680     }
681   }
682
683   // work out what colormap to use
684   if(gif->Image.ColorMap)
685   {
686     colorMap = gif->Image.ColorMap;
687   }
688   else
689   {
690     colorMap = gif->SColorMap;
691   }
692
693   // if we need to deal with transparent pixels at all...
694   if(transparent >= 0)
695   {
696     // if we are told to FILL (overwrite with transparency kept)
697     if(fill)
698     {
699       for(yy = 0; yy < h; yy++)
700       {
701         p = data + ((y + yy) * rowpix) + x;
702         for(xx = 0; xx < w; xx++)
703         {
704           pix = rows[yin + yy][xin + xx];
705           if(pix != transparent)
706           {
707             *p = PixelLookup(colorMap, pix);
708           }
709           else
710           {
711             *p = 0;
712           }
713           p++;
714         }
715       }
716     }
717     // paste on top with transparent pixels untouched
718     else
719     {
720       for(yy = 0; yy < h; yy++)
721       {
722         p = data + ((y + yy) * rowpix) + x;
723         for(xx = 0; xx < w; xx++)
724         {
725           pix = rows[yin + yy][xin + xx];
726           if(pix != transparent)
727           {
728             *p = PixelLookup(colorMap, pix);
729           }
730           p++;
731         }
732       }
733     }
734   }
735   else
736   {
737     // walk pixels without worring about transparency at all
738     for(yy = 0; yy < h; yy++)
739     {
740       p = data + ((y + yy) * rowpix) + x;
741       for(xx = 0; xx < w; xx++)
742       {
743         pix = rows[yin + yy][xin + xx];
744         *p  = PixelLookup(colorMap, pix);
745         p++;
746       }
747     }
748   }
749   ret = true;
750
751 on_error:
752   if(rows)
753   {
754     free(rows);
755   }
756   return ret;
757 }
758
759 /**
760  * @brief Reader header from the gif file and populates structures accordingly.
761  *
762  * @param[in] loaderInfo A LoaderInfo structure containing file descriptor and other data about GIF.
763  * @param[out] prop A ImageProperties structure for storing information about GIF data.
764  * @param[out] error Error code
765  * @return The true or false whether reading was successful or not.
766  */
767 bool ReadHeader(LoaderInfo&      loaderInfo,
768                 ImageProperties& prop, //output struct
769                 int*             error)
770 {
771   GifAnimationData&     animated = loaderInfo.animated;
772   LoaderInfo::FileData& fileData = loaderInfo.fileData;
773   bool                  success  = false;
774   LoaderInfo::FileInfo  fileInfo;
775   GifRecordType         rec;
776
777   // it is possible which gif file have error midle of frames,
778   // in that case we should play gif file until meet error frame.
779   int        imageNumber = 0;
780   int        loopCount   = -1;
781   FrameInfo* frameInfo   = NULL;
782   bool       full        = true;
783
784   success = fileData.LoadFile();
785   if(!success || !fileData.globalMap)
786   {
787     success = false;
788     DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
789   }
790   else
791   {
792     fileInfo.map      = fileData.globalMap;
793     fileInfo.length   = fileData.length;
794     fileInfo.position = 0;
795     GifAccessor gifAccessor(fileInfo);
796
797     if(gifAccessor.gif)
798     {
799       // get the gif "screen size" (the actual image size)
800       prop.w = gifAccessor.gif->SWidth;
801       prop.h = gifAccessor.gif->SHeight;
802
803       // if size is invalid - abort here
804       if((prop.w < 1) || (prop.h < 1) || (prop.w > IMG_MAX_SIZE) || (prop.h > IMG_MAX_SIZE) || IMG_TOO_BIG(prop.w, prop.h))
805       {
806         if(IMG_TOO_BIG(prop.w, prop.h))
807         {
808           success = false;
809           DALI_LOG_ERROR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
810         }
811         else
812         {
813           success = false;
814           DALI_LOG_ERROR("LOAD_ERROR_GENERIC");
815         }
816       }
817       else
818       {
819         // walk through gif records in file to figure out info
820         success = true;
821         do
822         {
823           if(DGifGetRecordType(gifAccessor.gif, &rec) == GIF_ERROR)
824           {
825             // if we have a gif that ends part way through a sequence
826             // (or animation) consider it valid and just break - no error
827             if(imageNumber <= 1)
828             {
829               success = true;
830             }
831             else
832             {
833               success = false;
834               DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
835             }
836             break;
837           }
838
839           // get image description section
840           if(rec == IMAGE_DESC_RECORD_TYPE)
841           {
842             int          img_code;
843             GifByteType* img;
844
845             // get image desc
846             if(DGifGetImageDesc(gifAccessor.gif) == GIF_ERROR)
847             {
848               success = false;
849               DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
850               break;
851             }
852             // skip decoding and just walk image to next
853             if(DGifGetCode(gifAccessor.gif, &img_code, &img) == GIF_ERROR)
854             {
855               success = false;
856               DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
857               break;
858             }
859             // skip till next...
860             while(img)
861             {
862               img = NULL;
863               DGifGetCodeNext(gifAccessor.gif, &img);
864             }
865             // store geometry in the last frame info data
866             if(frameInfo)
867             {
868               StoreFrameInfo(gifAccessor.gif, frameInfo);
869               CheckTransparency(full, frameInfo, prop.w, prop.h);
870             }
871             // or if we dont have a frameInfo entry - create one even for stills
872             else
873             {
874               // allocate and save frame with field data
875               frameInfo = NewFrame(animated, -1, 0, 0, imageNumber + 1);
876               if(!frameInfo)
877               {
878                 success = false;
879                 DALI_LOG_ERROR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
880                 break;
881               }
882               // store geometry info from gif image
883               StoreFrameInfo(gifAccessor.gif, frameInfo);
884               // check for transparency/alpha
885               CheckTransparency(full, frameInfo, prop.w, prop.h);
886             }
887             imageNumber++;
888           }
889           // we have an extension code block - for animated gifs for sure
890           else if(rec == EXTENSION_RECORD_TYPE)
891           {
892             int          ext_code;
893             GifByteType* ext = NULL;
894
895             // get the first extension entry
896             DGifGetExtension(gifAccessor.gif, &ext_code, &ext);
897             while(ext)
898             {
899               // graphic control extension - for animated gif data
900               // and transparent index + flag
901               if(ext_code == 0xf9)
902               {
903                 // create frame and store it in image
904                 int transparencyIndex = (ext[1] & 1) ? ext[4] : -1;
905                 int disposeMode       = (ext[1] >> 2) & 0x7;
906                 int delay             = (int(ext[3]) << 8) | int(ext[2]);
907                 frameInfo             = NewFrame(animated, transparencyIndex, disposeMode, delay, imageNumber + 1);
908                 if(!frameInfo)
909                 {
910                   success = false;
911                   DALI_LOG_ERROR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
912                   break;
913                 }
914               }
915               // netscape extension indicating loop count.
916               else if(ext_code == 0xff) /* application extension */
917               {
918                 if(!strncmp(reinterpret_cast<char*>(&ext[1]), "NETSCAPE2.0", 11) ||
919                    !strncmp(reinterpret_cast<char*>(&ext[1]), "ANIMEXTS1.0", 11))
920                 {
921                   ext = NULL;
922                   DGifGetExtensionNext(gifAccessor.gif, &ext);
923                   if(ext[1] == 0x01)
924                   {
925                     loopCount = (int(ext[3]) << 8) | int(ext[2]);
926                     if(loopCount > 0)
927                     {
928                       loopCount++;
929                     }
930                   }
931                 }
932               }
933
934               // and continue onto the next extension entry
935               ext = NULL;
936               DGifGetExtensionNext(gifAccessor.gif, &ext);
937             }
938           }
939         } while(rec != TERMINATE_RECORD_TYPE && success);
940
941         if(success)
942         {
943           // if the gif main says we have more than one image or our image counting
944           // says so, then this image is animated - indicate this
945           if((gifAccessor.gif->ImageCount > 1) || (imageNumber > 1))
946           {
947             animated.animated  = 1;
948             animated.loopCount = loopCount;
949           }
950           animated.frameCount = std::min(gifAccessor.gif->ImageCount, imageNumber);
951
952           if(!full)
953           {
954             prop.alpha = 1;
955           }
956
957           animated.currentFrame = 1;
958
959           // no errors in header scan etc. so set err and return value
960           *error = 0;
961         }
962       }
963     }
964   }
965   return success;
966 }
967
968 /**
969  * @brief Reader next frame of the gif file and populates structures accordingly.
970  *
971  * @param[in,out] loaderInfo A LoaderInfo structure containing file descriptor and other data about GIF.
972  * @param[in,out] prop A ImageProperties structure containing information about gif data.
973  * @param[out] pixels A pointer to buffer which will contain all pixel data of the frame on return.
974  * @param[out] error Error code
975  * @return The true or false whether reading was successful or not.
976  */
977 bool ReadNextFrame(LoaderInfo& loaderInfo, ImageProperties& prop, //  use for w and h
978                    unsigned char* pixels,
979                    int*           error)
980 {
981   GifAnimationData&     animated = loaderInfo.animated;
982   LoaderInfo::FileData& fileData = loaderInfo.fileData;
983   bool                  ret      = false;
984   GifRecordType         rec;
985   int                   index = 0, imageNumber = 0;
986   FrameInfo*            frameInfo;
987   ImageFrame*           frame              = NULL;
988   ImageFrame*           lastPreservedFrame = NULL;
989
990   index = animated.currentFrame;
991
992   // if index is invalid for animated image - error out
993   if((animated.animated) && ((index <= 0) || (index > animated.frameCount)))
994   {
995     DALI_LOG_ERROR("LOAD_ERROR_GENERIC");
996     return false;
997   }
998
999   // find the given frame index
1000   frame = FindFrame(animated, index);
1001   if(!frame)
1002   {
1003     DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
1004     return false;
1005   }
1006   else if(!(frame->loaded) || !(frame->data))
1007   {
1008     // if we want to go backwards, we likely need/want to re-decode from the
1009     // start as we have nothing to build on. If there is a gif, imageNumber
1010     // has been set already.
1011     if(loaderInfo.gifAccessor && loaderInfo.imageNumber > 0)
1012     {
1013       if((index > 0) && (index < loaderInfo.imageNumber) && (animated.animated))
1014       {
1015         loaderInfo.gifAccessor.reset();
1016         loaderInfo.imageNumber = 0;
1017       }
1018     }
1019
1020     // actually ask libgif to open the file
1021     if(!loaderInfo.gifAccessor)
1022     {
1023       loaderInfo.fileInfo.map      = fileData.globalMap;
1024       loaderInfo.fileInfo.length   = fileData.length;
1025       loaderInfo.fileInfo.position = 0;
1026       if(!loaderInfo.fileInfo.map)
1027       {
1028         DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
1029         return false;
1030       }
1031       std::unique_ptr<GifAccessor> gifAccessor = std::make_unique<GifAccessor>(loaderInfo.fileInfo);
1032       if(!gifAccessor->gif)
1033       {
1034         DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1035         return false;
1036       }
1037       loaderInfo.gifAccessor = std::move(gifAccessor);
1038       loaderInfo.imageNumber = 1;
1039     }
1040
1041     // our current position is the previous frame we decoded from the file
1042     imageNumber = loaderInfo.imageNumber;
1043
1044     // walk through gif records in file to figure out info
1045     do
1046     {
1047       if(DGifGetRecordType(loaderInfo.gifAccessor->gif, &rec) == GIF_ERROR)
1048       {
1049         DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1050         return false;
1051       }
1052
1053       if(rec == EXTENSION_RECORD_TYPE)
1054       {
1055         int          ext_code;
1056         GifByteType* ext = NULL;
1057         DGifGetExtension(loaderInfo.gifAccessor->gif, &ext_code, &ext);
1058
1059         while(ext)
1060         {
1061           ext = NULL;
1062           DGifGetExtensionNext(loaderInfo.gifAccessor->gif, &ext);
1063         }
1064       }
1065       // get image description section
1066       else if(rec == IMAGE_DESC_RECORD_TYPE)
1067       {
1068         int          xin = 0, yin = 0, x = 0, y = 0, w = 0, h = 0;
1069         int          img_code;
1070         GifByteType* img;
1071         ImageFrame*  previousFrame = NULL;
1072         ImageFrame*  thisFrame     = NULL;
1073
1074         // get image desc
1075         if(DGifGetImageDesc(loaderInfo.gifAccessor->gif) == GIF_ERROR)
1076         {
1077           DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1078           return false;
1079         }
1080
1081         // get the previous frame entry AND the current one to fill in
1082         previousFrame = FindFrame(animated, imageNumber - 1);
1083         thisFrame     = FindFrame(animated, imageNumber);
1084
1085         // if we have a frame AND we're animated AND we have no data...
1086         if((thisFrame) && (!thisFrame->data) && (animated.animated))
1087         {
1088           bool first = false;
1089
1090           // allocate it
1091           thisFrame->data = new uint32_t[prop.w * prop.h];
1092
1093           if(!thisFrame->data)
1094           {
1095             DALI_LOG_ERROR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
1096             return false;
1097           }
1098
1099           // if we have no prior frame OR prior frame data... empty
1100           if((!previousFrame) || (!previousFrame->data))
1101           {
1102             first     = true;
1103             frameInfo = &(thisFrame->info);
1104             memset(thisFrame->data, 0, prop.w * prop.h * sizeof(uint32_t));
1105           }
1106           // we have a prior frame to copy data from...
1107           else
1108           {
1109             frameInfo = &(previousFrame->info);
1110
1111             // fix coords of sub image in case it goes out...
1112             ClipCoordinates(prop.w, prop.h, &xin, &yin, frameInfo->x, frameInfo->y, frameInfo->w, frameInfo->h, &x, &y, &w, &h);
1113
1114             // if dispose mode is not restore - then copy pre frame
1115             if(frameInfo->dispose != DISPOSE_PREVIOUS)
1116             {
1117               memcpy(thisFrame->data, previousFrame->data, prop.w * prop.h * sizeof(uint32_t));
1118             }
1119
1120             // if dispose mode is "background" then fill with bg
1121             if(frameInfo->dispose == DISPOSE_BACKGROUND)
1122             {
1123               FillFrame(thisFrame->data, prop.w, loaderInfo.gifAccessor->gif, frameInfo, x, y, w, h);
1124             }
1125             else if(frameInfo->dispose == DISPOSE_PREVIOUS) // GIF_DISPOSE_RESTORE
1126             {
1127               int prevIndex = 2;
1128               do
1129               {
1130                 // Find last preserved frame.
1131                 lastPreservedFrame = FindFrame(animated, imageNumber - prevIndex);
1132                 if(!lastPreservedFrame)
1133                 {
1134                   DALI_LOG_ERROR("LOAD_ERROR_LAST_PRESERVED_FRAME_NOT_FOUND");
1135                   return false;
1136                 }
1137                 prevIndex++;
1138               } while(lastPreservedFrame && lastPreservedFrame->info.dispose == DISPOSE_PREVIOUS);
1139
1140               if(lastPreservedFrame)
1141               {
1142                 memcpy(thisFrame->data, lastPreservedFrame->data, prop.w * prop.h * sizeof(uint32_t));
1143               }
1144             }
1145           }
1146           // now draw this frame on top
1147           frameInfo = &(thisFrame->info);
1148           ClipCoordinates(prop.w, prop.h, &xin, &yin, frameInfo->x, frameInfo->y, frameInfo->w, frameInfo->h, &x, &y, &w, &h);
1149           if(!DecodeImage(loaderInfo.gifAccessor->gif, thisFrame->data, prop.w, xin, yin, frameInfo->transparent, x, y, w, h, first))
1150           {
1151             DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
1152             return false;
1153           }
1154
1155           // mark as loaded and done
1156           thisFrame->loaded = true;
1157
1158           FlushFrames(animated, prop.w, prop.h, thisFrame, previousFrame, lastPreservedFrame);
1159         }
1160         // if we have a frame BUT the image is not animated. different
1161         // path
1162         else if((thisFrame) && (!thisFrame->data) && (!animated.animated))
1163         {
1164           // if we don't have the data decoded yet - decode it
1165           if((!thisFrame->loaded) || (!thisFrame->data))
1166           {
1167             // use frame info but we WONT allocate frame pixels
1168             frameInfo = &(thisFrame->info);
1169             ClipCoordinates(prop.w, prop.h, &xin, &yin, frameInfo->x, frameInfo->y, frameInfo->w, frameInfo->h, &x, &y, &w, &h);
1170
1171             // clear out all pixels
1172             FillFrame(reinterpret_cast<uint32_t*>(pixels), prop.w, loaderInfo.gifAccessor->gif, frameInfo, 0, 0, prop.w, prop.h);
1173
1174             // and decode the gif with overwriting
1175             if(!DecodeImage(loaderInfo.gifAccessor->gif, reinterpret_cast<uint32_t*>(pixels), prop.w, xin, yin, frameInfo->transparent, x, y, w, h, true))
1176             {
1177               DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
1178               return false;
1179             }
1180
1181             // mark as loaded and done
1182             thisFrame->loaded = true;
1183           }
1184           // flush mem we don't need (at expense of decode cpu)
1185         }
1186         else
1187         {
1188           // skip decoding and just walk image to next
1189           if(DGifGetCode(loaderInfo.gifAccessor->gif, &img_code, &img) == GIF_ERROR)
1190           {
1191             DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1192             return false;
1193           }
1194
1195           while(img)
1196           {
1197             img = NULL;
1198             DGifGetCodeNext(loaderInfo.gifAccessor->gif, &img);
1199           }
1200         }
1201
1202         imageNumber++;
1203         // if we found the image we wanted - get out of here
1204         if(imageNumber > index)
1205         {
1206           break;
1207         }
1208       }
1209     } while(rec != TERMINATE_RECORD_TYPE);
1210
1211     // if we are at the end of the animation or not animated, close file
1212     loaderInfo.imageNumber = imageNumber;
1213     if((animated.frameCount <= 1) || (rec == TERMINATE_RECORD_TYPE))
1214     {
1215       loaderInfo.gifAccessor.reset();
1216       loaderInfo.imageNumber = 0;
1217     }
1218   }
1219
1220   // no errors in header scan etc. so set err and return value
1221   *error = 0;
1222   ret    = true;
1223
1224   // if it was an animated image we need to copy the data to the
1225   // pixels for the image from the frame holding the data
1226   if(animated.animated && frame->data)
1227   {
1228     memcpy(pixels, frame->data, prop.w * prop.h * sizeof(uint32_t));
1229   }
1230
1231   return ret;
1232 }
1233
1234 } // unnamed namespace
1235
1236 struct GifLoading::Impl
1237 {
1238 public:
1239   Impl(const std::string& url, bool isLocalResource)
1240   : mUrl(url),
1241     mLoadSucceeded(true)
1242   {
1243     loaderInfo.gifAccessor = nullptr;
1244     int error;
1245     loaderInfo.fileData.fileName        = mUrl.c_str();
1246     loaderInfo.fileData.isLocalResource = isLocalResource;
1247
1248     mLoadSucceeded = ReadHeader(loaderInfo, imageProperties, &error);
1249   }
1250
1251   // Moveable but not copyable
1252   Impl(const Impl&) = delete;
1253   Impl& operator=(const Impl&) = delete;
1254   Impl(Impl&&)                 = default;
1255   Impl& operator=(Impl&&) = default;
1256
1257   std::string     mUrl;
1258   LoaderInfo      loaderInfo;
1259   ImageProperties imageProperties;
1260   bool            mLoadSucceeded;
1261 };
1262
1263 AnimatedImageLoadingPtr GifLoading::New(const std::string& url, bool isLocalResource)
1264 {
1265   return AnimatedImageLoadingPtr(new GifLoading(url, isLocalResource));
1266 }
1267
1268 GifLoading::GifLoading(const std::string& url, bool isLocalResource)
1269 : mImpl(new GifLoading::Impl(url, isLocalResource))
1270 {
1271 }
1272
1273 GifLoading::~GifLoading()
1274 {
1275   delete mImpl;
1276 }
1277
1278 bool GifLoading::LoadNextNFrames(uint32_t frameStartIndex, int count, std::vector<Dali::PixelData>& pixelData)
1279 {
1280   int  error;
1281   bool ret = false;
1282   if(!mImpl->mLoadSucceeded)
1283   {
1284     return false;
1285   }
1286
1287   const int bufferSize = mImpl->imageProperties.w * mImpl->imageProperties.h * sizeof(uint32_t);
1288
1289   DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "LoadNextNFrames( frameStartIndex:%d, count:%d )\n", frameStartIndex, count);
1290
1291   for(int i = 0; i < count; ++i)
1292   {
1293     auto pixelBuffer = new unsigned char[bufferSize];
1294
1295     mImpl->loaderInfo.animated.currentFrame = 1 + ((frameStartIndex + i) % mImpl->loaderInfo.animated.frameCount);
1296
1297     if(ReadNextFrame(mImpl->loaderInfo, mImpl->imageProperties, pixelBuffer, &error))
1298     {
1299       if(pixelBuffer)
1300       {
1301         pixelData.push_back(Dali::PixelData::New(pixelBuffer, bufferSize, mImpl->imageProperties.w, mImpl->imageProperties.h, Dali::Pixel::RGBA8888, Dali::PixelData::DELETE_ARRAY));
1302         ret = true;
1303       }
1304     }
1305   }
1306
1307   return ret;
1308 }
1309
1310 Dali::Devel::PixelBuffer GifLoading::LoadFrame(uint32_t frameIndex)
1311 {
1312   int                      error;
1313   Dali::Devel::PixelBuffer pixelBuffer;
1314   if(!mImpl->mLoadSucceeded)
1315   {
1316     return pixelBuffer;
1317   }
1318
1319   DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
1320
1321   pixelBuffer = Dali::Devel::PixelBuffer::New(mImpl->imageProperties.w, mImpl->imageProperties.h, Dali::Pixel::RGBA8888);
1322
1323   mImpl->loaderInfo.animated.currentFrame = 1 + (frameIndex % mImpl->loaderInfo.animated.frameCount);
1324   ReadNextFrame(mImpl->loaderInfo, mImpl->imageProperties, pixelBuffer.GetBuffer(), &error);
1325
1326   if(error != 0)
1327   {
1328     pixelBuffer = Dali::Devel::PixelBuffer();
1329   }
1330   return pixelBuffer;
1331 }
1332
1333 ImageDimensions GifLoading::GetImageSize() const
1334 {
1335   return ImageDimensions(mImpl->imageProperties.w, mImpl->imageProperties.h);
1336 }
1337
1338 uint32_t GifLoading::GetImageCount() const
1339 {
1340   return mImpl->loaderInfo.animated.frameCount;
1341 }
1342
1343 uint32_t GifLoading::GetFrameInterval(uint32_t frameIndex) const
1344 {
1345   return mImpl->loaderInfo.animated.frames[frameIndex].info.delay * 10;
1346 }
1347
1348 std::string GifLoading::GetUrl() const
1349 {
1350   return mImpl->mUrl;
1351 }
1352
1353 bool GifLoading::HasLoadingSucceeded() const
1354 {
1355   return mImpl->mLoadSucceeded;
1356 }
1357
1358 } // namespace Adaptor
1359
1360 } // namespace Internal
1361
1362 } // namespace Dali