/* * Copyright (C) 2014 Team Kodi * http://kodi.tv * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC; see the file COPYING. If not, see * . * */ #include "GifHelper.h" #include #include #include #define UNSIGNED_LITTLE_ENDIAN(lo, hi) ((lo) | ((hi) << 8)) #define GIF_MAX_MEMORY 82944000U // about 79 MB, which is equivalent to 10 full hd frames. class Gifreader { public: unsigned char* buffer = nullptr; unsigned int buffSize = 0; unsigned int readPosition = 0; Gifreader() = default; }; int ReadFromVfs(GifFileType* gif, GifByteType* gifbyte, int len) { CFile *gifFile = static_cast(gif->UserData); return gifFile->Read(gifbyte, len); } GifHelper::GifHelper() { m_gifFile = new CFile(); } GifHelper::~GifHelper() { Close(m_gif); Release(); delete m_gifFile; } bool GifHelper::Open(GifFileType*& gif, void *dataPtr, InputFunc readFunc) { int err = 0; #if GIFLIB_MAJOR == 5 gif = DGifOpen(dataPtr, readFunc, &err); #else gif = DGifOpen(dataPtr, readFunc); if (!gif) err = GifLastError(); #endif if (!gif) { fprintf(stderr, "Gif::Open(): Could not open file %s. Reason: %s\n", m_filename.c_str(), GifErrorString(err)); return false; } return true; } void GifHelper::Close(GifFileType* gif) { int err = 0; int reason = 0; #if GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1 err = DGifCloseFile(gif, &reason); #else err = DGifCloseFile(gif); #if GIFLIB_MAJOR < 5 reason = GifLastError(); #endif if (err == GIF_ERROR) free(gif); #endif if (err == GIF_ERROR) { fprintf(stderr, "GifHelper::Close(): closing file %s failed. Reason: %s\n", m_filename.c_str(), Reason(reason)); } } const char* GifHelper::Reason(int reason) { const char* err = GifErrorString(reason); if (err) return err; return "unknown"; } void GifHelper::Release() { delete[] m_pTemplate; m_pTemplate = nullptr; m_globalPalette.clear(); m_frames.clear(); } void GifHelper::ConvertColorTable(std::vector &dest, ColorMapObject* src, unsigned int size) { for (unsigned int i = 0; i < size; ++i) { GifColor c; c.r = src->Colors[i].Red; c.g = src->Colors[i].Green; c.b = src->Colors[i].Blue; c.a = 0xff; dest.push_back(c); } } bool GifHelper::LoadGifMetaData(GifFileType* gif) { if (!Slurp(gif)) return false; m_height = gif->SHeight; m_width = gif->SWidth; if (!m_height || !m_width) { fprintf(stderr, "Gif::LoadGif(): Zero sized image. File %s\n", m_filename.c_str()); return false; } m_numFrames = gif->ImageCount; if (m_numFrames > 0) { ExtensionBlock* extb = gif->SavedImages[0].ExtensionBlocks; if (extb && extb->Function == APPLICATION_EXT_FUNC_CODE) { // Read number of loops if (++extb && extb->Function == CONTINUE_EXT_FUNC_CODE) { uint8_t low = static_cast(extb->Bytes[1]); uint8_t high = static_cast(extb->Bytes[2]); m_loops = UNSIGNED_LITTLE_ENDIAN(low, high); } } } else { fprintf(stderr, "Gif::LoadGif(): No images found in file %s\n", m_filename.c_str()); return false; } m_pitch = m_width * sizeof(GifColor); m_imageSize = m_pitch * m_height; unsigned long memoryUsage = m_numFrames * m_imageSize; if (memoryUsage > GIF_MAX_MEMORY) { // at least 1 image m_numFrames = std::max(1U, GIF_MAX_MEMORY / m_imageSize); fprintf(stderr, "Gif::LoadGif(): Memory consumption too high: %lu bytes. Restricting animation to %u. File %s\n", memoryUsage, m_numFrames, m_filename.c_str()); } return true; } bool GifHelper::LoadGifMetaData(const char* file) { m_gifFile->Close(); if (!m_gifFile->Open(file) || !Open(m_gif, m_gifFile, ReadFromVfs)) return false; return LoadGifMetaData(m_gif); } bool GifHelper::Slurp(GifFileType* gif) { if (DGifSlurp(gif) == GIF_ERROR) { int reason = 0; #if GIFLIB_MAJOR == 5 reason = gif->Error; #else reason = GifLastError(); #endif fprintf(stderr, "Gif::LoadGif(): Could not read file %s. Reason: %s\n", m_filename.c_str(), GifErrorString(reason)); return false; } return true; } bool GifHelper::LoadGif(const char* file) { m_filename = file; if (!LoadGifMetaData(m_filename.c_str())) return false; try { InitTemplateAndColormap(); int extractedFrames = ExtractFrames(m_numFrames); if (extractedFrames < 0) { fprintf(stderr, "Gif::LoadGif(): Could not extract any frame. File %s\n", m_filename.c_str()); return false; } else if (extractedFrames < (int)m_numFrames) { fprintf(stderr, "Gif::LoadGif(): Could only extract %d/%d frames. File %s\n", extractedFrames, m_numFrames, m_filename.c_str()); m_numFrames = extractedFrames; } return true; } catch (std::bad_alloc& ba) { fprintf(stderr, "Gif::Load(): Out of memory while reading file %s - %s\n", m_filename.c_str(), ba.what()); Release(); return false; } } void GifHelper::InitTemplateAndColormap() { m_pTemplate = new unsigned char[m_imageSize]; memset(m_pTemplate, 0, m_imageSize); if (m_gif->SColorMap) { m_globalPalette.clear(); ConvertColorTable(m_globalPalette, m_gif->SColorMap, m_gif->SColorMap->ColorCount); } else m_globalPalette.clear(); } bool GifHelper::GcbToFrame(GifFrame &frame, unsigned int imgIdx) { int transparent = -1; frame.m_delay = 0; frame.m_disposal = 0; if (m_gif->ImageCount > 0) { #if GIFLIB_MAJOR == 5 GraphicsControlBlock gcb; if (DGifSavedExtensionToGCB(m_gif, imgIdx, &gcb)) { // delay in ms frame.m_delay = gcb.DelayTime * 10; frame.m_disposal = gcb.DisposalMode; transparent = gcb.TransparentColor; } #else ExtensionBlock* extb = m_gif->SavedImages[imgIdx].ExtensionBlocks; while (extb && extb->Function != GRAPHICS_EXT_FUNC_CODE) extb++; if (extb && extb->ByteCount == 4) { uint8_t low = static_cast(extb->Bytes[1]); uint8_t high = static_cast(extb->Bytes[2]); frame.m_delay = UNSIGNED_LITTLE_ENDIAN(low, high) * 10; frame.m_disposal = (extb->Bytes[0] >> 2) & 0x07; if (extb->Bytes[0] & 0x01) { transparent = static_cast(extb->Bytes[3]); } else transparent = -1; } #endif } if (transparent >= 0 && (unsigned)transparent < frame.m_palette.size()) frame.m_palette[transparent].a = 0; return true; } int GifHelper::ExtractFrames(unsigned int count) { if (!m_gif) return -1; if (!m_pTemplate) { fprintf(stderr, "Gif::ExtractFrames(): No frame template available\n"); return -1; } int extracted = 0; for (unsigned int i = 0; i < count; i++) { FramePtr frame(new GifFrame); SavedImage savedImage = m_gif->SavedImages[i]; GifImageDesc imageDesc = m_gif->SavedImages[i].ImageDesc; frame->m_height = imageDesc.Height; frame->m_width = imageDesc.Width; frame->m_top = imageDesc.Top; frame->m_left = imageDesc.Left; if (frame->m_top + frame->m_height > m_height || frame->m_left + frame->m_width > m_width || !frame->m_width || !frame->m_height || frame->m_width > m_width || frame->m_height > m_height) { fprintf(stderr, "Gif::ExtractFrames(): Illegal frame dimensions: width: %d, height: %d, left: %d, top: %d instead of (%d,%d), skip it\n", frame->m_width, frame->m_height, frame->m_left, frame->m_top, m_width, m_height); continue; } if (imageDesc.ColorMap) { frame->m_palette.clear(); ConvertColorTable(frame->m_palette, imageDesc.ColorMap, imageDesc.ColorMap->ColorCount); // TODO save a backup of the palette for frames without a table in case there's no global table. } else if (m_gif->SColorMap) { frame->m_palette = m_globalPalette; } else { fprintf(stderr, "Gif::ExtractFrames(): No color map found for frame %d, skip it\n", i); continue; } // fill delay, disposal and transparent color into frame if (!GcbToFrame(*frame, i)) { fprintf(stderr, "Gif::ExtractFrames(): Corrupted Graphics Control Block for frame %d, skip it\n", i); continue; } frame->m_pImage = new unsigned char[m_imageSize]; frame->m_imageSize = m_imageSize; memcpy(frame->m_pImage, m_pTemplate, m_imageSize); ConstructFrame(*frame, savedImage.RasterBits); if (!PrepareTemplate(*frame)) { fprintf(stderr, "Gif::ExtractFrames(): Could not prepare template after frame %d, skip it\n", i); continue; } extracted++; m_frames.push_back(frame); } return extracted; } void GifHelper::ConstructFrame(GifFrame &frame, const unsigned char* src) const { size_t paletteSize = frame.m_palette.size(); for (unsigned int dest_y = frame.m_top, src_y = 0; src_y < frame.m_height; ++dest_y, ++src_y) { unsigned char *to = frame.m_pImage + (dest_y * m_pitch) + (frame.m_left * sizeof(GifColor)); const unsigned char *from = src + (src_y * frame.m_width); for (unsigned int src_x = 0; src_x < frame.m_width; ++src_x) { unsigned char index = *from++; if (index >= paletteSize) { fprintf(stderr, "Gif::ConstructFrame(): Pixel (%d,%d) has no valid palette entry, skip it\n", src_x, src_y); continue; } GifColor col = frame.m_palette[index]; if (col.a != 0) memcpy(to, &col, sizeof(GifColor)); to += 4; } } } bool GifHelper::PrepareTemplate(GifFrame &frame) { switch (frame.m_disposal) { /* No disposal specified. */ case DISPOSAL_UNSPECIFIED: /* Leave image in place */ case DISPOSE_DO_NOT: memcpy(m_pTemplate, frame.m_pImage, m_imageSize); break; /* Clear the frame's area to transparency. The disposal names is misleading. Do not restore to the background color because this part of the specification is ignored by all browsers/image viewers. */ case DISPOSE_BACKGROUND: { ClearFrameAreaToTransparency(m_pTemplate, frame); break; } /* Restore to previous content */ case DISPOSE_PREVIOUS: { /* * This disposal method makes no sense for the first frame * Since browsers etc. handle that too, we'll fall back to DISPOSE_DO_NOT */ if (m_frames.empty()) { frame.m_disposal = DISPOSE_DO_NOT; return PrepareTemplate(frame); } bool valid = false; for (int i = m_frames.size() - 1; i >= 0; --i) { if (m_frames[i]->m_disposal != DISPOSE_PREVIOUS) { memcpy(m_pTemplate, m_frames[i]->m_pImage, m_imageSize); valid = true; break; } } if (!valid) { fprintf(stderr, "Gif::PrepareTemplate(): Disposal method DISPOSE_PREVIOUS encountered, but could not find a suitable frame.\n"); return false; } break; } default: { fprintf(stderr, "Gif::PrepareTemplate(): Unknown disposal method: %d. Using DISPOSAL_UNSPECIFIED, the animation might be wrong now.\n", frame.m_disposal); frame.m_disposal = DISPOSAL_UNSPECIFIED; return PrepareTemplate(frame); } } return true; } void GifHelper::ClearFrameAreaToTransparency(unsigned char* dest, const GifFrame &frame) { for (unsigned int dest_y = frame.m_top, src_y = 0; src_y < frame.m_height; ++dest_y, ++src_y) { unsigned char *to = dest + (dest_y * m_pitch) + (frame.m_left * sizeof(GifColor)); for (unsigned int src_x = 0; src_x < frame.m_width; ++src_x) { to += 3; *to++ = 0; } } } GifFrame::GifFrame(const GifFrame& src) : m_delay(src.m_delay), m_top(src.m_top), m_left(src.m_left), m_disposal(src.m_disposal), m_height(src.m_height), m_width(src.m_width), m_imageSize(src.m_imageSize) { if (src.m_pImage) { m_pImage = new unsigned char[m_imageSize]; memcpy(m_pImage, src.m_pImage, m_imageSize); } if (src.m_palette.size()) { m_palette = src.m_palette; } } GifFrame::~GifFrame() { delete[] m_pImage; m_pImage = nullptr; }