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