pax_global_header00006660000000000000000000000064142012435770014517gustar00rootroot0000000000000052 comment=108190311984b1431f47d0b84a8697ddf8213846 cgif-0.2.0/000077500000000000000000000000001420124357700124265ustar00rootroot00000000000000cgif-0.2.0/.github/000077500000000000000000000000001420124357700137665ustar00rootroot00000000000000cgif-0.2.0/.github/FUNDING.yml000066400000000000000000000012431420124357700156030ustar00rootroot00000000000000# These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: dloebl issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] cgif-0.2.0/.github/workflows/000077500000000000000000000000001420124357700160235ustar00rootroot00000000000000cgif-0.2.0/.github/workflows/ci.yml000066400000000000000000000033731420124357700171470ustar00rootroot00000000000000name: CI on: - push - pull_request jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: - ubuntu-20.04 - macos-10.15 - windows-2019 steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - name: Dependencies (ubuntu) if: contains(matrix.os, 'ubuntu') run: sudo apt install meson gifsicle valgrind - name: Dependencies (macos) if: contains(matrix.os, 'macos') run: brew install meson gifsicle - name: Dependencies (windows) if: contains(matrix.os, 'windows') run: | choco install gifsicle python -m pip install pip==21.3.1 - name: Build and test (windows) if: contains(matrix.os, 'windows') uses: BSFishy/meson-build@v1.0.3 with: action: test meson-version: "0.59.0" ninja-version: "1.10.0.post3" - name: Build and test (ubuntu & macos) if: ${{ !contains(matrix.os, 'windows') }} run: meson setup build && meson test -C build - name: Check MD5 hashes if: contains(matrix.os, 'ubuntu') working-directory: build run: md5sum -c ../tests/tests.md5 shell: bash - name: Verify test output run: for f in build/*.gif; do gifsicle --no-ignore-errors --info $f || exit 1; done shell: bash - name: Valgrind if: contains(matrix.os, 'ubuntu') run: basename -s .c tests/*.c | xargs meson test -C build --wrap "valgrind --memcheck:leak-check=full --memcheck:show-leak-kinds=definite --memcheck:error-exitcode=1" shell: bash - name: Valgrind output if: contains(matrix.os, 'ubuntu') run: cat build/meson-logs/testlog-valgrind.txt cgif-0.2.0/AUTHORS000066400000000000000000000003011420124357700134700ustar00rootroot00000000000000cgif has been initially developed and is being maintained by: - Daniel Löbl - Matthias Löbl We thank the following people for valuable contributions: - Lovell Fuller - Kleis Auke Wolthuizen cgif-0.2.0/LICENSE000066400000000000000000000020551420124357700134350ustar00rootroot00000000000000MIT License Copyright (c) 2021 Daniel Löbl 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. cgif-0.2.0/README.md000066400000000000000000000122201420124357700137020ustar00rootroot00000000000000## CGIF, a GIF encoder written in C A fast and lightweight GIF encoder that can create GIF animations and images. Summary of the main features: - user-defined global or local color-palette with up to 256 colors (limit of the GIF format) - size-optimizations for GIF animations: - option to set a pixel to transparent if it has identical color in the previous frame (transparency optimization) - do encoding just for the rectangular area that differs from the previous frame (width/height optimization) - fast: a GIF with 256 colors and 1024x1024 pixels can be created in below 50 ms even on a minimalistic system - MIT license (permissive) - different options for GIF animations: static image, N repetitions, infinite repetitions - additional source-code for verifying the encoder after making changes - user-defined delay time from one frame to the next (can be set independently for each frame) - source-code conforms to the C99 standard ## Examples To get started, we suggest that you have a look at our code examples. ```examples/cgif_example_video.c``` is an example that creates a GIF animation. ```examples/cgif_example.c``` is an example for a static GIF image. ## Overview To get an overview of the API, we recommend having a look at our wiki (https://github.com/dloebl/cgif/wiki/General-API) where types and functions are described. The corresponding implementations can be found in ```src/cgif.c``` and ```src/cgif_raw.c```. Here the most important types and functions: ```C // These are the four struct types that contain all GIF data and parameters: typedef CGIF_Config // global cofinguration parameters of the GIF typedef CGIF_FrameConfig // local configuration parameters for a frame typedef CGIF // struct for the full GIF typedef CGIF_Frame // struct for a single frame // The user needs only these three functions to create a GIF image: CGIF* cgif_newgif (CGIF_Config* pConfig); // creates a new GIF int cgif_addframe (CGIF* pGIF, CGIF_FrameConfig* pConfig); // adds a frame to an existing GIF int cgif_close (CGIF* pGIF); // close the created file and free memory ``` With our encoder you can create animated or static GIFs, you can or cannot use certain optimizations, and so on. You can switch between all these different options easily using the two attributes ```attrFlags``` and ```genFlags``` in the configurations ```CGIF_Config``` and ```CGIF_FrameConfig```. These attributes are of type ```uint32_t``` and bundle yes/no-options with a bit-wise logic. So far only a few of the 32 bits are used leaving space to include further functionalities ensuring backward compatibility. We provide the following flag settings which can be combined by bit-wise or-operations: ```C CGIF_ATTR_IS_ANIMATED // make an animated GIF (default is non-animated GIF) CGIF_ATTR_NO_GLOBAL_TABLE // disable global color table (global color table is default) CGIF_ATTR_HAS_TRANSPARENCY // first entry in color table contains transparency (alpha channel) CGIF_FRAME_ATTR_USE_LOCAL_TABLE // use a local color table for a frame (not used by default) CGIF_FRAME_ATTR_HAS_ALPHA // frame contains alpha channel (index set via transIndex field) CGIF_FRAME_ATTR_HAS_SET_TRANS // transparency setting provided by user (transIndex field) CGIF_FRAME_GEN_USE_TRANSPARENCY // use transparency optimization (size optimization) CGIF_FRAME_GEN_USE_DIFF_WINDOW // do encoding just for the sub-window that changed (size optimization) ``` If you didn't understand the point of ```attrFlags``` and ```genFlags``` and the flags, please don't worry. The example files ```examples/cgif_example.c``` and ```examples/cgif_example_video.c``` are all you need to get started and the used default settings for ```attrFlags``` and ```genFlags``` cover most cases quite well. ## Compiling the example An example can be compiled and tested simply by: ``` $ c99 -o cgif_example -Iinc examples/cgif_example_video.c src/cgif.c src/cgif_raw.c $ ./cgif_example ``` ## Validating the encoder In the folder ```tests```, we provide several testing routines that you can run via the script ```tests/performtests.sh```. To perform the tests you need to install the programs [ImageMagick](https://github.com/ImageMagick/ImageMagick), [gifsicle](https://github.com/kohler/gifsicle) and [tcc (tiny c compiler)](https://bellard.org/tcc/). With the provided tests you can validate that the encoder still generates correct GIF files after making changes on the encoder itself. ## Further explanations The GIF format employs the [Lempel-Ziv-Welch (LZW)](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch) algorithm for image compression. If you are interested in details of the GIF format, please have a look at the official GIF documentation (https://www.w3.org/Graphics/GIF/spec-gif89a.txt). ## Versioning scheme Releases of cgif follow the semantic versioning scheme as described here: [semver.org](https://semver.org/) The following additional guarantees are provided: * Public API of versions 0.x.x are stable. ## License Licensed under the MIT license (permissive). For more details please see ```LICENSE``` cgif-0.2.0/examples/000077500000000000000000000000001420124357700142445ustar00rootroot00000000000000cgif-0.2.0/examples/cgif_example.c000066400000000000000000000047261420124357700170440ustar00rootroot00000000000000#include #include #include #include "cgif.h" #define WIDTH 1024 #define HEIGHT 1024 /* Small helper functions to initialize GIF- and frame-configuration */ static void initGIFConfig(CGIF_Config* pConfig, char* path, uint16_t width, uint16_t height, uint8_t* pPalette, uint16_t numColors) { memset(pConfig, 0, sizeof(CGIF_Config)); pConfig->width = width; pConfig->height = height; pConfig->pGlobalPalette = pPalette; pConfig->numGlobalPaletteEntries = numColors; pConfig->path = path; } static void initFrameConfig(CGIF_FrameConfig* pConfig, uint8_t* pImageData) { memset(pConfig, 0, sizeof(CGIF_FrameConfig)); pConfig->pImageData = pImageData; } /* This is an example code that creates a GIF-image with random pixels. */ int main(void) { CGIF* pGIF; // struct containing the GIF CGIF_Config gConfig; // global configuration parameters for the GIF CGIF_FrameConfig fConfig; // configuration parameters for a frame uint8_t* pImageData; // image data (an array of color-indices) uint8_t aPalette[] = {0xFF, 0x00, 0x00, // red 0x00, 0xFF, 0x00, // green 0x00, 0x00, 0xFF}; // blue uint16_t numColors = 3; // number of colors in aPalette (up to 256 possible) // initialize the GIF-configuration and create a new GIF initGIFConfig(&gConfig, "example_cgif.gif", WIDTH, HEIGHT, aPalette, numColors); pGIF = cgif_newgif(&gConfig); // create image frame with stripe pattern pImageData = malloc(WIDTH * HEIGHT); // allocate memory for image data for (int i = 0; i < (WIDTH * HEIGHT); ++i) { // Generate pattern pImageData[i] = (unsigned char)((i % WIDTH)/4 % numColors); // stripe pattern (4 pixels per stripe) } // add frame to GIF initFrameConfig(&fConfig, pImageData); // initialize the frame-configuration cgif_addframe(pGIF, &fConfig); // add a new frame to the GIF free(pImageData); // free image data when frame is added // close GIF and free allocated space cgif_close(pGIF); return 0; } cgif-0.2.0/examples/cgif_example_video.c000066400000000000000000000054671420124357700202350ustar00rootroot00000000000000#include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 /* Small helper functions to initialize GIF- and frame-configuration */ static void initGIFConfig(CGIF_Config* pConfig, char* path, uint16_t width, uint16_t height, uint8_t* pPalette, uint16_t numColors) { memset(pConfig, 0, sizeof(CGIF_Config)); pConfig->width = width; pConfig->height = height; pConfig->pGlobalPalette = pPalette; pConfig->numGlobalPaletteEntries = numColors; pConfig->path = path; pConfig->attrFlags = CGIF_ATTR_IS_ANIMATED; } static void initFrameConfig(CGIF_FrameConfig* pConfig, uint8_t* pImageData, uint16_t delay) { memset(pConfig, 0, sizeof(CGIF_FrameConfig)); pConfig->delay = delay; pConfig->pImageData = pImageData; } /* This is an example code that creates a GIF-animation with a moving stripe-pattern. */ int main(void) { CGIF* pGIF; // struct containing the GIF CGIF_Config gConfig; // global configuration parameters for the GIF CGIF_FrameConfig fConfig; // configuration parameters for a frame uint8_t* pImageData; // image data (an array of color-indices) uint8_t aPalette[] = {0xFF, 0x00, 0x00, // red 0x00, 0xFF, 0x00, // green 0x00, 0x00, 0xFF}; // blue uint16_t numColors = 3; // number of colors in aPalette (up to 256 possible) int numFrames = 12; // number of frames in the video // initialize the GIF-configuration and create a new GIF initGIFConfig(&gConfig, "example_video_cgif.gif", WIDTH, HEIGHT, aPalette, numColors); pGIF = cgif_newgif(&gConfig); // create image frames and add them to GIF pImageData = malloc(WIDTH * HEIGHT); // allocate memory for image data for (int f = 0; f < numFrames; ++f) { for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = (unsigned char)((f + i % WIDTH ) / 4 % numColors); // moving stripe pattern (4 pixels per stripe) } initFrameConfig(&fConfig, pImageData, 10); // initialize the frame-configuration cgif_addframe(pGIF, &fConfig); // append the new frame } free(pImageData); // free image data when all frames are added // close created GIF-file and free allocated space cgif_close(pGIF); return 0; } cgif-0.2.0/inc/000077500000000000000000000000001420124357700131775ustar00rootroot00000000000000cgif-0.2.0/inc/cgif.h000066400000000000000000000111071420124357700142600ustar00rootroot00000000000000#ifndef CGIF_H #define CGIF_H #include #include #ifdef __cplusplus extern "C" { #endif // flags to set the GIF/frame-attributes #define CGIF_ATTR_IS_ANIMATED (1uL << 1) // make an animated GIF (default is non-animated GIF) #define CGIF_ATTR_NO_GLOBAL_TABLE (1uL << 2) // disable global color table (global color table is default) #define CGIF_ATTR_HAS_TRANSPARENCY (1uL << 3) // first entry in color table contains transparency (alpha channel) #define CGIF_FRAME_ATTR_USE_LOCAL_TABLE (1uL << 0) // use a local color table for a frame (local color table is not used by default) #define CGIF_FRAME_ATTR_HAS_ALPHA (1uL << 1) // alpha channel index provided by user (transIndex field) #define CGIF_FRAME_ATTR_HAS_SET_TRANS (1uL << 2) // transparency setting provided by user (transIndex field) // flags to decrease GIF-size #define CGIF_FRAME_GEN_USE_TRANSPARENCY (1uL << 0) // use transparency optimization (setting pixels identical to previous frame transparent) #define CGIF_FRAME_GEN_USE_DIFF_WINDOW (1uL << 1) // do encoding just for the sub-window that has changed from previous frame #define CGIF_INFINITE_LOOP (0x0000uL) // for animated GIF: 0 specifies infinite loop typedef enum { CGIF_ERROR = -1, // something unspecified failed CGIF_OK = 0, // everything OK CGIF_EWRITE, // writing GIF data failed CGIF_EALLOC, // allocating memory failed CGIF_ECLOSE, // final call to fclose failed CGIF_EOPEN, // failed to open output file CGIF_EINDEX, // invalid index in image data provided by user // internal section (values subject to change) CGIF_PENDING, } cgif_result; typedef struct st_gif CGIF; // struct for the full GIF typedef struct st_gifconfig CGIF_Config; // global cofinguration parameters of the GIF typedef struct st_frameconfig CGIF_FrameConfig; // local configuration parameters for a frame typedef int cgif_write_fn(void* pContext, const uint8_t* pData, const size_t numBytes); // callback function for stream-based output // prototypes CGIF* cgif_newgif (CGIF_Config* pConfig); // creates a new GIF (returns pointer to new GIF or NULL on error) int cgif_addframe (CGIF* pGIF, CGIF_FrameConfig* pConfig); // adds the next frame to an existing GIF (returns 0 on success) int cgif_close (CGIF* pGIF); // close file and free allocated memory (returns 0 on success) // CGIF_Config type (parameters passed by user) // note: must stay AS IS for backward compatibility struct st_gifconfig { uint8_t* pGlobalPalette; // global color table of the GIF const char* path; // path of the GIF to be created, mutually exclusive with pWriteFn uint32_t attrFlags; // fixed attributes of the GIF (e.g. whether it is animated or not) uint32_t genFlags; // flags that determine how the GIF is generated (e.g. optimization) uint16_t width; // width of each frame in the GIF uint16_t height; // height of each frame in the GIF uint16_t numGlobalPaletteEntries; // size of the global color table uint16_t numLoops; // number of repetitons of an animated GIF (set to INFINITE_LOOP for infinite loop) cgif_write_fn *pWriteFn; // callback function for chunks of output data, mutually exclusive with path void* pContext; // opaque pointer passed as the first parameter to pWriteFn }; // CGIF_FrameConfig type (parameters passed by user) // note: must stay AS IS for backward compatibility struct st_frameconfig { uint8_t* pLocalPalette; // local color table of a frame uint8_t* pImageData; // image data to be encoded uint32_t attrFlags; // fixed attributes of the GIF frame uint32_t genFlags; // flags that determine how the GIF frame is created (e.g. optimization) uint16_t delay; // delay before the next frame is shown (units of 0.01 s) uint16_t numLocalPaletteEntries; // size of the local color table uint8_t transIndex; // introduced with V0.2.0 }; #ifdef __cplusplus } #endif #endif // CGIF_H cgif-0.2.0/inc/cgif_raw.h000066400000000000000000000051321420124357700151320ustar00rootroot00000000000000#ifndef CGIF_RAW_H #define CGIF_RAW_H #include #include "cgif.h" #ifdef __cplusplus extern "C" { #endif #define DISPOSAL_METHOD_LEAVE (1uL << 2) #define DISPOSAL_METHOD_BACKGROUND (2uL << 2) #define DISPOSAL_METHOD_PREVIOUS (3uL << 2) // flags to set the GIF attributes #define CGIF_RAW_ATTR_IS_ANIMATED (1uL << 0) // make an animated GIF (default is non-animated GIF) // flags to set the Frame attributes #define CGIF_RAW_FRAME_ATTR_HAS_TRANS (1uL << 0) // provided transIndex should be set // CGIFRaw_Config type // note: internal sections, subject to change. typedef struct { cgif_write_fn *pWriteFn; // callback function for chunks of output data void* pContext; // opaque pointer passed as the first parameter to pWriteFn uint8_t* pGCT; // global color table of the GIF uint32_t attrFlags; // fixed attributes of the GIF (e.g. whether it is animated or not) uint16_t width; // effective width of each frame in the GIF uint16_t height; // effective height of each frame in the GIF uint16_t sizeGCT; // size of the global color table (GCT) uint16_t numLoops; // number of repetitons of an animated GIF (set to INFINITE_LOOP for infinite loop) } CGIFRaw_Config; // CGIFRaw_FrameConfig type // note: internal sections, subject to chage. typedef struct { uint8_t* pLCT; // local color table of the frame (LCT) uint8_t* pImageData; // image data to be encoded (indices to CT) uint32_t attrFlags; // fixed attributes of the GIF frame uint16_t width; // width of frame uint16_t height; // height of frame uint16_t top; // top offset of frame uint16_t left; // left offset of frame uint16_t delay; // delay before the next frame is shown (units of 0.01 s [cs]) uint16_t sizeLCT; // size of the local color table (LCT) uint8_t disposalMethod; // specifies how this frame should be disposed after being displayed. uint8_t transIndex; // transparency index } CGIFRaw_FrameConfig; // CGIFRaw type // note: internal sections, subject to change. typedef struct { CGIFRaw_Config config; // configutation parameters of the GIF (see above) cgif_result curResult; // current result status of GIFRaw stream } CGIFRaw; // prototypes CGIFRaw* cgif_raw_newgif (const CGIFRaw_Config* pConfig); cgif_result cgif_raw_addframe (CGIFRaw* pGIF, const CGIFRaw_FrameConfig* pConfig); cgif_result cgif_raw_close (CGIFRaw* pGIF); #ifdef __cplusplus } #endif #endif // CGIF_RAW_H cgif-0.2.0/meson.build000066400000000000000000000010631420124357700145700ustar00rootroot00000000000000project( 'libcgif', 'c', version : '0.2.0', license : 'MIT', default_options : ['c_std=c99'], ) cgif_sources = ['src/cgif.c', 'src/cgif_raw.c'] lib = library( 'cgif', cgif_sources, include_directories : ['inc/'], soversion : '0', version : '0.2.0', install : true, ) install_headers('inc/cgif.h') import('pkgconfig').generate( lib, name : 'cgif', description : 'A fast and lightweight GIF encoder', ) if get_option('tests') and not meson.is_cross_build() libcgif_dep = declare_dependency(link_with : lib) subdir('tests') endif cgif-0.2.0/meson_options.txt000066400000000000000000000001301420124357700160550ustar00rootroot00000000000000option( 'tests', type : 'boolean', value : true, description : 'build tests', ) cgif-0.2.0/src/000077500000000000000000000000001420124357700132155ustar00rootroot00000000000000cgif-0.2.0/src/cgif.c000066400000000000000000000425121420124357700142750ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #include "cgif_raw.h" #define MULU16(a, b) (((uint32_t)a) * ((uint32_t)b)) // helper macro to correctly multiply two U16's without default signed int promotion #define SIZE_FRAME_QUEUE (3) // CGIF_Frame type // note: internal sections, subject to change in future versions typedef struct { CGIF_FrameConfig config; uint8_t disposalMethod; uint8_t transIndex; } CGIF_Frame; // CGIF type // note: internal sections, subject to change in future versions struct st_gif { CGIF_Frame* aFrames[SIZE_FRAME_QUEUE]; // (internal) we need to keep the last three frames in memory. CGIF_Config config; // (internal) configuration parameters of the GIF CGIFRaw* pGIFRaw; // (internal) raw GIF stream FILE* pFile; cgif_result curResult; }; // dimension result type typedef struct { uint16_t width; uint16_t height; uint16_t top; uint16_t left; } DimResult; /* calculate next power of two exponent of given number (n MUST be <= 256) */ static uint8_t calcNextPower2Ex(uint16_t n) { uint8_t nextPow2; for (nextPow2 = 0; n > (1uL << nextPow2); ++nextPow2); return nextPow2; } /* write callback. returns 0 on success or -1 on error. */ static int writecb(void* pContext, const uint8_t* pData, const size_t numBytes) { CGIF* pGIF; size_t r; pGIF = (CGIF*)pContext; if(pGIF->pFile) { r = fwrite(pData, 1, numBytes, pGIF->pFile); if(r == numBytes) return 0; else return -1; } else if(pGIF->config.pWriteFn) { return pGIF->config.pWriteFn(pGIF->config.pContext, pData, numBytes); } return 0; } /* free space allocated for CGIF struct */ static void freeCGIF(CGIF* pGIF) { if((pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) { free(pGIF->config.pGlobalPalette); } free(pGIF); } /* create a new GIF */ CGIF* cgif_newgif(CGIF_Config* pConfig) { FILE* pFile; CGIF* pGIF; CGIFRaw* pGIFRaw; // raw GIF stream CGIFRaw_Config rawConfig = {0}; pFile = NULL; // open output file (if necessary) if(pConfig->path) { pFile = fopen(pConfig->path, "wb"); if(pFile == NULL) { return NULL; // error: fopen failed } } // allocate space for CGIF context pGIF = malloc(sizeof(CGIF)); if(pGIF == NULL) { if(pFile) { fclose(pFile); } return NULL; // error -> malloc failed } memset(pGIF, 0, sizeof(CGIF)); pGIF->pFile = pFile; memcpy(&(pGIF->config), pConfig, sizeof(CGIF_Config)); // make a deep copy of GCT, if required. if((pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) { pGIF->config.pGlobalPalette = malloc(pConfig->numGlobalPaletteEntries * 3); memcpy(pGIF->config.pGlobalPalette, pConfig->pGlobalPalette, pConfig->numGlobalPaletteEntries * 3); } rawConfig.pGCT = pConfig->pGlobalPalette; rawConfig.sizeGCT = (pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) ? 0 : pConfig->numGlobalPaletteEntries; // translate CGIF_ATTR_* to CGIF_RAW_ATTR_* flags rawConfig.attrFlags = (pConfig->attrFlags & CGIF_ATTR_IS_ANIMATED) ? CGIF_RAW_ATTR_IS_ANIMATED : 0; rawConfig.width = pConfig->width; rawConfig.height = pConfig->height; rawConfig.numLoops = pConfig->numLoops; rawConfig.pWriteFn = writecb; rawConfig.pContext = (void*)pGIF; // pass config down and create a new raw GIF stream. pGIFRaw = cgif_raw_newgif(&rawConfig); // check for errors if(pGIFRaw == NULL) { if(pFile) { fclose(pFile); } freeCGIF(pGIF); return NULL; } pGIF->pGIFRaw = pGIFRaw; // assume error per default. // set to CGIF_OK by the first successful cgif_addframe() call, as a GIF without frames is invalid. pGIF->curResult = CGIF_PENDING; return pGIF; } /* compare given pixel indices using the correct local or global color table; returns 0 if the two pixels are RGB equal */ static int cmpPixel(const CGIF* pGIF, const CGIF_FrameConfig* pCur, const CGIF_FrameConfig* pBef, const uint8_t iCur, const uint8_t iBef) { uint8_t* pBefCT; // color table to use for pBef uint8_t* pCurCT; // color table to use for pCur if((pCur->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iCur == pCur->transIndex) { return 0; // identical } if((pBef->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iBef == pBef->transIndex) { return 1; // done: cannot compare } // TBD add safety checks pBefCT = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used? pCurCT = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used? return memcmp(pBefCT + iBef * 3, pCurCT + iCur * 3, 3); } /* optimize GIF file size by only redrawing the rectangular area that differs from previous frame */ static uint8_t* doWidthHeightOptim(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult* pResult) { uint8_t* pNewImageData; const uint8_t* pCurImageData; const uint8_t* pBefImageData; uint16_t i, x; uint16_t newHeight, newWidth, newLeft, newTop; const uint16_t width = pGIF->config.width; const uint16_t height = pGIF->config.height; uint8_t iCur, iBef; pCurImageData = pCur->pImageData; pBefImageData = pBef->pImageData; // find top i = 0; while(i < height) { for(int c = 0; c < width; ++c) { iCur = *(pCurImageData + MULU16(i, width) + c); iBef = *(pBefImageData + MULU16(i, width) + c); if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) { goto FoundTop; } } ++i; } FoundTop: if(i == height) { // need dummy pixel (frame is identical with one before) // TBD we might make it possible to merge identical frames in the future newWidth = 1; newHeight = 1; newLeft = 0; newTop = 0; goto Done; } newTop = i; // find actual height i = height - 1; while(i > newTop) { for(int c = 0; c < width; ++c) { iCur = *(pCurImageData + MULU16(i, width) + c); iBef = *(pBefImageData + MULU16(i, width) + c); if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) { goto FoundHeight; } } --i; } FoundHeight: newHeight = (i + 1) - newTop; // find left i = newTop; x = 0; while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) { ++i; if(i > (newTop + newHeight - 1)) { ++x; //(x==width cannot happen as goto Done is triggered in the only possible case before) i = newTop; } } newLeft = x; // find actual width i = newTop; x = width - 1; while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) { ++i; if(i > (newTop + newHeight - 1)) { --x; //(xwidth = newWidth; pResult->height = newHeight; pResult->top = newTop; pResult->left = newLeft; return pNewImageData; } /* move frame down to the raw GIF API */ static cgif_result flushFrame(CGIF* pGIF, CGIF_Frame* pCur, CGIF_Frame* pBef) { CGIFRaw_FrameConfig rawConfig; DimResult dimResult; uint8_t* pTmpImageData; uint8_t* pBefImageData; int isFirstFrame, useLCT, hasAlpha, hasSetTransp; uint16_t numPaletteEntries; uint16_t imageWidth, imageHeight, width, height, top, left; uint8_t transIndex, disposalMethod; cgif_result r; imageWidth = pGIF->config.width; imageHeight = pGIF->config.height; isFirstFrame = (pBef == NULL) ? 1 : 0; useLCT = (pCur->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? 1 : 0; hasAlpha = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0; hasSetTransp = (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) ? 1 : 0; disposalMethod = pCur->disposalMethod; transIndex = pCur->transIndex; // deactivate impossible size optimizations // => in case alpha channel is used // CGIF_FRAME_GEN_USE_TRANSPARENCY and CGIF_FRAME_GEN_USE_DIFF_WINDOW are not possible if(isFirstFrame || hasAlpha) { pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW); } // transparency setting (which areas are identical to the frame before) provided by user: // CGIF_FRAME_GEN_USE_TRANSPARENCY not possible if(hasSetTransp) { pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY); } numPaletteEntries = (useLCT) ? pCur->config.numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries; // switch off transparency optimization if color table is full (no free spot for the transparent index), TBD: count used colors, adapt table if(numPaletteEntries == 256) { pCur->config.genFlags &= ~CGIF_FRAME_GEN_USE_TRANSPARENCY; } // purge overlap of current frame and frame before (width - height optim), if required (CGIF_FRAME_GEN_USE_DIFF_WINDOW set) if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_DIFF_WINDOW) { pTmpImageData = doWidthHeightOptim(pGIF, &pCur->config, &pBef->config, &dimResult); width = dimResult.width; height = dimResult.height; top = dimResult.top; left = dimResult.left; } else { pTmpImageData = NULL; width = imageWidth; height = imageHeight; top = 0; left = 0; } // mark matching areas of the previous frame as transparent, if required (CGIF_FRAME_GEN_USE_TRANSPARENCY set) if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) { // set transIndex to next free index int pow2 = calcNextPower2Ex(numPaletteEntries); pow2 = (pow2 < 2) ? 2 : pow2; // TBD keep transparency index behavior as in V0.1.0 (for now) transIndex = (1 << pow2) - 1; if(transIndex < numPaletteEntries) { transIndex = (1 << (pow2 + 1)) - 1; } if(pTmpImageData == NULL) { pTmpImageData = malloc(MULU16(imageWidth, imageHeight)); // TBD check return value of malloc memcpy(pTmpImageData, pCur->config.pImageData, MULU16(imageWidth, imageHeight)); } pBefImageData = pBef->config.pImageData; for(int i = 0; i < height; ++i) { for(int x = 0; x < width; ++x) { if(cmpPixel(pGIF, &pCur->config, &pBef->config, pTmpImageData[MULU16(i, width) + x], pBefImageData[MULU16(top + i, imageWidth) + (left + x)]) == 0) { pTmpImageData[MULU16(i, width) + x] = transIndex; } } } } // move frame down to GIF raw API rawConfig.pLCT = pCur->config.pLocalPalette; rawConfig.pImageData = (pTmpImageData) ? pTmpImageData : pCur->config.pImageData; rawConfig.attrFlags = 0; if(hasAlpha || (pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) || hasSetTransp) { rawConfig.attrFlags |= CGIF_RAW_FRAME_ATTR_HAS_TRANS; } rawConfig.width = width; rawConfig.height = height; rawConfig.top = top; rawConfig.left = left; rawConfig.delay = pCur->config.delay; rawConfig.sizeLCT = (useLCT) ? pCur->config.numLocalPaletteEntries : 0; rawConfig.disposalMethod = disposalMethod; rawConfig.transIndex = transIndex; r = cgif_raw_addframe(pGIF->pGIFRaw, &rawConfig); free(pTmpImageData); return r; } static void freeFrame(CGIF_Frame* pFrame) { if(pFrame) { free(pFrame->config.pImageData); if(pFrame->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) { free(pFrame->config.pLocalPalette); } free(pFrame); } } static void copyFrameConfig(CGIF_FrameConfig* pDest, CGIF_FrameConfig* pSrc) { pDest->pLocalPalette = pSrc->pLocalPalette; // might need a deep copy pDest->pImageData = pSrc->pImageData; // might need a deep copy pDest->attrFlags = pSrc->attrFlags; pDest->genFlags = pSrc->genFlags; pDest->delay = pSrc->delay; pDest->numLocalPaletteEntries = pSrc->numLocalPaletteEntries; // copy transIndex if necessary (field added with V0.2.0; avoid binary incompatibility) if(pSrc->attrFlags & (CGIF_FRAME_ATTR_HAS_ALPHA | CGIF_FRAME_ATTR_HAS_SET_TRANS)) { pDest->transIndex = pSrc->transIndex; } } /* queue a new GIF frame */ int cgif_addframe(CGIF* pGIF, CGIF_FrameConfig* pConfig) { CGIF_Frame* pNewFrame; int hasAlpha, hasSetTransp; int i; cgif_result r; // check for previous errors if(pGIF->curResult != CGIF_OK && pGIF->curResult != CGIF_PENDING) { return pGIF->curResult; } hasAlpha = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0; // alpha channel is present hasSetTransp = (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) ? 1 : 0; // user provided transparency setting (identical areas marked by user) // check for invalid configs: // cannot set alpha channel and user-provided transparency at the same time. if(hasAlpha && hasSetTransp) { pGIF->curResult = CGIF_ERROR; return pGIF->curResult; } // cannot set global and local alpha channel at the same time if((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) { pGIF->curResult = CGIF_ERROR; return pGIF->curResult; } // search for free slot in frame queue for(i = 1; i < SIZE_FRAME_QUEUE && pGIF->aFrames[i] != NULL; ++i); // check whether the queue is full // when queue is full: we need to flush one frame. if(i == SIZE_FRAME_QUEUE) { r = flushFrame(pGIF, pGIF->aFrames[1], pGIF->aFrames[0]); freeFrame(pGIF->aFrames[0]); // check for errors if(r != CGIF_OK) { pGIF->curResult = r; return pGIF->curResult; } i = SIZE_FRAME_QUEUE - 1; // keep the flushed frame in memory, as we might need it to write the next one. pGIF->aFrames[0] = pGIF->aFrames[1]; pGIF->aFrames[1] = pGIF->aFrames[2]; } // create new Frame struct + make a deep copy of pConfig. pNewFrame = malloc(sizeof(CGIF_Frame)); copyFrameConfig(&(pNewFrame->config), pConfig); pNewFrame->config.pImageData = malloc(MULU16(pGIF->config.width, pGIF->config.height)); memcpy(pNewFrame->config.pImageData, pConfig->pImageData, MULU16(pGIF->config.width, pGIF->config.height)); // make a deep copy of the local color table, if required. if(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) { pNewFrame->config.pLocalPalette = malloc(pConfig->numLocalPaletteEntries * 3); memcpy(pNewFrame->config.pLocalPalette, pConfig->pLocalPalette, pConfig->numLocalPaletteEntries * 3); } pNewFrame->disposalMethod = DISPOSAL_METHOD_LEAVE; pNewFrame->transIndex = 0; pGIF->aFrames[i] = pNewFrame; // add frame to queue // check whether we need to adapt the disposal method of the frame before. if(pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) { pGIF->aFrames[i]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // TBD might be removed pGIF->aFrames[i]->transIndex = 0; if(pGIF->aFrames[i - 1] != NULL) { pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW); pGIF->aFrames[i - 1]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // restore to background color } } // set per-frame alpha channel (we need to adapt the disposal method of the frame before) if(pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA) { pGIF->aFrames[i]->transIndex = pConfig->transIndex; if(pGIF->aFrames[i - 1] != NULL) { pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_DIFF_WINDOW); // width/height optim not possible for frame before pGIF->aFrames[i - 1]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // restore to background color } } // user provided transparency setting if(hasSetTransp) { pGIF->aFrames[i]->transIndex = pConfig->transIndex; } pGIF->curResult = CGIF_OK; return pGIF->curResult; } /* close the GIF-file and free allocated space */ int cgif_close(CGIF* pGIF) { int r; cgif_result result; // check for previous errors if(pGIF->curResult != CGIF_OK) { goto CGIF_CLOSE_Cleanup; } // flush all remaining frames in queue for(int i = 1; i < SIZE_FRAME_QUEUE; ++i) { if(pGIF->aFrames[i] != NULL) { r = flushFrame(pGIF, pGIF->aFrames[i], pGIF->aFrames[i - 1]); if(r != CGIF_OK) { pGIF->curResult = r; break; } } } // cleanup CGIF_CLOSE_Cleanup: r = cgif_raw_close(pGIF->pGIFRaw); // close raw GIF stream // check for errors if(r != CGIF_OK) { pGIF->curResult = r; } if(pGIF->pFile) { r = fclose(pGIF->pFile); // we are done at this point => close the file if(r) { pGIF->curResult = CGIF_ECLOSE; // error: fclose failed } } for(int i = 0; i < 3; ++i) { freeFrame(pGIF->aFrames[i]); } result = pGIF->curResult; freeCGIF(pGIF); // catch internal value CGIF_PENDING if(result == CGIF_PENDING) { result = CGIF_ERROR; } return result; // return previous result } cgif-0.2.0/src/cgif_raw.c000066400000000000000000000654421420124357700151550ustar00rootroot00000000000000#include #include #include "cgif_raw.h" #define SIZE_MAIN_HEADER (13) #define SIZE_APP_EXT (19) #define SIZE_FRAME_HEADER (10) #define SIZE_GRAPHIC_EXT ( 8) #define HEADER_OFFSET_SIGNATURE (0x00) #define HEADER_OFFSET_VERSION (0x03) #define HEADER_OFFSET_WIDTH (0x06) #define HEADER_OFFSET_HEIGHT (0x08) #define HEADER_OFFSET_PACKED_FIELD (0x0A) #define HEADER_OFFSET_BACKGROUND (0x0B) #define HEADER_OFFSET_MAP (0x0C) #define IMAGE_OFFSET_LEFT (0x01) #define IMAGE_OFFSET_TOP (0x03) #define IMAGE_OFFSET_WIDTH (0x05) #define IMAGE_OFFSET_HEIGHT (0x07) #define IMAGE_OFFSET_PACKED_FIELD (0x09) #define IMAGE_PACKED_FIELD(a) (*((uint8_t*) (a + IMAGE_OFFSET_PACKED_FIELD))) #define APPEXT_OFFSET_NAME (0x03) #define APPEXT_NETSCAPE_OFFSET_LOOPS (APPEXT_OFFSET_NAME + 13) #define GEXT_OFFSET_DELAY (0x04) #define MAX_CODE_LEN 12 // maximum code length for lzw #define MAX_DICT_LEN (1uL << MAX_CODE_LEN) // maximum length of the dictionary #define BLOCK_SIZE 0xFF // number of bytes in one block of the image data #define MULU16(a, b) (((uint32_t)a) * ((uint32_t)b)) // helper macro to correctly multiply two U16's without default signed int promotion typedef struct { uint8_t* pRasterData; uint32_t sizeRasterData; } LZWResult; typedef struct { uint16_t* pTreeInit; // LZW dictionary tree for the initial dictionary (0-255 max) uint16_t* pTreeList; // LZW dictionary tree as list (max. number of children per node = 1) uint16_t* pTreeMap; // LZW dictionary tree as map (backup to pTreeList in case more than 1 child is present) uint16_t* pLZWData; // pointer to LZW data const uint8_t* pImageData; // pointer to image data uint32_t numPixel; // number of pixels per frame uint32_t LZWPos; // position of the current LZW code uint16_t dictPos; // currrent position in dictionary, we need to store 0-4096 -- so there are at least 13 bits needed here uint16_t mapPos; // current position in LZW tree mapping table } LZWGenState; /* converts host U16 to little-endian (LE) U16 */ static uint16_t hU16toLE(const uint16_t n) { int isBE; uint16_t newVal; uint16_t one; one = 1; isBE = *((uint8_t*)&one) ? 0 : 1; if(isBE) { newVal = (n >> 8) | (n << 8); } else { newVal = n; // already LE } return newVal; } /* calculate next power of two exponent of given number (n MUST be <= 256) */ static uint8_t calcNextPower2Ex(uint16_t n) { uint8_t nextPow2; for (nextPow2 = 0; n > (1uL << nextPow2); ++nextPow2); return nextPow2; } /* compute which initial LZW-code length is needed */ static uint8_t calcInitCodeLen(uint16_t numEntries) { uint8_t index; index = calcNextPower2Ex(numEntries); return (index < 3) ? 3 : index + 1; } /* reset the dictionary of known LZW codes -- will reset the current code length as well */ static void resetDict(LZWGenState* pContext, const uint16_t initDictLen) { pContext->dictPos = initDictLen + 2; // reset current position in dictionary (number of colors + 2 for start and end code) pContext->mapPos = 1; pContext->pLZWData[pContext->LZWPos] = initDictLen; // issue clear-code ++(pContext->LZWPos); // increment position in LZW data // reset LZW list memset(pContext->pTreeInit, 0, initDictLen * sizeof(uint16_t) * initDictLen); memset(pContext->pTreeList, 0, ((sizeof(uint16_t) * 2) + sizeof(uint16_t)) * MAX_DICT_LEN); } /* add new child node */ static void add_child(LZWGenState* pContext, const uint16_t parentIndex, const uint16_t LZWIndex, const uint16_t initDictLen, const uint8_t nextColor) { uint16_t* pTreeList; uint16_t mapPos; pTreeList = pContext->pTreeList; mapPos = pTreeList[parentIndex * (2 + 1)]; if(!mapPos) { // if pTreeMap is not used yet for the parent node if(pTreeList[parentIndex * (2 + 1) + 2]) { // if at least one child node exists, switch to pTreeMap mapPos = pContext->mapPos; // add child to mapping table (pTreeMap) memset(pContext->pTreeMap + ((mapPos - 1) * initDictLen), 0, initDictLen * sizeof(uint16_t)); pContext->pTreeMap[(mapPos - 1) * initDictLen + nextColor] = LZWIndex; pTreeList[parentIndex * (2 + 1)] = mapPos; ++(pContext->mapPos); } else { // use the free spot in pTreeList for the child node pTreeList[parentIndex * (2 + 1) + 1] = nextColor; // color that leads to child node pTreeList[parentIndex * (2 + 1) + 2] = LZWIndex; // position of child node } } else { // directly add child node to pTreeMap pContext->pTreeMap[(mapPos - 1) * initDictLen + nextColor] = LZWIndex; } ++(pContext->dictPos); // increase current position in the dictionary } /* find next LZW code representing the longest pixel sequence that is still in the dictionary*/ static int lzw_crawl_tree(LZWGenState* pContext, uint32_t* pStrPos, uint16_t parentIndex, const uint16_t initDictLen) { uint16_t* pTreeInit; uint16_t* pTreeList; uint32_t strPos; uint16_t nextParent; uint16_t mapPos; if(parentIndex >= initDictLen) { return CGIF_EINDEX; // error: index in image data out-of-bounds } pTreeInit = pContext->pTreeInit; pTreeList = pContext->pTreeList; strPos = *pStrPos; // get the next LZW code from pTreeInit: // the initial nodes (0-255 max) have more children on average. // use the mapping approach right from the start for these nodes. if(strPos < (pContext->numPixel - 1)) { if(pContext->pImageData[strPos + 1] >= initDictLen) { return CGIF_EINDEX; // error: index in image data out-of-bounds } nextParent = pTreeInit[parentIndex * initDictLen + pContext->pImageData[strPos + 1]]; if(nextParent) { parentIndex = nextParent; ++strPos; } else { pContext->pLZWData[pContext->LZWPos] = parentIndex; // write last LZW code in LZW data ++(pContext->LZWPos); if(pContext->dictPos < MAX_DICT_LEN) { pTreeInit[parentIndex * initDictLen + pContext->pImageData[strPos + 1]] = pContext->dictPos; ++(pContext->dictPos); } else { resetDict(pContext, initDictLen); } ++strPos; *pStrPos = strPos; return CGIF_OK; } } // inner loop for codes > initDictLen while(strPos < (pContext->numPixel - 1)) { if(pContext->pImageData[strPos + 1] >= initDictLen) { return CGIF_EINDEX; // error: index in image data out-of-bounds } // first try to find child in LZW list if(pTreeList[parentIndex * (2 + 1) + 2] && pTreeList[parentIndex * (2 + 1) + 1] == pContext->pImageData[strPos + 1]) { parentIndex = pTreeList[parentIndex * (2 + 1) + 2]; ++strPos; continue; } // not found child yet? try to look into the LZW mapping table mapPos = pContext->pTreeList[parentIndex * (2 + 1)]; if(mapPos) { nextParent = pContext->pTreeMap[(mapPos - 1) * initDictLen + pContext->pImageData[strPos + 1]]; if(nextParent) { parentIndex = nextParent; ++strPos; continue; } } // still not found child? add current parentIndex to LZW data and add new child pContext->pLZWData[pContext->LZWPos] = parentIndex; // write last LZW code in LZW data ++(pContext->LZWPos); if(pContext->dictPos < MAX_DICT_LEN) { // if LZW-dictionary is not full yet add_child(pContext, parentIndex, pContext->dictPos, initDictLen, pContext->pImageData[strPos + 1]); // add new LZW code to dictionary } else { // the dictionary reached its maximum code => reset it (not required by GIF-standard but mostly done like this) resetDict(pContext, initDictLen); } ++strPos; *pStrPos = strPos; return CGIF_OK; } pContext->pLZWData[pContext->LZWPos] = parentIndex; // if the end of the image is reached, write last LZW code ++(pContext->LZWPos); ++strPos; *pStrPos = strPos; return CGIF_OK; } /* generate LZW-codes that compress the image data*/ static int lzw_generate(LZWGenState* pContext, uint16_t initDictLen) { uint32_t strPos; int r; uint8_t parentIndex; strPos = 0; // start at beginning of the image data resetDict(pContext, initDictLen); // reset dictionary and issue clear-code at first while(strPos < pContext->numPixel) { // while there are still image data to be encoded parentIndex = pContext->pImageData[strPos]; // start at root node // get longest sequence that is still in dictionary, return new position in image data r = lzw_crawl_tree(pContext, &strPos, (uint16_t)parentIndex, initDictLen); if(r != CGIF_OK) { return r; // error: return error code to callee } } pContext->pLZWData[pContext->LZWPos] = initDictLen + 1; // termination code ++(pContext->LZWPos); return CGIF_OK; } /* pack the LZW data into a byte sequence*/ static uint32_t create_byte_list(uint8_t *byteList, uint32_t lzwPos, uint16_t *lzwStr, uint16_t initDictLen, uint8_t initCodeLen){ uint32_t i; uint32_t dictPos; // counting new LZW codes uint16_t n = 2 * initDictLen; // if n - initDictLen == dictPos, the LZW code size is incremented by 1 bit uint32_t bytePos = 0; // position of current byte uint8_t bitOffset = 0; // number of bits used in the last byte uint8_t lzwCodeLen = initCodeLen; // dynamically increasing length of the LZW codes int correctLater = 0; // 1: one empty byte too much if end is reached after current code, 0 otherwise byteList[0] = 0; // except from the 1st byte all other bytes should be initialized stepwise (below) // the very first symbol might be the clear-code. However, this is not mandatory. Quote: // "Encoders should output a Clear code as the first code of each image data stream." // We keep the option to NOT output the clear code as the first symbol in this function. dictPos = 1; for(i = 0; i < lzwPos; ++i) { // loop over all LZW codes if((lzwCodeLen < MAX_CODE_LEN) && ((uint32_t)(n - (initDictLen)) == dictPos)) { // larger code is used for the 1st time at i = 256 ...+ 512 ...+ 1024 -> 256, 768, 1792 ++lzwCodeLen; // increment the length of the LZW codes (bit units) n *= 2; // set threshold for next increment of LZW code size } correctLater = 0; // 1 indicates that one empty byte is too much at the end byteList[bytePos] |= ((uint8_t)(lzwStr[i] << bitOffset)); // add 1st bits of the new LZW code to the byte containing part of the previous code if(lzwCodeLen + bitOffset >= 8) { // if the current byte is not enough of the LZW code if(lzwCodeLen + bitOffset == 8) { // if just this byte is filled exactly byteList[++bytePos] = 0; // byte is full -- go to next byte and initialize as 0 correctLater = 1; // use if one 0byte to much at the end } else if(lzwCodeLen + bitOffset < 16) { // if the next byte is not completely filled byteList[++bytePos] = (uint8_t)(lzwStr[i] >> (8-bitOffset)); } else if(lzwCodeLen + bitOffset == 16) { // if the next byte is exactly filled by LZW code byteList[++bytePos] = (uint8_t)(lzwStr[i] >> (8-bitOffset)); byteList[++bytePos] = 0; // byte is full -- go to next byte and initialize as 0 correctLater = 1; // use if one 0byte to much at the end } else { // lzw-code ranges over 3 bytes in total byteList[++bytePos] = (uint8_t)(lzwStr[i] >> (8-bitOffset)); // write part of LZW code to next byte byteList[++bytePos] = (uint8_t)(lzwStr[i] >> (16-bitOffset)); // write part of LZW code to byte after next byte } } bitOffset = (lzwCodeLen + bitOffset) % 8; // how many bits of the last byte are used? ++dictPos; // increment count of LZW codes if(lzwStr[i] == initDictLen) { // if a clear code appears in the LZW data lzwCodeLen = initCodeLen; // reset length of LZW codes n = 2 * initDictLen; // reset threshold for next increment of LZW code length dictPos = 1; // reset (see comment below) // take first code already into account to increment lzwCodeLen exactly when the code length cannot represent the current maximum symbol. // Note: This is usually done implicitly, as the very first symbol is a clear-code itself. } } // comment: the last byte can be zero in the following case only: // terminate code has been written (initial dict length + 1), but current code size is larger so padding zero bits were added and extend into the next byte(s). if(correctLater) { // if an unneccessaray empty 0-byte was initialized at the end --bytePos; // don't consider the last empty byte } return bytePos; } /* put byte sequence in blocks as required by GIF-format */ static uint32_t create_byte_list_block(uint8_t *byteList, uint8_t *byteListBlock, const uint32_t numBytes) { uint32_t i; uint32_t numBlock = numBytes / BLOCK_SIZE; // number of byte blocks with length BLOCK_SIZE uint8_t numRest = numBytes % BLOCK_SIZE; // number of bytes in last block (if not completely full) for(i = 0; i < numBlock; ++i) { // loop over all blocks byteListBlock[i * (BLOCK_SIZE+1)] = BLOCK_SIZE; // number of bytes in the following block memcpy(byteListBlock + 1+i*(BLOCK_SIZE+1), byteList + i*BLOCK_SIZE, BLOCK_SIZE); // copy block from byteList to byteListBlock } if(numRest>0) { byteListBlock[numBlock*(BLOCK_SIZE+1)] = numRest; // number of bytes in the following block memcpy(byteListBlock + 1+numBlock*(BLOCK_SIZE+1), byteList + numBlock*BLOCK_SIZE, numRest); // copy block from byteList to byteListBlock byteListBlock[1 + numBlock * (BLOCK_SIZE + 1) + numRest] = 0; // set 0 at end of frame return 1 + numBlock * (BLOCK_SIZE + 1) + numRest; // index of last entry in byteListBlock } // all LZW blocks in the frame have the same block size (BLOCK_SIZE), so there are no remaining bytes to be writen. byteListBlock[numBlock *(BLOCK_SIZE + 1)] = 0; // set 0 at end of frame return numBlock *(BLOCK_SIZE + 1); // index of last entry in byteListBlock } /* create all LZW raster data in GIF-format */ static int LZW_GenerateStream(LZWResult* pResult, const uint32_t numPixel, const uint8_t* pImageData, const uint16_t initDictLen, const uint8_t initCodeLen){ LZWGenState* pContext; uint32_t lzwPos, bytePos; uint32_t bytePosBlock; int r; // TBD recycle LZW tree list and map (if possible) to decrease the number of allocs pContext = malloc(sizeof(LZWGenState)); // TBD check return value of malloc pContext->pTreeInit = malloc((initDictLen * sizeof(uint16_t)) * initDictLen); // TBD check return value of malloc pContext->pTreeList = malloc(((sizeof(uint16_t) * 2) + sizeof(uint16_t)) * MAX_DICT_LEN); // TBD check return value of malloc TBD check size pContext->pTreeMap = malloc(((MAX_DICT_LEN / 2) + 1) * (initDictLen * sizeof(uint16_t))); // TBD check return value of malloc pContext->numPixel = numPixel; pContext->pImageData = pImageData; pContext->pLZWData = malloc(sizeof(uint16_t) * (numPixel + 2)); // TBD check return value of malloc pContext->LZWPos = 0; // actually generate the LZW sequence. r = lzw_generate(pContext, initDictLen); if(r != CGIF_OK) { goto LZWGENERATE_Cleanup; } lzwPos = pContext->LZWPos; // pack the generated LZW data into blocks of 255 bytes uint8_t *byteList; // lzw-data packed in byte-list uint8_t *byteListBlock; // lzw-data packed in byte-list with 255-block structure uint64_t MaxByteListLen = MAX_CODE_LEN*lzwPos/8ul +2ul +1ul; // conservative upper bound uint64_t MaxByteListBlockLen = MAX_CODE_LEN*lzwPos*(BLOCK_SIZE+1ul)/8ul/BLOCK_SIZE +2ul +1ul +1ul; // conservative upper bound byteList = malloc(MaxByteListLen); // TBD check return value of malloc byteListBlock = malloc(MaxByteListBlockLen); // TBD check return value of malloc bytePos = create_byte_list(byteList,lzwPos, pContext->pLZWData, initDictLen, initCodeLen); bytePosBlock = create_byte_list_block(byteList, byteListBlock, bytePos+1); free(byteList); pResult->sizeRasterData = bytePosBlock + 1; // save pResult->pRasterData = byteListBlock; LZWGENERATE_Cleanup: free(pContext->pLZWData); free(pContext->pTreeInit); free(pContext->pTreeList); free(pContext->pTreeMap); free(pContext); return r; } /* initialize the header of the GIF */ static void initMainHeader(const CGIFRaw_Config* pConfig, uint8_t* pHeader) { uint16_t width, height; uint8_t pow2GlobalPalette; width = pConfig->width; height = pConfig->height; // set header to a clean state memset(pHeader, 0, SIZE_MAIN_HEADER); // set Signature field to value "GIF" pHeader[HEADER_OFFSET_SIGNATURE] = 'G'; pHeader[HEADER_OFFSET_SIGNATURE + 1] = 'I'; pHeader[HEADER_OFFSET_SIGNATURE + 2] = 'F'; // set Version field to value "89a" pHeader[HEADER_OFFSET_VERSION] = '8'; pHeader[HEADER_OFFSET_VERSION + 1] = '9'; pHeader[HEADER_OFFSET_VERSION + 2] = 'a'; // set width of screen (LE ordering) const uint16_t widthLE = hU16toLE(width); memcpy(pHeader + HEADER_OFFSET_WIDTH, &widthLE, sizeof(uint16_t)); // set height of screen (LE ordering) const uint16_t heightLE = hU16toLE(height); memcpy(pHeader + HEADER_OFFSET_HEIGHT, &heightLE, sizeof(uint16_t)); // init packed field if(pConfig->sizeGCT) { pHeader[HEADER_OFFSET_PACKED_FIELD] = (1 << 7); // M = 1 (see GIF specc): global color table is present // calculate needed size of global color table (GCT). // MUST be a power of two. pow2GlobalPalette = calcNextPower2Ex(pConfig->sizeGCT); pow2GlobalPalette = (pow2GlobalPalette < 1) ? 1 : pow2GlobalPalette; // minimum size is 2^1 pHeader[HEADER_OFFSET_PACKED_FIELD] |= ((pow2GlobalPalette - 1) << 0); // set size of GCT (0 - 7 in header + 1) } } /* initialize NETSCAPE app extension block (needed for animation) */ static void initAppExtBlock(uint8_t* pAppExt, uint16_t numLoops) { memset(pAppExt, 0, SIZE_APP_EXT); // set data pAppExt[0] = 0x21; pAppExt[1] = 0xFF; // start of block pAppExt[2] = 0x0B; // eleven bytes to follow // write identifier for Netscape animation extension pAppExt[APPEXT_OFFSET_NAME] = 'N'; pAppExt[APPEXT_OFFSET_NAME + 1] = 'E'; pAppExt[APPEXT_OFFSET_NAME + 2] = 'T'; pAppExt[APPEXT_OFFSET_NAME + 3] = 'S'; pAppExt[APPEXT_OFFSET_NAME + 4] = 'C'; pAppExt[APPEXT_OFFSET_NAME + 5] = 'A'; pAppExt[APPEXT_OFFSET_NAME + 6] = 'P'; pAppExt[APPEXT_OFFSET_NAME + 7] = 'E'; pAppExt[APPEXT_OFFSET_NAME + 8] = '2'; pAppExt[APPEXT_OFFSET_NAME + 9] = '.'; pAppExt[APPEXT_OFFSET_NAME + 10] = '0'; pAppExt[APPEXT_OFFSET_NAME + 11] = 0x03; // 3 bytes to follow pAppExt[APPEXT_OFFSET_NAME + 12] = 0x01; // TBD clarify // set number of repetitions (animation; LE ordering) const uint16_t netscapeLE = hU16toLE(numLoops); memcpy(pAppExt + APPEXT_NETSCAPE_OFFSET_LOOPS, &netscapeLE, sizeof(uint16_t)); } /* write numBytes dummy bytes */ static int writeDummyBytes(cgif_write_fn* pWriteFn, void* pContext, int numBytes) { int rWrite = 0; const uint8_t dummyByte = 0; for(int i = 0; i < numBytes; ++i) { rWrite |= pWriteFn(pContext, &dummyByte, 1); } return rWrite; } CGIFRaw* cgif_raw_newgif(const CGIFRaw_Config* pConfig) { uint8_t aAppExt[SIZE_APP_EXT]; uint8_t aHeader[SIZE_MAIN_HEADER]; CGIFRaw* pGIF; int rWrite; pGIF = malloc(sizeof(CGIFRaw)); if(!pGIF) { return NULL; } memcpy(&(pGIF->config), pConfig, sizeof(CGIFRaw_Config)); // initiate all sections we can at this stage: // - main GIF header // - global color table (GCT), if required // - netscape application extension (for animation), if required initMainHeader(pConfig, aHeader); rWrite = pConfig->pWriteFn(pConfig->pContext, aHeader, SIZE_MAIN_HEADER); // GCT required? => write it. if(pConfig->sizeGCT) { rWrite |= pConfig->pWriteFn(pConfig->pContext, pConfig->pGCT, pConfig->sizeGCT * 3); uint8_t pow2GCT = calcNextPower2Ex(pConfig->sizeGCT); pow2GCT = (pow2GCT < 1) ? 1 : pow2GCT; // minimum size is 2^1 const uint16_t numBytesLeft = ((1 << pow2GCT) - pConfig->sizeGCT) * 3; rWrite |= writeDummyBytes(pConfig->pWriteFn, pConfig->pContext, numBytesLeft); } // GIF should be animated? => init & write app extension header ("NETSCAPE2.0") if(pConfig->attrFlags & CGIF_RAW_ATTR_IS_ANIMATED) { initAppExtBlock(aAppExt, pConfig->numLoops); rWrite |= pConfig->pWriteFn(pConfig->pContext, aAppExt, SIZE_APP_EXT); } // check for write errors if(rWrite) { free(pGIF); return NULL; } // assume error per default. // set to CGIF_OK by the first successful cgif_raw_addframe() call, as a GIF without frames is invalid. pGIF->curResult = CGIF_PENDING; return pGIF; } /* add new frame to the raw GIF stream */ cgif_result cgif_raw_addframe(CGIFRaw* pGIF, const CGIFRaw_FrameConfig* pConfig) { uint8_t aFrameHeader[SIZE_FRAME_HEADER]; uint8_t aGraphicExt[SIZE_GRAPHIC_EXT]; LZWResult encResult; int r, rWrite; const int useLCT = pConfig->sizeLCT; uint16_t numEffColors; // number of effective colors uint16_t initDictLen; uint8_t pow2LCT, initCodeLen; if(pGIF->curResult != CGIF_OK && pGIF->curResult != CGIF_PENDING) { return pGIF->curResult; // return previous error } rWrite = 0; // set frame header to a clean state memset(aFrameHeader, 0, SIZE_FRAME_HEADER); // set needed fields in frame header aFrameHeader[0] = ','; // set frame seperator if(useLCT) { pow2LCT = calcNextPower2Ex(pConfig->sizeLCT); pow2LCT = (pow2LCT < 1) ? 1 : pow2LCT; // minimum size is 2^1 IMAGE_PACKED_FIELD(aFrameHeader) = (1 << 7); // set size of local color table (0-7 in header + 1) IMAGE_PACKED_FIELD(aFrameHeader) |= ((pow2LCT- 1) << 0); numEffColors = pConfig->sizeLCT; } else { numEffColors = pGIF->config.sizeGCT; // global color table in use } // transparency in use? we might need to increase numEffColors if((pGIF->config.attrFlags & (CGIF_RAW_ATTR_IS_ANIMATED)) && (pConfig->attrFlags & (CGIF_RAW_FRAME_ATTR_HAS_TRANS)) && pConfig->transIndex >= numEffColors) { numEffColors = pConfig->transIndex + 1; } // calculate initial code length and initial dict length initCodeLen = calcInitCodeLen(numEffColors); initDictLen = 1uL << (initCodeLen - 1); const uint16_t frameWidthLE = hU16toLE(pConfig->width); const uint16_t frameHeightLE = hU16toLE(pConfig->height); const uint16_t frameTopLE = hU16toLE(pConfig->top); const uint16_t frameLeftLE = hU16toLE(pConfig->left); memcpy(aFrameHeader + IMAGE_OFFSET_WIDTH, &frameWidthLE, sizeof(uint16_t)); memcpy(aFrameHeader + IMAGE_OFFSET_HEIGHT, &frameHeightLE, sizeof(uint16_t)); memcpy(aFrameHeader + IMAGE_OFFSET_TOP, &frameTopLE, sizeof(uint16_t)); memcpy(aFrameHeader + IMAGE_OFFSET_LEFT, &frameLeftLE, sizeof(uint16_t)); // generate LZW raster data (actual image data) r = LZW_GenerateStream(&encResult, MULU16(pConfig->width, pConfig->height), pConfig->pImageData, initDictLen, initCodeLen); // check for errors if(r != CGIF_OK) { pGIF->curResult = r; return r; } // do things for animation / transparency, if required. if(pGIF->config.attrFlags & CGIF_RAW_ATTR_IS_ANIMATED) { memset(aGraphicExt, 0, SIZE_GRAPHIC_EXT); aGraphicExt[0] = 0x21; aGraphicExt[1] = 0xF9; aGraphicExt[2] = 0x04; aGraphicExt[3] = pConfig->disposalMethod; // set flag indicating that transparency is used, if required. if(pConfig->attrFlags & CGIF_RAW_FRAME_ATTR_HAS_TRANS) { aGraphicExt[3] |= 0x01; aGraphicExt[6] = pConfig->transIndex; } // set delay (LE ordering) const uint16_t delayLE = hU16toLE(pConfig->delay); memcpy(aGraphicExt + GEXT_OFFSET_DELAY, &delayLE, sizeof(uint16_t)); } // write frame const uint8_t initialCodeSize = initCodeLen - 1; if(pGIF->config.attrFlags & CGIF_RAW_ATTR_IS_ANIMATED) { rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, aGraphicExt, SIZE_GRAPHIC_EXT); } rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, aFrameHeader, SIZE_FRAME_HEADER); if(useLCT) { rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, pConfig->pLCT, pConfig->sizeLCT * 3); const uint16_t numBytesLeft = ((1 << pow2LCT) - pConfig->sizeLCT) * 3; rWrite |= writeDummyBytes(pGIF->config.pWriteFn, pGIF->config.pContext, numBytesLeft); } rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, &initialCodeSize, 1); rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, encResult.pRasterData, encResult.sizeRasterData); // check for write errors if(rWrite) { pGIF->curResult = CGIF_EWRITE; } else { pGIF->curResult = CGIF_OK; } // cleanup free(encResult.pRasterData); return pGIF->curResult; } cgif_result cgif_raw_close(CGIFRaw* pGIF) { int rWrite; cgif_result result; rWrite = pGIF->config.pWriteFn(pGIF->config.pContext, (unsigned char*) ";", 1); // write term symbol // check for write errors if(rWrite) { pGIF->curResult = CGIF_EWRITE; } result = pGIF->curResult; free(pGIF); return result; } cgif-0.2.0/tests/000077500000000000000000000000001420124357700135705ustar00rootroot00000000000000cgif-0.2.0/tests/all_optim.c000066400000000000000000000045741420124357700157260ustar00rootroot00000000000000#include #include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 static void initGIFConfig(CGIF_Config* pConfig, uint8_t* pGlobalPalette, uint16_t numColors, uint32_t attrFlags, uint16_t width, uint16_t height) { memset(pConfig, 0, sizeof(CGIF_Config)); pConfig->attrFlags = attrFlags; pConfig->width = width; pConfig->height = height; pConfig->pGlobalPalette = pGlobalPalette; pConfig->numGlobalPaletteEntries = numColors; pConfig->path = "all_optim.gif"; } static void initFrameConfig(CGIF_FrameConfig* pConfig, uint8_t* pImageData, uint16_t delay, uint32_t genFlags) { memset(pConfig, 0, sizeof(CGIF_FrameConfig)); pConfig->pImageData = pImageData; pConfig->delay = delay; pConfig->genFlags = genFlags; } int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; cgif_result r; uint8_t numColors = 2; // number of colors in aPalette int numFrames = 2; // number of frames in the video // // create new GIF initGIFConfig(&gConfig, aPalette, numColors, CGIF_ATTR_IS_ANIMATED, WIDTH, HEIGHT); pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); // Actual image data memset(pImageData, 0, WIDTH * HEIGHT); initFrameConfig(&fConfig, pImageData, 100, CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW); cgif_addframe(pGIF, &fConfig); // append the new frame memset(pImageData + 7, 1, 41); memset(pImageData + 7 + WIDTH, 1, 41); memset(pImageData + 7 + WIDTH * 2, 1, 41); memset(pImageData + 7 + WIDTH * 2, 0, 29); memset(pImageData + 7 + WIDTH * 3, 1, 41); memset(pImageData + 7 + WIDTH * 3, 0, 23); memset(pImageData + 7 + WIDTH * 4, 0, 37); r = cgif_addframe(pGIF, &fConfig); // append the next frame // free(pImageData); // // Free allocated space at the end of the session r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/alpha.c000066400000000000000000000032111420124357700150160ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 99 #define HEIGHT 99 int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0xFF, 0x00, 0x00, // green 0xFF, 0xFF, 0xFF, // white }; cgif_result r; memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 2; gConfig.path = "alpha.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); memset(pImageData, 0, WIDTH * HEIGHT); fConfig.pImageData = pImageData; fConfig.delay = 100; // create an off/on pattern for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = i % 2; } cgif_addframe(pGIF, &fConfig); // create transparent frame (alpha channel) for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = 2; } fConfig.attrFlags = CGIF_FRAME_ATTR_HAS_ALPHA; fConfig.transIndex = 2; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // write GIF to file r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/animated_color_gradient.c000066400000000000000000000051751420124357700206010ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 256 #define HEIGHT 16 /* This is an example code that creates a GIF-animation with a moving red color gradient with the maximum number of colors (1st and last color is not red)*/ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[3*WIDTH]; cgif_result r; // define red color gradient for (int j = 0; j < WIDTH; ++j) { aPalette[j*3] = j; aPalette[j*3+1] = 0; aPalette[j*3+2] = 0; } // set first color to blue aPalette[0] = 0; aPalette[1] = 0; aPalette[2] = 0xFF; // set last color to green aPalette[3*(WIDTH-1)] = 0; aPalette[3*(WIDTH-1)+1] = 0xFF; aPalette[3*(WIDTH-1)+2] = 0; uint16_t numColors = (uint16_t)(WIDTH); // number of colors in aPalette int numFrames = WIDTH; // number of frames in the video memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; // set needed attribution flag (as GIF is animated) gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "animated_color_gradient.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); // actual image data fConfig.genFlags = CGIF_FRAME_GEN_USE_DIFF_WINDOW | CGIF_FRAME_GEN_USE_TRANSPARENCY; // it should be automatically detected that transparency optimization is not possible if 256 colors are used fConfig.pImageData = pImageData; // set pointer to image data fConfig.delay = 5; // set time before next frame (in units of 0.01 s) for (int f = 0; f < numFrames; ++f) { for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = (unsigned char)((i + f) % WIDTH); // ceate a moving stripe pattern } r = cgif_addframe(pGIF, &fConfig); // append the new frame if(r != CGIF_OK) { break; } } free(pImageData); // // write GIF to file r = cgif_close(pGIF); // free allocated space at the end of the session // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/animated_single_pixel.c000066400000000000000000000077631420124357700202750ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 40 #define HEIGHT 40 /* Small helper functions to initialize GIF- and frame-configuration */ static void initGIFConfig(CGIF_Config* pConfig, char* path, uint16_t width, uint16_t height, uint8_t* pPalette, uint16_t numColors) { memset(pConfig, 0, sizeof(CGIF_Config)); pConfig->width = width; pConfig->height = height; pConfig->pGlobalPalette = pPalette; pConfig->numGlobalPaletteEntries = numColors; pConfig->path = path; pConfig->attrFlags = CGIF_ATTR_IS_ANIMATED; } static void initFrameConfig(CGIF_FrameConfig* pConfig, uint8_t* pImageData, uint16_t delay) { memset(pConfig, 0, sizeof(CGIF_FrameConfig)); pConfig->delay = delay; pConfig->pImageData = pImageData; pConfig->genFlags = CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW; // use optimizations } /* This is an example code that creates a GIF-animation with a single moving pixel. */ int main(void) { CGIF* pGIF; // struct containing the GIF CGIF_Config gConfig; // global configuration parameters for the GIF CGIF_FrameConfig fConfig; // configuration parameters for a frame uint8_t* pImageData; // image data (an array of color-indices) int x,y; // position of the pixel uint8_t aPalette[] = {0xFF, 0x00, 0x00, // red 0x00, 0xFF, 0x00, // green 0x00, 0x00, 0xFF}; // blue cgif_result r; uint8_t numColors = 3; // number of colors in aPalette (up to 256 possible) int numFrames = 39*4; // number of frames in the video // initialize the GIF-configuration and create a new GIF initGIFConfig(&gConfig, "animated_single_pixel.gif", WIDTH, HEIGHT, aPalette, numColors); pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // create image frames and add them to GIF pImageData = malloc(WIDTH * HEIGHT); // allocate memory for image data memset(pImageData, 0, WIDTH * HEIGHT); // set everything to the first color x = 0; // start position of the pixel (x) y = 0; // start position of the pixel (y) for (int f = 0; f < numFrames; ++f) { // loop over all frames pImageData[x + y*WIDTH] = 0; // pixel at previous position back to the 1st color if (x==0 && y0){ y--; } else{ x--; } pImageData[x + y*WIDTH] = 1; // set color of next pixel to green initFrameConfig(&fConfig, pImageData, 5); // initialize the frame-configuration r = cgif_addframe(pGIF, &fConfig); // append the new frame if(r != CGIF_OK) { break; } } free(pImageData); // free image data when all frames are added // close created GIF-file and free allocated space r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/animated_snake.c000066400000000000000000000102771420124357700167060ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 40 #define HEIGHT 40 /* Small helper functions to initialize GIF- and frame-configuration */ static void initGIFConfig(CGIF_Config* pConfig, char* path, uint16_t width, uint16_t height, uint8_t* pPalette, uint16_t numColors) { memset(pConfig, 0, sizeof(CGIF_Config)); pConfig->width = width; pConfig->height = height; pConfig->pGlobalPalette = pPalette; pConfig->numGlobalPaletteEntries = numColors; pConfig->path = path; pConfig->attrFlags = CGIF_ATTR_IS_ANIMATED; } static void initFrameConfig(CGIF_FrameConfig* pConfig, uint8_t* pImageData, uint16_t delay) { memset(pConfig, 0, sizeof(CGIF_FrameConfig)); pConfig->delay = delay; pConfig->pImageData = pImageData; pConfig->genFlags = CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW; // use optimizations } /* This is an example code that creates a GIF-animation with a moving snake. */ int main(void) { CGIF* pGIF; // struct containing the GIF CGIF_Config gConfig; // global configuration parameters for the GIF CGIF_FrameConfig fConfig; // configuration parameters for a frame uint8_t* pImageData; // image data (an array of color-indices) int x,y,x1,y1; // position of snake beginning and end cgif_result r; uint8_t aPalette[] = {0xFF, 0x00, 0x00, // red 0x00, 0xFF, 0x00, // green 0x00, 0x00, 0xFF}; // blue uint8_t numColors = 3; // number of colors in aPalette (up to 256 possible) int numFrames = 37*4; // number of frames in the video // initialize the GIF-configuration and create a new GIF initGIFConfig(&gConfig, "animated_snake.gif", WIDTH, HEIGHT, aPalette, numColors); pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // create image frames and add them to GIF pImageData = malloc(WIDTH * HEIGHT); // allocate memory for image data memset(pImageData, 0, WIDTH * HEIGHT); // set the background color to 1st color in palette x = 1; // set start position... y = 1; x1 = 1; y1 = 15; for (int i = 1; i < 15; ++i) { // draw the snake pImageData[1 + i*WIDTH] = 1; } for (int f = 0; f < numFrames; ++f) { // loop over all frames pImageData[x + y*WIDTH] = 0; // remove end of the snake pImageData[x1 + y1*WIDTH] = 1; // moving pixel at snake head if (x==1 && y1){ y--; } else{ x--; } if (x1==1 && y11){ y1--; } else{ x1--; } initFrameConfig(&fConfig, pImageData, 5); // initialize the frame-configuration r = cgif_addframe(pGIF, &fConfig); // append the new frame if(r != CGIF_OK) { break; } } free(pImageData); // free image data when all frames are added // close created GIF-file and free allocated space r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/animated_stripe_pattern.c000066400000000000000000000043021420124357700206400ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 /* This is an example code that creates a GIF-animation with a moving stripe-pattern. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white 0xFF, 0x00, 0x00, // red 0x00, 0xFF, 0x00, // green 0x00, 0x00, 0xFF, // blue }; cgif_result r; uint8_t numColors = 5; // number of colors in aPalette int numFrames = 30; // number of frames in the video memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; // set needed attribution flag (as GIF is animated) gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "animated_stripe_pattern.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); // actual image data fConfig.genFlags = CGIF_FRAME_GEN_USE_DIFF_WINDOW | CGIF_FRAME_GEN_USE_TRANSPARENCY; fConfig.pImageData = pImageData; // set pointer to image data fConfig.delay = 10; // set time before next frame (in units of 0.01 s) for (int f = 0; f < numFrames; ++f) { for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = (unsigned char)((f + i % WIDTH) % numColors); // ceate a moving stripe pattern } r = cgif_addframe(pGIF, &fConfig); // append the new frame if(r != CGIF_OK) { break; } } free(pImageData); // // write GIF to file r = cgif_close(pGIF); // free allocated space at the end of the session // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/animated_stripe_pattern_2.c000066400000000000000000000067651420124357700211000ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 /* Small helper functions to initialize GIF- and frame-configuration */ static void initGIFConfig(CGIF_Config* pConfig, char* path, uint16_t width, uint16_t height, uint8_t* pPalette, uint16_t numColors) { memset(pConfig, 0, sizeof(CGIF_Config)); pConfig->width = width; pConfig->height = height; pConfig->pGlobalPalette = pPalette; pConfig->numGlobalPaletteEntries = numColors; pConfig->path = path; pConfig->attrFlags = CGIF_ATTR_IS_ANIMATED; } static void initFrameConfig(CGIF_FrameConfig* pConfig, uint8_t* pImageData, uint16_t delay) { memset(pConfig, 0, sizeof(CGIF_FrameConfig)); pConfig->delay = delay; pConfig->pImageData = pImageData; pConfig->genFlags = CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW; // use optimizations } /* This is an example code that creates a GIF-animation with a moving stripe-pattern in part of the frame. */ int main(void) { CGIF* pGIF; // struct containing the GIF CGIF_Config gConfig; // global configuration parameters for the GIF CGIF_FrameConfig fConfig; // configuration parameters for a frame uint8_t* pImageData; // image data (an array of color-indices) uint8_t aPalette[] = {0xFF, 0x00, 0x00, // red 0x00, 0xFF, 0x00, // green 0x00, 0x00, 0xFF}; // blue cgif_result r; uint8_t numColors = 3; // number of colors in aPalette (up to 256 possible) int numFrames = 24; // number of frames in the video // initialize the GIF-configuration and create a new GIF initGIFConfig(&gConfig, "animated_stripe_pattern_2.gif", WIDTH, HEIGHT, aPalette, numColors); pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // create image frames and add them to GIF pImageData = malloc(WIDTH * HEIGHT); // allocate memory for image data memset(pImageData, 0, WIDTH * HEIGHT); // set everything to 1st color in the palette for (int f = 0; f < numFrames; ++f) { // loop over all frames for (int i = (WIDTH * HEIGHT)/2; i < (WIDTH * HEIGHT)/4*3; ++i) { // only draw in certain window of the frame pImageData[i] = (unsigned char)((f + i % WIDTH ) / 8 % numColors); // moving stripe pattern (4 pixels per stripe) } initFrameConfig(&fConfig, pImageData, 10); // initialize the frame-configuration r = cgif_addframe(pGIF, &fConfig); // append the new frame if(r != CGIF_OK) { break; } } free(pImageData); // free image data when all frames are added // close created GIF-file and free allocated space r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/animated_stripes_horizontal.c000066400000000000000000000043451420124357700215460ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 /* This is an example code that creates a GIF-animation with a moving stripe-pattern. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0xFF, 0x00, 0x00, 0xEE, 0x00, 0x00, 0xDD, 0x00, 0x00, 0xCC, 0x00, 0x00, 0xBB, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x99, 0x00, 0x00, 0x88, 0x00, 0x00, 0x77, 0x00, 0x00, 0x66, 0x00, 0x00, }; cgif_result r; uint8_t numColors = 10; // number of colors in aPalette int numFrames = 10; // number of frames in the video memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; // set needed attribution flag (as GIF is animated) gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "animated_stripes_horizontal.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); // actual image data fConfig.genFlags = CGIF_FRAME_GEN_USE_DIFF_WINDOW | CGIF_FRAME_GEN_USE_TRANSPARENCY; fConfig.pImageData = pImageData; // set pointer to image data fConfig.delay = 50; // set time before next frame (in units of 0.01 s) for (int f = 0; f < numFrames; ++f) { for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = (unsigned char)((f + numColors * i / WIDTH / HEIGHT) % numColors); // ceate a moving stripe pattern } r = cgif_addframe(pGIF, &fConfig); // append the new frame } free(pImageData); // // write GIF to file r = cgif_close(pGIF); // free allocated space at the end of the session // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/earlyclose.c000066400000000000000000000025061420124357700161010ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 static int pWriteFn(void* pContext, const uint8_t* pData, const size_t numBytes) { (void)pContext; (void)pData; (void)numBytes; // just ignore GIF data return 0; } int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; cgif_result r; memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 2; gConfig.pWriteFn = pWriteFn; gConfig.pContext = NULL; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // close GIF without adding frames before r = cgif_close(pGIF); // free allocated space at the end of the session // check for correct error if(r != CGIF_OK) { return 0; } fputs("CGIF_ERROR expected as result code\n", stderr); return 2; } cgif-0.2.0/tests/eindex.c000066400000000000000000000031121420124357700152050ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 static int pWriteFn(void* pContext, const uint8_t* pData, const size_t numBytes) { (void)pContext; (void)pData; (void)numBytes; // just ignore GIF data return 0; } int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; cgif_result r; memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 2; gConfig.pWriteFn = pWriteFn; gConfig.pContext = NULL; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); memset(pImageData, 0, WIDTH * HEIGHT); pImageData[2] = 32; // 32 is not a valid index in this case. fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // write GIF to file r = cgif_close(pGIF); // free allocated space at the end of the session // check for correct error: CGIF_EINDEX if(r == CGIF_EINDEX) { return 0; } fputs("CGIF_EINDEX expected as result code\n", stderr); return 2; } cgif-0.2.0/tests/ewrite.c000066400000000000000000000027501420124357700152370ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 static int pWriteFn(void* pContext, const uint8_t* pData, const size_t numBytes) { (void)pContext; (void)pData; (void)numBytes; return -1; // return error code -1: should lead to EWRITE } int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; cgif_result r; memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 2; gConfig.pWriteFn = pWriteFn; gConfig.pContext = NULL; // // create new GIF (might call pWriteFn as well) pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { return 0; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); memset(pImageData, 0, WIDTH * HEIGHT); fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // write GIF to file r = cgif_close(pGIF); // free allocated space at the end of the session // check for errors if(r == CGIF_EWRITE) { return 0; } fputs("CGIF_EWRITE expected as result code\n", stderr); return 1; } cgif-0.2.0/tests/global_plus_local_table.c000066400000000000000000000042471420124357700205670ustar00rootroot00000000000000#include #include #include #include #include #include "cgif.h" #define WIDTH 110 #define HEIGHT 125 /* This is an example code that creates a GIF-animation with a moving stripe-pattern. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aLocalPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; uint8_t aGlobalPalette[] = { 0x00, 0xFF, 0x00, // green 0x00, 0x00, 0xFF, // blue 0xFF, 0x00, 0x00, // red }; cgif_result r; uint8_t numColorsLocal = 2; // number of colors in aPalette uint8_t numColorsGlobal = 3; // number of colors in aPalette // // Create new GIF // memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.pGlobalPalette = aGlobalPalette; gConfig.numGlobalPaletteEntries = numColorsGlobal; gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.path = "global_plus_local_table.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // Add frame to GIF // pImageData = malloc(WIDTH * HEIGHT); // Actual image data memset(pImageData, 1, WIDTH * HEIGHT); memset(pImageData + WIDTH * 2, 0, WIDTH * 3); fConfig.pImageData = pImageData; fConfig.pLocalPalette = aLocalPalette; fConfig.numLocalPaletteEntries = numColorsLocal; fConfig.attrFlags = CGIF_FRAME_ATTR_USE_LOCAL_TABLE; fConfig.delay = 100; r = cgif_addframe(pGIF, &fConfig); // append the new frame // // add next frame fConfig.attrFlags = 0; fConfig.genFlags = 0; memset(pImageData + WIDTH * 9, 2, WIDTH * 10); r = cgif_addframe(pGIF, &fConfig); // append the new frame // free(pImageData); // // Free allocated space at the end of the session // r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/global_plus_local_table_with_optim.c000066400000000000000000000122341420124357700230250ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 40 #define HEIGHT 40 /* Small helper functions to initialize GIF- and frame-configuration */ static void initGIFConfig(CGIF_Config* pConfig, char* path, uint16_t width, uint16_t height, uint8_t* pPalette, uint16_t numColors) { memset(pConfig, 0, sizeof(CGIF_Config)); pConfig->width = width; pConfig->height = height; pConfig->pGlobalPalette = pPalette; pConfig->numGlobalPaletteEntries = numColors; pConfig->path = path; pConfig->attrFlags = CGIF_ATTR_IS_ANIMATED; } static void initFrameConfig(CGIF_FrameConfig* pConfig, uint8_t* pImageData, uint16_t delay) { memset(pConfig, 0, sizeof(CGIF_FrameConfig)); pConfig->delay = delay; pConfig->pImageData = pImageData; pConfig->genFlags = CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW; // use optimizations } /* This is an example code that creates a GIF-animation with global and local color table, also using the main optimizations to reduce the GIF size */ int main(void) { CGIF* pGIF; // struct containing the GIF CGIF_Config gConfig; // global configuration parameters for the GIF CGIF_FrameConfig fConfig; // configuration parameters for a frame uint8_t* pImageData; // image data (an array of color-indices) int f; // frame number uint8_t aPalette[] = {0xFF, 0x00, 0x00, // red 0x00, 0xFF, 0x00, // green 0x00, 0x00, 0xFF}; // blue uint8_t aPalette_local[] = {0xFF, 0x00, 0x00, // red 0x00, 0xFF, 0x00, // green 0x00, 0x00, 0x00}; // black cgif_result r; uint8_t numColors_global = 3; // number of colors in global color palette (up to 256 possible) uint8_t numColors_local = 3; // number of colors in local color palette (up to 256 possible) int numFrames = 10; // number of frames in the animation // initialize the GIF-configuration and create a new GIF initGIFConfig(&gConfig, "global_plus_local_table_with_optim.gif", WIDTH, HEIGHT, aPalette, numColors_global); pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // create image frames and add them to GIF (use global color table first) pImageData = malloc(WIDTH * HEIGHT); // allocate memory for image data memset(pImageData, 0, WIDTH * HEIGHT); // set the background color to 1st color in global palette for (f = 0; f < numFrames-2; ++f) { pImageData[f + WIDTH * (HEIGHT/2)] = 1; initFrameConfig(&fConfig, pImageData, 20); // initialize the frame-configuration (3rd parameter is the delay in 0.01 s) r = cgif_addframe(pGIF, &fConfig); // append the new frame if(r != CGIF_OK) { break; } } // add frame with local color table (one additional black pixel) pImageData[WIDTH * (HEIGHT/2) + f] = 2; fConfig.pImageData = pImageData; fConfig.pLocalPalette = aPalette_local; fConfig.numLocalPaletteEntries = numColors_local; fConfig.attrFlags = CGIF_FRAME_ATTR_USE_LOCAL_TABLE; fConfig.delay = 200; // delay in 0.01 s r = cgif_addframe(pGIF, &fConfig); // append the new frame // add another frame with local color table pImageData[WIDTH * (HEIGHT/2) + f + 2] = 1; pImageData[WIDTH * (HEIGHT/2) + f + 4] = 1; pImageData[WIDTH * (HEIGHT/2) + f + 6] = 1; pImageData[WIDTH * (HEIGHT/2) + f + 8] = 1; r = cgif_addframe(pGIF, &fConfig); // append the new frame // refresh pattern and go on with global color table memset(pImageData, 0, WIDTH * HEIGHT); // set the background color to 1st color in global palette for (f = 0; f < numFrames-2; ++f) { pImageData[2*f + WIDTH * (HEIGHT/2)] = 2; initFrameConfig(&fConfig, pImageData, 50); // initialize the frame-configuration (3rd parameter is the delay in 0.01 s) r = cgif_addframe(pGIF, &fConfig); // append the new frame if(r != CGIF_OK) { break; } } free(pImageData); // free image data when all frames are added // close created GIF-file and free allocated space r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/has_transparency.c000066400000000000000000000032321420124357700173000ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 99 #define HEIGHT 99 int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black (transparent) 0xFF, 0xFF, 0xFF, // white }; cgif_result r; memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED | CGIF_ATTR_HAS_TRANSPARENCY; // first entry in color table is transparency gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 2; gConfig.path = "has_transparency.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); memset(pImageData, 0, WIDTH * HEIGHT); fConfig.pImageData = pImageData; fConfig.delay = 100; // create an off/on pattern for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = i % 2; } cgif_addframe(pGIF, &fConfig); // create opposite pattern for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = 1 - (i % 2); } r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // write GIF to file r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/has_transparency_2.c000066400000000000000000000032521420124357700175230ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 99 #define HEIGHT 99 int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black (transparent) 0xFF, 0xFF, 0xFF, // white }; cgif_result r; memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED | CGIF_ATTR_HAS_TRANSPARENCY; // first entry in color table is transparency gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 2; gConfig.path = "has_transparency_2.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); memset(pImageData, 0, WIDTH * HEIGHT); fConfig.pImageData = pImageData; fConfig.delay = 100; // create an off/on pattern for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = (i/10) % 2; } r = cgif_addframe(pGIF, &fConfig); // create opposite pattern for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = 1 - ((i/10) % 2); } r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // write GIF to file r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/local_transp.c000066400000000000000000000034441420124357700164220ustar00rootroot00000000000000#include #include #include #include #include #include "cgif.h" #define WIDTH 103 #define HEIGHT 104 /* This is an example code that creates a GIF-animation with a moving stripe-pattern. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[3 * 256] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; cgif_result r; uint16_t numColors = 256; // number of colors in aPalette // // Create new GIF // memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_NO_GLOBAL_TABLE | CGIF_ATTR_IS_ANIMATED; gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.path = "local_transp.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // Add frame to GIF // pImageData = malloc(WIDTH * HEIGHT); // Actual image data memset(pImageData, 1, WIDTH * HEIGHT); memset(pImageData + WIDTH * 2, 0, WIDTH * 3); fConfig.pImageData = pImageData; fConfig.pLocalPalette = aPalette; fConfig.numLocalPaletteEntries = numColors; fConfig.attrFlags = CGIF_FRAME_ATTR_USE_LOCAL_TABLE; fConfig.genFlags = CGIF_FRAME_GEN_USE_TRANSPARENCY; r = cgif_addframe(pGIF, &fConfig); // append the new frame r = cgif_addframe(pGIF, &fConfig); // append the new frame // free(pImageData); // // Free allocated space at the end of the session // r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/max_color_table_test.c000066400000000000000000000027541420124357700201350ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 /* This is an example code that creates a GIF-image with all green pixels. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t* aPalette; cgif_result r; uint16_t numColors = 256; // number of colors in aPalette // // create an image aPalette = malloc(256 * 3); memset(aPalette, 0, 256 * 3); pImageData = malloc(WIDTH * HEIGHT); // actual image data memset(pImageData, 0, WIDTH * HEIGHT); // // create new GIF memset(&gConfig, 0, sizeof(CGIF_Config)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "max_color_table_test.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(pImageData); free(aPalette); // // free allocated space at the end of the session r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/max_size.c000066400000000000000000000026501420124357700155560ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 0xFFFFUL #define HEIGHT 0xFFFFUL /* This is an example code that creates a GIF-image with all green pixels. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0xFF, 0x00, 0x00 }; cgif_result r; uint16_t numColors = 1; // number of colors in aPalette // // create an image pImageData = malloc(WIDTH * HEIGHT); // actual image data memset(pImageData, 0, WIDTH * HEIGHT); // // create new GIF memset(&gConfig, 0, sizeof(CGIF_Config)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "max_size.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // free allocated space at the end of the session r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/meson.build000066400000000000000000000022401420124357700157300ustar00rootroot00000000000000tests = [ 'all_optim', 'alpha', 'animated_color_gradient', 'animated_single_pixel', 'animated_snake', 'animated_stripes_horizontal', 'animated_stripe_pattern', 'animated_stripe_pattern_2', 'earlyclose', 'eindex', 'ewrite', 'global_plus_local_table', 'global_plus_local_table_with_optim', 'has_transparency', 'has_transparency_2', 'local_transp', 'max_color_table_test', 'min_color_table_test', 'min_size', 'more_than_256_colors', 'noise256', 'noise6', 'one_full_block', 'only_local_table', 'overlap_everything', 'overlap_everything_only_trans', 'overlap_some_rows', 'trans_inc_initdict', 'user_trans', 'write_fn', ] foreach t : tests test_exe = executable( 'test_' + t, t + '.c', dependencies : [libcgif_dep], include_directories : ['../inc/'], ) test(t, test_exe, priority : 0) endforeach md5sumc = find_program('scripts/md5sum.py') # get the ordering right: # md5sum check on output GIFs should be run once all of the above tests are done. test('check md5sums', md5sumc, args : [join_paths(meson.source_root(), 'tests/tests.md5')], workdir : meson.build_root(), priority : -1, is_parallel : false) cgif-0.2.0/tests/min_color_table_test.c000066400000000000000000000027401420124357700201260ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 /* This is an example code that creates a GIF-image with all green pixels. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0xFF, 0x00 }; // green cgif_result r; uint8_t numColors = 1; // number of colors in aPalette // // create an image with all green pixels pImageData = malloc(WIDTH * HEIGHT); // actual image data memset(pImageData, 0, WIDTH * HEIGHT); // set to all green // // create new GIF memset(&gConfig, 0, sizeof(CGIF_Config)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "min_color_table_test.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // free allocated space at the end of the session r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/min_size.c000066400000000000000000000026341420124357700155560ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 1 #define HEIGHT 1 /* This is an example code that creates a GIF-image with all green pixels. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0xFF, 0x00, 0x00 }; cgif_result r; uint16_t numColors = 1; // number of colors in aPalette // // create an image pImageData = malloc(WIDTH * HEIGHT); // actual image data memset(pImageData, 0, WIDTH * HEIGHT); // // create new GIF memset(&gConfig, 0, sizeof(CGIF_Config)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "min_size.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // free allocated space at the end of the session r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/more_than_256_colors.c000066400000000000000000000063101420124357700176650ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 255 #define HEIGHT 255 /* This code is an example of how a GIF with frames including more than 256 colors can be created by reusing colors from a previous frame. The general restriction of GIF that only 256 new colors can be introduced in one frame remains of course */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint16_t numColorsLocal = 255; uint8_t aLocalPalette[3*numColorsLocal]; // local palette uint8_t aPalette[3*numColorsLocal]; // global palette (only used for 1st frame here) cgif_result r; memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColorsLocal; gConfig.path = "more_than_256_colors.gif"; // define the global color palette for(int i=0; i #include #include #include #include "cgif.h" #define WIDTH 500 #define HEIGHT 500 static uint64_t seed; int psdrand(void) { // simple pseudo random function from musl libc seed = 6364136223846793005ULL * seed + 1; return seed >> 33; } int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; cgif_result r; uint8_t aPalette[256 * 3]; seed = 22; for(int i = 0; i < 256; ++i) { aPalette[i * 3] = psdrand() % 256; aPalette[i * 3 + 1] = psdrand() % 256; aPalette[i * 3 + 2] = psdrand() % 256; } memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 256; gConfig.path = "noise256.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); for(int i = 0; i < WIDTH * HEIGHT; ++i) pImageData[i] = psdrand() % 256; fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // write GIF to file r = cgif_close(pGIF); // free allocated space at the end of the session // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/noise6.c000066400000000000000000000031111420124357700151330ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 700 #define HEIGHT 320 static uint64_t seed; int psdrand(void) { // simple pseudo random function from musl libc seed = 6364136223846793005ULL * seed + 1; return seed >> 33; } int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; cgif_result r; uint8_t aPalette[6 * 3]; seed = 42; for(int i = 0; i < 6; ++i) { aPalette[i * 3] = psdrand() % 256; aPalette[i * 3 + 1] = psdrand() % 256; aPalette[i * 3 + 2] = psdrand() % 256; } memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 6; gConfig.path = "noise6.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); for(int i = 0; i < WIDTH * HEIGHT; ++i) pImageData[i] = psdrand() % 6; fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // write GIF to file r = cgif_close(pGIF); // free allocated space at the end of the session // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/one_full_block.c000066400000000000000000000030371420124357700167140ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 1 #define HEIGHT 224 // ceil((224 + 2) * 9bit) = 255byte (+2 for start- and clear-code) /* This test creates a GIF-frame with exactly 255 LZW bytes */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t* aPalette = malloc(3 * HEIGHT); cgif_result r; uint16_t numColors = HEIGHT; // number of colors in aPalette memset(aPalette, 0, 3 * HEIGHT); // // create an image pImageData = malloc(WIDTH * HEIGHT); // actual image data for(int i = 0; i < HEIGHT; ++i) pImageData[i] = i; // // create new GIF memset(&gConfig, 0, sizeof(CGIF_Config)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "one_full_block.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(aPalette); free(pImageData); // // free allocated space at the end of the session r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/only_local_table.c000066400000000000000000000032241420124357700172370ustar00rootroot00000000000000#include #include #include #include #include #include "cgif.h" #define WIDTH 103 #define HEIGHT 104 /* This is an example code that creates a GIF-animation with a moving stripe-pattern. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; cgif_result r; uint8_t numColors = 2; // number of colors in aPalette // // Create new GIF // memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_NO_GLOBAL_TABLE; gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.path = "only_local_table.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // Add frame to GIF // pImageData = malloc(WIDTH * HEIGHT); // Actual image data memset(pImageData, 1, WIDTH * HEIGHT); memset(pImageData + WIDTH * 2, 0, WIDTH * 3); fConfig.pImageData = pImageData; fConfig.pLocalPalette = aPalette; fConfig.numLocalPaletteEntries = numColors; fConfig.attrFlags = CGIF_FRAME_ATTR_USE_LOCAL_TABLE; r = cgif_addframe(pGIF, &fConfig); // append the new frame // free(pImageData); // // Free allocated space at the end of the session // r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/overlap_everything.c000066400000000000000000000035451420124357700176570ustar00rootroot00000000000000#include #include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 /* This is an example code that creates a GIF-animation with two identical black frames in sequence, using CGIF_FRAME_GEN_USE_DIFF_WINDOW*/ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; uint8_t numColors = 2; // number of colors in aPalette cgif_result r; int numFrames = 2; // number of frames in the video // // Create new GIF // memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "overlap_everything.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // Add frames to GIF // pImageData = malloc(WIDTH * HEIGHT); // Actual image data memset(pImageData, 0, WIDTH * HEIGHT); fConfig.pImageData = pImageData; fConfig.delay = 100; // set time before next frame (in units of 0.01 s) fConfig.genFlags = CGIF_FRAME_GEN_USE_DIFF_WINDOW; r = cgif_addframe(pGIF, &fConfig); // append the new frame r = cgif_addframe(pGIF, &fConfig); // append the next frame // free(pImageData); // // free allocated space at the end of the session r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/overlap_everything_only_trans.c000066400000000000000000000035621420124357700221260ustar00rootroot00000000000000#include #include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 /* This is an example code that creates a GIF-animation with two identical black frames in sequence, using CGIF_FRAME_GEN_USE_TRANSPARENCY*/ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; uint8_t numColors = 2; // number of colors in aPalette cgif_result r; int numFrames = 2; // number of frames in the video // // Create new GIF // memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "overlap_everything_only_trans.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // Add frames to GIF // pImageData = malloc(WIDTH * HEIGHT); // Actual image data memset(pImageData, 0, WIDTH * HEIGHT); fConfig.pImageData = pImageData; fConfig.delay = 100; // set time before next frame (in units of 0.01 s) fConfig.genFlags = CGIF_FRAME_GEN_USE_TRANSPARENCY; r = cgif_addframe(pGIF, &fConfig); // append the new frame r = cgif_addframe(pGIF, &fConfig); // append the next frame // free(pImageData); // // free allocated space at the end of the session r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/overlap_some_rows.c000066400000000000000000000037321420124357700175060ustar00rootroot00000000000000#include #include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 /* This is an example code that creates a GIF-animation with a moving stripe-pattern. */ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; uint8_t numColors = 2; // number of colors in aPalette cgif_result r; int numFrames = 2; // number of frames in the video // // Create new GIF // memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "overlap_some_rows.gif"; pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // Add frames to GIF // pImageData = malloc(WIDTH * HEIGHT); // Actual image data memset(pImageData, 0, WIDTH * HEIGHT); fConfig.pImageData = pImageData; fConfig.delay = 100; // set time before next frame (in units of 0.01 s) fConfig.genFlags = CGIF_FRAME_GEN_USE_DIFF_WINDOW; r = cgif_addframe(pGIF, &fConfig); // append the new frame memset(pImageData + 7, 1, 55); memset(pImageData + 7 + WIDTH, 1, 55); memset(pImageData + 7 + WIDTH * 2, 1, 55); memset(pImageData + 7 + WIDTH * 3, 1, 55); r = cgif_addframe(pGIF, &fConfig); // append the next frame // free(pImageData); // // Free allocated space at the end of the session // r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/performtests.sh000077500000000000000000000020601420124357700166620ustar00rootroot00000000000000#!/bin/sh # # Requires: # - tcc (tiny c compiler) # - ImageMagick # - gifsicle # RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' rAll=0 tcc -c -o cgif_raw.o -I../inc ../src/cgif_raw.c tcc -c -o cgif.o -I../inc ../src/cgif.c for sTest in *.c do printf %s "$sTest: " basename=`basename $sTest .c` tcc -o $basename.out -I../inc $sTest cgif.o cgif_raw.o idontcare=`./$basename.out` r=$? # # Check GIF with ImageMagick identify $basename.gif > /dev/null 2> /dev/null r=$(($r + $?)) # # Check GIF with gifsicle gifsicle --no-ignore-errors --info $basename.gif -o /dev/null 2> /dev/null r=$(($r + $?)) rm -f $basename.out rAll=$(($rAll + $r)) if [ $r != 0 ] then printf "${RED}FAILED${NC}\n" else printf "${GREEN}OK${NC}\n" fi done rm -f cgif.o cgif_raw.o # # check MD5 hashes md5sum -c --quiet tests.md5 rAll=$(($rAll + $?)) rm *.gif echo "-------------------------------" printf "result of all tests: " if [ $rAll != 0 ] then printf "${RED}FAILED${NC}\n" exit 1 # error else printf "${GREEN}OK${NC}\n" exit 0 # O.K. fi cgif-0.2.0/tests/scripts/000077500000000000000000000000001420124357700152575ustar00rootroot00000000000000cgif-0.2.0/tests/scripts/md5sum.py000077500000000000000000000025761420124357700170600ustar00rootroot00000000000000#!/usr/bin/env python3 import hashlib import re import sys # returns 0 if the md5sums are identical def checkFile(sExpMD5Hex, sFileName): try: f = open(sFileName, "rb") except OSError: print("%s: FAILED to open" % sFileName) return 2 byteData = f.read() f.close() # compare MD5 HEX digest sRealMD5Hex = hashlib.md5(byteData).hexdigest() if sRealMD5Hex == sExpMD5Hex: print("%s: OK" % sFileName) return 0 else: print("%s: FAILED" % sFileName) return 3 # handle input line in tests.md5 def handleLine(sLine): reComment = re.match("^\\s*#", sLine) if reComment != None: return 0 # line is comment # extract MD5 HEX digest and filename mLine = re.fullmatch("^\\s*([0-9a-f]{32})\\s*(.*)\\n$", sLine) if mLine == None: return 1 # error # extract info sMD5Hex = mLine.group(1) sFileName = mLine.group(2) return checkFile(sMD5Hex, sFileName) # ./md5sum.py if len(sys.argv) != 2: print("Invalid number of arguments.") exit(1) # try to open md5 input file try: f = open(sys.argv[1], "rt") except OSError: print("Failed to open ") exit(2) r = 0 sNextLine = f.readline() while sNextLine != "": r |= handleLine(sNextLine) sNextLine = f.readline() f.close() if r: print("md5sum.py: ERROR: At least 1 computed checksum did NOT match or error while parsing input checksums.") exit(3) cgif-0.2.0/tests/tests.md5000066400000000000000000000030561420124357700153450ustar00rootroot00000000000000cffd10a00f40b7e9c4a002251e5e9b7e all_optim.gif 3419edce150c2f30dc3979207f0e516d alpha.gif 334dc7f22fb6ad225f7e6c8c164eeaab animated_color_gradient.gif 9996ce895768140e06941161e6fb42ee animated_single_pixel.gif 59234443f5b84b0332c686c3416b6a7b animated_snake.gif b30bd5a37fbaf2eca0ed00fd0822627a animated_stripe_pattern_2.gif a8c2e6153396de90eaa1947178b1e383 animated_stripe_pattern.gif 7a86f68119152391ecb1f2cc68de00ef animated_stripes_horizontal.gif 0f099cb93337738ee8a91fe7b317d696 global_plus_local_table.gif 11b1703658c920b6f86d32827291dde4 global_plus_local_table_with_optim.gif 3eda7f993b36270db09929a289a5a106 has_transparency_2.gif c8bc7f745090e5f386e2c69f697c1343 has_transparency.gif b702bac8f8ded19ec1f58c1f80ee9208 local_transp.gif 8c0e65694eef4f3ed986381cfe914b50 max_color_table_test.gif # too large for CI: dd8cb01f5b02c16cc346b51d1faadc63 max_size.gif 30ab701c8e56bc8b09c824305002de5a min_color_table_test.gif f2bfeb47a7ecc834ae21140f02c60297 min_size.gif bcf6674e99f31e67da5bc17ad93cb7a7 more_than_256_colors.gif c6a8e6f6d6d0c969cb300ea7eb18391d noise256.gif 7de24bdbff1dec81aab1840bcf69b19f noise6.gif 3337de21095a6b19effb40c518af96e5 one_full_block.gif a7c04cb7b6d19a16023497da03384408 only_local_table.gif 013513307101f1d6b80164cdb3318c1c overlap_everything.gif 5a72df08de28f6a72dfed41c635db9e9 overlap_everything_only_trans.gif e9e789738541e15028dfe2abdbc67314 overlap_some_rows.gif 41b1b51c1aebb0ec72b44d0d2a395d58 trans_inc_initdict.gif 327d60a4a703fc6892061bd701ff6e58 user_trans.gif 4c58f4fcd9dbf119385a99cdcd353114 write_fn.gif cgif-0.2.0/tests/trans_inc_initdict.c000066400000000000000000000043201420124357700176020ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 200 #define HEIGHT 200 /* This is an example code that creates a moving stripe pattern. Special case: using CGIF_FRAME_GEN_USE_TRANSPARENCY requires increasing color table to next power of 2*/ int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white 0xFF, 0x00, 0x00, // red 0x00, 0xFF, 0x00, // green }; cgif_result r; uint8_t numColors = 4; // number of colors in aPalette int numFrames = 30; // number of frames in the video memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; // set needed attribution flag (as GIF is animated) gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = numColors; gConfig.path = "trans_inc_initdict.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); // actual image data fConfig.genFlags = CGIF_FRAME_GEN_USE_TRANSPARENCY; fConfig.pImageData = pImageData; // set pointer to image data fConfig.delay = 10; // set time before next frame (in units of 0.01 s) for (int f = 0; f < numFrames; ++f) { for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = (unsigned char)((f + i % WIDTH) % numColors); // ceate a moving stripe pattern } r = cgif_addframe(pGIF, &fConfig); // append the new frame if(r != CGIF_OK) { break; } } free(pImageData); // // write GIF to file r = cgif_close(pGIF); // free allocated space at the end of the session // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/user_trans.c000066400000000000000000000032141420124357700161210ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0xFF, 0x00, // green 0xFF, 0xFF, 0xFF, // white }; cgif_result r; memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED; gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 2; gConfig.path = "user_trans.gif"; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); memset(pImageData, 0, WIDTH * HEIGHT); fConfig.pImageData = pImageData; fConfig.delay = 100; // create an off/on pattern for (int i = 0; i < (WIDTH * HEIGHT); ++i) { pImageData[i] = i % 2; } cgif_addframe(pGIF, &fConfig); // set everything to transparent (frame from before shines through) memset(pImageData, 2, WIDTH * HEIGHT); fConfig.attrFlags = CGIF_FRAME_ATTR_HAS_SET_TRANS; fConfig.transIndex = 2; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // write GIF to file r = cgif_close(pGIF); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; } cgif-0.2.0/tests/write_fn.c000066400000000000000000000031441420124357700155530ustar00rootroot00000000000000#include #include #include #include #include "cgif.h" #define WIDTH 100 #define HEIGHT 100 static int pWriteFn(void* pContext, const uint8_t* pData, const size_t numBytes) { size_t r = fwrite(pData, 1, numBytes, (FILE*) pContext); if(r == numBytes) { return 0; } else { return -1; } } int main(void) { CGIF* pGIF; CGIF_Config gConfig; CGIF_FrameConfig fConfig; uint8_t* pImageData; uint8_t aPalette[] = { 0x00, 0x00, 0x00, // black 0xFF, 0xFF, 0xFF, // white }; cgif_result r; FILE* file = fopen("write_fn.gif", "wb"); memset(&gConfig, 0, sizeof(CGIF_Config)); memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); gConfig.width = WIDTH; gConfig.height = HEIGHT; gConfig.pGlobalPalette = aPalette; gConfig.numGlobalPaletteEntries = 2; gConfig.pWriteFn = pWriteFn; gConfig.pContext = (void*) file; // // create new GIF pGIF = cgif_newgif(&gConfig); if(pGIF == NULL) { fputs("failed to create new GIF via cgif_newgif()\n", stderr); return 1; } // // add frames to GIF pImageData = malloc(WIDTH * HEIGHT); memset(pImageData, 0, WIDTH * HEIGHT); fConfig.pImageData = pImageData; r = cgif_addframe(pGIF, &fConfig); free(pImageData); // // write GIF to file r = cgif_close(pGIF); // free allocated space at the end of the session fclose(file); // check for errors if(r != CGIF_OK) { fprintf(stderr, "failed to create GIF. error code: %d\n", r); return 2; } return 0; }