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