REPLACED: rgif.h by msf_gif.h

The improvement in performance is considerable!
This commit is contained in:
Ray 2020-11-20 00:34:18 +01:00
parent 4a7ab0ae24
commit 4eae76302f
4 changed files with 229 additions and 1114 deletions

View File

@ -55,7 +55,7 @@
// Allow automatic screen capture of current screen pressing F12, defined in KeyCallback()
#define SUPPORT_SCREEN_CAPTURE 1
// Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback()
//#define SUPPORT_GIF_RECORDING 1
#define SUPPORT_GIF_RECORDING 1
// Allow scale all the drawn content to match the high-DPI equivalent size (only PLATFORM_DESKTOP)
//#define SUPPORT_HIGH_DPI 1
// Support CompressData() and DecompressData() functions

View File

@ -147,11 +147,11 @@
#endif
#if defined(SUPPORT_GIF_RECORDING)
#define RGIF_MALLOC RL_MALLOC
#define RGIF_FREE RL_FREE
//#define MSF_GIF_MALLOC RL_MALLOC
//#define MSF_GIF_FREE RL_FREE
#define RGIF_IMPLEMENTATION
#include "external/rgif.h" // Support GIF recording
#define MSF_GIF_IMPL
#include "external/msf_gif.h" // Support GIF recording
#endif
#include <stdlib.h> // Required for: srand(), rand(), atexit()
@ -484,6 +484,7 @@ static int screenshotCounter = 0; // Screenshots counter
#if defined(SUPPORT_GIF_RECORDING)
static int gifFramesCounter = 0; // GIF frames counter
static bool gifRecording = false; // GIF recording state
static MsfGifState gifState = { 0 }; // MSGIF context state
#endif
//-----------------------------------------------------------------------------------
@ -788,7 +789,8 @@ void CloseWindow(void)
#if defined(SUPPORT_GIF_RECORDING)
if (gifRecording)
{
GifEnd();
MsfGifResult result = msf_gif_end(&gifState);
msf_gif_free(result);
gifRecording = false;
}
#endif
@ -1521,9 +1523,9 @@ void EndDrawing(void)
if ((gifFramesCounter%GIF_RECORD_FRAMERATE) == 0)
{
// Get image data for the current frame (from backbuffer)
// NOTE: This process is very slow... :(
// NOTE: This process is quite slow... :(
unsigned char *screenData = rlReadScreenPixels(CORE.Window.screen.width, CORE.Window.screen.height);
GifWriteFrame(screenData, CORE.Window.screen.width, CORE.Window.screen.height, 10, 8, false);
msf_gif_frame(&gifState, screenData, 10, 16, CORE.Window.screen.width*4);
RL_FREE(screenData); // Free image data
}
@ -4225,8 +4227,20 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i
{
if (gifRecording)
{
GifEnd();
gifRecording = false;
MsfGifResult result = msf_gif_end(&gifState);
char path[512] = { 0 };
#if defined(PLATFORM_ANDROID)
strcpy(path, CORE.Android.internalDataPath);
strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter));
#else
strcpy(path, TextFormat("./screenrec%03i.gif", screenshotCounter));
#endif
SaveFileData(path, result.data, result.dataSize);
msf_gif_free(result);
#if defined(PLATFORM_WEB)
// Download file from MEMFS (emscripten memory filesystem)
@ -4241,17 +4255,7 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i
gifRecording = true;
gifFramesCounter = 0;
char path[512] = { 0 };
#if defined(PLATFORM_ANDROID)
strcpy(path, CORE.Android.internalDataPath);
strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter));
#else
strcpy(path, TextFormat("./screenrec%03i.gif", screenshotCounter));
#endif
// NOTE: delay represents the time between frames in the gif, if we capture a gif frame every
// 10 game frames and each frame trakes 16.6ms (60fps), delay between gif frames should be ~16.6*10.
GifBegin(path, CORE.Window.screen.width, CORE.Window.screen.height, (int)(GetFrameTime()*10.0f), 8, false);
msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height);
screenshotCounter++;
TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter));
@ -5594,8 +5598,12 @@ void UWPKeyDownEvent(int key, bool down, bool controlKey)
{
if (gifRecording)
{
GifEnd();
gifRecording = false;
MsfGifResult result = msf_gif_end(&gifState);
SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.UWP.internalDataPath, screenshotCounter), result.data, result.dataSize);
msf_gif_free(result);
#if defined(PLATFORM_WEB)
// Download file from MEMFS (emscripten memory filesystem)
@ -5609,13 +5617,7 @@ void UWPKeyDownEvent(int key, bool down, bool controlKey)
gifRecording = true;
gifFramesCounter = 0;
char path[512] = { 0 };
strcpy(path, CORE.UWP.internalDataPath);
strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter));
// NOTE: delay represents the time between frames in the gif, if we capture a gif frame every
// 10 game frames and each frame trakes 16.6ms (60fps), delay between gif frames should be ~16.6*10.
GifBegin(path, CORE.Window.screen.width, CORE.Window.screen.height, (int)(GetFrameTime() * 10.0f), 8, false);
msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height);
screenshotCounter++;
TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter));

355
src/external/msf_gif.h vendored
View File

@ -13,29 +13,36 @@ USAGE EXAMPLE:
int width = 480, height = 320, centisecondsPerFrame = 5, bitDepth = 16;
MsfGifState gifState = {};
msf_gif_begin(&gifState, "example.gif", width, height);
msf_gif_begin(&gifState, width, height);
msf_gif_frame(&gifState, ..., centisecondsPerFrame, bitDepth, width * 4); //frame 1
msf_gif_frame(&gifState, ..., centisecondsPerFrame, bitDepth, width * 4); //frame 2
msf_gif_frame(&gifState, ..., centisecondsPerFrame, bitDepth, width * 4); //frame 3, etc...
msf_gif_end(&gifState);
MsfGifResult result = msf_gif_end(&gifState);
FILE * fp = fopen("MyGif.gif", "wb");
fwrite(result.data, result.dataSize, 1, fp);
fclose(fp);
msf_gif_free(result);
Detailed function documentation can be found in the header section below.
REPLACING MALLOC AND FWRITE:
This library uses malloc+free internally for memory allocation and fopen+fwrite+fclose for file output.
To facilitate custom memory management and I/O, these calls go through macros, which can be redefined.
In order to replace them, simply #define the relevant macros in the same place where you #define MSF_GIF_IMPL.
The macros are: MSF_GIF_MALLOC, MSF_GIF_FREE, MSF_GIF_FOPEN, MSF_GIF_FWRITE, MSF_GIF_FCLOSE.
Search for their default definitions below to see the exactly what arguments they take.
REPLACING MALLOC:
This library uses malloc+realloc+free internally for memory allocation.
To facilitate integration with custom memory allocators, these calls go through macros, which can be redefined.
The expected function signature equivalents of the macros are as follows:
void * MSF_GIF_MALLOC(void * context, size_t newSize)
void * MSF_GIF_REALLOC(void * context, void * oldMemory, size_t oldSize, size_t newSize)
void MSF_GIF_FREE(void * context, void * oldMemory, size_t oldSize)
If your allocator needs a context pointer, you can set the `customAllocatorContext` field of the MsfGifState struct
before calling msf_gif_begin(), and it will be passed to all subsequent allocator macro calls.
The same goes for file I/O macros, using the `customOutputContext` field.
See end of file for license information.
*/
//version 1.2
//version 2.1
#ifndef MSF_GIF_H
#define MSF_GIF_H
@ -44,18 +51,24 @@ See end of file for license information.
#include <stddef.h>
typedef struct {
void * data;
size_t dataSize;
size_t allocSize; //internal use
void * contextPointer; //internal use
} MsfGifResult;
typedef struct { //internal use
uint32_t * pixels;
uint8_t * used;
int depth, count, rbits, gbits, bbits;
} MsfCookedFrame;
typedef struct {
void * fp;
MsfCookedFrame previousFrame;
uint8_t * listHead;
uint8_t * listTail;
int width, height;
int totalBytesWritten;
void * customAllocatorContext;
void * customOutputContext;
} MsfGifState;
#ifdef __cplusplus
@ -63,12 +76,11 @@ extern "C" {
#endif //__cplusplus
/**
* @param outputFilePath Relative path to the output file, as a null-terminated string of UTF-8 bytes.
* @param width Image width in pixels - must be the same for the whole gif.
* @param height Image height in pixels - must be the same for the whole gif.
* @return The size of the file written so far, or 0 on error.
* @param width Image width in pixels.
* @param height Image height in pixels.
* @return Non-zero on success, 0 on error.
*/
size_t msf_gif_begin(MsfGifState * handle, const char * outputFilePath, int width, int height);
int msf_gif_begin(MsfGifState * handle, int width, int height);
/**
* @param pixelData Pointer to raw framebuffer data. Rows must be contiguous in memory, in RGBA8 format.
@ -84,15 +96,20 @@ size_t msf_gif_begin(MsfGifState * handle, const char * outputFilePath, int widt
* Please experiment with this value to find what works best for your application.
* @param pitchInBytes The number of bytes from the beginning of one row of pixels to the beginning of the next.
* If you want to flip the image, just pass in a negative pitch.
* @return The size of the file written so far, or 0 on error.
* @return Non-zero on success, 0 on error.
*/
size_t msf_gif_frame(MsfGifState * handle,
uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes);
int msf_gif_frame(MsfGifState * handle, uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes);
/**
* @return The size of the written file in bytes, or 0 on error.
* @return A block of memory containing the gif file data, or NULL on error.
* You are responsible for freeing this via `msf_gif_free()`.
*/
size_t msf_gif_end(MsfGifState * handle);
MsfGifResult msf_gif_end(MsfGifState * handle);
/**
* @param result The MsfGifResult struct, verbatim as it was returned from `msf_gif_end()`.
*/
void msf_gif_free(MsfGifResult result);
#ifdef __cplusplus
}
@ -108,35 +125,25 @@ size_t msf_gif_end(MsfGifState * handle);
#ifndef MSF_GIF_ALREADY_IMPLEMENTED_IN_THIS_TRANSLATION_UNIT
#define MSF_GIF_ALREADY_IMPLEMENTED_IN_THIS_TRANSLATION_UNIT
//ensure the library user has either defined both of malloc/free, or neither
#if defined(MSF_GIF_MALLOC) && defined(MSF_GIF_FREE)
#elif !defined(MSF_GIF_MALLOC) && !defined(MSF_GIF_FREE)
#ifndef MSF_GIF_BUFFER_INIT_SIZE
#define MSF_GIF_BUFFER_INIT_SIZE 1024 * 1024 * 4 //4MB by default, you can increase this if you want to realloc less
#endif
//ensure the library user has either defined all of malloc/realloc/free, or none
#if defined(MSF_GIF_MALLOC) && defined(MSF_GIF_REALLOC) && defined(MSF_GIF_FREE) //ok
#elif !defined(MSF_GIF_MALLOC) && !defined(MSF_GIF_REALLOC) && !defined(MSF_GIF_FREE) //ok
#else
#error "You must either define both MSF_GIF_MALLOC and MSF_GIF_FREE, or define neither of them"
#error "You must either define all of MSF_GIF_MALLOC, MSF_GIF_REALLOC, and MSF_GIF_FREE, or define none of them"
#endif
//provide default allocator definitions that redirect to the standard global allocator
#if !defined(MSF_GIF_MALLOC)
#include <stdlib.h> //malloc, etc.
#define MSF_GIF_MALLOC(contextPointer, newSize) malloc(newSize)
#define MSF_GIF_REALLOC(contextPointer, oldMemory, oldSize, newSize) realloc(oldMemory, newSize)
#define MSF_GIF_FREE(contextPointer, oldMemory, oldSize) free(oldMemory)
#endif
//ensure the library user has either defined all of fopen/fwrite/fclose, or none
#if defined(MSF_GIF_FOPEN) && defined(MSF_GIF_FWRITE) && defined(MSF_GIF_FCLOSE)
#elif !defined(MSF_GIF_FOPEN) && !defined(MSF_GIF_FWRITE) && !defined(MSF_GIF_FCLOSE)
#else
#error "You must either define all of MSF_GIF_FOPEN, MSF_GIF_FWRITE, and MSF_GIF_FCLOSE, or define none of them"
#endif
//provide default file ops that redirect to the standard library ones
#if !defined(MSF_GIF_FOPEN)
#include <stdio.h> //FILE ops (fopen, etc.)
#define MSF_GIF_FOPEN(contextPointer, filePath) fopen(filePath, "wb")
#define MSF_GIF_FWRITE(contextPointer, filePointer, data, dataSize) fwrite(data, dataSize, 1, (FILE *) filePointer)
#define MSF_GIF_FCLOSE(contextPointer, filePointer) fclose((FILE *) filePointer)
#endif
//instrumentation for capturing profiling traces (useless for the library user, but useful for the library author)
#ifdef MSF_GIF_ENABLE_TRACING
#define MsfTimeFunc TimeFunc
@ -182,12 +189,9 @@ static inline int msf_imax(int a, int b) { return b < a? a : b; }
#include <emmintrin.h>
#endif
static const int msfUsedAllocSize = (1 << 16) * sizeof(uint8_t);
static MsfCookedFrame msf_cook_frame(void * allocContext, int width, int height, int pitch, int depth, uint8_t * raw)
static MsfCookedFrame msf_cook_frame(void * allocContext, uint8_t * raw, uint8_t * used,
int width, int height, int pitch, int depth)
{ MsfTimeFunc
MsfCookedFrame blank = {0};
//bit depth for each channel
const static int rdepths[17] = { 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5 };
const static int gdepths[17] = { 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6 };
@ -200,10 +204,8 @@ static MsfCookedFrame msf_cook_frame(void * allocContext, int width, int height,
15 << 12, 7 << 12, 13 << 12, 5 << 12,
};
uint8_t * used = (uint8_t *) MSF_GIF_MALLOC(allocContext, msfUsedAllocSize);
if (!used) return blank;
uint32_t * cooked = (uint32_t *) MSF_GIF_MALLOC(allocContext, width * height * sizeof(uint32_t));
if (!cooked) { MSF_GIF_FREE(allocContext, used, msfUsedAllocSize); return blank; }
if (!cooked) { MsfCookedFrame blank = {0}; return blank; }
int count = 0;
MsfTimeLoop("do") do {
@ -275,28 +277,7 @@ static MsfCookedFrame msf_cook_frame(void * allocContext, int width, int height,
}
} while (count >= 256 && --depth);
MsfCookedFrame ret = { cooked, used, depth, count, rdepths[depth], gdepths[depth], bdepths[depth] };
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// MsfFileBuffer ///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct {
uint8_t * block;
uint8_t * head;
uint8_t * end;
} MsfFileBuffer;
static inline void msf_fb_write_data(MsfFileBuffer * buf, void * data, size_t bytes) {
memcpy(buf->head, data, bytes);
buf->head += bytes;
}
static MsfFileBuffer msf_create_file_buffer(void * allocContext, size_t bytes) {
uint8_t * block = (uint8_t *) MSF_GIF_MALLOC(allocContext, bytes);
MsfFileBuffer ret = { block, block, block + bytes };
MsfCookedFrame ret = { cooked, depth, count, rdepths[depth], gdepths[depth], bdepths[depth] };
return ret;
}
@ -304,23 +285,28 @@ static MsfFileBuffer msf_create_file_buffer(void * allocContext, size_t bytes) {
/// Frame Compression ///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static inline int msf_put_code(MsfFileBuffer * buf, uint32_t * blockBits, int len, uint32_t code) {
typedef struct {
uint8_t * next;
size_t size;
} MsfBufferHeader;
static inline int msf_put_code(uint8_t * * writeHead, uint32_t * blockBits, int len, uint32_t code) {
//insert new code into block buffer
int idx = *blockBits / 8;
int bit = *blockBits % 8;
buf->head[idx + 0] |= code << bit ;
buf->head[idx + 1] |= code >> ( 8 - bit);
buf->head[idx + 2] |= code >> (16 - bit);
(*writeHead)[idx + 0] |= code << bit ;
(*writeHead)[idx + 1] |= code >> ( 8 - bit);
(*writeHead)[idx + 2] |= code >> (16 - bit);
*blockBits += len;
//prep the next block buffer if the current one is full
if (*blockBits >= 256 * 8) {
*blockBits -= 255 * 8;
buf->head += 256;
buf->head[2] = buf->head[1];
buf->head[1] = buf->head[0];
buf->head[0] = 255;
memset(buf->head + 4, 0, 256);
(*writeHead) += 256;
(*writeHead)[2] = (*writeHead)[1];
(*writeHead)[1] = (*writeHead)[0];
(*writeHead)[0] = 255;
memset((*writeHead) + 4, 0, 256);
}
return 1;
@ -338,18 +324,19 @@ static inline void msf_lzw_reset(MsfStridedList * lzw, int tableSize, int stride
lzw->stride = stride;
}
static MsfFileBuffer msf_compress_frame(void * allocContext, int width, int height, int centiSeconds,
MsfCookedFrame frame, MsfCookedFrame previous)
static uint8_t * msf_compress_frame(void * allocContext, int width, int height, int centiSeconds,
MsfCookedFrame frame, MsfCookedFrame previous, uint8_t * used)
{ MsfTimeFunc
MsfFileBuffer blank = {0};
//NOTE: we allocate enough memory for the worst case upfront because it's a reasonable amount
// (about half the size of a CookedFrame), and prevents us from ever having to check size or realloc
MsfFileBuffer buf = msf_create_file_buffer(allocContext, 1024 + width * height * 2);
if (!buf.block) return blank;
int lzwAllocSize = 4096 * 256 * sizeof(int16_t);
//NOTE: we reserve enough memory for theoretical the worst case upfront because it's a reasonable amount,
// and prevents us from ever having to check size or realloc during compression
int maxBufSize = sizeof(MsfBufferHeader) + 32 + 256 * 3 + width * height * 3 / 2; //headers + color table + data
uint8_t * allocation = (uint8_t *) MSF_GIF_MALLOC(allocContext, maxBufSize);
if (!allocation) { return NULL; }
uint8_t * writeBase = allocation + sizeof(MsfBufferHeader);
uint8_t * writeHead = writeBase;
int lzwAllocSize = 4096 * (frame.count + 1) * sizeof(int16_t);
MsfStridedList lzw = { (int16_t *) MSF_GIF_MALLOC(allocContext, lzwAllocSize) };
if (!lzw.data) { MSF_GIF_FREE(allocContext, buf.block, buf.end - buf.block); return blank; }
if (!lzw.data) { MSF_GIF_FREE(allocContext, allocation, maxBufSize); return NULL; }
//allocate tlb
int totalBits = frame.rbits + frame.gbits + frame.bbits;
@ -361,7 +348,7 @@ static MsfFileBuffer msf_compress_frame(void * allocContext, int width, int heig
Color3 table[256] = { {0} };
int tableIdx = 1; //we start counting at 1 because 0 is the transparent color
MsfTimeLoop("table") for (int i = 0; i < tlbSize; ++i) {
if (frame.used[i]) {
if (used[i]) {
tlb[i] = tableIdx;
int rmask = (1 << frame.rbits) - 1;
int gmask = (1 << frame.gbits) - 1;
@ -380,7 +367,9 @@ static MsfFileBuffer msf_compress_frame(void * allocContext, int width, int heig
}
}
int tableBits = msf_bit_log(tableIdx - 1);
//SPEC: "Because of some algorithmic constraints however, black & white images which have one color bit
// must be indicated as having a code size of 2."
int tableBits = msf_imax(2, msf_bit_log(tableIdx - 1));
int tableSize = 1 << tableBits;
//NOTE: we don't just compare `depth` field here because it will be wrong for the first frame and we will segfault
int hasSamePal = frame.rbits == previous.rbits && frame.gbits == previous.gbits && frame.bbits == previous.bbits;
@ -391,19 +380,23 @@ static MsfFileBuffer msf_compress_frame(void * allocContext, int width, int heig
memcpy(&headerBytes[13], &width, 2);
memcpy(&headerBytes[15], &height, 2);
headerBytes[17] |= tableBits - 1;
msf_fb_write_data(&buf, headerBytes, 18);
memcpy(writeHead, headerBytes, 18);
writeHead += 18;
//local color table
msf_fb_write_data(&buf, table, tableSize * sizeof(Color3));
*buf.head++ = tableBits;
msf_lzw_reset(&lzw, tableSize, tableIdx);
memcpy(writeHead, table, tableSize * sizeof(Color3));
writeHead += tableSize * sizeof(Color3);
*writeHead++ = tableBits;
//prep block
memset(buf.head, 0, 260);
buf.head[0] = 255;
memset(writeHead, 0, 260);
writeHead[0] = 255;
uint32_t blockBits = 8; //relative to block.head
//SPEC: "Encoders should output a Clear code as the first code of each image data stream."
msf_lzw_reset(&lzw, tableSize, tableIdx);
msf_put_code(&writeHead, &blockBits, msf_bit_log(lzw.len - 1), tableSize);
int lastCode = hasSamePal && frame.pixels[0] == previous.pixels[0]? 0 : tlb[frame.pixels[0]];
MsfTimeLoop("compress") for (int i = 1; i < width * height; ++i) {
//PERF: branching vs. branchless version of this line is observed to have no discernable impact on speed
@ -414,15 +407,11 @@ static MsfFileBuffer msf_compress_frame(void * allocContext, int width, int heig
if (code < 0) {
//write to code stream
int codeBits = msf_bit_log(lzw.len - 1);
msf_put_code(&buf, &blockBits, codeBits, lastCode);
msf_put_code(&writeHead, &blockBits, codeBits, lastCode);
//NOTE: [I THINK] we need to leave room for 2 more codes (leftover and end code)
// because we don't ever reset the table after writing the leftover bits
//Q: is my thinking correct on this one?
//Q: why can't we just check when writing out those codes? too verbose? can't we factor to a funtion?
if (lzw.len > 4094) {
if (lzw.len > 4095) {
//reset buffer code table
msf_put_code(&buf, &blockBits, codeBits, tableSize);
msf_put_code(&writeHead, &blockBits, codeBits, tableSize);
msf_lzw_reset(&lzw, tableSize, tableIdx);
} else {
(&lzw.data[lastCode * lzw.stride])[color] = lzw.len;
@ -435,88 +424,142 @@ static MsfFileBuffer msf_compress_frame(void * allocContext, int width, int heig
}
}
//write code for leftover index buffer contents, then the end code
MSF_GIF_FREE(allocContext, lzw.data, lzwAllocSize);
msf_put_code(&buf, &blockBits, msf_bit_log(lzw.len - 1), lastCode);
msf_put_code(&buf, &blockBits, msf_bit_log(lzw.len), tableSize + 1);
MSF_GIF_FREE(allocContext, previous.pixels, width * height * sizeof(uint32_t));
//write code for leftover index buffer contents, then the end code
msf_put_code(&writeHead, &blockBits, msf_imin(12, msf_bit_log(lzw.len - 1)), lastCode);
msf_put_code(&writeHead, &blockBits, msf_imin(12, msf_bit_log(lzw.len)), tableSize + 1);
//flush remaining data
if (blockBits > 8) {
int bytes = (blockBits + 7) / 8; //round up
buf.head[0] = bytes - 1;
buf.head += bytes;
writeHead[0] = bytes - 1;
writeHead += bytes;
}
*writeHead++ = 0; //terminating block
*buf.head++ = 0; //terminating block
return buf;
//filling in buffer header and shrink buffer to fit data
MsfBufferHeader * header = (MsfBufferHeader *) allocation;
header->next = NULL;
header->size = writeHead - writeBase;
uint8_t * moved = (uint8_t *) MSF_GIF_REALLOC(allocContext, allocation, maxBufSize, writeHead - allocation);
if (!moved) { MSF_GIF_FREE(allocContext, allocation, maxBufSize); return NULL; }
return moved;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Incremental API ///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
size_t msf_gif_begin(MsfGifState * handle, const char * outputFilePath, int width, int height) { MsfTimeFunc
//TODO: convert this to UTF-16 to correctly handle unicode on windows!?!?
// (or just say "do it yourself" now that replacing fopen is a thing?)
if (!(handle->fp = MSF_GIF_FOPEN(handle->customOutputContext, outputFilePath))) return 0;
int msf_gif_begin(MsfGifState * handle, int width, int height) { MsfTimeFunc
MsfCookedFrame empty = {0}; //god I hate MSVC...
handle->previousFrame = empty;
handle->previousFrame.depth = 15;
handle->width = width;
handle->height = height;
handle->totalBytesWritten = 0;
//setup header buffer header (lol)
handle->listHead = (uint8_t *) MSF_GIF_MALLOC(handle->customAllocatorContext, sizeof(MsfBufferHeader) + 32);
if (!handle->listHead) { return 0; }
handle->listTail = handle->listHead;
MsfBufferHeader * header = (MsfBufferHeader *) handle->listHead;
header->next = NULL;
header->size = 32;
//NOTE: because __attribute__((__packed__)) is annoyingly compiler-specific, we do this unreadable weirdness
char headerBytes[33] = "GIF89a\0\0\0\0\x10\0\0" "\x21\xFF\x0BNETSCAPE2.0\x03\x01\0\0\0";
memcpy(&headerBytes[6], &width, 2);
memcpy(&headerBytes[8], &height, 2);
if (!MSF_GIF_FWRITE(handle->customOutputContext, handle->fp, headerBytes, 32)) {
MSF_GIF_FCLOSE(handle->customOutputContext, handle->fp);
return 0;
}
handle->totalBytesWritten += 32;
return handle->totalBytesWritten;
memcpy(handle->listHead + sizeof(MsfBufferHeader), headerBytes, 32);
return 1;
}
size_t msf_gif_frame(MsfGifState * handle,
uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes)
int msf_gif_frame(MsfGifState * handle, uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes)
{ MsfTimeFunc
if (!handle->listHead) { return 0; }
maxBitDepth = msf_imax(1, msf_imin(16, maxBitDepth));
if (pitchInBytes == 0) pitchInBytes = handle->width * 4;
if (pitchInBytes < 0) pixelData -= pitchInBytes * (handle->height - 1);
MsfCookedFrame frame = msf_cook_frame(handle->customAllocatorContext, handle->width, handle->height, pitchInBytes,
// msf_imin(maxBitDepth, handle->previousFrame.depth + 1), pixelData);
msf_imin(maxBitDepth, handle->previousFrame.depth + 160 / msf_imax(1, handle->previousFrame.count)), pixelData);
if (!frame.pixels) { MSF_GIF_FCLOSE(handle->customOutputContext, handle->fp); return 0; }
MsfFileBuffer buf = msf_compress_frame(handle->customAllocatorContext,
handle->width, handle->height, centiSecondsPerFame, frame, handle->previousFrame);
if (!buf.block) { MSF_GIF_FCLOSE(handle->customOutputContext, handle->fp); return 0; }
if (!MSF_GIF_FWRITE(handle->customOutputContext, handle->fp, buf.block, buf.head - buf.block)) {
MSF_GIF_FCLOSE(handle->customOutputContext, handle->fp);
uint8_t used[1 << 16]; //only 64k, so stack allocating is fine
MsfCookedFrame frame =
msf_cook_frame(handle->customAllocatorContext, pixelData, used, handle->width, handle->height, pitchInBytes,
msf_imin(maxBitDepth, handle->previousFrame.depth + 160 / msf_imax(1, handle->previousFrame.count)));
//TODO: de-duplicate cleanup code
if (!frame.pixels) {
MSF_GIF_FREE(handle->customAllocatorContext,
handle->previousFrame.pixels, handle->width * handle->height * sizeof(uint32_t));
for (uint8_t * node = handle->listHead; node;) {
MsfBufferHeader * header = (MsfBufferHeader *) node;
node = header->next;
MSF_GIF_FREE(handle->customAllocatorContext, header, sizeof(MsfBufferHeader) + header->size);
}
handle->listHead = handle->listTail = NULL;
return 0;
}
handle->totalBytesWritten += buf.head - buf.block;
MSF_GIF_FREE(handle->customAllocatorContext, buf.block, buf.end - buf.block);
MSF_GIF_FREE(handle->customAllocatorContext, frame.used, msfUsedAllocSize);
MSF_GIF_FREE(handle->customAllocatorContext,
handle->previousFrame.pixels, handle->width * handle->height * sizeof(uint32_t));
uint8_t * buffer = msf_compress_frame(handle->customAllocatorContext,
handle->width, handle->height, centiSecondsPerFame, frame, handle->previousFrame, used);
((MsfBufferHeader *) handle->listTail)->next = buffer;
handle->listTail = buffer;
if (!buffer) {
MSF_GIF_FREE(handle->customAllocatorContext, frame.pixels, handle->width * handle->height * sizeof(uint32_t));
MSF_GIF_FREE(handle->customAllocatorContext,
handle->previousFrame.pixels, handle->width * handle->height * sizeof(uint32_t));
for (uint8_t * node = handle->listHead; node;) {
MsfBufferHeader * header = (MsfBufferHeader *) node;
node = header->next;
MSF_GIF_FREE(handle->customAllocatorContext, header, sizeof(MsfBufferHeader) + header->size);
}
handle->listHead = handle->listTail = NULL;
return 0;
}
handle->previousFrame = frame;
return handle->totalBytesWritten;
return 1;
}
size_t msf_gif_end(MsfGifState * handle) { MsfTimeFunc
int allocSize = handle->width * handle->height * sizeof(uint32_t);
uint8_t trailingMarker = 0x3B;
if (!MSF_GIF_FWRITE(handle->customOutputContext, handle->fp, &trailingMarker, 1)) {
MSF_GIF_FCLOSE(handle->customOutputContext, handle->fp);
MSF_GIF_FREE(handle->customAllocatorContext, handle->previousFrame.pixels, allocSize);
return 0;
MsfGifResult msf_gif_end(MsfGifState * handle) { MsfTimeFunc
if (!handle->listHead) { MsfGifResult empty = {0}; return empty; }
MSF_GIF_FREE(handle->customAllocatorContext,
handle->previousFrame.pixels, handle->width * handle->height * sizeof(uint32_t));
//first pass: determine total size
size_t total = 1; //1 byte for trailing marker
for (uint8_t * node = handle->listHead; node;) {
MsfBufferHeader * header = (MsfBufferHeader *) node;
node = header->next;
total += header->size;
}
handle->totalBytesWritten += 1;
if (MSF_GIF_FCLOSE(handle->customOutputContext, handle->fp)) return 0;
MSF_GIF_FREE(handle->customAllocatorContext, handle->previousFrame.pixels, allocSize);
return handle->totalBytesWritten;
//second pass: write data
uint8_t * buffer = (uint8_t *) MSF_GIF_MALLOC(handle->customAllocatorContext, total);
if (buffer) {
uint8_t * writeHead = buffer;
for (uint8_t * node = handle->listHead; node;) {
MsfBufferHeader * header = (MsfBufferHeader *) node;
memcpy(writeHead, node + sizeof(MsfBufferHeader), header->size);
writeHead += header->size;
node = header->next;
}
*writeHead++ = 0x3B;
}
//third pass: free buffers
for (uint8_t * node = handle->listHead; node;) {
MsfBufferHeader * header = (MsfBufferHeader *) node;
node = header->next;
MSF_GIF_FREE(handle->customAllocatorContext, header, sizeof(MsfBufferHeader) + header->size);
}
MsfGifResult ret = { buffer, total, total, handle->customAllocatorContext };
return ret;
}
void msf_gif_free(MsfGifResult result) {
if (result.data) { MSF_GIF_FREE(result.contextPointer, result.data, result.allocSize); }
}
#endif //MSF_GIF_ALREADY_IMPLEMENTED_IN_THIS_TRANSLATION_UNIT

930
src/external/rgif.h vendored
View File

@ -1,930 +0,0 @@
/**********************************************************************************************
*
* rgif.h v0.5
*
* Original implementation (gif.h) by Charlie Tangora [ctangora -at- gmail -dot- com]
* adapted to C99, reformatted and renamed by Ramon Santamaria (@raysan5)
*
* 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.)
*
* CONFIGURATION:
*
* #define RGIF_IMPLEMENTATION
* Generates the implementation of the library into the included file.
* If not defined, the library is in header only mode and can be included in other headers
* or source files without problems. But only ONE file should hold the implementation.
*
* USAGE:
* 1) Create a GifWriter struct. Pass it to GifBegin() to initialize and write the header.
* 2) Pass subsequent frames to GifWriteFrame().
* 3) Finally, call GifEnd() to close the file handle and free memory.
*
*
* LICENSE: This software is available under 2 licenses -- choose whichever you prefer
*
* ALTERNATIVE A - MIT License
*
* Copyright (c) 2017-2019 Ramon Santamaria (@raysan5)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* ------------------------------------------------------------------------------
*
* ALTERNATIVE B - public domain (www.unlicense.org)
*
* This is free and unencumbered software released into the public domain.
* Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
* software, either in source code form or as a compiled binary, for any purpose,
* commercial or non-commercial, and by any means.
*
* In jurisdictions that recognize copyright laws, the author or authors of this
* software dedicate any and all copyright interest in the software to the public
* domain. We make this dedication for the benefit of the public at large and to
* the detriment of our heirs and successors. We intend this dedication to be an
* overt act of relinquishment in perpetuity of all present and future rights to
* this software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
**********************************************************************************************/
#ifndef RGIF_H
#define RGIF_H
#include <stdio.h> // Required for: FILE
//#define RGIF_STATIC
#ifdef RGIF_STATIC
#define RGIFDEF static // Functions just visible to module including this file
#else
#ifdef __cplusplus
#define RGIFDEF extern "C" // Functions visible from other files (no name mangling of functions in C++)
#else
#define RGIFDEF extern // Functions visible from other files
#endif
#endif
//----------------------------------------------------------------------------------
// Module Functions Declaration
//----------------------------------------------------------------------------------
// NOTE: By default use bitDepth = 8, dither = false
RGIFDEF bool GifBegin(const char *filename, unsigned int width, unsigned int height, unsigned int delay, unsigned int bitDepth, bool dither);
RGIFDEF bool GifWriteFrame(const unsigned char *image, unsigned int width, unsigned int height, unsigned int delay, int bitDepth, bool dither);
RGIFDEF bool GifEnd();
#endif // RGIF_H
/***********************************************************************************
*
* GIF IMPLEMENTATION
*
************************************************************************************/
#if defined(RGIF_IMPLEMENTATION)
#include <stdio.h> // Required for: FILE, fopen(), fclose()
#include <string.h> // Required for: memcpy()
// Check if custom malloc/free functions defined, if not, using standard ones
// RGIF_MALLOC and RGIF_FREE are used only by GifBegin and GifEnd respectively,
// to allocate a buffer the size of the image, which is used to find changed pixels for delta-encoding.
#if !defined(RGIF_MALLOC)
#include <stdlib.h> // Required for: malloc(), free()
#define RGIF_MALLOC(size) malloc(size)
#define RGIF_FREE(ptr) free(ptr)
#endif
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
#define GIFMIN(a, b) (((a)<(b))?(a):(b))
#define GIFMAX(a, b) (((a)>(b))?(a):(b))
#define GIFABS(x) ((x)<0?-(x):(x))
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Gif palette structure
typedef struct GifPalette {
int bitDepth;
unsigned char r[256];
unsigned char g[256];
unsigned char 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
unsigned char treeSplitElt[255];
unsigned char treeSplit[255];
} GifPalette;
// Simple structure to write out the LZW-compressed
// portion of the imageone bit at a time
typedef struct GifBitStatus {
unsigned char bitIndex; // how many bits in the partial byte written so far
unsigned char byte; // current partial byte
unsigned int chunkIndex;
unsigned char chunk[256]; // bytes are written in here until we have 256 of them, then written to the file
} GifBitStatus;
// The LZW dictionary is a 256-ary tree constructed
// as the file is encoded, this is one node
typedef struct GifLzwNode {
unsigned short m_next[256];
} GifLzwNode;
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
const int gifTransparentIndex = 0; // Transparent color index
static FILE *gifFile = NULL;
unsigned char *gifFrame;
//----------------------------------------------------------------------------------
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
static void GifGetClosestPaletteColor(GifPalette *pPal, int r, int g, int b, int *bestInd, int *bestDiff, int treeRoot);
static void GifSwapPixels(unsigned char *image, int pixA, int pixB);
static int GifPartition(unsigned char *image, const int left, const int right, const int elt, int pivotIndex);
static void GifPartitionByMedian(unsigned char *image, int left, int right, int com, int neededCenter);
static void GifSplitPalette(unsigned char *image, int numPixels, int firstElt, int lastElt, int splitElt, int splitDist, int treeNode, bool buildForDither, GifPalette *pal);
static int GifPickChangedPixels(const unsigned char *lastFrame, unsigned char *frame, int numPixels);
static void GifMakePalette(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned int width, unsigned int height, int bitDepth, bool buildForDither, GifPalette *pPal);
static void GifDitherImage(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned char *outFrame, unsigned int width, unsigned int height, GifPalette *pPal);
static void GifThresholdImage(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned char *outFrame, unsigned int width, unsigned int height, GifPalette *pPal);
static void GifWriteBit(GifBitStatus *stat, unsigned int bit);
static void GifWriteChunk(FILE *f, GifBitStatus *stat);
static void GifWritePalette(FILE *f, const GifPalette *pPal);
static void GifWriteCode(FILE *f, GifBitStatus *stat, unsigned int code, unsigned int length);
static void GifWriteLzwImage(FILE *f, unsigned char *image, unsigned int left, unsigned int top, unsigned int width, unsigned int height, unsigned int delay, GifPalette *pPal);
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
// Creates a gif file
// NOTE: Initializes internal file pointer (only one gif recording at a time)
// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value.
RGIFDEF bool GifBegin(const char *filename, unsigned int width, unsigned int height, unsigned int delay, unsigned int bitDepth, bool dither)
{
#if _MSC_VER >= 1400
gifFile = 0;
fopen_s(&gifFile, filename, "wb");
#else
gifFile = fopen(filename, "wb");
#endif
if (!gifFile) return false;
// Allocate space for one gif frame
gifFrame = (unsigned char *)RGIF_MALLOC(width*height*4);
// GIF Header
fputs("GIF89a",gifFile);
// Reference: http://www.onicos.com/staff/iz/formats/gif.html
// GIF Screen Descriptor
fputc(width & 0xff, gifFile);
fputc((width >> 8) & 0xff, gifFile); // Screen width (2 byte)
fputc(height & 0xff, gifFile);
fputc((height >> 8) & 0xff, gifFile); // Screen height (2 byte)
fputc(0xf0, gifFile); // Color table flags: unsorted global color table of 2 entries (1 byte, bit-flags)
fputc(0, gifFile); // Background color index
fputc(0, gifFile); // Pixel Aspect Ratio (square, we need to specify this because it's 1989)
// GIF Global Color table (just a dummy palette)
// Color 0: black
fputc(0, gifFile);
fputc(0, gifFile);
fputc(0, gifFile);
// Color 1: also black
fputc(0, gifFile);
fputc(0, gifFile);
fputc(0, gifFile);
if (delay != 0)
{
// Application Extension Block (19 bytes long)
fputc(0x21, gifFile); // GIF Extension code
fputc(0xff, gifFile); // Application Extension Label
fputc(11, gifFile); // Length of Application Block (11 byte)
fputs("NETSCAPE2.0", gifFile); // Application Identifier (Netscape 2.0 block)
fputc(0x03, gifFile); // Length of Data Sub-Block (3 bytes)
fputc(0x01, gifFile); // 0x01
fputc(0x00, gifFile); // This specifies the number of times,
fputc(0x00, gifFile); // the loop should be executed (infinitely)
fputc(0x00, gifFile); // Data Sub-Block Terminator.
}
return true;
}
// Writes out a new frame to a GIF in progress.
// NOTE: gifFile should have been initialized with 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.
RGIFDEF bool GifWriteFrame(const unsigned char *image, unsigned int width, unsigned int height, unsigned int delay, int bitDepth, bool dither)
{
if (!gifFile) return false;
const unsigned char *oldImage = gifFrame;
GifPalette pal;
GifMakePalette((dither ? NULL : oldImage), image, width, height, bitDepth, dither, &pal);
if (dither) GifDitherImage(oldImage, image, gifFrame, width, height, &pal);
else GifThresholdImage(oldImage, image, gifFrame, width, height, &pal);
GifWriteLzwImage(gifFile, gifFrame, 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.
RGIFDEF bool GifEnd()
{
if (!gifFile) return false;
fputc(0x3b, gifFile); // Trailer (end of file)
fclose(gifFile);
RGIF_FREE(gifFrame);
gifFile = NULL;
gifFrame = NULL;
return true;
}
//----------------------------------------------------------------------------------
// Module specific Functions Definition
//----------------------------------------------------------------------------------
// 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.
static void GifGetClosestPaletteColor(GifPalette *pPal, int r, int g, int b, int *bestInd, int *bestDiff, int treeRoot)
{
// base case, reached the bottom of the tree
if (treeRoot > (1<<pPal->bitDepth)-1)
{
int ind = treeRoot-(1<<pPal->bitDepth);
if (ind == gifTransparentIndex) return;
// check whether this color is better than the current winner
int r_err = r - ((int)pPal->r[ind]);
int g_err = g - ((int)pPal->g[ind]);
int b_err = b - ((int)pPal->b[ind]);
int diff = GIFABS(r_err)+GIFABS(g_err)+GIFABS(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);
}
}
}
static void GifSwapPixels(unsigned char *image, int pixA, int pixB)
{
unsigned char rA = image[pixA*4];
unsigned char gA = image[pixA*4 + 1];
unsigned char bA = image[pixA*4+2];
unsigned char aA = image[pixA*4+3];
unsigned char rB = image[pixB*4];
unsigned char gB = image[pixB*4 + 1];
unsigned char bB = image[pixB*4+2];
unsigned char 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
static int GifPartition(unsigned char *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<right-1; ++ii)
{
int arrayVal = image[ii*4+elt];
if (arrayVal < pivotValue)
{
GifSwapPixels(image, ii, storeIndex);
++storeIndex;
}
else if (arrayVal == pivotValue)
{
if (split)
{
GifSwapPixels(image, ii, storeIndex);
++storeIndex;
}
split = !split;
}
}
GifSwapPixels(image, storeIndex, right-1);
return storeIndex;
}
// Perform an incomplete sort, finding all elements above and below the desired median
static void GifPartitionByMedian(unsigned char *image, int left, int right, int com, int neededCenter)
{
if (left < right-1)
{
int pivotIndex = left + (right-left)/2;
pivotIndex = GifPartition(image, left, right, com, pivotIndex);
// Only "sort" the section of the array that contains the median
if (pivotIndex > 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
static void GifSplitPalette(unsigned char *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
unsigned int r=255, g=255, b=255;
for (int ii=0; ii<numPixels; ++ii)
{
r = GIFMIN(r, image[ii*4+0]);
g = GIFMIN(g, image[ii*4 + 1]);
b = GIFMIN(b, image[ii*4+2]);
}
pal->r[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
unsigned int r=0, g=0, b=0;
for (int ii=0; ii<numPixels; ++ii)
{
r = GIFMAX(r, image[ii*4+0]);
g = GIFMAX(g, image[ii*4 + 1]);
b = GIFMAX(b, image[ii*4+2]);
}
pal->r[firstElt] = r;
pal->g[firstElt] = g;
pal->b[firstElt] = b;
return;
}
}
// otherwise, take the average of all colors in this subcube
unsigned long long r=0, g=0, b=0;
for (int ii=0; ii<numPixels; ++ii)
{
r += image[ii*4+0];
g += image[ii*4 + 1];
b += image[ii*4+2];
}
r += numPixels / 2; // round to nearest
g += numPixels / 2;
b += numPixels / 2;
r /= numPixels;
g /= numPixels;
b /= numPixels;
pal->r[firstElt] = (unsigned char)r;
pal->g[firstElt] = (unsigned char)g;
pal->b[firstElt] = (unsigned char)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<numPixels; ++ii)
{
int r = image[ii*4+0];
int g = image[ii*4 + 1];
int b = image[ii*4+2];
if (r > 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.
static int GifPickChangedPixels(const unsigned char *lastFrame, unsigned char *frame, int numPixels)
{
int numChanged = 0;
unsigned char *writeIter = frame;
for (int ii=0; ii<numPixels; ++ii)
{
if (lastFrame[0] != frame[0] ||
lastFrame[1] != frame[1] ||
lastFrame[2] != frame[2])
{
writeIter[0] = frame[0];
writeIter[1] = frame[1];
writeIter[2] = frame[2];
++numChanged;
writeIter += 4;
}
lastFrame += 4;
frame += 4;
}
return numChanged;
}
// 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
static void GifMakePalette(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned int width, unsigned int height, int bitDepth, bool buildForDither, GifPalette *pPal)
{
pPal->bitDepth = 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(unsigned char);
unsigned char *destroyableImage = (unsigned char*)RGIF_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);
RGIF_FREE(destroyableImage);
// add the bottom node for the transparency index
pPal->treeSplit[1 << (bitDepth-1)] = 0;
pPal->treeSplitElt[1 << (bitDepth-1)] = 0;
pPal->r[0] = pPal->g[0] = pPal->b[0] = 0;
}
// Implements Floyd-Steinberg dithering, writes palette value to alpha
static void GifDitherImage(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned char *outFrame, unsigned int width, unsigned int 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
int *quantPixels = (int*)RGIF_MALLOC(sizeof(int)*numPixels*4);
for (int ii=0; ii<numPixels*4; ++ii)
{
unsigned char pix = nextFrame[ii];
int pix16 = (int)pix*256;
quantPixels[ii] = pix16;
}
for (unsigned int yy=0; yy<height; ++yy)
{
for (unsigned int xx=0; xx<width; ++xx)
{
int *nextPix = quantPixels + 4*(yy*width+xx);
const unsigned char *lastPix = lastFrame? lastFrame + 4*(yy*width+xx) : NULL;
// Compute the colors we want (rounding to nearest)
int rr = (nextPix[0] + 127) / 256;
int gg = (nextPix[1] + 127) / 256;
int bb = (nextPix[2] + 127) / 256;
// if it happens that we want the color from last frame, then just write out
// a transparent pixel
if (lastFrame &&
lastPix[0] == rr &&
lastPix[1] == gg &&
lastPix[2] == bb)
{
nextPix[0] = rr;
nextPix[1] = gg;
nextPix[2] = bb;
nextPix[3] = gifTransparentIndex;
continue;
}
int bestDiff = 1000000;
int bestInd = gifTransparentIndex;
// Search the palete
GifGetClosestPaletteColor(pPal, rr, gg, bb, &bestInd, &bestDiff, 1);
// Write the result to the temp buffer
int r_err = nextPix[0] - (int)(pPal->r[bestInd])*256;
int g_err = nextPix[1] - (int)(pPal->g[bestInd])*256;
int b_err = nextPix[2] - (int)(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)
{
int *pix7 = quantPixels+4*quantloc_7;
pix7[0] += GIFMAX(-pix7[0], r_err*7 / 16);
pix7[1] += GIFMAX(-pix7[1], g_err*7 / 16);
pix7[2] += GIFMAX(-pix7[2], b_err*7 / 16);
}
if (quantloc_3 < numPixels)
{
int *pix3 = quantPixels+4*quantloc_3;
pix3[0] += GIFMAX(-pix3[0], r_err*3 / 16);
pix3[1] += GIFMAX(-pix3[1], g_err*3 / 16);
pix3[2] += GIFMAX(-pix3[2], b_err*3 / 16);
}
if (quantloc_5 < numPixels)
{
int *pix5 = quantPixels+4*quantloc_5;
pix5[0] += GIFMAX(-pix5[0], r_err*5 / 16);
pix5[1] += GIFMAX(-pix5[1], g_err*5 / 16);
pix5[2] += GIFMAX(-pix5[2], b_err*5 / 16);
}
if (quantloc_1 < numPixels)
{
int *pix1 = quantPixels+4*quantloc_1;
pix1[0] += GIFMAX(-pix1[0], r_err / 16);
pix1[1] += GIFMAX(-pix1[1], g_err / 16);
pix1[2] += GIFMAX(-pix1[2], b_err / 16);
}
}
}
// Copy the palettized result to the output buffer
for (int ii=0; ii<numPixels*4; ++ii)
{
outFrame[ii] = quantPixels[ii];
}
RGIF_FREE(quantPixels);
}
// Picks palette colors for the image using simple thresholding, no dithering
static void GifThresholdImage(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned char *outFrame, unsigned int width, unsigned int height, GifPalette *pPal)
{
unsigned int numPixels = width*height;
for (unsigned int ii=0; ii<numPixels; ++ii)
{
// if a previous color is available, and it matches the current color,
// set the pixel to transparent
if (lastFrame &&
lastFrame[0] == nextFrame[0] &&
lastFrame[1] == nextFrame[1] &&
lastFrame[2] == nextFrame[2])
{
outFrame[0] = lastFrame[0];
outFrame[1] = lastFrame[1];
outFrame[2] = lastFrame[2];
outFrame[3] = gifTransparentIndex;
}
else
{
// palettize the pixel
int bestDiff = 1000000;
int bestInd = 1;
GifGetClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2], &bestInd, &bestDiff, 1);
// Write the resulting color to the output buffer
outFrame[0] = pPal->r[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
static void GifWriteBit(GifBitStatus *stat, unsigned int 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
static 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;
}
static void GifWriteCode(FILE *f, GifBitStatus *stat, unsigned int code, unsigned int length)
{
for (unsigned int ii=0; ii<length; ++ii)
{
GifWriteBit(stat, code);
code = code >> 1;
if (stat->chunkIndex == 255)
{
GifWriteChunk(f, stat);
}
}
}
// write a 256-color (8-bit) image palette to the file
static void GifWritePalette(FILE *f, const GifPalette *pPal)
{
fputc(0, f); // first color: transparency
fputc(0, f);
fputc(0, f);
for (int ii=1; ii<(1 << pPal->bitDepth); ++ii)
{
unsigned int r = pPal->r[ii];
unsigned int g = pPal->g[ii];
unsigned int b = pPal->b[ii];
fputc(r, f);
fputc(g, f);
fputc(b, f);
}
}
// write the image header, LZW-compress and write out the image
static void GifWriteLzwImage(FILE *f, unsigned char *image, unsigned int left, unsigned int top, unsigned int width, unsigned int height, unsigned int 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(gifTransparentIndex, 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(f, pPal);
const int minCodeSize = pPal->bitDepth;
const unsigned int clearCode = 1 << pPal->bitDepth;
fputc(minCodeSize, f); // min code size 8 bits
GifLzwNode *codetree = (GifLzwNode *)RGIF_MALLOC(sizeof(GifLzwNode)*4096);
memset(codetree, 0, sizeof(GifLzwNode)*4096);
int curCode = -1;
unsigned int codeSize = minCodeSize + 1;
unsigned int 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 (unsigned int yy=0; yy<height; ++yy)
{
for (unsigned int xx=0; xx<width; ++xx)
{
unsigned char nextValue = image[(yy*width+xx)*4+3];
// "loser mode" - no compression, every single code is followed immediately by a clear
//WriteCode(f, stat, nextValue, codeSize);
//WriteCode(f, stat, 256, codeSize);
if (curCode < 0)
{
// first value in a new run
curCode = nextValue;
}
else if (codetree[curCode].m_next[nextValue])
{
// current run already in the dictionary
curCode = codetree[curCode].m_next[nextValue];
}
else
{
// finish the current run, write a code
GifWriteCode(f, &stat, curCode, codeSize);
// insert the new run into the dictionary
codetree[curCode].m_next[nextValue] = ++maxCode;
if (maxCode >= (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);
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
RGIF_FREE(codetree);
}
#endif // RGIF_IMPLEMENTATION