Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/cgif.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:cgif)
## CGIF, a GIF encoder written in C
## CGIF, the fastest GIF encoder

A fast and lightweight GIF encoder that can create GIF animations and images. Summary of the main features:
A fast and lightweight GIF encoder written in C. **Up to 4x faster than giflib and 5x faster than ffmpeg** - benchmarked across every major GIF encoder and every workload pattern.

### Performance

Encoding a single 8192x8192 frame, `-O3`, Apple Silicon:

| Pattern | cgif | giflib | giflib-turbo | ffmpeg | vs giflib | vs ffmpeg |
|---|---|---|---|---|---|---|
| **dither** (256 colors, structured) | **88 ms** | 360 ms | 348 ms | 511 ms | **4.10x** | **5.81x** |
| **solid** (single color) | **98 ms** | 227 ms | 115 ms | 351 ms | **2.10x** | **3.34x** |
| **gradient** (256 colors, smooth) | **112 ms** | 306 ms | 300 ms | 310 ms | **2.64x** | **2.77x** |
| **checker** (alternating pixels) | **115 ms** | 205 ms | 191 ms | 369 ms | **1.80x** | **3.19x** |
| **stripe** (3 colors, repeating) | **124 ms** | 252 ms | 246 ms | 409 ms | **2.02x** | **3.30x** |
| **noise** (256 colors, random) | **334 ms** | 709 ms | 607 ms | 550 ms | **2.09x** | **1.65x** |
| **fewnoise** (4 colors, random) | **361 ms** | 809 ms | 531 ms | 1,139 ms | **2.22x** | **3.16x** |

Reproduce with `bench/run.sh`. Requires [hyperfine](https://github.com/sharkdp/hyperfine).

### Features
- user-defined global or local color-palette with up to 256 colors (limit of the GIF format)
- True Color to GIF conversion (RGB/RGBA input) with quantization and dithering
- 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
Expand Down
98 changes: 98 additions & 0 deletions bench/cgif_bench.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include "cgif.h"

#define WIDTH 8192
#define HEIGHT 8192

static uint64_t seed = 42;

__attribute__((no_sanitize("integer")))
static int psdrand(void) {
seed = 6364136223846793005ULL * seed + 1;
return seed >> 33;
}

static void encode(const char *path, uint8_t *palette, uint16_t numColors, uint8_t *pImageData) {
CGIF_Config gConfig;
CGIF_FrameConfig fConfig;

memset(&gConfig, 0, sizeof(gConfig));
gConfig.width = WIDTH;
gConfig.height = HEIGHT;
gConfig.pGlobalPalette = palette;
gConfig.numGlobalPaletteEntries = numColors;
gConfig.path = path;

CGIF *pGIF = cgif_newgif(&gConfig);
memset(&fConfig, 0, sizeof(fConfig));
fConfig.pImageData = pImageData;
cgif_addframe(pGIF, &fConfig);
cgif_close(pGIF);
}

int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "usage: %s <stripe|gradient|noise|solid>\n", argv[0]);
return 1;
}

uint8_t palette256[256 * 3];
for (int i = 0; i < 256; ++i) {
palette256[i * 3] = i;
palette256[i * 3 + 1] = i;
palette256[i * 3 + 2] = i;
}

uint8_t palette3[] = {0xFF,0x00,0x00, 0x00,0xFF,0x00, 0x00,0x00,0xFF};
uint8_t palette4[] = {0xFF,0x00,0x00, 0x00,0xFF,0x00, 0x00,0x00,0xFF, 0xFF,0xFF,0x00};
uint8_t *img = malloc(WIDTH * HEIGHT);

if (strcmp(argv[1], "stripe") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = (i % WIDTH) / 4 % 3;
encode("/dev/null", palette3, 3, img);

} else if (strcmp(argv[1], "gradient") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = (i % WIDTH) * 256 / WIDTH;
encode("/dev/null", palette256, 256, img);

} else if (strcmp(argv[1], "noise") == 0) {
seed = 42;
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = psdrand() % 256;
encode("/dev/null", palette256, 256, img);

} else if (strcmp(argv[1], "solid") == 0) {
memset(img, 0, WIDTH * HEIGHT);
encode("/dev/null", palette3, 3, img);

} else if (strcmp(argv[1], "checker") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = (((i % WIDTH) + (i / WIDTH)) & 1);
encode("/dev/null", palette3, 3, img);

} else if (strcmp(argv[1], "dither") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = ((i % WIDTH) + (i / WIDTH) * 7) % 256;
encode("/dev/null", palette256, 256, img);

} else if (strcmp(argv[1], "fewnoise") == 0) {
seed = 42;
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = psdrand() % 4;
encode("/dev/null", palette4, 4, img);

} else {
fprintf(stderr, "unknown pattern: %s\n", argv[1]);
free(img);
return 1;
}

free(img);
return 0;
}
110 changes: 110 additions & 0 deletions bench/ffmpeg_bench.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libavutil/imgutils.h>

#define WIDTH 8192
#define HEIGHT 8192

static uint64_t seed = 42;

__attribute__((no_sanitize("integer")))
static int psdrand(void) {
seed = 6364136223846793005ULL * seed + 1;
return seed >> 33;
}

static void encode(const char *path, uint32_t *palette, uint8_t *img) {
const AVCodec *codec = avcodec_find_encoder_by_name("gif");
AVCodecContext *ctx = avcodec_alloc_context3(codec);
ctx->width = WIDTH;
ctx->height = HEIGHT;
ctx->pix_fmt = AV_PIX_FMT_PAL8;
ctx->time_base = (AVRational){1, 25};
avcodec_open2(ctx, codec, NULL);

AVFrame *frame = av_frame_alloc();
frame->format = AV_PIX_FMT_PAL8;
frame->width = WIDTH;
frame->height = HEIGHT;
frame->pts = 0;
av_frame_get_buffer(frame, 0);

memcpy(frame->data[1], palette, 256 * 4);
for (int y = 0; y < HEIGHT; ++y)
memcpy(frame->data[0] + y * frame->linesize[0], img + y * WIDTH, WIDTH);

AVPacket *pkt = av_packet_alloc();
avcodec_send_frame(ctx, frame);
avcodec_send_frame(ctx, NULL);

FILE *f = fopen(path, "wb");
while (avcodec_receive_packet(ctx, pkt) == 0) {
fwrite(pkt->data, 1, pkt->size, f);
av_packet_unref(pkt);
}
fclose(f);

av_packet_free(&pkt);
av_frame_free(&frame);
avcodec_free_context(&ctx);
}

int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "usage: %s <stripe|gradient|noise|solid>\n", argv[0]);
return 1;
}

uint32_t palette256[256];
for (int i = 0; i < 256; ++i)
palette256[i] = 0xFF000000 | (i << 16) | (i << 8) | i;

uint32_t palette3[256] = {0};
palette3[0] = 0xFF0000FF;
palette3[1] = 0xFF00FF00;
palette3[2] = 0xFFFF0000;

uint32_t palette4[256] = {0};
palette4[0] = 0xFF0000FF;
palette4[1] = 0xFF00FF00;
palette4[2] = 0xFFFF0000;
palette4[3] = 0xFF00FFFF;

uint8_t *img = malloc(WIDTH * HEIGHT);

if (strcmp(argv[1], "stripe") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i) img[i] = (i % WIDTH) / 4 % 3;
encode("/dev/null", palette3, img);
} else if (strcmp(argv[1], "gradient") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i) img[i] = (i % WIDTH) * 256 / WIDTH;
encode("/dev/null", palette256, img);
} else if (strcmp(argv[1], "noise") == 0) {
seed = 42;
for (int i = 0; i < WIDTH * HEIGHT; ++i) img[i] = psdrand() % 256;
encode("/dev/null", palette256, img);
} else if (strcmp(argv[1], "solid") == 0) {
memset(img, 0, WIDTH * HEIGHT);
encode("/dev/null", palette3, img);
} else if (strcmp(argv[1], "checker") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i) img[i] = (((i % WIDTH) + (i / WIDTH)) & 1);
encode("/dev/null", palette3, img);
} else if (strcmp(argv[1], "dither") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i) img[i] = ((i % WIDTH) + (i / WIDTH) * 7) % 256;
encode("/dev/null", palette256, img);
} else if (strcmp(argv[1], "fewnoise") == 0) {
seed = 42;
for (int i = 0; i < WIDTH * HEIGHT; ++i) img[i] = psdrand() % 4;
encode("/dev/null", palette4, img);
} else {
fprintf(stderr, "unknown pattern: %s\n", argv[1]);
free(img);
return 1;
}

free(img);
return 0;
}
97 changes: 97 additions & 0 deletions bench/giflib_bench.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <gif_lib.h>

#define WIDTH 8192
#define HEIGHT 8192

static uint64_t seed = 42;

__attribute__((no_sanitize("integer")))
static int psdrand(void) {
seed = 6364136223846793005ULL * seed + 1;
return seed >> 33;
}

static void encode(const char *path, GifColorType *colors, int numColors, uint8_t *img) {
int error;
int mapSize = 1;
while (mapSize < numColors) mapSize *= 2;

GifFileType *gif = EGifOpenFileName(path, false, &error);
ColorMapObject *cmap = GifMakeMapObject(mapSize, NULL);
memcpy(cmap->Colors, colors, numColors * sizeof(GifColorType));

EGifSetGifVersion(gif, true);
EGifPutScreenDesc(gif, WIDTH, HEIGHT, 8, 0, cmap);
EGifPutImageDesc(gif, 0, 0, WIDTH, HEIGHT, false, NULL);

for (int y = 0; y < HEIGHT; ++y)
EGifPutLine(gif, img + y * WIDTH, WIDTH);

GifFreeMapObject(cmap);
EGifCloseFile(gif, &error);
}

int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "usage: %s <stripe|gradient|noise|solid>\n", argv[0]);
return 1;
}

GifColorType palette256[256];
for (int i = 0; i < 256; ++i) {
palette256[i].Red = i; palette256[i].Green = i; palette256[i].Blue = i;
}

GifColorType palette3[3] = {{0xFF,0,0},{0,0xFF,0},{0,0,0xFF}};
GifColorType palette4[4] = {{0xFF,0,0},{0,0xFF,0},{0,0,0xFF},{0xFF,0xFF,0}};
uint8_t *img = malloc(WIDTH * HEIGHT);

if (strcmp(argv[1], "stripe") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = (i % WIDTH) / 4 % 3;
encode("/dev/null", palette3, 3, img);

} else if (strcmp(argv[1], "gradient") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = (i % WIDTH) * 256 / WIDTH;
encode("/dev/null", palette256, 256, img);

} else if (strcmp(argv[1], "noise") == 0) {
seed = 42;
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = psdrand() % 256;
encode("/dev/null", palette256, 256, img);

} else if (strcmp(argv[1], "solid") == 0) {
memset(img, 0, WIDTH * HEIGHT);
encode("/dev/null", palette3, 3, img);

} else if (strcmp(argv[1], "checker") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = (((i % WIDTH) + (i / WIDTH)) & 1);
encode("/dev/null", palette3, 3, img);

} else if (strcmp(argv[1], "dither") == 0) {
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = ((i % WIDTH) + (i / WIDTH) * 7) % 256;
encode("/dev/null", palette256, 256, img);

} else if (strcmp(argv[1], "fewnoise") == 0) {
seed = 42;
for (int i = 0; i < WIDTH * HEIGHT; ++i)
img[i] = psdrand() % 4;
encode("/dev/null", palette4, 4, img);

} else {
fprintf(stderr, "unknown pattern: %s\n", argv[1]);
free(img);
return 1;
}

free(img);
return 0;
}
Loading
Loading