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