minesweeper

A minewseeper implementation to play around with Hare and Raylib
git clone https://git.tronto.net/minesweeper
Download | Log | Files | Refs | README | LICENSE

msf_gif.h (35114B)


      1 /*
      2 HOW TO USE:
      3 
      4     In exactly one translation unit (.c or .cpp file), #define MSF_GIF_IMPL before including the header, like so:
      5 
      6     #define MSF_GIF_IMPL
      7     #include "msf_gif.h"
      8 
      9     Everywhere else, just include the header like normal.
     10 
     11 
     12 USAGE EXAMPLE:
     13 
     14     int width = 480, height = 320, centisecondsPerFrame = 5, bitDepth = 16;
     15     MsfGifState gifState = {};
     16     // msf_gif_bgra_flag = true; //optionally, set this flag if your pixels are in BGRA format instead of RGBA
     17     // msf_gif_alpha_threshold = 128; //optionally, enable transparency (see function documentation below for details)
     18     msf_gif_begin(&gifState, width, height);
     19     msf_gif_frame(&gifState, ..., centisecondsPerFrame, bitDepth, width * 4); //frame 1
     20     msf_gif_frame(&gifState, ..., centisecondsPerFrame, bitDepth, width * 4); //frame 2
     21     msf_gif_frame(&gifState, ..., centisecondsPerFrame, bitDepth, width * 4); //frame 3, etc...
     22     MsfGifResult result = msf_gif_end(&gifState);
     23     if (result.data) {
     24         FILE * fp = fopen("MyGif.gif", "wb");
     25         fwrite(result.data, result.dataSize, 1, fp);
     26         fclose(fp);
     27     }
     28     msf_gif_free(result);
     29 
     30 Detailed function documentation can be found in the header section below.
     31 
     32 
     33 ERROR HANDLING:
     34 
     35     If memory allocation fails, the functions will signal the error via their return values.
     36     If one function call fails, the library will free all of its allocations,
     37     and all subsequent calls will safely no-op and return 0 until the next call to `msf_gif_begin()`.
     38     Therefore, it's safe to check only the return value of `msf_gif_end()`.
     39 
     40 
     41 REPLACING MALLOC:
     42 
     43     This library uses malloc+realloc+free internally for memory allocation.
     44     To facilitate integration with custom memory allocators, these calls go through macros, which can be redefined.
     45     The expected function signature equivalents of the macros are as follows:
     46 
     47     void * MSF_GIF_MALLOC(void * context, size_t newSize)
     48     void * MSF_GIF_REALLOC(void * context, void * oldMemory, size_t oldSize, size_t newSize)
     49     void MSF_GIF_FREE(void * context, void * oldMemory, size_t oldSize)
     50 
     51     If your allocator needs a context pointer, you can set the `customAllocatorContext` field of the MsfGifState struct
     52     before calling msf_gif_begin(), and it will be passed to all subsequent allocator macro calls.
     53 
     54     The maximum number of bytes the library will allocate to encode a single gif is bounded by the following formula:
     55     `(2 * 1024 * 1024) + (width * height * 8) + ((1024 + width * height * 1.5) * 3 * frameCount)`
     56     The peak heap memory usage in bytes, if using a general-purpose heap allocator, is bounded by the following formula:
     57     `(2 * 1024 * 1024) + (width * height * 9.5) + 1024 + (16 * frameCount) + (2 * sizeOfResultingGif)
     58 
     59 
     60 See end of file for license information.
     61 */
     62 
     63 //version 2.2
     64 
     65 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     66 /// HEADER                                                                                                           ///
     67 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     68 
     69 #ifndef MSF_GIF_H
     70 #define MSF_GIF_H
     71 
     72 #include <stdint.h>
     73 #include <stddef.h>
     74 
     75 typedef struct {
     76     void * data;
     77     size_t dataSize;
     78 
     79     size_t allocSize; //internal use
     80     void * contextPointer; //internal use
     81 } MsfGifResult;
     82 
     83 typedef struct { //internal use
     84     uint32_t * pixels;
     85     int depth, count, rbits, gbits, bbits;
     86 } MsfCookedFrame;
     87 
     88 typedef struct MsfGifBuffer {
     89     struct MsfGifBuffer * next;
     90     size_t size;
     91     uint8_t data[1];
     92 } MsfGifBuffer;
     93 
     94 typedef size_t (* MsfGifFileWriteFunc) (const void * buffer, size_t size, size_t count, void * stream);
     95 typedef struct {
     96     MsfGifFileWriteFunc fileWriteFunc;
     97     void * fileWriteData;
     98     MsfCookedFrame previousFrame;
     99     MsfCookedFrame currentFrame;
    100     int16_t * lzwMem;
    101     MsfGifBuffer * listHead;
    102     MsfGifBuffer * listTail;
    103     int width, height;
    104     void * customAllocatorContext;
    105     int framesSubmitted; //needed for transparency to work correctly (because we reach into the previous frame)
    106 } MsfGifState;
    107 
    108 #ifdef __cplusplus
    109 extern "C" {
    110 #endif //__cplusplus
    111 
    112 /**
    113  * @param width                Image width in pixels.
    114  * @param height               Image height in pixels.
    115  * @return                     Non-zero on success, 0 on error.
    116  */
    117 int msf_gif_begin(MsfGifState * handle, int width, int height);
    118 
    119 /**
    120  * @param pixelData            Pointer to raw framebuffer data. Rows must be contiguous in memory, in RGBA8 format
    121  *                             (or BGRA8 if you have set `msf_gif_bgra_flag = true`).
    122  *                             Note: This function does NOT free `pixelData`. You must free it yourself afterwards.
    123  * @param centiSecondsPerFrame How many hundredths of a second this frame should be displayed for.
    124  *                             Note: This being specified in centiseconds is a limitation of the GIF format.
    125  * @param maxBitDepth          Limits how many bits per pixel can be used when quantizing the gif.
    126  *                             The actual bit depth chosen for a given frame will be less than or equal to
    127  *                             the supplied maximum, depending on the variety of colors used in the frame.
    128  *                             `maxBitDepth` will be clamped between 1 and 16. The recommended default is 16.
    129  *                             Lowering this value can result in faster exports and smaller gifs,
    130  *                             but the quality may suffer.
    131  *                             Please experiment with this value to find what works best for your application.
    132  * @param pitchInBytes         The number of bytes from the beginning of one row of pixels to the beginning of the next.
    133  *                             If you want to flip the image, just pass in a negative pitch.
    134  * @return                     Non-zero on success, 0 on error.
    135  */
    136 int msf_gif_frame(MsfGifState * handle, uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes);
    137 
    138 /**
    139  * @return                     A block of memory containing the gif file data, or NULL on error.
    140  *                             You are responsible for freeing this via `msf_gif_free()`.
    141  */
    142 MsfGifResult msf_gif_end(MsfGifState * handle);
    143 
    144 /**
    145  * @param result                The MsfGifResult struct, verbatim as it was returned from `msf_gif_end()`.
    146  */
    147 void msf_gif_free(MsfGifResult result);
    148 
    149 //The gif format only supports 1-bit transparency, meaning a pixel will either be fully transparent or fully opaque.
    150 //Pixels with an alpha value less than the alpha threshold will be treated as transparent.
    151 //To enable exporting transparent gifs, set it to a value between 1 and 255 (inclusive) before calling msf_gif_frame().
    152 //Setting it to 0 causes the alpha channel to be ignored. Its initial value is 0.
    153 extern int msf_gif_alpha_threshold;
    154 
    155 //Set `msf_gif_bgra_flag = true` before calling `msf_gif_frame()` if your pixels are in BGRA byte order instead of RBGA.
    156 extern int msf_gif_bgra_flag;
    157 
    158 
    159 
    160 //TO-FILE FUNCTIONS
    161 //These functions are equivalent to the ones above, but they write results to a file incrementally,
    162 //instead of building a buffer in memory. This can result in lower memory usage when saving large gifs,
    163 //because memory usage is bounded by only the size of a single frame, and is not dependent on the number of frames.
    164 //There is currently no reason to use these unless you are on a memory-constrained platform.
    165 //If in doubt about which API to use, for now you should use the normal (non-file) functions above.
    166 //The signature of MsfGifFileWriteFunc matches fwrite for convenience, so that you can use the C file API like so:
    167 //  FILE * fp = fopen("MyGif.gif", "wb");
    168 //  msf_gif_begin_to_file(&handle, width, height, (MsfGifFileWriteFunc) fwrite, (void *) fp);
    169 //  msf_gif_frame_to_file(...)
    170 //  msf_gif_end_to_file(&handle);
    171 //  fclose(fp);
    172 //If you use a custom file write function, you must take care to return the same values that fwrite() would return.
    173 //Note that all three functions will potentially write to the file.
    174 int msf_gif_begin_to_file(MsfGifState * handle, int width, int height, MsfGifFileWriteFunc func, void * filePointer);
    175 int msf_gif_frame_to_file(MsfGifState * handle, uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes);
    176 int msf_gif_end_to_file(MsfGifState * handle); //returns 0 on error and non-zero on success
    177 
    178 #ifdef __cplusplus
    179 }
    180 #endif //__cplusplus
    181 
    182 #endif //MSF_GIF_H
    183 
    184 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    185 /// IMPLEMENTATION                                                                                                   ///
    186 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    187 
    188 #ifdef MSF_GIF_IMPL
    189 #ifndef MSF_GIF_ALREADY_IMPLEMENTED_IN_THIS_TRANSLATION_UNIT
    190 #define MSF_GIF_ALREADY_IMPLEMENTED_IN_THIS_TRANSLATION_UNIT
    191 
    192 //ensure the library user has either defined all of malloc/realloc/free, or none
    193 #if defined(MSF_GIF_MALLOC) && defined(MSF_GIF_REALLOC) && defined(MSF_GIF_FREE) //ok
    194 #elif !defined(MSF_GIF_MALLOC) && !defined(MSF_GIF_REALLOC) && !defined(MSF_GIF_FREE) //ok
    195 #else
    196 #error "You must either define all of MSF_GIF_MALLOC, MSF_GIF_REALLOC, and MSF_GIF_FREE, or define none of them"
    197 #endif
    198 
    199 //provide default allocator definitions that redirect to the standard global allocator
    200 #if !defined(MSF_GIF_MALLOC)
    201 #include <stdlib.h> //malloc, etc.
    202 #define MSF_GIF_MALLOC(contextPointer, newSize) malloc(newSize)
    203 #define MSF_GIF_REALLOC(contextPointer, oldMemory, oldSize, newSize) realloc(oldMemory, newSize)
    204 #define MSF_GIF_FREE(contextPointer, oldMemory, oldSize) free(oldMemory)
    205 #endif
    206 
    207 //instrumentation for capturing profiling traces (useless for the library user, but useful for the library author)
    208 #ifdef MSF_GIF_ENABLE_TRACING
    209 #define MsfTimeFunc TimeFunc
    210 #define MsfTimeLoop TimeLoop
    211 #define msf_init_profiling_thread init_profiling_thread
    212 #else
    213 #define MsfTimeFunc
    214 #define MsfTimeLoop(name)
    215 #define msf_init_profiling_thread()
    216 #endif //MSF_GIF_ENABLE_TRACING
    217 
    218 #include <string.h> //memcpy
    219 
    220 //TODO: use compiler-specific notation to force-inline functions currently marked inline
    221 #if defined(__GNUC__) //gcc, clang
    222 static inline int msf_bit_log(int i) { return 32 - __builtin_clz(i); }
    223 #elif defined(_MSC_VER) //msvc
    224 #include <intrin.h>
    225 static inline int msf_bit_log(int i) { unsigned long idx; _BitScanReverse(&idx, i); return idx + 1; }
    226 #else //fallback implementation for other compilers
    227 //from https://stackoverflow.com/a/31718095/3064745 - thanks!
    228 static inline int msf_bit_log(int i) {
    229     static const int MultiplyDeBruijnBitPosition[32] = {
    230         0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
    231         8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31,
    232     };
    233     i |= i >> 1;
    234     i |= i >> 2;
    235     i |= i >> 4;
    236     i |= i >> 8;
    237     i |= i >> 16;
    238     return MultiplyDeBruijnBitPosition[(uint32_t)(i * 0x07C4ACDDU) >> 27] + 1;
    239 }
    240 #endif
    241 static inline int msf_imin(int a, int b) { return a < b? a : b; }
    242 static inline int msf_imax(int a, int b) { return b < a? a : b; }
    243 
    244 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    245 /// Frame Cooking                                                                                                    ///
    246 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    247 
    248 #if (defined (__SSE2__) || defined (_M_X64) || _M_IX86_FP == 2) && !defined(MSF_GIF_NO_SSE2)
    249 #include <emmintrin.h>
    250 #endif
    251 
    252 int msf_gif_alpha_threshold = 0;
    253 int msf_gif_bgra_flag = 0;
    254 
    255 static void msf_cook_frame(MsfCookedFrame * frame, uint8_t * raw, uint8_t * used,
    256                            int width, int height, int pitch, int depth)
    257 { MsfTimeFunc
    258     //bit depth for each channel
    259     static const int rdepthsArray[17] = { 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5 };
    260     static const int gdepthsArray[17] = { 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6 };
    261     static const int bdepthsArray[17] = { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5 };
    262     //this extra level of indirection looks unnecessary but we need to explicitly decay the arrays to pointers
    263     //in order to be able to swap them because of C's annoying not-quite-pointers, not-quite-value-types stack arrays.
    264     const int * rdepths = msf_gif_bgra_flag? bdepthsArray : rdepthsArray;
    265     const int * gdepths =                                   gdepthsArray;
    266     const int * bdepths = msf_gif_bgra_flag? rdepthsArray : bdepthsArray;
    267 
    268     static const int ditherKernel[16] = {
    269          0 << 12,  8 << 12,  2 << 12, 10 << 12,
    270         12 << 12,  4 << 12, 14 << 12,  6 << 12,
    271          3 << 12, 11 << 12,  1 << 12,  9 << 12,
    272         15 << 12,  7 << 12, 13 << 12,  5 << 12,
    273     };
    274 
    275     uint32_t * cooked = frame->pixels;
    276     int count = 0;
    277     MsfTimeLoop("do") do {
    278         int rbits = rdepths[depth], gbits = gdepths[depth], bbits = bdepths[depth];
    279         int paletteSize = (1 << (rbits + gbits + bbits)) + 1;
    280         memset(used, 0, paletteSize * sizeof(uint8_t));
    281 
    282         //TODO: document what this math does and why it's correct
    283         int rdiff = (1 << (8 - rbits)) - 1;
    284         int gdiff = (1 << (8 - gbits)) - 1;
    285         int bdiff = (1 << (8 - bbits)) - 1;
    286         short rmul = (short) ((255.0f - rdiff) / 255.0f * 257);
    287         short gmul = (short) ((255.0f - gdiff) / 255.0f * 257);
    288         short bmul = (short) ((255.0f - bdiff) / 255.0f * 257);
    289 
    290         int gmask = ((1 << gbits) - 1) << rbits;
    291         int bmask = ((1 << bbits) - 1) << rbits << gbits;
    292 
    293         MsfTimeLoop("cook") for (int y = 0; y < height; ++y) {
    294             int x = 0;
    295 
    296             #if (defined (__SSE2__) || defined (_M_X64) || _M_IX86_FP == 2) && !defined(MSF_GIF_NO_SSE2)
    297                 __m128i k = _mm_loadu_si128((__m128i *) &ditherKernel[(y & 3) * 4]);
    298                 __m128i k2 = _mm_or_si128(_mm_srli_epi32(k, rbits), _mm_slli_epi32(_mm_srli_epi32(k, bbits), 16));
    299                 for (; x < width - 3; x += 4) {
    300                     uint8_t * pixels = &raw[y * pitch + x * 4];
    301                     __m128i p = _mm_loadu_si128((__m128i *) pixels);
    302 
    303                     __m128i rb = _mm_and_si128(p, _mm_set1_epi32(0x00FF00FF));
    304                     __m128i rb1 = _mm_mullo_epi16(rb, _mm_set_epi16(bmul, rmul, bmul, rmul, bmul, rmul, bmul, rmul));
    305                     __m128i rb2 = _mm_adds_epu16(rb1, k2);
    306                     __m128i r3 = _mm_srli_epi32(_mm_and_si128(rb2, _mm_set1_epi32(0x0000FFFF)), 16 - rbits);
    307                     __m128i b3 = _mm_and_si128(_mm_srli_epi32(rb2, 32 - rbits - gbits - bbits), _mm_set1_epi32(bmask));
    308 
    309                     __m128i g = _mm_and_si128(_mm_srli_epi32(p, 8), _mm_set1_epi32(0x000000FF));
    310                     __m128i g1 = _mm_mullo_epi16(g, _mm_set1_epi32(gmul));
    311                     __m128i g2 = _mm_adds_epu16(g1, _mm_srli_epi32(k, gbits));
    312                     __m128i g3 = _mm_and_si128(_mm_srli_epi32(g2, 16 - rbits - gbits), _mm_set1_epi32(gmask));
    313 
    314                     __m128i out = _mm_or_si128(_mm_or_si128(r3, g3), b3);
    315 
    316                     //mask in transparency based on threshold
    317                     //NOTE: we can theoretically do a sub instead of srli by doing an unsigned compare via bias
    318                     //      to maybe save a TINY amount of throughput? but lol who cares maybe I'll do it later -m
    319                     __m128i invAlphaMask = _mm_cmplt_epi32(_mm_srli_epi32(p, 24), _mm_set1_epi32(msf_gif_alpha_threshold));
    320                     out = _mm_or_si128(_mm_and_si128(invAlphaMask, _mm_set1_epi32(paletteSize - 1)), _mm_andnot_si128(invAlphaMask, out));
    321 
    322                     //TODO: does storing this as a __m128i then reading it back as a uint32_t violate strict aliasing?
    323                     uint32_t * c = &cooked[y * width + x];
    324                     _mm_storeu_si128((__m128i *) c, out);
    325                 }
    326             #endif
    327 
    328             //scalar cleanup loop
    329             for (; x < width; ++x) {
    330                 uint8_t * p = &raw[y * pitch + x * 4];
    331 
    332                 //transparent pixel if alpha is low
    333                 if (p[3] < msf_gif_alpha_threshold) {
    334                     cooked[y * width + x] = paletteSize - 1;
    335                     continue;
    336                 }
    337 
    338                 int dx = x & 3, dy = y & 3;
    339                 int k = ditherKernel[dy * 4 + dx];
    340                 cooked[y * width + x] =
    341                     (msf_imin(65535, p[2] * bmul + (k >> bbits)) >> (16 - rbits - gbits - bbits) & bmask) |
    342                     (msf_imin(65535, p[1] * gmul + (k >> gbits)) >> (16 - rbits - gbits        ) & gmask) |
    343                      msf_imin(65535, p[0] * rmul + (k >> rbits)) >> (16 - rbits                );
    344             }
    345         }
    346 
    347         count = 0;
    348         MsfTimeLoop("mark") for (int i = 0; i < width * height; ++i) {
    349             used[cooked[i]] = 1;
    350         }
    351 
    352         //count used colors, transparent is ignored
    353         MsfTimeLoop("count") for (int j = 0; j < paletteSize - 1; ++j) {
    354             count += used[j];
    355         }
    356     } while (count >= 256 && --depth);
    357 
    358     MsfCookedFrame ret = { cooked, depth, count, rdepths[depth], gdepths[depth], bdepths[depth] };
    359     *frame = ret;
    360 }
    361 
    362 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    363 /// Frame Compression                                                                                                ///
    364 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    365 
    366 static inline void msf_put_code(uint8_t * * writeHead, uint32_t * blockBits, int len, uint32_t code) {
    367     //insert new code into block buffer
    368     int idx = *blockBits / 8;
    369     int bit = *blockBits % 8;
    370     (*writeHead)[idx + 0] |= code <<       bit ;
    371     (*writeHead)[idx + 1] |= code >> ( 8 - bit);
    372     (*writeHead)[idx + 2] |= code >> (16 - bit);
    373     *blockBits += len;
    374 
    375     //prep the next block buffer if the current one is full
    376     if (*blockBits >= 256 * 8) {
    377         *blockBits -= 255 * 8;
    378         (*writeHead) += 256;
    379         (*writeHead)[2] = (*writeHead)[1];
    380         (*writeHead)[1] = (*writeHead)[0];
    381         (*writeHead)[0] = 255;
    382         memset((*writeHead) + 4, 0, 256);
    383     }
    384 }
    385 
    386 typedef struct {
    387     int16_t * data;
    388     int len;
    389     int stride;
    390 } MsfStridedList;
    391 
    392 static inline void msf_lzw_reset(MsfStridedList * lzw, int tableSize, int stride) { MsfTimeFunc
    393     memset(lzw->data, 0xFF, 4096 * stride * sizeof(int16_t));
    394     lzw->len = tableSize + 2;
    395     lzw->stride = stride;
    396 }
    397 
    398 static MsfGifBuffer * msf_compress_frame(void * allocContext, int width, int height, int centiSeconds,
    399                                          MsfCookedFrame frame, MsfGifState * handle, uint8_t * used, int16_t * lzwMem)
    400 { MsfTimeFunc
    401     //NOTE: we reserve enough memory for theoretical the worst case upfront because it's a reasonable amount,
    402     //      and prevents us from ever having to check size or realloc during compression
    403     int maxBufSize = offsetof(MsfGifBuffer, data) + 32 + 256 * 3 + width * height * 3 / 2; //headers + color table + data
    404     MsfGifBuffer * buffer = (MsfGifBuffer *) MSF_GIF_MALLOC(allocContext, maxBufSize);
    405     if (!buffer) { return NULL; }
    406     uint8_t * writeHead = buffer->data;
    407     MsfStridedList lzw = { lzwMem, 0, 0 };
    408 
    409     //allocate tlb
    410     int totalBits = frame.rbits + frame.gbits + frame.bbits;
    411     int tlbSize = (1 << totalBits) + 1;
    412     uint8_t tlb[(1 << 16) + 1]; //only 64k, so stack allocating is fine
    413 
    414     //generate palette
    415     typedef struct { uint8_t r, g, b; } Color3;
    416     Color3 table[256] = { {0} };
    417     int tableIdx = 1; //we start counting at 1 because 0 is the transparent color
    418     //transparent is always last in the table
    419     tlb[tlbSize-1] = 0;
    420     MsfTimeLoop("table") for (int i = 0; i < tlbSize-1; ++i) {
    421         if (used[i]) {
    422             tlb[i] = tableIdx;
    423             int rmask = (1 << frame.rbits) - 1;
    424             int gmask = (1 << frame.gbits) - 1;
    425             //isolate components
    426             int r = i & rmask;
    427             int g = i >> frame.rbits & gmask;
    428             int b = i >> (frame.rbits + frame.gbits);
    429             //shift into highest bits
    430             r <<= 8 - frame.rbits;
    431             g <<= 8 - frame.gbits;
    432             b <<= 8 - frame.bbits;
    433             table[tableIdx].r = r | r >> frame.rbits | r >> (frame.rbits * 2) | r >> (frame.rbits * 3);
    434             table[tableIdx].g = g | g >> frame.gbits | g >> (frame.gbits * 2) | g >> (frame.gbits * 3);
    435             table[tableIdx].b = b | b >> frame.bbits | b >> (frame.bbits * 2) | b >> (frame.bbits * 3);
    436             if (msf_gif_bgra_flag) {
    437                 uint8_t temp = table[tableIdx].r;
    438                 table[tableIdx].r = table[tableIdx].b;
    439                 table[tableIdx].b = temp;
    440             }
    441             ++tableIdx;
    442         }
    443     }
    444     int hasTransparentPixels = used[tlbSize-1];
    445 
    446     //SPEC: "Because of some algorithmic constraints however, black & white images which have one color bit
    447     //       must be indicated as having a code size of 2."
    448     int tableBits = msf_imax(2, msf_bit_log(tableIdx - 1));
    449     int tableSize = 1 << tableBits;
    450     //NOTE: we don't just compare `depth` field here because it will be wrong for the first frame and we will segfault
    451     MsfCookedFrame previous = handle->previousFrame;
    452     int hasSamePal = frame.rbits == previous.rbits && frame.gbits == previous.gbits && frame.bbits == previous.bbits;
    453     int framesCompatible = hasSamePal && !hasTransparentPixels;
    454 
    455     //NOTE: because __attribute__((__packed__)) is annoyingly compiler-specific, we do this unreadable weirdness
    456     char headerBytes[19] = "\x21\xF9\x04\x05\0\0\0\0" "\x2C\0\0\0\0\0\0\0\0\x80";
    457     //NOTE: we need to check the frame number because if we reach into the buffer prior to the first frame,
    458     //      we'll just clobber the file header instead, which is a bug
    459     if (hasTransparentPixels && handle->framesSubmitted > 0) {
    460         handle->listTail->data[3] = 0x09; //set the previous frame's disposal to background, so transparency is possible
    461     }
    462     memcpy(&headerBytes[4], &centiSeconds, 2);
    463     memcpy(&headerBytes[13], &width, 2);
    464     memcpy(&headerBytes[15], &height, 2);
    465     headerBytes[17] |= tableBits - 1;
    466     memcpy(writeHead, headerBytes, 18);
    467     writeHead += 18;
    468 
    469     //local color table
    470     memcpy(writeHead, table, tableSize * sizeof(Color3));
    471     writeHead += tableSize * sizeof(Color3);
    472     *writeHead++ = tableBits;
    473 
    474     //prep block
    475     memset(writeHead, 0, 260);
    476     writeHead[0] = 255;
    477     uint32_t blockBits = 8; //relative to block.head
    478 
    479     //SPEC: "Encoders should output a Clear code as the first code of each image data stream."
    480     msf_lzw_reset(&lzw, tableSize, tableIdx);
    481     msf_put_code(&writeHead, &blockBits, msf_bit_log(lzw.len - 1), tableSize);
    482 
    483     int lastCode = framesCompatible && frame.pixels[0] == previous.pixels[0]? 0 : tlb[frame.pixels[0]];
    484     MsfTimeLoop("compress") for (int i = 1; i < width * height; ++i) {
    485         //PERF: branching vs. branchless version of this line is observed to have no discernable impact on speed
    486         int color = framesCompatible && frame.pixels[i] == previous.pixels[i]? 0 : tlb[frame.pixels[i]];
    487         int code = (&lzw.data[lastCode * lzw.stride])[color];
    488         if (code < 0) {
    489             //write to code stream
    490             int codeBits = msf_bit_log(lzw.len - 1);
    491             msf_put_code(&writeHead, &blockBits, codeBits, lastCode);
    492 
    493             if (lzw.len > 4095) {
    494                 //reset buffer code table
    495                 msf_put_code(&writeHead, &blockBits, codeBits, tableSize);
    496                 msf_lzw_reset(&lzw, tableSize, tableIdx);
    497             } else {
    498                 (&lzw.data[lastCode * lzw.stride])[color] = lzw.len;
    499                 ++lzw.len;
    500             }
    501 
    502             lastCode = color;
    503         } else {
    504             lastCode = code;
    505         }
    506     }
    507 
    508     //write code for leftover index buffer contents, then the end code
    509     msf_put_code(&writeHead, &blockBits, msf_imin(12, msf_bit_log(lzw.len - 1)), lastCode);
    510     msf_put_code(&writeHead, &blockBits, msf_imin(12, msf_bit_log(lzw.len)), tableSize + 1);
    511 
    512     //flush remaining data
    513     if (blockBits > 8) {
    514         int bytes = (blockBits + 7) / 8; //round up
    515         writeHead[0] = bytes - 1;
    516         writeHead += bytes;
    517     }
    518     *writeHead++ = 0; //terminating block
    519 
    520     //fill in buffer header and shrink buffer to fit data
    521     buffer->next = NULL;
    522     buffer->size = writeHead - buffer->data;
    523     MsfGifBuffer * moved =
    524         (MsfGifBuffer *) MSF_GIF_REALLOC(allocContext, buffer, maxBufSize, offsetof(MsfGifBuffer, data) + buffer->size);
    525     if (!moved) { MSF_GIF_FREE(allocContext, buffer, maxBufSize); return NULL; }
    526     return moved;
    527 }
    528 
    529 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    530 /// To-memory API                                                                                                    ///
    531 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    532 
    533 static const int lzwAllocSize = 4096 * 256 * sizeof(int16_t);
    534 
    535 //NOTE: by C standard library conventions, freeing NULL should be a no-op,
    536 //      but just in case the user's custom free doesn't follow that rule, we do null checks on our end as well.
    537 static void msf_free_gif_state(MsfGifState * handle) {
    538     if (handle->previousFrame.pixels) MSF_GIF_FREE(handle->customAllocatorContext, handle->previousFrame.pixels,
    539                                                    handle->width * handle->height * sizeof(uint32_t));
    540     if (handle->currentFrame.pixels)  MSF_GIF_FREE(handle->customAllocatorContext, handle->currentFrame.pixels,
    541                                                    handle->width * handle->height * sizeof(uint32_t));
    542     if (handle->lzwMem) MSF_GIF_FREE(handle->customAllocatorContext, handle->lzwMem, lzwAllocSize);
    543     for (MsfGifBuffer * node = handle->listHead; node;) {
    544         MsfGifBuffer * next = node->next; //NOTE: we have to copy the `next` pointer BEFORE freeing the node holding it
    545         MSF_GIF_FREE(handle->customAllocatorContext, node, offsetof(MsfGifBuffer, data) + node->size);
    546         node = next;
    547     }
    548     handle->listHead = NULL; //this implicitly marks the handle as invalid until the next msf_gif_begin() call
    549 }
    550 
    551 int msf_gif_begin(MsfGifState * handle, int width, int height) { MsfTimeFunc
    552     //NOTE: we cannot stomp the entire struct to zero because we must preserve `customAllocatorContext`.
    553     MsfCookedFrame empty = {0}; //god I hate MSVC...
    554     handle->previousFrame = empty;
    555     handle->currentFrame = empty;
    556     handle->width = width;
    557     handle->height = height;
    558     handle->framesSubmitted = 0;
    559 
    560     //allocate memory for LZW buffer
    561     //NOTE: Unfortunately we can't just use stack memory for the LZW table because it's 2MB,
    562     //      which is more stack space than most operating systems give by default,
    563     //      and we can't realistically expect users to be willing to override that just to use our library,
    564     //      so we have to allocate this on the heap.
    565     handle->lzwMem = (int16_t *) MSF_GIF_MALLOC(handle->customAllocatorContext, lzwAllocSize);
    566     handle->previousFrame.pixels =
    567         (uint32_t *) MSF_GIF_MALLOC(handle->customAllocatorContext, handle->width * handle->height * sizeof(uint32_t));
    568     handle->currentFrame.pixels =
    569         (uint32_t *) MSF_GIF_MALLOC(handle->customAllocatorContext, handle->width * handle->height * sizeof(uint32_t));
    570 
    571     //setup header buffer header (lol)
    572     handle->listHead = (MsfGifBuffer *) MSF_GIF_MALLOC(handle->customAllocatorContext, offsetof(MsfGifBuffer, data) + 32);
    573     if (!handle->listHead || !handle->lzwMem || !handle->previousFrame.pixels || !handle->currentFrame.pixels) {
    574         msf_free_gif_state(handle);
    575         return 0;
    576     }
    577     handle->listTail = handle->listHead;
    578     handle->listHead->next = NULL;
    579     handle->listHead->size = 32;
    580 
    581     //NOTE: because __attribute__((__packed__)) is annoyingly compiler-specific, we do this unreadable weirdness
    582     char headerBytes[33] = "GIF89a\0\0\0\0\x70\0\0" "\x21\xFF\x0BNETSCAPE2.0\x03\x01\0\0\0";
    583     memcpy(&headerBytes[6], &width, 2);
    584     memcpy(&headerBytes[8], &height, 2);
    585     memcpy(handle->listHead->data, headerBytes, 32);
    586     return 1;
    587 }
    588 
    589 int msf_gif_frame(MsfGifState * handle, uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes)
    590 { MsfTimeFunc
    591     if (!handle->listHead) { return 0; }
    592 
    593     maxBitDepth = msf_imax(1, msf_imin(16, maxBitDepth));
    594     if (pitchInBytes == 0) pitchInBytes = handle->width * 4;
    595     if (pitchInBytes < 0) pixelData -= pitchInBytes * (handle->height - 1);
    596 
    597     uint8_t used[(1 << 16) + 1]; //only 64k, so stack allocating is fine
    598     msf_cook_frame(&handle->currentFrame, pixelData, used, handle->width, handle->height, pitchInBytes,
    599         msf_imin(maxBitDepth, handle->previousFrame.depth + 160 / msf_imax(1, handle->previousFrame.count)));
    600 
    601     MsfGifBuffer * buffer = msf_compress_frame(handle->customAllocatorContext, handle->width, handle->height,
    602         centiSecondsPerFame, handle->currentFrame, handle, used, handle->lzwMem);
    603     if (!buffer) { msf_free_gif_state(handle); return 0; }
    604     handle->listTail->next = buffer;
    605     handle->listTail = buffer;
    606 
    607     //swap current and previous frames
    608     MsfCookedFrame tmp = handle->previousFrame;
    609     handle->previousFrame = handle->currentFrame;
    610     handle->currentFrame = tmp;
    611 
    612     handle->framesSubmitted += 1;
    613     return 1;
    614 }
    615 
    616 MsfGifResult msf_gif_end(MsfGifState * handle) { MsfTimeFunc
    617     if (!handle->listHead) { MsfGifResult empty = {0}; return empty; }
    618 
    619     //first pass: determine total size
    620     size_t total = 1; //1 byte for trailing marker
    621     for (MsfGifBuffer * node = handle->listHead; node; node = node->next) { total += node->size; }
    622 
    623     //second pass: write data
    624     uint8_t * buffer = (uint8_t *) MSF_GIF_MALLOC(handle->customAllocatorContext, total);
    625     if (buffer) {
    626         uint8_t * writeHead = buffer;
    627         for (MsfGifBuffer * node = handle->listHead; node; node = node->next) {
    628             memcpy(writeHead, node->data, node->size);
    629             writeHead += node->size;
    630         }
    631         *writeHead++ = 0x3B;
    632     }
    633 
    634     //third pass: free buffers
    635     msf_free_gif_state(handle);
    636 
    637     MsfGifResult ret = { buffer, total, total, handle->customAllocatorContext };
    638     return ret;
    639 }
    640 
    641 void msf_gif_free(MsfGifResult result) { MsfTimeFunc
    642     if (result.data) { MSF_GIF_FREE(result.contextPointer, result.data, result.allocSize); }
    643 }
    644 
    645 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    646 /// To-file API                                                                                                      ///
    647 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    648 
    649 int msf_gif_begin_to_file(MsfGifState * handle, int width, int height, MsfGifFileWriteFunc func, void * filePointer) {
    650     handle->fileWriteFunc = func;
    651     handle->fileWriteData = filePointer;
    652     return msf_gif_begin(handle, width, height);
    653 }
    654 
    655 int msf_gif_frame_to_file(MsfGifState * handle, uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes) {
    656     if (!msf_gif_frame(handle, pixelData, centiSecondsPerFame, maxBitDepth, pitchInBytes)) { return 0; }
    657 
    658     //NOTE: this is a somewhat hacky implementation which is not perfectly efficient, but it's good enough for now
    659     MsfGifBuffer * head = handle->listHead;
    660     if (!handle->fileWriteFunc(head->data, head->size, 1, handle->fileWriteData)) { msf_free_gif_state(handle); return 0; }
    661     handle->listHead = head->next;
    662     MSF_GIF_FREE(handle->customAllocatorContext, head, offsetof(MsfGifBuffer, data) + head->size);
    663     return 1;
    664 }
    665 
    666 int msf_gif_end_to_file(MsfGifState * handle) {
    667     //NOTE: this is a somewhat hacky implementation which is not perfectly efficient, but it's good enough for now
    668     MsfGifResult result = msf_gif_end(handle);
    669     int ret = (int) handle->fileWriteFunc(result.data, result.dataSize, 1, handle->fileWriteData);
    670     msf_gif_free(result);
    671     return ret;
    672 }
    673 
    674 #endif //MSF_GIF_ALREADY_IMPLEMENTED_IN_THIS_TRANSLATION_UNIT
    675 #endif //MSF_GIF_IMPL
    676 
    677 /*
    678 ------------------------------------------------------------------------------
    679 This software is available under 2 licenses -- choose whichever you prefer.
    680 ------------------------------------------------------------------------------
    681 ALTERNATIVE A - MIT License
    682 Copyright (c) 2021 Miles Fogle
    683 Permission is hereby granted, free of charge, to any person obtaining a copy of
    684 this software and associated documentation files (the "Software"), to deal in
    685 the Software without restriction, including without limitation the rights to
    686 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
    687 of the Software, and to permit persons to whom the Software is furnished to do
    688 so, subject to the following conditions:
    689 The above copyright notice and this permission notice shall be included in all
    690 copies or substantial portions of the Software.
    691 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    692 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    693 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    694 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    695 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    696 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    697 SOFTWARE.
    698 ------------------------------------------------------------------------------
    699 ALTERNATIVE B - Public Domain (www.unlicense.org)
    700 This is free and unencumbered software released into the public domain.
    701 Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
    702 software, either in source code form or as a compiled binary, for any purpose,
    703 commercial or non-commercial, and by any means.
    704 In jurisdictions that recognize copyright laws, the author or authors of this
    705 software dedicate any and all copyright interest in the software to the public
    706 domain. We make this dedication for the benefit of the public at large and to
    707 the detriment of our heirs and successors. We intend this dedication to be an
    708 overt act of relinquishment in perpetuity of all present and future rights to
    709 this software under copyright law.
    710 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    711 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    712 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    713 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
    714 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    715 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    716 ------------------------------------------------------------------------------
    717 */