#include "../include/Gif.hpp" #include "../include/FileUtil.hpp" using namespace std; namespace dchat { void* bitmapCreate(int width, int height) { return calloc(width * height, 4); } void bitmapDestroy(void *bitmap) { free(bitmap); } unsigned char* bitmapGetBuffer(void *bitmap) { return (unsigned char*)bitmap; } void bitmapSetOpaque(void *bitmap, bool opaque) { } bool bitmapTestOpaque(void *bitmap) { return false; } void bitmapModified(void *bitmap) { } 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 e; } } Gif::Gif(StringView &&_fileContent) : fileContent(move(_fileContent)), currentFrame(0), timeElapsedCs(0.0) { try { init(); } catch(GifLoadException &e) { delete[] fileContent.data; throw e; } } 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); if(!texture.create(gif.width, gif.height)) throw GifLoadException("Failed to create texture for gif"); texture.setSmooth(true); sprite.setTexture(texture, true); } Gif::~Gif() { gif_finalise(&gif); delete[] fileContent.data; } sf::Vector2u Gif::getSize() const { return sprite.getTexture()->getSize(); } void Gif::setPosition(const sf::Vector2f &position) { sprite.setPosition(position); } sf::Vector2f Gif::getPosition() const { return sprite.getPosition(); } void Gif::setScale(const sf::Vector2f &scale) { sprite.setScale(scale); } void Gif::setColor(sf::Color color) { sprite.setColor(color); } void Gif::draw(sf::RenderTarget &target, const sf::RenderStates &renderState) { double timeElapsedMilli = (double)frameTimer.getElapsedTime().asMilliseconds(); // 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) { texture.update(image); // TODO: Check if this is too heavy texture.generateMipmap(); sprite.setTexture(texture, true); } target.draw(sprite, renderState); } bool Gif::isDataGif(const StringView &data) { return data.size >= 6 && (memcmp(data.data, "GIF87a", 6) == 0 || memcmp(data.data, "GIF89a", 6) == 0); } }