1 /*
2  *      Copyright (C) 2014 Team Kodi
3  *      http://kodi.tv
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include "GifHelper.h"
22 
23 #include <algorithm>
24 #include <cstdlib>
25 #include <cstring>
26 
27 #define UNSIGNED_LITTLE_ENDIAN(lo, hi)	((lo) | ((hi) << 8))
28 #define GIF_MAX_MEMORY 82944000U // about 79 MB, which is equivalent to 10 full hd frames.
29 
30 class Gifreader
31 {
32 public:
33   unsigned char* buffer = nullptr;
34   unsigned int buffSize = 0;
35   unsigned int readPosition = 0;
36 
37   Gifreader() = default;
38 };
39 
ReadFromVfs(GifFileType * gif,GifByteType * gifbyte,int len)40 int ReadFromVfs(GifFileType* gif, GifByteType* gifbyte, int len)
41 {
42   CFile *gifFile = static_cast<CFile*>(gif->UserData);
43   return gifFile->Read(gifbyte, len);
44 }
45 
GifHelper()46 GifHelper::GifHelper()
47 {
48   m_gifFile = new CFile();
49 }
50 
~GifHelper()51 GifHelper::~GifHelper()
52 {
53     Close(m_gif);
54     Release();
55     delete m_gifFile;
56 }
57 
Open(GifFileType * & gif,void * dataPtr,InputFunc readFunc)58 bool GifHelper::Open(GifFileType*& gif, void *dataPtr, InputFunc readFunc)
59 {
60   int err = 0;
61 #if GIFLIB_MAJOR == 5
62   gif = DGifOpen(dataPtr, readFunc, &err);
63 #else
64   gif = DGifOpen(dataPtr, readFunc);
65   if (!gif)
66     err = GifLastError();
67 #endif
68 
69   if (!gif)
70   {
71     fprintf(stderr, "Gif::Open(): Could not open file %s. Reason: %s\n", m_filename.c_str(), GifErrorString(err));
72     return false;
73   }
74 
75   return true;
76 }
77 
Close(GifFileType * gif)78 void GifHelper::Close(GifFileType* gif)
79 {
80   int err = 0;
81   int reason = 0;
82 #if GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1
83   err = DGifCloseFile(gif, &reason);
84 #else
85   err = DGifCloseFile(gif);
86 #if GIFLIB_MAJOR < 5
87   reason = GifLastError();
88 #endif
89   if (err == GIF_ERROR)
90     free(gif);
91 #endif
92   if (err == GIF_ERROR)
93   {
94     fprintf(stderr, "GifHelper::Close(): closing file %s failed. Reason: %s\n", m_filename.c_str(), Reason(reason));
95   }
96 }
97 
Reason(int reason)98 const char* GifHelper::Reason(int reason)
99 {
100   const char* err = GifErrorString(reason);
101   if (err)
102     return err;
103 
104   return "unknown";
105 
106 }
107 
Release()108 void GifHelper::Release()
109 {
110   delete[] m_pTemplate;
111   m_pTemplate = nullptr;
112   m_globalPalette.clear();
113   m_frames.clear();
114 }
115 
ConvertColorTable(std::vector<GifColor> & dest,ColorMapObject * src,unsigned int size)116 void GifHelper::ConvertColorTable(std::vector<GifColor> &dest, ColorMapObject* src, unsigned int size)
117 {
118   for (unsigned int i = 0; i < size; ++i)
119   {
120     GifColor c;
121 
122     c.r = src->Colors[i].Red;
123     c.g = src->Colors[i].Green;
124     c.b = src->Colors[i].Blue;
125     c.a = 0xff;
126     dest.push_back(c);
127   }
128 }
129 
LoadGifMetaData(GifFileType * gif)130 bool GifHelper::LoadGifMetaData(GifFileType* gif)
131 {
132   if (!Slurp(gif))
133     return false;
134 
135   m_height = gif->SHeight;
136   m_width = gif->SWidth;
137   if (!m_height || !m_width)
138   {
139     fprintf(stderr, "Gif::LoadGif(): Zero sized image. File %s\n", m_filename.c_str());
140     return false;
141   }
142 
143   m_numFrames = gif->ImageCount;
144   if (m_numFrames > 0)
145   {
146     ExtensionBlock* extb = gif->SavedImages[0].ExtensionBlocks;
147     if (extb && extb->Function == APPLICATION_EXT_FUNC_CODE)
148     {
149       // Read number of loops
150       if (++extb && extb->Function == CONTINUE_EXT_FUNC_CODE)
151       {
152         uint8_t low = static_cast<uint8_t>(extb->Bytes[1]);
153         uint8_t high = static_cast<uint8_t>(extb->Bytes[2]);
154         m_loops = UNSIGNED_LITTLE_ENDIAN(low, high);
155       }
156     }
157   }
158   else
159   {
160     fprintf(stderr, "Gif::LoadGif(): No images found in file %s\n", m_filename.c_str());
161     return false;
162   }
163 
164   m_pitch = m_width * sizeof(GifColor);
165   m_imageSize = m_pitch * m_height;
166   unsigned long memoryUsage = m_numFrames * m_imageSize;
167   if (memoryUsage > GIF_MAX_MEMORY)
168   {
169     // at least 1 image
170     m_numFrames = std::max(1U, GIF_MAX_MEMORY / m_imageSize);
171     fprintf(stderr, "Gif::LoadGif(): Memory consumption too high: %lu bytes. Restricting animation to %u. File %s\n", memoryUsage, m_numFrames, m_filename.c_str());
172   }
173 
174   return true;
175 }
176 
LoadGifMetaData(const char * file)177 bool GifHelper::LoadGifMetaData(const char* file)
178 {
179   m_gifFile->Close();
180   if (!m_gifFile->Open(file) || !Open(m_gif, m_gifFile, ReadFromVfs))
181     return false;
182 
183   return LoadGifMetaData(m_gif);
184 }
185 
Slurp(GifFileType * gif)186 bool GifHelper::Slurp(GifFileType* gif)
187 {
188   if (DGifSlurp(gif) == GIF_ERROR)
189   {
190     int reason = 0;
191 #if GIFLIB_MAJOR == 5
192     reason = gif->Error;
193 #else
194     reason = GifLastError();
195 #endif
196     fprintf(stderr, "Gif::LoadGif(): Could not read file %s. Reason: %s\n", m_filename.c_str(), GifErrorString(reason));
197     return false;
198   }
199 
200   return true;
201 }
202 
LoadGif(const char * file)203 bool GifHelper::LoadGif(const char* file)
204 {
205   m_filename = file;
206   if (!LoadGifMetaData(m_filename.c_str()))
207     return false;
208 
209   try
210   {
211     InitTemplateAndColormap();
212 
213     int extractedFrames = ExtractFrames(m_numFrames);
214     if (extractedFrames < 0)
215     {
216       fprintf(stderr, "Gif::LoadGif(): Could not extract any frame. File %s\n", m_filename.c_str());
217       return false;
218     }
219     else if (extractedFrames < (int)m_numFrames)
220     {
221       fprintf(stderr, "Gif::LoadGif(): Could only extract %d/%d frames. File %s\n", extractedFrames, m_numFrames, m_filename.c_str());
222       m_numFrames = extractedFrames;
223     }
224 
225     return true;
226   }
227   catch (std::bad_alloc& ba)
228   {
229     fprintf(stderr, "Gif::Load(): Out of memory while reading file %s - %s\n", m_filename.c_str(), ba.what());
230     Release();
231     return false;
232   }
233 }
234 
InitTemplateAndColormap()235 void GifHelper::InitTemplateAndColormap()
236 {
237   m_pTemplate = new unsigned char[m_imageSize];
238   memset(m_pTemplate, 0, m_imageSize);
239 
240   if (m_gif->SColorMap)
241   {
242     m_globalPalette.clear();
243     ConvertColorTable(m_globalPalette, m_gif->SColorMap, m_gif->SColorMap->ColorCount);
244   }
245   else
246     m_globalPalette.clear();
247 }
248 
GcbToFrame(GifFrame & frame,unsigned int imgIdx)249 bool GifHelper::GcbToFrame(GifFrame &frame, unsigned int imgIdx)
250 {
251   int transparent = -1;
252   frame.m_delay = 0;
253   frame.m_disposal = 0;
254 
255   if (m_gif->ImageCount > 0)
256   {
257 #if GIFLIB_MAJOR == 5
258     GraphicsControlBlock gcb;
259     if (DGifSavedExtensionToGCB(m_gif, imgIdx, &gcb))
260     {
261       // delay in ms
262       frame.m_delay = gcb.DelayTime * 10;
263       frame.m_disposal = gcb.DisposalMode;
264       transparent = gcb.TransparentColor;
265     }
266 #else
267     ExtensionBlock* extb = m_gif->SavedImages[imgIdx].ExtensionBlocks;
268     while (extb && extb->Function != GRAPHICS_EXT_FUNC_CODE)
269       extb++;
270 
271     if (extb && extb->ByteCount == 4)
272     {
273       uint8_t low = static_cast<uint8_t>(extb->Bytes[1]);
274       uint8_t high = static_cast<uint8_t>(extb->Bytes[2]);
275       frame.m_delay = UNSIGNED_LITTLE_ENDIAN(low, high) * 10;
276       frame.m_disposal = (extb->Bytes[0] >> 2) & 0x07;
277       if (extb->Bytes[0] & 0x01)
278       {
279         transparent = static_cast<uint8_t>(extb->Bytes[3]);
280       }
281       else
282         transparent = -1;
283     }
284 #endif
285   }
286 
287   if (transparent >= 0 && (unsigned)transparent < frame.m_palette.size())
288     frame.m_palette[transparent].a = 0;
289   return true;
290 }
291 
ExtractFrames(unsigned int count)292 int GifHelper::ExtractFrames(unsigned int count)
293 {
294   if (!m_gif)
295     return -1;
296 
297   if (!m_pTemplate)
298   {
299     fprintf(stderr, "Gif::ExtractFrames(): No frame template available\n");
300     return -1;
301   }
302 
303   int extracted = 0;
304   for (unsigned int i = 0; i < count; i++)
305   {
306     FramePtr frame(new GifFrame);
307     SavedImage savedImage = m_gif->SavedImages[i];
308     GifImageDesc imageDesc = m_gif->SavedImages[i].ImageDesc;
309     frame->m_height = imageDesc.Height;
310     frame->m_width = imageDesc.Width;
311     frame->m_top = imageDesc.Top;
312     frame->m_left = imageDesc.Left;
313 
314     if (frame->m_top + frame->m_height > m_height || frame->m_left + frame->m_width > m_width
315       || !frame->m_width || !frame->m_height
316       || frame->m_width > m_width || frame->m_height > m_height)
317     {
318       fprintf(stderr, "Gif::ExtractFrames(): Illegal frame dimensions: width: %d, height: %d, left: %d, top: %d instead of (%d,%d), skip it\n",
319         frame->m_width, frame->m_height, frame->m_left, frame->m_top, m_width, m_height);
320       continue;
321     }
322 
323     if (imageDesc.ColorMap)
324     {
325       frame->m_palette.clear();
326       ConvertColorTable(frame->m_palette, imageDesc.ColorMap, imageDesc.ColorMap->ColorCount);
327       // TODO save a backup of the palette for frames without a table in case there's no global table.
328     }
329     else if (m_gif->SColorMap)
330     {
331       frame->m_palette = m_globalPalette;
332     }
333     else
334     {
335       fprintf(stderr, "Gif::ExtractFrames(): No color map found for frame %d, skip it\n", i);
336       continue;
337     }
338 
339     // fill delay, disposal and transparent color into frame
340     if (!GcbToFrame(*frame, i))
341     {
342       fprintf(stderr, "Gif::ExtractFrames(): Corrupted Graphics Control Block for frame %d, skip it\n", i);
343       continue;
344     }
345 
346     frame->m_pImage = new unsigned char[m_imageSize];
347     frame->m_imageSize = m_imageSize;
348     memcpy(frame->m_pImage, m_pTemplate, m_imageSize);
349 
350     ConstructFrame(*frame, savedImage.RasterBits);
351 
352     if (!PrepareTemplate(*frame))
353     {
354       fprintf(stderr, "Gif::ExtractFrames(): Could not prepare template after frame %d, skip it\n", i);
355       continue;
356     }
357 
358     extracted++;
359     m_frames.push_back(frame);
360   }
361   return extracted;
362 }
363 
ConstructFrame(GifFrame & frame,const unsigned char * src) const364 void GifHelper::ConstructFrame(GifFrame &frame, const unsigned char* src) const
365 {
366   size_t paletteSize = frame.m_palette.size();
367 
368   for (unsigned int dest_y = frame.m_top, src_y = 0; src_y < frame.m_height; ++dest_y, ++src_y)
369   {
370     unsigned char *to = frame.m_pImage + (dest_y * m_pitch) + (frame.m_left * sizeof(GifColor));
371 
372     const unsigned char *from = src + (src_y * frame.m_width);
373     for (unsigned int src_x = 0; src_x < frame.m_width; ++src_x)
374     {
375       unsigned char index = *from++;
376 
377       if (index >= paletteSize)
378       {
379         fprintf(stderr, "Gif::ConstructFrame(): Pixel (%d,%d) has no valid palette entry, skip it\n", src_x, src_y);
380         continue;
381       }
382 
383       GifColor col = frame.m_palette[index];
384       if (col.a != 0)
385         memcpy(to, &col, sizeof(GifColor));
386 
387       to += 4;
388     }
389   }
390 }
391 
PrepareTemplate(GifFrame & frame)392 bool GifHelper::PrepareTemplate(GifFrame &frame)
393 {
394   switch (frame.m_disposal)
395   {
396     /* No disposal specified. */
397   case DISPOSAL_UNSPECIFIED:
398     /* Leave image in place */
399   case DISPOSE_DO_NOT:
400     memcpy(m_pTemplate, frame.m_pImage, m_imageSize);
401     break;
402 
403     /*
404        Clear the frame's area to transparency.
405        The disposal names is misleading. Do not restore to the background color because
406        this part of the specification is ignored by all browsers/image viewers.
407     */
408   case DISPOSE_BACKGROUND:
409   {
410     ClearFrameAreaToTransparency(m_pTemplate, frame);
411     break;
412   }
413   /* Restore to previous content */
414   case DISPOSE_PREVIOUS:
415   {
416 
417     /*
418     * This disposal method makes no sense for the first frame
419     * Since browsers etc. handle that too, we'll fall back to DISPOSE_DO_NOT
420     */
421     if (m_frames.empty())
422     {
423       frame.m_disposal = DISPOSE_DO_NOT;
424       return PrepareTemplate(frame);
425     }
426 
427     bool valid = false;
428 
429     for (int i = m_frames.size() - 1; i >= 0; --i)
430     {
431       if (m_frames[i]->m_disposal != DISPOSE_PREVIOUS)
432       {
433         memcpy(m_pTemplate, m_frames[i]->m_pImage, m_imageSize);
434         valid = true;
435         break;
436       }
437     }
438     if (!valid)
439     {
440       fprintf(stderr, "Gif::PrepareTemplate(): Disposal method DISPOSE_PREVIOUS encountered, but could not find a suitable frame.\n");
441       return false;
442     }
443     break;
444   }
445   default:
446   {
447     fprintf(stderr, "Gif::PrepareTemplate(): Unknown disposal method: %d. Using DISPOSAL_UNSPECIFIED, the animation might be wrong now.\n", frame.m_disposal);
448     frame.m_disposal = DISPOSAL_UNSPECIFIED;
449     return PrepareTemplate(frame);
450   }
451   }
452   return true;
453 }
454 
ClearFrameAreaToTransparency(unsigned char * dest,const GifFrame & frame)455 void GifHelper::ClearFrameAreaToTransparency(unsigned char* dest, const GifFrame &frame)
456 {
457   for (unsigned int dest_y = frame.m_top, src_y = 0; src_y < frame.m_height; ++dest_y, ++src_y)
458   {
459     unsigned char *to = dest + (dest_y * m_pitch) + (frame.m_left * sizeof(GifColor));
460     for (unsigned int src_x = 0; src_x < frame.m_width; ++src_x)
461     {
462       to += 3;
463       *to++ = 0;
464     }
465   }
466 }
467 
GifFrame(const GifFrame & src)468 GifFrame::GifFrame(const GifFrame& src)
469   : m_delay(src.m_delay),
470     m_top(src.m_top),
471     m_left(src.m_left),
472     m_disposal(src.m_disposal),
473     m_height(src.m_height),
474     m_width(src.m_width),
475     m_imageSize(src.m_imageSize)
476 {
477   if (src.m_pImage)
478   {
479     m_pImage = new unsigned char[m_imageSize];
480     memcpy(m_pImage, src.m_pImage, m_imageSize);
481   }
482 
483   if (src.m_palette.size())
484   {
485     m_palette = src.m_palette;
486   }
487 }
488 
~GifFrame()489 GifFrame::~GifFrame()
490 {
491   delete[] m_pImage;
492   m_pImage = nullptr;
493 }
494