From 87568f7d9979769f03eb0b706e119dbdc24c2a27 Mon Sep 17 00:00:00 2001 From: Relintai Date: Sat, 10 Dec 2022 17:06:57 +0100 Subject: [PATCH] Initial commit. --- .gitignore | 8 + LICENSE | 19 ++ README.md | 23 ++ SCsub | 155 ++++++++++++++ config.py | 6 + gif_loader.cpp | 2 + gif_loader.h | 11 + register_types.cpp | 7 + register_types.h | 7 + thirdparty/gif_load/README.md | 378 +++++++++++++++++++++++++++++++++ thirdparty/gif_load/gif_load.h | 299 ++++++++++++++++++++++++++ 11 files changed, 915 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SCsub create mode 100644 config.py create mode 100644 gif_loader.cpp create mode 100644 gif_loader.h create mode 100644 register_types.cpp create mode 100644 register_types.h create mode 100644 thirdparty/gif_load/README.md create mode 100644 thirdparty/gif_load/gif_load.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7001be7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.import +*.d +*.o +*.meta +*.obj +*.pyc +*.bc +*.os \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..542b31b --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Péter Magyar + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6822c8 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# webm + +A c++ Pandemonium engine module that adds support for loading gifs using https://github.com/hidefromkgb/gif_load . + +# Building + +1. Get the source code for the engine. + +``` git clone https://github.com/Relintai/pandemonium_engine.git pandemonium_engine ``` + +2. Go into Pandemonium's modules directory. + +``` +cd ./pandemonium_engine/modules/ +``` + +3. Clone this repository + +``` +git clone https://github.com/Relintai/gif_loader.git gif_loader +``` + +4. Build Pandemonium. You can use the official godot docs. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html) diff --git a/SCsub b/SCsub new file mode 100644 index 0000000..79d7e2e --- /dev/null +++ b/SCsub @@ -0,0 +1,155 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_webp = env_modules.Clone() + +# Thirdparty source files + +thirdparty_obj = [] + +thirdparty_dir = "./thirdparty/libwebp/" +thirdparty_sources = [ + "sharpyuv/sharpyuv.c", + "sharpyuv/sharpyuv_csp.c", + "sharpyuv/sharpyuv_dsp.c", + "sharpyuv/sharpyuv_gamma.c", + "sharpyuv/sharpyuv_neon.c", + "sharpyuv/sharpyuv_sse2.c", + "src/dec/alpha_dec.c", + "src/dec/buffer_dec.c", + "src/dec/frame_dec.c", + "src/dec/idec_dec.c", + "src/dec/io_dec.c", + "src/dec/quant_dec.c", + "src/dec/tree_dec.c", + "src/dec/vp8_dec.c", + "src/dec/vp8l_dec.c", + "src/dec/webp_dec.c", + "src/demux/anim_decode.c", + "src/demux/demux.c", + "src/dsp/alpha_processing.c", + "src/dsp/alpha_processing_mips_dsp_r2.c", + "src/dsp/alpha_processing_neon.c", + "src/dsp/alpha_processing_sse2.c", + "src/dsp/alpha_processing_sse41.c", + "src/dsp/cost.c", + "src/dsp/cost_mips32.c", + "src/dsp/cost_mips_dsp_r2.c", + "src/dsp/cost_neon.c", + "src/dsp/cost_sse2.c", + "src/dsp/cpu.c", + "src/dsp/dec.c", + "src/dsp/dec_clip_tables.c", + "src/dsp/dec_mips32.c", + "src/dsp/dec_mips_dsp_r2.c", + "src/dsp/dec_msa.c", + "src/dsp/dec_neon.c", + "src/dsp/dec_sse2.c", + "src/dsp/dec_sse41.c", + "src/dsp/enc.c", + "src/dsp/enc_mips32.c", + "src/dsp/enc_mips_dsp_r2.c", + "src/dsp/enc_msa.c", + "src/dsp/enc_neon.c", + "src/dsp/enc_sse2.c", + "src/dsp/enc_sse41.c", + "src/dsp/filters.c", + "src/dsp/filters_mips_dsp_r2.c", + "src/dsp/filters_msa.c", + "src/dsp/filters_neon.c", + "src/dsp/filters_sse2.c", + "src/dsp/lossless.c", + "src/dsp/lossless_enc.c", + "src/dsp/lossless_enc_mips32.c", + "src/dsp/lossless_enc_mips_dsp_r2.c", + "src/dsp/lossless_enc_msa.c", + "src/dsp/lossless_enc_neon.c", + "src/dsp/lossless_enc_sse2.c", + "src/dsp/lossless_enc_sse41.c", + "src/dsp/lossless_mips_dsp_r2.c", + "src/dsp/lossless_msa.c", + "src/dsp/lossless_neon.c", + "src/dsp/lossless_sse2.c", + "src/dsp/lossless_sse41.c", + "src/dsp/rescaler.c", + "src/dsp/rescaler_mips32.c", + "src/dsp/rescaler_mips_dsp_r2.c", + "src/dsp/rescaler_msa.c", + "src/dsp/rescaler_neon.c", + "src/dsp/rescaler_sse2.c", + "src/dsp/ssim.c", + "src/dsp/ssim_sse2.c", + "src/dsp/upsampling.c", + "src/dsp/upsampling_mips_dsp_r2.c", + "src/dsp/upsampling_msa.c", + "src/dsp/upsampling_neon.c", + "src/dsp/upsampling_sse2.c", + "src/dsp/upsampling_sse41.c", + "src/dsp/yuv.c", + "src/dsp/yuv_mips32.c", + "src/dsp/yuv_mips_dsp_r2.c", + "src/dsp/yuv_neon.c", + "src/dsp/yuv_sse2.c", + "src/dsp/yuv_sse41.c", + "src/enc/alpha_enc.c", + "src/enc/analysis_enc.c", + "src/enc/backward_references_cost_enc.c", + "src/enc/backward_references_enc.c", + "src/enc/config_enc.c", + "src/enc/cost_enc.c", + "src/enc/filter_enc.c", + "src/enc/frame_enc.c", + "src/enc/histogram_enc.c", + "src/enc/iterator_enc.c", + "src/enc/near_lossless_enc.c", + "src/enc/picture_csp_enc.c", + "src/enc/picture_enc.c", + "src/enc/picture_psnr_enc.c", + "src/enc/picture_rescale_enc.c", + "src/enc/picture_tools_enc.c", + "src/enc/predictor_enc.c", + "src/enc/quant_enc.c", + "src/enc/syntax_enc.c", + "src/enc/token_enc.c", + "src/enc/tree_enc.c", + "src/enc/vp8l_enc.c", + "src/enc/webp_enc.c", + "src/mux/anim_encode.c", + "src/mux/muxedit.c", + "src/mux/muxinternal.c", + "src/mux/muxread.c", + "src/utils/bit_reader_utils.c", + "src/utils/bit_writer_utils.c", + "src/utils/color_cache_utils.c", + "src/utils/filters_utils.c", + "src/utils/huffman_encode_utils.c", + "src/utils/huffman_utils.c", + "src/utils/quant_levels_dec_utils.c", + "src/utils/quant_levels_utils.c", + "src/utils/random_utils.c", + "src/utils/rescaler_utils.c", + "src/utils/thread_utils.c", + "src/utils/utils.c", +] + +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +env_webp.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "src/"]) + +env_thirdparty = env_webp.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + + +# Godot source files + +module_obj = [] + +env_webp.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/config.py b/config.py new file mode 100644 index 0000000..d22f945 --- /dev/null +++ b/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return True + + +def configure(env): + pass diff --git a/gif_loader.cpp b/gif_loader.cpp new file mode 100644 index 0000000..4de56cd --- /dev/null +++ b/gif_loader.cpp @@ -0,0 +1,2 @@ + +#include "gif_loader.h" diff --git a/gif_loader.h b/gif_loader.h new file mode 100644 index 0000000..0addeb7 --- /dev/null +++ b/gif_loader.h @@ -0,0 +1,11 @@ + +#ifndef GIF_LOADER_H +#define GIF_LOADER_H + +#include "core/object/reference.h" + +class GIFLoader : public Reference { +public: +}; + +#endif diff --git a/register_types.cpp b/register_types.cpp new file mode 100644 index 0000000..1913c35 --- /dev/null +++ b/register_types.cpp @@ -0,0 +1,7 @@ +#include "register_types.h" + +void register_gif_loader_types() { +} + +void unregister_gif_loader_types() { +} diff --git a/register_types.h b/register_types.h new file mode 100644 index 0000000..8aef742 --- /dev/null +++ b/register_types.h @@ -0,0 +1,7 @@ +#ifndef GIF_LOADER_REGISTER_TYPES_H +#define GIF_LOADER_REGISTER_TYPES_H + +void register_gif_loader_types(); +void unregister_gif_loader_types(); + +#endif diff --git a/thirdparty/gif_load/README.md b/thirdparty/gif_load/README.md new file mode 100644 index 0000000..0f15eb6 --- /dev/null +++ b/thirdparty/gif_load/README.md @@ -0,0 +1,378 @@ +# gif_load + +74b674de2704bc1b20fc3bf482a5ee3e774b67c5 +https://github.com/hidefromkgb/gif_load + +This is an ANSI C compatible animated GIF loader in a single header file of +less than 300 lines of code (less than 200 without empty lines and comments). +It defines 1 new struct and 1 new enum, and requires 1 function call to load +a GIF. 'ANSI C compatible' means that it builds fine with `-pedantic -ansi` +compiler flags but includes `stdint.h` which is unavailable prior to C99. + +`gif_load` is free and unencumbered software released into the public domain, +blah blah. See the header file for details. + +There are no strict dependencies on the standard C library. The only external +function used by default is `realloc()` (both for freeing and allocation), but +it\`s possible to override it by defining a macro called `GIF_MGET(m,s,a,c)` +prior to including the header; `m` stands for a `uint8_t*`-typed pointer to +the memory block being allocated or freed, `s` is the target block size, typed +`unsigned long`, `a` is the value of the fifth parameter passed to `GIF_Load()` +(mainly used to hold a pointer to some user-defined structure if need be; see +below), typed `void*`, and `c` equals 0 on freeing and 1 on allocation. For +example, `GIF_MGET` might be defined as follows if `malloc()` / `free()` pair +is to be used instead of `realloc()`: + +```c +#include +#define GIF_MGET(m,s,a,c) if (c) m = (uint8_t*)malloc(s); else free(m); +#include "gif_load.h" +``` + +Loading GIFs immediately from disk is not supported: target files must +be read or otherwise mapped into RAM by the caller. + +The main function that does the actual loading is called `GIF_Load()`. +It requires a callback function to create the animation structure of +any user-defined format. This function, further referred to as the +'frame writer callback', will be executed once every frame. + +Aditionally, the main function accepts a second callback used to process GIF +[application-specific extensions](https://stackoverflow.com/a/28486261/7019311), +i.e. metadata; in the vast majority of GIFs no such extensions are present, so +this callback is optional. + +Both frame writer callback and metadata callback need 2 parameters: + +1. callback-specific data +2. pointer to a `struct GIF_WHDR` that encapsulates GIF frame information +(callbacks may alter any fields at will, as the structure passed to them is a +proxy that is discarded after every call): + * `GIF_WHDR::xdim` - global GIF width, always constant across frames + (further referred to as 'ACAF'); [0; 65535] + * `GIF_WHDR::ydim` - global GIF height, ACAF; [0; 65535] + * `GIF_WHDR::clrs` - number of colors in the current palette (local palettes + are not that rare so it may vary across frames, further + referred to as 'MVAF'); {2; 4; 8; 16; 32; 64; 128; 256} + * `GIF_WHDR::bkgd` - 0-based background color index for the current palette, + ACAF (sic ACAF, as this index is set globally) + * `GIF_WHDR::tran` - 0-based transparent color index for the current palette + (or −1 when transparency is disabled), MVAF + * `GIF_WHDR::intr` - boolean flag indicating whether the current frame is + [interlaced](https://en.wikipedia.org/wiki/GIF#Interlacing); + deinterlacing it is up to the caller (see the examples + below), MVAF + * `GIF_WHDR::mode` - next frame (sic next, not current) blending mode, MVAF: + [`GIF_NONE`:] no blending, mainly used in single-frame + GIFs, functionally equivalent to `GIF_CURR`; + [`GIF_CURR`:] leave the current image state as is; + [`GIF_BKGD`:] restore the background color (or + transparency, in case `GIF_WHDR::tran` ≠ −1) in the + boundaries of the current frame; [`GIF_PREV`:] restore + the last image whose mode differed from this one, + functionally equivalent to `GIF_BKGD` when assigned to + the first frame in a GIF; *N.B.:* if right before a + `GIF_PREV` frame came a `GIF_BKGD` one, the state to + be restored is before a certain part of the resulting + image was filled with the background color, not after! + * `GIF_WHDR::frxd` - current frame width, MVAF; [0; 65535] + * `GIF_WHDR::fryd` - current frame height, MVAF; [0; 65535] + * `GIF_WHDR::frxo` - current frame horizontal offset, MVAF; [0; 65535] + * `GIF_WHDR::fryo` - current frame vertical offset, MVAF; [0; 65535] + * `GIF_WHDR::time` - next frame delay in GIF time units (1 unit = 10 msec), + MVAF; negative values are possible here, they mean + that the frame requires user input to advance, and the + actual delay equals −(`time` + 1) GIF time units: zero + delay + user input = wait for input indefinitely, + nonzero delay + user input = wait for either input or + timeout (whichever comes first); *N.B.:* user input + requests can be safely ignored, disregarding the GIF + standard + * `GIF_WHDR::ifrm` - 0-based index of the current frame, always varies + across frames (further referred to as 'AVAF') + * `GIF_WHDR::nfrm` - total frame count, negative if the GIF data supplied + is incomplete, ACAF during a single `GIF_Load()` call + but may vary across `GIF_Load()` calls + * `GIF_WHDR::bptr` - [frame writer:] pixel indices for the current frame, + ACAF (it is only the pointer address that is constant; + the pixel indices stored inside = MVAF); [metadata + callback:] app metadata header (8+3 bytes) followed by + a GIF chunk (1 byte designating length L, then L bytes + of metadata, and so forth; L = 0 means end of chunk), + AVAF + * `GIF_WHDR::cpal` - the current palette containing 3 `uint8_t` values for + each of the colors: `R` for the red channel, `G` for + green and `B` for blue; this pointer is guaranteed + to be the same across frames if and only if the global + palette is used for those frames (local palettes are + strictly frame-specific, even when they contain the + same number of identical colors in identical order), + MVAF + +Neither of the two callbacks needs to return a value, thus having `void` for +a return type. + +`GIF_Load()`, in its turn, needs 6 parameters: + +1. a pointer to GIF data in RAM +2. GIF data size; may be larger than the actual data if the GIF has a proper + ending mark +3. a pointer to the frame writer callback +4. a pointer to the metadata callback; may be left empty +5. callback-specific data +6. number of frames to skip before executing the callback; useful to resume + loading the partial file + +Partial GIFs are also supported, but only at a granularity of one frame. For +example, if the file ends in the middle of the fifth frame, no attempt would +be made to recover the upper half, and the resulting animation will only +contain 4 frames. When more data is available the loader might be called +again, this time with the `skip` parameter equalling 4 to skip those 4 frames. +Note that the metadata callback is not affected by `skip` and gets called +again every time the frames between which the metadata was written are +skipped. + +The return value of the function above, if positive, equals the total number +of frames in the animation and indicates that the GIF data stream ended with +a proper termination mark. Negative return value is the number of frames +loaded per current call multiplied by −1, suggesting that the GIF data stream +being decoded is still incomplete. Zero, in its turn, means that the call +could not decode any more frames. + +`gif_load` is endian-aware. If the target machine can be big-endian the user +has to determine that manually and add `#define GIF_BIGE 1` to the source +prior to the header being included if that\`s the case, or otherwise define +the endianness to be used (0 = little, 1 = big), e.g. by declaring a helper +function and setting `GIF_BIGE` to expand into its call, or by passing it as a +compiler parameter (e.g. `-DGIF_BIGE=1` for GCC / Clang). Although GIF data is +little-endian, all multibyte integers passed to the user through `long`-typed +fields of `GIF_WHDR` have correct byte order regardless of the endianness of +the target machine, provided that `GIF_BIGE` is set correctly. Most other +data, e.g. pixel indices of a frame, consists of single bytes and thus does +not require endianness correction. One notable exception is GIF application +metadata which is passed as the raw chunk of bytes (for details see the +description of `GIF_WHDR::bptr` provided above), and then it\`s the +callback\`s job to parse it and decide whether to decode and how to do that. + +There is a possibility to build `gif_load` as a shared library. `GIF_EXTR` is +a convenience macro to be defined so that the `GIF_Load()` function gets an +entry in the export table of the library. See the Python example for further +information. + + + +# C / C++ usage example +Here is an example of how to use `GIF_Load()` to transform an animated GIF +file into a 32-bit uncompressed TGA. For the sake of simplicity all frames +are concatenated one below the other and no attempt is made to keep all of +them if the resulting height exceeds the TGA height limit which is 0xFFFF. + +```c +#include "gif_load.h" +#include +#ifdef _MSC_VER + /** MSVC is definitely not my favourite compiler... >_< **/ + #pragma warning(disable:4996) + #include +#else + #include +#endif +#ifndef O_BINARY + #define O_BINARY 0 +#endif + +#pragma pack(push, 1) +typedef struct { + void *data, *pict, *prev; + unsigned long size, last; + int uuid; +} STAT; /** #pragma avoids -Wpadded on 64-bit machines **/ +#pragma pack(pop) + +void Frame(void*, struct GIF_WHDR*); /** keeps -Wmissing-prototypes happy **/ +void Frame(void *data, struct GIF_WHDR *whdr) { + uint32_t *pict, *prev, x, y, yoff, iter, ifin, dsrc, ddst; + uint8_t head[18] = {0}; + STAT *stat = (STAT*)data; + + #define BGRA(i) ((whdr->bptr[i] == whdr->tran)? 0 : \ + ((uint32_t)(whdr->cpal[whdr->bptr[i]].R << ((GIF_BIGE)? 8 : 16)) \ + | (uint32_t)(whdr->cpal[whdr->bptr[i]].G << ((GIF_BIGE)? 16 : 8)) \ + | (uint32_t)(whdr->cpal[whdr->bptr[i]].B << ((GIF_BIGE)? 24 : 0)) \ + | ((GIF_BIGE)? 0xFF : 0xFF000000))) + if (!whdr->ifrm) { + /** TGA doesn`t support heights over 0xFFFF, so we have to trim: **/ + whdr->nfrm = ((whdr->nfrm < 0)? -whdr->nfrm : whdr->nfrm) * whdr->ydim; + whdr->nfrm = (whdr->nfrm < 0xFFFF)? whdr->nfrm : 0xFFFF; + /** this is the very first frame, so we must write the header **/ + head[ 2] = 2; + head[12] = (uint8_t)(whdr->xdim ); + head[13] = (uint8_t)(whdr->xdim >> 8); + head[14] = (uint8_t)(whdr->nfrm ); + head[15] = (uint8_t)(whdr->nfrm >> 8); + head[16] = 32; /** 32 bits depth **/ + head[17] = 0x20; /** top-down flag **/ + write(stat->uuid, head, 18UL); + ddst = (uint32_t)(whdr->xdim * whdr->ydim); + stat->pict = calloc(sizeof(uint32_t), ddst); + stat->prev = calloc(sizeof(uint32_t), ddst); + } + /** [TODO:] the frame is assumed to be inside global bounds, + however it might exceed them in some GIFs; fix me. **/ + pict = (uint32_t*)stat->pict; + ddst = (uint32_t)(whdr->xdim * whdr->fryo + whdr->frxo); + ifin = (!(iter = (whdr->intr)? 0 : 4))? 4 : 5; /** interlacing support **/ + for (dsrc = (uint32_t)-1; iter < ifin; iter++) + for (yoff = 16U >> ((iter > 1)? iter : 1), y = (8 >> iter) & 7; + y < (uint32_t)whdr->fryd; y += yoff) + for (x = 0; x < (uint32_t)whdr->frxd; x++) + if (whdr->tran != (long)whdr->bptr[++dsrc]) + pict[(uint32_t)whdr->xdim * y + x + ddst] = BGRA(dsrc); + write(stat->uuid, pict, sizeof(uint32_t) * (uint32_t)whdr->xdim + * (uint32_t)whdr->ydim); + if ((whdr->mode == GIF_PREV) && !stat->last) { + whdr->frxd = whdr->xdim; + whdr->fryd = whdr->ydim; + whdr->mode = GIF_BKGD; + ddst = 0; + } + else { + stat->last = (whdr->mode == GIF_PREV)? + stat->last : (unsigned long)(whdr->ifrm + 1); + pict = (uint32_t*)((whdr->mode == GIF_PREV)? stat->pict : stat->prev); + prev = (uint32_t*)((whdr->mode == GIF_PREV)? stat->prev : stat->pict); + for (x = (uint32_t)(whdr->xdim * whdr->ydim); --x; + pict[x - 1] = prev[x - 1]); + } + if (whdr->mode == GIF_BKGD) /** cutting a hole for the next frame **/ + for (whdr->bptr[0] = (uint8_t)((whdr->tran >= 0)? + whdr->tran : whdr->bkgd), y = 0, + pict = (uint32_t*)stat->pict; y < (uint32_t)whdr->fryd; y++) + for (x = 0; x < (uint32_t)whdr->frxd; x++) + pict[(uint32_t)whdr->xdim * y + x + ddst] = BGRA(0); + #undef BGRA +} + +int main(int argc, char *argv[]) { + STAT stat = {0}; + + if (argc < 3) + write(1, "arguments: .gif .tga (1 or more times)\n", 48UL); + for (stat.uuid = 2, argc -= (~argc & 1); argc >= 3; argc -= 2) { + if ((stat.uuid = open(argv[argc - 2], O_RDONLY | O_BINARY)) <= 0) + return 1; + stat.size = (unsigned long)lseek(stat.uuid, 0UL, 2 /** SEEK_END **/); + lseek(stat.uuid, 0UL, 0 /** SEEK_SET **/); + read(stat.uuid, stat.data = realloc(0, stat.size), stat.size); + close(stat.uuid); + unlink(argv[argc - 1]); + stat.uuid = open(argv[argc - 1], O_CREAT | O_WRONLY | O_BINARY, 0644); + if (stat.uuid > 0) { + GIF_Load(stat.data, (long)stat.size, Frame, 0, (void*)&stat, 0L); + stat.pict = realloc(stat.pict, 0L); + stat.prev = realloc(stat.prev, 0L); + close(stat.uuid); + stat.uuid = 0; + } + stat.data = realloc(stat.data, 0L); + } + return stat.uuid; +} +``` + + + +# Python usage example +Here is an example of how to use `GIF_Load()` from Python 2.x or 3.x. + +*N.B.:* The implementation shown here complies to the GIF standard much +better than the one PIL has (at least as of 2018-02-07): for example +it preserves transparency and supports local frame palettes. + +First of all, `gif_load.h` has to be built as a shared library: + +Linux / macOS: +```bash +# Only works when executed from the directory where gif_load.h resides +rm gif_load.so 2>/dev/null +uname -s | grep -q ^Darwin && CC=clang || CC=gcc +$CC -pedantic -ansi -s -DGIF_EXTR=extern -xc gif_load.h -o gif_load.so \ + -shared -fPIC -Wl,--version-script=<(echo "{global:GIF_Load;local:*;};") +``` + +Windows: +```bash +rem Only works when executed from the directory where gif_load.h resides +del gif_load.exp gif_load.lib gif_load.dll +cl /LD /Zl /DGIF_EXTR=__declspec(dllexport) /Tc gif_load.h msvcrt.lib /Fegif_load.dll +``` + +Then the loading function can be called using CTypes +([Python 2 reference](https://docs.python.org/2/library/ctypes.html), + [Python 3 reference](https://docs.python.org/3/library/ctypes.html)): + +```python +from PIL import Image + +def GIF_Load(file): + from platform import system + from ctypes import string_at, Structure, c_long as cl, c_ubyte, \ + py_object, pointer, POINTER as PT, CFUNCTYPE, CDLL + class GIF_WHDR(Structure): _fields_ = \ + [("xdim", cl), ("ydim", cl), ("clrs", cl), ("bkgd", cl), + ("tran", cl), ("intr", cl), ("mode", cl), ("frxd", cl), ("fryd", cl), + ("frxo", cl), ("fryo", cl), ("time", cl), ("ifrm", cl), ("nfrm", cl), + ("bptr", PT(c_ubyte)), ("cpal", PT(c_ubyte))] + def intr(y, x, w, base, tran): base.paste(tran.crop((0, y, x, y + 1)), w) + def skew(i, r): return r >> ((7 - (i & 2)) >> (1 + (i & 1))) + def fill(w, d, p): + retn = Image.new("L", d, w.bkgd) if (w.tran < 0) else \ + Image.new("RGBA", d) + if (w.tran < 0): + retn.putpalette(p) + return retn + def WriteFunc(d, w): + cpal = string_at(w[0].cpal, w[0].clrs * 3) + list = d.contents.value[0] + if (len(list) == 0): + list.append(Image.new("RGBA", (w[0].xdim, w[0].ydim))) + tail = len(list) - 1 + base = Image.frombytes("L", (w[0].frxd, w[0].fryd), + string_at(w[0].bptr, w[0].frxd * w[0].fryd)) + if (w[0].intr != 0): + tran = base.copy() + [intr(skew(y, y) + (skew(y, w[0].fryd - 1) + 1, 0)[(y & 7) == 0], + w[0].frxd, (0, y), base, tran) for y in range(w[0].fryd)] + tran = Image.eval(base, lambda indx: (255, 0)[indx == w[0].tran]) + base.putpalette(cpal) + list[tail].paste(base, (w[0].frxo, w[0].fryo), tran) + list[tail].info = {"delay" : w[0].time} + if (w[0].ifrm != (w[0].nfrm - 1)): + trgt = (tail, d.contents.value[1])[w[0].mode == 3] + list.append(list[trgt].copy() if (trgt >= 0) else + fill(w[0], (w[0].xdim, w[0].ydim), cpal)) + if (w[0].mode != 3): + d.contents.value[1] = w[0].ifrm + if (w[0].mode == 2): + list[tail + 1].paste(fill(w[0], (w[0].frxd, w[0].fryd), cpal), + (w[0].frxo, w[0].fryo)) + try: file = open(file, "rb") + except IOError: return [] + file.seek(0, 2) + size = file.tell() + file.seek(0, 0) + list = [[], -1] + CDLL(("%s.so", "%s.dll")[system() == "Windows"] % "./gif_load"). \ + GIF_Load(file.read(), size, + CFUNCTYPE(None, PT(py_object), PT(GIF_WHDR))(WriteFunc), + None, pointer(py_object(list)), 0) + file.close() + return list[0] + +def GIF_Save(file, fext): + list = GIF_Load("%s.gif" % file) + [pict.save("%s_f%d.%s" % (file, indx, fext)) + for (indx, pict) in enumerate(list)] + +GIF_Save("insert_gif_name_here_without_extension", "png") +``` diff --git a/thirdparty/gif_load/gif_load.h b/thirdparty/gif_load/gif_load.h new file mode 100644 index 0000000..12cc402 --- /dev/null +++ b/thirdparty/gif_load/gif_load.h @@ -0,0 +1,299 @@ +#ifndef GIF_LOAD_H +#define GIF_LOAD_H + +/** gif_load: A slim, fast and header-only GIF loader written in C. + Original author: hidefromkgb (hidefromkgb@gmail.com) + _________________________________________________________________________ + + 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. + _________________________________________________________________________ +**/ + +#ifdef __cplusplus +extern "C" { +#endif +#include /** imports uint8_t, uint16_t and uint32_t **/ +#ifndef GIF_MGET + #include + #define GIF_MGET(m,s,a,c) m = (uint8_t*)realloc((c)? 0 : m, (c)? s : 0UL); +#endif +#ifndef GIF_BIGE + #define GIF_BIGE 0 +#endif +#ifndef GIF_EXTR + #define GIF_EXTR static +#endif +#define _GIF_SWAP(h) ((GIF_BIGE)? ((uint16_t)(h << 8) | (h >> 8)) : h) + +#pragma pack(push, 1) +struct GIF_WHDR { /** ======== frame writer info: ======== **/ + long xdim, ydim, clrs, /** global dimensions, palette size **/ + bkgd, tran, /** background index, transparent index **/ + intr, mode, /** interlace flag, frame blending mode **/ + frxd, fryd, frxo, fryo, /** current frame dimensions and offset **/ + time, ifrm, nfrm; /** delay, frame number, frame count **/ + uint8_t *bptr; /** frame pixel indices or metadata **/ + struct { /** [==== GIF RGB palette element: ====] **/ + uint8_t R, G, B; /** [color values - red, green, blue ] **/ + } *cpal; /** current palette **/ +}; +#pragma pack(pop) + +enum {GIF_NONE = 0, GIF_CURR = 1, GIF_BKGD = 2, GIF_PREV = 3}; + +/** [ internal function, do not use ] **/ +static long _GIF_SkipChunk(uint8_t **buff, long size) { + long skip; + + for (skip = 2, ++size, ++(*buff); ((size -= skip) > 0) && (skip > 1); + *buff += (skip = 1 + **buff)); + return size; +} + +/** [ internal function, do not use ] **/ +static long _GIF_LoadHeader(unsigned gflg, uint8_t **buff, void **rpal, + unsigned fflg, long *size, long flen) { + if (flen && (!(*buff += flen) || ((*size -= flen) <= 0))) + return -2; /** v--[ 0x80: "palette is present" flag ]--, **/ + if (flen && (fflg & 0x80)) { /** local palette has priority | **/ + *rpal = *buff; /** [ 3L: 3 uint8_t color channels ]--, | **/ + *buff += (flen = 2 << (fflg & 7)) * 3L; /** <--| | **/ + return ((*size -= flen * 3L) > 0)? flen : -1; /** <--' | **/ + } /** no local palette found, checking for the global one | **/ + return (gflg & 0x80)? (2 << (gflg & 7)) : 0; /** <-----' **/ +} + +/** [ internal function, do not use ] **/ +static long _GIF_LoadFrame(uint8_t **buff, long *size, + uint8_t *bptr, uint8_t *blen) { + typedef uint16_t GIF_H; + const long GIF_HLEN = sizeof(GIF_H), /** to rid the scope of sizeof **/ + GIF_CLEN = 1 << 12; /** code table length: 4096 items **/ + GIF_H accu, mask; /** bit accumulator / bit mask **/ + long ctbl, iter, /** last code table index / index string iterator **/ + prev, curr, /** codes from the stream: previous / current **/ + ctsz, ccsz, /** code table bit sizes: min LZW / current **/ + bseq, bszc; /** counters: block sequence / bit size **/ + uint32_t *code = (uint32_t*)bptr - GIF_CLEN; /** code table pointer **/ + + /** preparing initial values **/ + if ((--(*size) <= GIF_HLEN) || !*++(*buff)) + return -4; /** unexpected end of the stream: insufficient size **/ + mask = (GIF_H)((1 << (ccsz = (ctsz = *(*buff - 1)) + 1)) - 1); + if ((ctsz < 2) || (ctsz > 8)) + return -3; /** min LZW size is out of its nominal [2; 8] bounds **/ + if ((ctbl = (1L << ctsz)) != (mask & _GIF_SWAP(*(GIF_H*)(*buff + 1)))) + return -2; /** initial code is not equal to min LZW size **/ + for (curr = ++ctbl; curr; code[--curr] = 0); /** actual color codes **/ + + /** getting codes from stream (--size makes up for end-of-stream mark) **/ + for (--(*size), bszc = -ccsz, prev = curr = 0; + ((*size -= (bseq = *(*buff)++) + 1) >= 0) && bseq; *buff += bseq) + for (; bseq > 0; bseq -= GIF_HLEN, *buff += GIF_HLEN) + for (accu = (GIF_H)(_GIF_SWAP(*(GIF_H*)*buff) + & ((bseq < GIF_HLEN)? ((1U << (8 * bseq)) - 1U) : ~0U)), + curr |= accu << (ccsz + bszc), accu = (GIF_H)(accu >> -bszc), + bszc += 8 * ((bseq < GIF_HLEN)? bseq : GIF_HLEN); + bszc >= 0; bszc -= ccsz, prev = curr, curr = accu, + accu = (GIF_H)(accu >> ccsz)) + if (((curr &= mask) & ~1L) == (1L << ctsz)) { + if (~(ctbl = curr + 1) & 1) /** end-of-data code (ED). **/ + /** -1: no end-of-stream mark after ED; 1: decoded **/ + return (*((*buff += bseq + 1) - 1))? -1 : 1; + mask = (GIF_H)((1 << (ccsz = ctsz + 1)) - 1); + } /** ^- table drop code (TD). TD = 1 << ctsz, ED = TD + 1 **/ + else { /** single-pixel (SP) or multi-pixel (MP) code. **/ + if (ctbl < GIF_CLEN) { /** is the code table full? **/ + if ((ctbl == mask) && (ctbl < GIF_CLEN - 1)) { + mask = (GIF_H)(mask + mask + 1); + ccsz++; /** yes; extending **/ + } /** prev = TD? => curr < ctbl = prev **/ + code[ctbl] = (uint32_t)prev + (code[prev] & 0xFFF000); + } /** appending SP / MP decoded pixels to the frame **/ + prev = (long)code[iter = (ctbl > curr)? curr : prev]; + if ((bptr += (prev = (prev >> 12) & 0xFFF)) > blen) + continue; /** skipping pixels above frame capacity **/ + for (prev++; (iter &= 0xFFF) >> ctsz; + *bptr-- = (uint8_t)((iter = (long)code[iter]) >> 24)); + (bptr += prev)[-prev] = (uint8_t)iter; + if (ctbl < GIF_CLEN) { /** appending the code table **/ + if (ctbl == curr) + *bptr++ = (uint8_t)iter; + else if (ctbl < curr) + return -5; /** wrong code in the stream **/ + code[ctbl++] += ((uint32_t)iter << 24) + 0x1000; + } + } /** 0: no ED before end-of-stream mark; -4: see above **/ + return (++(*size) >= 0)? 0 : -4; /** ^- N.B.: 0 error is recoverable **/ +} + +/** _________________________________________________________________________ + The main loading function. Returns the total number of frames if the data + includes proper GIF ending, and otherwise it returns the number of frames + loaded per current call, multiplied by -1. So, the data may be incomplete + and in this case the function can be called again when more data arrives, + just remember to keep SKIP up to date. + _________________________________________________________________________ + DATA: raw data chunk, may be partial + SIZE: size of the data chunk that`s currently present + GWFR: frame writer function, MANDATORY + EAMF: metadata reader function, set to 0 if not needed + ANIM: implementation-specific data (e.g. a structure or a pointer to it) + SKIP: number of frames to skip before resuming + **/ +GIF_EXTR long GIF_Load(void *data, long size, + void (*gwfr)(void*, struct GIF_WHDR*), + void (*eamf)(void*, struct GIF_WHDR*), + void *anim, long skip) { + const long GIF_BLEN = (1 << 12) * sizeof(uint32_t); + const uint8_t GIF_EHDM = 0x21, /** extension header mark **/ + GIF_FHDM = 0x2C, /** frame header mark **/ + GIF_EOFM = 0x3B, /** end-of-file mark **/ + GIF_EGCM = 0xF9, /** extension: graphics control mark **/ + GIF_EAMM = 0xFF; /** extension: app metadata mark **/ + #pragma pack(push, 1) + struct GIF_GHDR { /** ========== GLOBAL GIF HEADER: ========== **/ + uint8_t head[6]; /** 'GIF87a' / 'GIF89a' header signature **/ + uint16_t xdim, ydim; /** total image width, total image height **/ + uint8_t flgs; /** FLAGS: + GlobalPlt bit 7 1: global palette exists + 0: local in each frame + ClrRes bit 6-4 bits/channel = ClrRes+1 + [reserved] bit 3 0 + PixelBits bit 2-0 |Plt| = 2 * 2^PixelBits + **/ + uint8_t bkgd, aspr; /** background color index, aspect ratio **/ + } *ghdr = (struct GIF_GHDR*)data; + struct GIF_FHDR { /** ======= GIF FRAME MASTER HEADER: ======= **/ + uint16_t frxo, fryo; /** offset of this frame in a "full" image **/ + uint16_t frxd, fryd; /** frame width, frame height **/ + uint8_t flgs; /** FLAGS: + LocalPlt bit 7 1: local palette exists + 0: global is used + Interlaced bit 6 1: interlaced frame + 0: non-interlaced frame + Sorted bit 5 usually 0 + [reserved] bit 4-3 [undefined] + PixelBits bit 2-0 |Plt| = 2 * 2^PixelBits + **/ + } *fhdr; + struct GIF_EGCH { /** ==== [EXT] GRAPHICS CONTROL HEADER: ==== **/ + uint8_t flgs; /** FLAGS: + [reserved] bit 7-5 [undefined] + BlendMode bit 4-2 000: not set; static GIF + 001: leave result as is + 010: restore background + 011: restore previous + 1--: [undefined] + UserInput bit 1 1: show frame till input + 0: default; ~99% of GIFs + TransColor bit 0 1: got transparent color + 0: frame is fully opaque + **/ + uint16_t time; /** delay in GIF time units; 1 unit = 10 ms **/ + uint8_t tran; /** transparent color index **/ + } *egch = 0; + #pragma pack(pop) + struct GIF_WHDR wtmp, whdr = {0}; + long desc, blen; + uint8_t *buff; + + /** checking if the stream is not empty and has a 'GIF8[79]a' signature, + the data has sufficient size and frameskip value is non-negative **/ + if (!ghdr || (size <= (long)sizeof(*ghdr)) || (*(buff = ghdr->head) != 71) + || (buff[1] != 73) || (buff[2] != 70) || (buff[3] != 56) || (skip < 0) + || ((buff[4] != 55) && (buff[4] != 57)) || (buff[5] != 97) || !gwfr) + return 0; + + buff = (uint8_t*)(ghdr + 1) /** skipping the global header and palette **/ + + _GIF_LoadHeader(ghdr->flgs, 0, 0, 0, 0, 0L) * 3L; + if ((size -= buff - (uint8_t*)ghdr) <= 0) + return 0; + + whdr.xdim = _GIF_SWAP(ghdr->xdim); + whdr.ydim = _GIF_SWAP(ghdr->ydim); + for (whdr.bptr = buff, whdr.bkgd = ghdr->bkgd, blen = --size; + (blen >= 0) && ((desc = *whdr.bptr++) != GIF_EOFM); /** sic: '>= 0' **/ + blen = _GIF_SkipChunk(&whdr.bptr, blen) - 1) /** count all frames **/ + if (desc == GIF_FHDM) { + fhdr = (struct GIF_FHDR*)whdr.bptr; + if (_GIF_LoadHeader(ghdr->flgs, &whdr.bptr, (void**)&whdr.cpal, + fhdr->flgs, &blen, sizeof(*fhdr)) <= 0) + break; + whdr.frxd = _GIF_SWAP(fhdr->frxd); + whdr.fryd = _GIF_SWAP(fhdr->fryd); + whdr.frxo = (whdr.frxd > whdr.frxo)? whdr.frxd : whdr.frxo; + whdr.fryo = (whdr.fryd > whdr.fryo)? whdr.fryd : whdr.fryo; + whdr.ifrm++; + } + blen = whdr.frxo * whdr.fryo * (long)sizeof(*whdr.bptr); + GIF_MGET(whdr.bptr, (unsigned long)(blen + GIF_BLEN + 2), anim, 1) + whdr.nfrm = (desc != GIF_EOFM)? -whdr.ifrm : whdr.ifrm; + for (whdr.bptr += GIF_BLEN, whdr.ifrm = -1; blen /** load all frames **/ + && (skip < ((whdr.nfrm < 0)? -whdr.nfrm : whdr.nfrm)) && (size >= 0); + size = (desc != GIF_EOFM)? ((desc != GIF_FHDM) || (skip > whdr.ifrm))? + _GIF_SkipChunk(&buff, size) - 1 : size - 1 : -1) + if ((desc = *buff++) == GIF_FHDM) { /** found a frame **/ + whdr.intr = !!((fhdr = (struct GIF_FHDR*)buff)->flgs & 0x40); + *(void**)&whdr.cpal = (void*)(ghdr + 1); /** interlaced? -^ **/ + whdr.clrs = _GIF_LoadHeader(ghdr->flgs, &buff, (void**)&whdr.cpal, + fhdr->flgs, &size, sizeof(*fhdr)); + if ((skip <= ++whdr.ifrm) && ((whdr.clrs <= 0) + || (_GIF_LoadFrame(&buff, &size, + whdr.bptr, whdr.bptr + blen) < 0))) + size = -(whdr.ifrm--) - 1; /** failed to load the frame **/ + else if (skip <= whdr.ifrm) { + whdr.frxd = _GIF_SWAP(fhdr->frxd); + whdr.fryd = _GIF_SWAP(fhdr->fryd); + whdr.frxo = _GIF_SWAP(fhdr->frxo); + whdr.fryo = _GIF_SWAP(fhdr->fryo); + whdr.time = (egch)? _GIF_SWAP(egch->time) : 0; + whdr.tran = (egch && (egch->flgs & 0x01))? egch->tran : -1; + whdr.time = (egch && (egch->flgs & 0x02))? -whdr.time - 1 + : whdr.time; + whdr.mode = (egch && !(egch->flgs & 0x10))? + (egch->flgs & 0x0C) >> 2 : GIF_NONE; + egch = 0; + wtmp = whdr; + gwfr(anim, &wtmp); /** passing the frame to the caller **/ + } + } + else if (desc == GIF_EHDM) { /** found an extension **/ + if (*buff == GIF_EGCM) /** graphics control ext. **/ + egch = (struct GIF_EGCH*)(buff + 1 + 1); + else if ((*buff == GIF_EAMM) && eamf) { /** app metadata ext. **/ + wtmp = whdr; + wtmp.bptr = buff + 1 + 1; /** just passing the raw chunk **/ + eamf(anim, &wtmp); + } + } + whdr.bptr -= GIF_BLEN; /** for excess pixel codes ----v (here & above) **/ + GIF_MGET(whdr.bptr, (unsigned long)(blen + GIF_BLEN + 2), anim, 0) + return (whdr.nfrm < 0)? (skip - whdr.ifrm - 1) : (whdr.ifrm + 1); +} + +#undef _GIF_SWAP +#ifdef __cplusplus +} +#endif +#endif /** GIF_LOAD_H **/