#include "../include/dchat/Gif.hpp" #include "../include/dchat/FileUtil.hpp" using namespace std; namespace dchat { static void* bitmapCreate(int width, int height) { return calloc(width * height, 4); } static void bitmapDestroy(void *bitmap) { free(bitmap); } static unsigned char* bitmapGetBuffer(void *bitmap) { return (unsigned char*)bitmap; } static void bitmapSetOpaque(void *bitmap, bool opaque) { } static bool bitmapTestOpaque(void *bitmap) { return false; } static void bitmapModified(void *bitmap) { } static const char* gifResultToString(gif_result code) { switch(code) { case GIF_INSUFFICIENT_FRAME_DATA: return "GIF_INSUFFICIENT_FRAME_DATA"; case GIF_FRAME_DATA_ERROR: return "GIF_FRAME_DATA_ERROR"; case GIF_INSUFFICIENT_DATA: return "GIF_INSUFFICIENT_DATA"; case GIF_DATA_ERROR: return "GIF_DATA_ERROR"; case GIF_INSUFFICIENT_MEMORY: return "GIF_INSUFFICIENT_MEMORY"; default: return "Unknown gif result code"; } } Gif::Gif(const boost::filesystem::path &filepath) : currentFrame(0), timeElapsedCs(0.0) { try { fileContent = getFileContent(filepath); } catch(FileException &e) { throw GifLoadException(e.what()); } try { init(); } catch(GifLoadException &e) { delete[] fileContent.data; throw; } } Gif::Gif(StringView _fileContent) : fileContent(move(_fileContent)), currentFrame(0), timeElapsedCs(0.0), created(false) { try { init(); } catch(GifLoadException &e) { delete[] fileContent.data; throw; } } void Gif::init() { gif_bitmap_callback_vt bitmapCallbacks = { bitmapCreate, bitmapDestroy, bitmapGetBuffer, bitmapSetOpaque, bitmapTestOpaque, bitmapModified }; gif_create(&gif, &bitmapCallbacks); gif_result code; do { code = gif_initialise(&gif, fileContent.size, (unsigned char*)fileContent.data); if(code != GIF_OK && code != GIF_WORKING) { string errMsg = "Failed to initialize gif, reason: "; errMsg += gifResultToString(code); throw GifLoadException(errMsg); } } while(code != GIF_OK); } Gif::~Gif() { gif_finalise(&gif); delete[] fileContent.data; } Vec2u Gif::getSize() const { return { gif.width, gif.height }; } void Gif::update() { if(!created) { created = true; if(!createTexture()) throw GifLoadException("Failed to create texture for gif"); } double timeElapsedMilli = (double)frameTimer.getElapsedTimeMillis(); // If gif is not redrawn for a while, then we reset timer (gif is paused). This happens when gif is not visible and then appears visible // (because it's visible in window). The reason this is done is to prevent too much time between rendering gif frames, as processing a gif // requires to process all frames between two points in time, if elapsed frame time is too high, then we would require to process several // frames of gif in one application render frame. if(timeElapsedMilli > 1000.0) timeElapsedMilli = 0.0; double frameDeltaCs = timeElapsedMilli * 0.1; // Centisecond frameTimer.restart(); timeElapsedCs += frameDeltaCs; unsigned char *image = nullptr; u32 startFrame = currentFrame; while(true) { u32 i = currentFrame % gif.frame_count; gif_result code = gif_decode_frame(&gif, i); if(code != GIF_OK) { printf("Warning: gif_decode_frame: %s\n", gifResultToString(code)); break; } gif_frame &frame = gif.frames[i]; // frame_delay is in centiseconds unsigned int frameDelay = frame.frame_delay; if(frameDelay == 0) frameDelay = 7; double fFrameDelay = (double)frameDelay; if(timeElapsedCs >= fFrameDelay) timeElapsedCs -= fFrameDelay; else break; image = (unsigned char*)gif.frame_image; ++currentFrame; } if(currentFrame != startFrame) { updateTexture(image); } } bool Gif::isDataGif(const StringView &data) { return data.size >= 6 && (memcmp(data.data, "GIF87a", 6) == 0 || memcmp(data.data, "GIF89a", 6) == 0); } }