diff --git a/cmake/externals/GifCreator/CMakeLists.txt b/cmake/externals/GifCreator/CMakeLists.txt new file mode 100644 index 0000000000..f3f4e6d2ad --- /dev/null +++ b/cmake/externals/GifCreator/CMakeLists.txt @@ -0,0 +1,20 @@ +set(EXTERNAL_NAME GifCreator) + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://hifi-public.s3.amazonaws.com/dependencies/GifCreator.zip + URL_MD5 8ac8ef5196f47c658dce784df5ecdb70 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/src/${EXTERNAL_NAME_UPPER} CACHE PATH "List of GifCreator include directories") \ No newline at end of file diff --git a/cmake/modules/FindGifCreator.cmake b/cmake/modules/FindGifCreator.cmake new file mode 100644 index 0000000000..88428cb833 --- /dev/null +++ b/cmake/modules/FindGifCreator.cmake @@ -0,0 +1,26 @@ +# +# FindGifCreator.cmake +# +# Try to find GifCreator include path. +# Once done this will define +# +# GIFCREATOR_INCLUDE_DIRS +# +# Created on 11/10/2016 by Zach Fox +# Copyright 2016 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +# setup hints for GifCreator search +include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("GifCreator") + +# locate header +find_path(GIFCREATOR_INCLUDE_DIRS "GifCreator/GifCreator.h" HINTS ${GIFCREATOR_SEARCH_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GIFCREATOR DEFAULT_MSG GIFCREATOR_INCLUDE_DIRS) + +mark_as_advanced(GIFCREATOR_INCLUDE_DIRS GIFCREATOR_SEARCH_DIRS) \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 131c4ee509..56e83a3171 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -351,3 +351,7 @@ if (ANDROID) qt_create_apk() endif () + +add_dependency_external_projects(GifCreator) +find_package(GIFCREATOR REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GIFCREATOR_INCLUDE_DIRS}) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1226e62944..ba442ede9c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5432,7 +5432,7 @@ void Application::toggleLogDialog() { // The frame will take too long to pack, the timer slot will // not execute properly, and the GIF will appear sped-up. // This is unacceptable and is probably a blocker for release. -#define SNAPSNOT_ANIMATED_WIDTH (500) +#define SNAPSNOT_ANIMATED_WIDTH (300) #define SNAPSNOT_ANIMATED_FRAMERATE_FPS (50) // This value should divide evenly into 100 #define SNAPSNOT_ANIMATED_DURATION_SECS (3) @@ -5490,7 +5490,7 @@ void Application::takeSnapshot(bool notify, float aspectRatio) { qApp->_currentAnimatedSnapshotFrame++; // If that was the last frame... - if (qApp->_currentAnimatedSnapshotFrame == SNAPSNOT_ANIMATED_NUM_FRAMES) + if (qApp->_currentAnimatedSnapshotFrame >= SNAPSNOT_ANIMATED_NUM_FRAMES) { // Reset the current frame number qApp->_currentAnimatedSnapshotFrame = 0; diff --git a/interface/src/Application.h b/interface/src/Application.h index 2754fdef52..963b4c8072 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -60,7 +61,6 @@ #include "scripting/DialogsManagerScriptingInterface.h" #include "ui/ApplicationOverlay.h" #include "ui/BandwidthDialog.h" -#include "ui/Gif.h" #include "ui/LodToolsDialog.h" #include "ui/LogDialog.h" #include "ui/OctreeStatsDialog.h" @@ -613,7 +613,7 @@ private: QTimer animatedSnapshotTimer; GifWriter _animatedSnapshotGifWriter; - uint8_t _currentAnimatedSnapshotFrame { 0 }; + uint32_t _currentAnimatedSnapshotFrame { 0 }; QString _animatedSnapshotPath; }; diff --git a/interface/src/ui/Gif.cpp b/interface/src/ui/Gif.cpp deleted file mode 100644 index 87716b0fb4..0000000000 --- a/interface/src/ui/Gif.cpp +++ /dev/null @@ -1,755 +0,0 @@ -// -// gif.c -// by Charlie Tangora -// Public domain. -// Email me : ctangora -at- gmail -dot- com -// -// This file offers a simple, very limited way to create animated GIFs directly in code. -// -// Those looking for particular cleverness are likely to be disappointed; it's pretty -// much a straight-ahead implementation of the GIF format with optional Floyd-Steinberg -// dithering. (It does at least use delta encoding - only the changed portions of each -// frame are saved.) -// -// So resulting files are often quite large. The hope is that it will be handy nonetheless -// as a quick and easily-integrated way for programs to spit out animations. -// -// Only RGBA8 is currently supported as an input format. (The alpha is ignored.) -// -// USAGE: -// Create a GifWriter struct. Pass it to GifBegin() to initialize and write the header. -// Pass subsequent frames to GifWriteFrame(). -// Finally, call GifEnd() to close the file handle and free memory. -// - -#ifndef gif_c -#define gif_c - -#include "Gif.h" - - -int GifIMax(int l, int r) { return l>r ? l : r; } -int GifIMin(int l, int r) { return l(1 << pPal->bitDepth) - 1) - { - int ind = treeRoot - (1 << pPal->bitDepth); - if (ind == kGifTransIndex) return; - - // check whether this color is better than the current winner - int r_err = r - ((int32_t)pPal->r[ind]); - int g_err = g - ((int32_t)pPal->g[ind]); - int b_err = b - ((int32_t)pPal->b[ind]); - int diff = GifIAbs(r_err) + GifIAbs(g_err) + GifIAbs(b_err); - - if (diff < bestDiff) - { - bestInd = ind; - bestDiff = diff; - } - - return; - } - - // take the appropriate color (r, g, or b) for this node of the k-d tree - int comps[3]; comps[0] = r; comps[1] = g; comps[2] = b; - int splitComp = comps[pPal->treeSplitElt[treeRoot]]; - - int splitPos = pPal->treeSplit[treeRoot]; - if (splitPos > splitComp) - { - // check the left subtree - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2); - if (bestDiff > splitPos - splitComp) - { - // cannot prove there's not a better value in the right subtree, check that too - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2 + 1); - } - } - else - { - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2 + 1); - if (bestDiff > splitComp - splitPos) - { - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2); - } - } -} - -void GifSwapPixels(uint8_t* image, int pixA, int pixB) -{ - uint8_t rA = image[pixA * 4]; - uint8_t gA = image[pixA * 4 + 1]; - uint8_t bA = image[pixA * 4 + 2]; - uint8_t aA = image[pixA * 4 + 3]; - - uint8_t rB = image[pixB * 4]; - uint8_t gB = image[pixB * 4 + 1]; - uint8_t bB = image[pixB * 4 + 2]; - uint8_t aB = image[pixA * 4 + 3]; - - image[pixA * 4] = rB; - image[pixA * 4 + 1] = gB; - image[pixA * 4 + 2] = bB; - image[pixA * 4 + 3] = aB; - - image[pixB * 4] = rA; - image[pixB * 4 + 1] = gA; - image[pixB * 4 + 2] = bA; - image[pixB * 4 + 3] = aA; -} - -// just the partition operation from quicksort -int GifPartition(uint8_t* image, const int left, const int right, const int elt, int pivotIndex) -{ - const int pivotValue = image[(pivotIndex)* 4 + elt]; - GifSwapPixels(image, pivotIndex, right - 1); - int storeIndex = left; - bool split = 0; - for (int ii = left; ii neededCenter) - GifPartitionByMedian(image, left, pivotIndex, com, neededCenter); - - if (pivotIndex < neededCenter) - GifPartitionByMedian(image, pivotIndex + 1, right, com, neededCenter); - } -} - -// Builds a palette by creating a balanced k-d tree of all pixels in the image -void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int lastElt, int splitElt, int splitDist, int treeNode, bool buildForDither, GifPalette* pal) -{ - if (lastElt <= firstElt || numPixels == 0) - return; - - // base case, bottom of the tree - if (lastElt == firstElt + 1) - { - if (buildForDither) - { - // Dithering needs at least one color as dark as anything - // in the image and at least one brightest color - - // otherwise it builds up error and produces strange artifacts - if (firstElt == 1) - { - // special case: the darkest color in the image - uint32_t r = 255, g = 255, b = 255; - for (int ii = 0; iir[firstElt] = r; - pal->g[firstElt] = g; - pal->b[firstElt] = b; - - return; - } - - if (firstElt == (1 << pal->bitDepth) - 1) - { - // special case: the lightest color in the image - uint32_t r = 0, g = 0, b = 0; - for (int ii = 0; iir[firstElt] = r; - pal->g[firstElt] = g; - pal->b[firstElt] = b; - - return; - } - } - - // otherwise, take the average of all colors in this subcube - uint64_t r = 0, g = 0, b = 0; - for (int ii = 0; iir[firstElt] = (uint8_t)r; - pal->g[firstElt] = (uint8_t)g; - pal->b[firstElt] = (uint8_t)b; - - return; - } - - // Find the axis with the largest range - int minR = 255, maxR = 0; - int minG = 255, maxG = 0; - int minB = 255, maxB = 0; - for (int ii = 0; ii maxR) maxR = r; - if (r < minR) minR = r; - - if (g > maxG) maxG = g; - if (g < minG) minG = g; - - if (b > maxB) maxB = b; - if (b < minB) minB = b; - } - - int rRange = maxR - minR; - int gRange = maxG - minG; - int bRange = maxB - minB; - - // and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it) - int splitCom = 1; - if (bRange > gRange) splitCom = 2; - if (rRange > bRange && rRange > gRange) splitCom = 0; - - int subPixelsA = numPixels * (splitElt - firstElt) / (lastElt - firstElt); - int subPixelsB = numPixels - subPixelsA; - - GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA); - - pal->treeSplitElt[treeNode] = splitCom; - pal->treeSplit[treeNode] = image[subPixelsA * 4 + splitCom]; - - GifSplitPalette(image, subPixelsA, firstElt, splitElt, splitElt - splitDist, splitDist / 2, treeNode * 2, buildForDither, pal); - GifSplitPalette(image + subPixelsA * 4, subPixelsB, splitElt, lastElt, splitElt + splitDist, splitDist / 2, treeNode * 2 + 1, buildForDither, pal); -} - -// Finds all pixels that have changed from the previous image and -// moves them to the fromt of th buffer. -// This allows us to build a palette optimized for the colors of the -// changed pixels only. -int GifPickChangedPixels(const uint8_t* lastFrame, uint8_t* frame, int numPixels) -{ - int numChanged = 0; - uint8_t* writeIter = frame; - - for (int ii = 0; iibitDepth = bitDepth; - - // SplitPalette is destructive (it sorts the pixels by color) so - // we must create a copy of the image for it to destroy - int imageSize = width*height * 4 * sizeof(uint8_t); - uint8_t* destroyableImage = (uint8_t*)GIF_TEMP_MALLOC(imageSize); - memcpy(destroyableImage, nextFrame, imageSize); - - int numPixels = width*height; - if (lastFrame) - numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels); - - const int lastElt = 1 << bitDepth; - const int splitElt = lastElt / 2; - const int splitDist = splitElt / 2; - - GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist, 1, buildForDither, pPal); - - GIF_TEMP_FREE(destroyableImage); - - // add the bottom node for the transparency index - pPal->treeSplit[1i64 << (bitDepth - 1)] = 0; - pPal->treeSplitElt[1i64 << (bitDepth - 1)] = 0; - - pPal->r[0] = pPal->g[0] = pPal->b[0] = 0; -} - -// Implements Floyd-Steinberg dithering, writes palette value to alpha -void GifDitherImage(const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal) -{ - int numPixels = width*height; - - // quantPixels initially holds color*256 for all pixels - // The extra 8 bits of precision allow for sub-single-color error values - // to be propagated - int32_t* quantPixels = (int32_t*)GIF_TEMP_MALLOC(sizeof(int32_t)*numPixels * 4); - - for (int ii = 0; iir[bestInd]) * 256; - int32_t g_err = nextPix[1] - int32_t(pPal->g[bestInd]) * 256; - int32_t b_err = nextPix[2] - int32_t(pPal->b[bestInd]) * 256; - - nextPix[0] = pPal->r[bestInd]; - nextPix[1] = pPal->g[bestInd]; - nextPix[2] = pPal->b[bestInd]; - nextPix[3] = bestInd; - - // Propagate the error to the four adjacent locations - // that we haven't touched yet - int quantloc_7 = (yy*width + xx + 1); - int quantloc_3 = (yy*width + width + xx - 1); - int quantloc_5 = (yy*width + width + xx); - int quantloc_1 = (yy*width + width + xx + 1); - - if (quantloc_7 < numPixels) - { - int32_t* pix7 = quantPixels + 4 * quantloc_7; - pix7[0] += GifIMax(-pix7[0], r_err * 7 / 16); - pix7[1] += GifIMax(-pix7[1], g_err * 7 / 16); - pix7[2] += GifIMax(-pix7[2], b_err * 7 / 16); - } - - if (quantloc_3 < numPixels) - { - int32_t* pix3 = quantPixels + 4 * quantloc_3; - pix3[0] += GifIMax(-pix3[0], r_err * 3 / 16); - pix3[1] += GifIMax(-pix3[1], g_err * 3 / 16); - pix3[2] += GifIMax(-pix3[2], b_err * 3 / 16); - } - - if (quantloc_5 < numPixels) - { - int32_t* pix5 = quantPixels + 4 * quantloc_5; - pix5[0] += GifIMax(-pix5[0], r_err * 5 / 16); - pix5[1] += GifIMax(-pix5[1], g_err * 5 / 16); - pix5[2] += GifIMax(-pix5[2], b_err * 5 / 16); - } - - if (quantloc_1 < numPixels) - { - int32_t* pix1 = quantPixels + 4 * quantloc_1; - pix1[0] += GifIMax(-pix1[0], r_err / 16); - pix1[1] += GifIMax(-pix1[1], g_err / 16); - pix1[2] += GifIMax(-pix1[2], b_err / 16); - } - } - } - - // Copy the palettized result to the output buffer - for (int ii = 0; iir[bestInd]; - outFrame[1] = pPal->g[bestInd]; - outFrame[2] = pPal->b[bestInd]; - outFrame[3] = bestInd; - } - - if (lastFrame) lastFrame += 4; - outFrame += 4; - nextFrame += 4; - } -} - -// insert a single bit -void GifWriteBit(GifBitStatus& stat, uint32_t bit) -{ - bit = bit & 1; - bit = bit << stat.bitIndex; - stat.byte |= bit; - - ++stat.bitIndex; - if (stat.bitIndex > 7) - { - // move the newly-finished byte to the chunk buffer - stat.chunk[stat.chunkIndex++] = stat.byte; - // and start a new byte - stat.bitIndex = 0; - stat.byte = 0; - } -} - -// write all bytes so far to the file -void GifWriteChunk(FILE* f, GifBitStatus& stat) -{ - fputc(stat.chunkIndex, f); - fwrite(stat.chunk, 1, stat.chunkIndex, f); - - stat.bitIndex = 0; - stat.byte = 0; - stat.chunkIndex = 0; -} - -void GifWriteCode(FILE* f, GifBitStatus& stat, uint32_t code, uint32_t length) -{ - for (uint32_t ii = 0; ii> 1; - - if (stat.chunkIndex == 255) - { - GifWriteChunk(f, stat); - } - } -} - -// write a 256-color (8-bit) image palette to the file -void GifWritePalette(const GifPalette* pPal, FILE* f) -{ - fputc(0, f); // first color: transparency - fputc(0, f); - fputc(0, f); - - for (int ii = 1; ii<(1 << pPal->bitDepth); ++ii) - { - uint32_t r = pPal->r[ii]; - uint32_t g = pPal->g[ii]; - uint32_t b = pPal->b[ii]; - - fputc(r, f); - fputc(g, f); - fputc(b, f); - } -} - -// write the image header, LZW-compress and write out the image -void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t delay, GifPalette* pPal) -{ - // graphics control extension - fputc(0x21, f); - fputc(0xf9, f); - fputc(0x04, f); - fputc(0x05, f); // leave prev frame in place, this frame has transparency - fputc(delay & 0xff, f); - fputc((delay >> 8) & 0xff, f); - fputc(kGifTransIndex, f); // transparent color index - fputc(0, f); - - fputc(0x2c, f); // image descriptor block - - fputc(left & 0xff, f); // corner of image in canvas space - fputc((left >> 8) & 0xff, f); - fputc(top & 0xff, f); - fputc((top >> 8) & 0xff, f); - - fputc(width & 0xff, f); // width and height of image - fputc((width >> 8) & 0xff, f); - fputc(height & 0xff, f); - fputc((height >> 8) & 0xff, f); - - //fputc(0, f); // no local color table, no transparency - //fputc(0x80, f); // no local color table, but transparency - - fputc(0x80 + pPal->bitDepth - 1, f); // local color table present, 2 ^ bitDepth entries - GifWritePalette(pPal, f); - - const int minCodeSize = pPal->bitDepth; - const uint32_t clearCode = 1 << pPal->bitDepth; - - fputc(minCodeSize, f); // min code size 8 bits - - GifLzwNode* codetree = (GifLzwNode*)GIF_TEMP_MALLOC(sizeof(GifLzwNode) * 4096); - - memset(codetree, 0, sizeof(GifLzwNode) * 4096); - int32_t curCode = -1; - uint32_t codeSize = minCodeSize + 1; - uint32_t maxCode = clearCode + 1; - - GifBitStatus stat; - stat.byte = 0; - stat.bitIndex = 0; - stat.chunkIndex = 0; - - GifWriteCode(f, stat, clearCode, codeSize); // start with a fresh LZW dictionary - - for (uint32_t yy = 0; yy= (1ul << codeSize)) - { - // dictionary entry count has broken a size barrier, - // we need more bits for codes - codeSize++; - } - if (maxCode == 4095) - { - // the dictionary is full, clear it out and begin anew - GifWriteCode(f, stat, clearCode, codeSize); // clear tree - - memset(codetree, 0, sizeof(GifLzwNode) * 4096); - curCode = -1; - codeSize = minCodeSize + 1; - maxCode = clearCode + 1; - } - - curCode = nextValue; - } - } - } - - // compression footer - GifWriteCode(f, stat, curCode, codeSize); - GifWriteCode(f, stat, clearCode, codeSize); - GifWriteCode(f, stat, clearCode + 1, minCodeSize + 1); - - // write out the last partial chunk - while (stat.bitIndex) GifWriteBit(stat, 0); - if (stat.chunkIndex) GifWriteChunk(f, stat); - - fputc(0, f); // image block terminator - - GIF_TEMP_FREE(codetree); -} - -// Creates a gif file. -// The input GIFWriter is assumed to be uninitialized. -// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value. -bool GifBegin(GifWriter* writer, const char* filename, uint32_t width, uint32_t height, uint32_t delay, int32_t bitDepth, bool dither) -{ -#if _MSC_VER >= 1400 - writer->f = 0; - fopen_s(&writer->f, filename, "wb"); -#else - writer->f = fopen(filename, "wb"); -#endif - if (!writer->f) return false; - - writer->firstFrame = true; - - // allocate - writer->oldImage = (uint8_t*)GIF_MALLOC(width*height * 4); - - fputs("GIF89a", writer->f); - - // screen descriptor - fputc(width & 0xff, writer->f); - fputc((width >> 8) & 0xff, writer->f); - fputc(height & 0xff, writer->f); - fputc((height >> 8) & 0xff, writer->f); - - fputc(0xf0, writer->f); // there is an unsorted global color table of 2 entries - fputc(0, writer->f); // background color - fputc(0, writer->f); // pixels are square (we need to specify this because it's 1989) - - // now the "global" palette (really just a dummy palette) - // color 0: black - fputc(0, writer->f); - fputc(0, writer->f); - fputc(0, writer->f); - // color 1: also black - fputc(0, writer->f); - fputc(0, writer->f); - fputc(0, writer->f); - - if (delay != 0) - { - // animation header - fputc(0x21, writer->f); // extension - fputc(0xff, writer->f); // application specific - fputc(11, writer->f); // length 11 - fputs("NETSCAPE2.0", writer->f); // yes, really - fputc(3, writer->f); // 3 bytes of NETSCAPE2.0 data - - fputc(1, writer->f); // JUST BECAUSE - fputc(0, writer->f); // loop infinitely (byte 0) - fputc(0, writer->f); // loop infinitely (byte 1) - - fputc(0, writer->f); // block terminator - } - - return true; -} - -// Writes out a new frame to a GIF in progress. -// The GIFWriter should have been created by GIFBegin. -// AFAIK, it is legal to use different bit depths for different frames of an image - -// this may be handy to save bits in animations that don't change much. -bool GifWriteFrame(GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, uint8_t bitDepth, bool dither) -{ - if (!writer->f) return false; - - const uint8_t* oldImage = writer->firstFrame ? NULL : writer->oldImage; - writer->firstFrame = false; - - GifPalette pal; - GifMakePalette((dither ? NULL : oldImage), image, width, height, bitDepth, dither, &pal); - - if (dither) - GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal); - else - GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal); - - GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal); - - return true; -} - -// Writes the EOF code, closes the file handle, and frees temp memory used by a GIF. -// Many if not most viewers will still display a GIF properly if the EOF code is missing, -// but it's still a good idea to write it out. -bool GifEnd(GifWriter* writer) -{ - if (!writer->f) return false; - - fputc(0x3b, writer->f); // end of file - fclose(writer->f); - GIF_FREE(writer->oldImage); - - writer->f = NULL; - writer->oldImage = NULL; - - return true; -} - -#endif \ No newline at end of file diff --git a/interface/src/ui/Gif.h b/interface/src/ui/Gif.h deleted file mode 100644 index 7d3929a3bb..0000000000 --- a/interface/src/ui/Gif.h +++ /dev/null @@ -1,162 +0,0 @@ -// -// gif.h -// by Charlie Tangora -// Public domain. -// Email me : ctangora -at- gmail -dot- com -// -// This file offers a simple, very limited way to create animated GIFs directly in code. -// -// Those looking for particular cleverness are likely to be disappointed; it's pretty -// much a straight-ahead implementation of the GIF format with optional Floyd-Steinberg -// dithering. (It does at least use delta encoding - only the changed portions of each -// frame are saved.) -// -// So resulting files are often quite large. The hope is that it will be handy nonetheless -// as a quick and easily-integrated way for programs to spit out animations. -// -// Only RGBA8 is currently supported as an input format. (The alpha is ignored.) -// -// USAGE: -// Create a GifWriter struct. Pass it to GifBegin() to initialize and write the header. -// Pass subsequent frames to GifWriteFrame(). -// Finally, call GifEnd() to close the file handle and free memory. -// - -#ifndef gif_h -#define gif_h - -#include -#include // for FILE* -#include // for memcpy and bzero - -#ifndef GIF_TEMP_MALLOC -#include -#define GIF_TEMP_MALLOC malloc -#endif - -#ifndef GIF_TEMP_FREE -#include -#define GIF_TEMP_FREE free -#endif - -#ifndef GIF_MALLOC -#include -#define GIF_MALLOC malloc -#endif - -#ifndef GIF_FREE -#include -#define GIF_FREE free -#endif - -const int kGifTransIndex = 0; - -struct GifPalette -{ - uint8_t bitDepth; - - uint8_t r[256]; - uint8_t g[256]; - uint8_t b[256]; - - // k-d tree over RGB space, organized in heap fashion - // i.e. left child of node i is node i*2, right child is node i*2+1 - // nodes 256-511 are implicitly the leaves, containing a color - uint8_t treeSplitElt[255]; - uint8_t treeSplit[255]; -}; - -// max, min, and abs functions -int GifIMax(int l, int r); -int GifIMin(int l, int r); -int GifIAbs(int i); - -// walks the k-d tree to pick the palette entry for a desired color. -// Takes as in/out parameters the current best color and its error - -// only changes them if it finds a better color in its subtree. -// this is the major hotspot in the code at the moment. -void GifGetClosestPaletteColor(GifPalette* pPal, int r, int g, int b, int& bestInd, int& bestDiff, int treeRoot); - -void GifSwapPixels(uint8_t* image, int pixA, int pixB); - -// just the partition operation from quicksort -int GifPartition(uint8_t* image, const int left, const int right, const int elt, int pivotIndex); - -// Perform an incomplete sort, finding all elements above and below the desired median -void GifPartitionByMedian(uint8_t* image, int left, int right, int com, int neededCenter); - -// Builds a palette by creating a balanced k-d tree of all pixels in the image -void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int lastElt, int splitElt, int splitDist, int treeNode, bool buildForDither, GifPalette* pal); - -// Finds all pixels that have changed from the previous image and -// moves them to the fromt of th buffer. -// This allows us to build a palette optimized for the colors of the -// changed pixels only. -int GifPickChangedPixels(const uint8_t* lastFrame, uint8_t* frame, int numPixels); - -// Creates a palette by placing all the image pixels in a k-d tree and then averaging the blocks at the bottom. -// This is known as the "modified median split" technique -void GifMakePalette(const uint8_t* lastFrame, const uint8_t* nextFrame, uint32_t width, uint32_t height, uint8_t bitDepth, bool buildForDither, GifPalette* pPal); - -// Implements Floyd-Steinberg dithering, writes palette value to alpha -void GifDitherImage(const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal); - -// Picks palette colors for the image using simple thresholding, no dithering -void GifThresholdImage(const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal); - -// Simple structure to write out the LZW-compressed portion of the image -// one bit at a time -struct GifBitStatus -{ - uint8_t bitIndex; // how many bits in the partial byte written so far - uint8_t byte; // current partial byte - - uint32_t chunkIndex; - uint8_t chunk[256]; // bytes are written in here until we have 256 of them, then written to the file -}; - -// insert a single bit -void GifWriteBit(GifBitStatus& stat, uint32_t bit); - -// write all bytes so far to the file -void GifWriteChunk(FILE* f, GifBitStatus& stat); - -void GifWriteCode(FILE* f, GifBitStatus& stat, uint32_t code, uint32_t length); - -// The LZW dictionary is a 256-ary tree constructed as the file is encoded, -// this is one node -struct GifLzwNode -{ - uint16_t m_next[256]; -}; - -// write a 256-color (8-bit) image palette to the file -void GifWritePalette(const GifPalette* pPal, FILE* f); - -// write the image header, LZW-compress and write out the image -void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t delay, GifPalette* pPal); - -struct GifWriter -{ - FILE* f; - uint8_t* oldImage; - bool firstFrame; -}; - -// Creates a gif file. -// The input GIFWriter is assumed to be uninitialized. -// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value. -bool GifBegin(GifWriter* writer, const char* filename, uint32_t width, uint32_t height, uint32_t delay, int32_t bitDepth, bool dither); - -// Writes out a new frame to a GIF in progress. -// The GIFWriter should have been created by GIFBegin. -// AFAIK, it is legal to use different bit depths for different frames of an image - -// this may be handy to save bits in animations that don't change much. -bool GifWriteFrame(GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, uint8_t bitDepth, bool dither); - -// Writes the EOF code, closes the file handle, and frees temp memory used by a GIF. -// Many if not most viewers will still display a GIF properly if the EOF code is missing, -// but it's still a good idea to write it out. -bool GifEnd(GifWriter* writer); - -#endif \ No newline at end of file