Skip to content

Commit

Permalink
Merge pull request #50 from dloebl/interlaced
Browse files Browse the repository at this point in the history
Add support for interlaced encoding
  • Loading branch information
dloebl authored Apr 19, 2022
2 parents b5ae14f + 547bd19 commit b05de32
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ CGIF_ATTR_NO_LOOP // run GIF animation only one time. numLoops
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_ATTR_INTERLACED // encode frame interlaced
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)
```
Expand Down
1 change: 1 addition & 0 deletions inc/cgif.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extern "C" {
#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)
#define CGIF_FRAME_ATTR_INTERLACED (1uL << 3) // encode frame interlaced (default is not interlaced)
// 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
Expand Down
3 changes: 2 additions & 1 deletion inc/cgif_raw.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ extern "C" {
#define CGIF_RAW_ATTR_NO_LOOP (1uL << 1) // don't loop a GIF animation: only play it one time.

// flags to set the Frame attributes
#define CGIF_RAW_FRAME_ATTR_HAS_TRANS (1uL << 0) // provided transIndex should be set
#define CGIF_RAW_FRAME_ATTR_HAS_TRANS (1uL << 0) // provided transIndex should be set
#define CGIF_RAW_FRAME_ATTR_INTERLACED (1uL << 1) // encode frame interlaced

// CGIFRaw_Config type
// note: internal sections, subject to change.
Expand Down
1 change: 1 addition & 0 deletions src/cgif.c
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ static cgif_result flushFrame(CGIF* pGIF, CGIF_Frame* pCur, CGIF_Frame* pBef) {
if(hasAlpha || (pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) || hasSetTransp) {
rawConfig.attrFlags |= CGIF_RAW_FRAME_ATTR_HAS_TRANS;
}
rawConfig.attrFlags |= (pCur->config.attrFlags & CGIF_FRAME_ATTR_INTERLACED) ? CGIF_RAW_FRAME_ATTR_INTERLACED : 0;
rawConfig.width = width;
rawConfig.height = height;
rawConfig.top = top;
Expand Down
39 changes: 38 additions & 1 deletion src/cgif_raw.c
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ cgif_result cgif_raw_addframe(CGIFRaw* pGIF, const CGIFRaw_FrameConfig* pConfig)
LZWResult encResult;
int r, rWrite;
const int useLCT = pConfig->sizeLCT; // LCT stands for "local color table"
const int isInterlaced = (pConfig->attrFlags & CGIF_RAW_FRAME_ATTR_INTERLACED) ? 1 : 0;
uint16_t numEffColors; // number of effective colors
uint16_t initDictLen;
uint8_t pow2LCT, initCodeLen;
Expand Down Expand Up @@ -501,6 +502,8 @@ cgif_result cgif_raw_addframe(CGIFRaw* pGIF, const CGIFRaw_FrameConfig* pConfig)
} else {
numEffColors = pGIF->config.sizeGCT; // global color table in use
}
// encode frame interlaced?
IMAGE_PACKED_FIELD(aFrameHeader) |= (isInterlaced << 6);

// 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) {
Expand All @@ -519,9 +522,43 @@ cgif_result cgif_raw_addframe(CGIFRaw* pGIF, const CGIFRaw_FrameConfig* pConfig)
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));
// apply interlaced pattern
// TBD creating a copy of pImageData is not ideal, but changes on the LZW encoding would
// be necessary otherwise.
if(isInterlaced) {
uint8_t* pInterlaced = malloc(MULU16(pConfig->width, pConfig->height));
if(pInterlaced == NULL) {
pGIF->curResult = CGIF_EALLOC;
return pGIF->curResult;
}
uint8_t* p = pInterlaced;
// every 8th row (starting with row 0)
for(uint32_t i = 0; i < pConfig->height; i += 8) {
memcpy(p, pConfig->pImageData + i * pConfig->width, pConfig->width);
p += pConfig->width;
}
// every 8th row (starting with row 4)
for(uint32_t i = 4; i < pConfig->height; i += 8) {
memcpy(p, pConfig->pImageData + i * pConfig->width, pConfig->width);
p += pConfig->width;
}
// every 4th row (starting with row 2)
for(uint32_t i = 2; i < pConfig->height; i += 4) {
memcpy(p, pConfig->pImageData + i * pConfig->width, pConfig->width);
p += pConfig->width;
}
// every 2th row (starting with row 1)
for(uint32_t i = 1; i < pConfig->height; i += 2) {
memcpy(p, pConfig->pImageData + i * pConfig->width, pConfig->width);
p += pConfig->width;
}
r = LZW_GenerateStream(&encResult, MULU16(pConfig->width, pConfig->height), pInterlaced, initDictLen, initCodeLen);
free(pInterlaced);
} else {
r = LZW_GenerateStream(&encResult, MULU16(pConfig->width, pConfig->height), pConfig->pImageData, initDictLen, initCodeLen);
}

// 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;
Expand Down
99 changes: 99 additions & 0 deletions tests/animated_interlaced.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#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->attrFlags = CGIF_FRAME_ATTR_INTERLACED;
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_interlaced.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 && y<WIDTH-2) { // rules for moving the snake...
y++;
} else if (y==WIDTH-2 && x<WIDTH-2){
x++;
} else if (x==WIDTH-2 && y>1){
y--;
} else{
x--;
}
if (x1==1 && y1<WIDTH-2) {
y1++;
} else if (y1==WIDTH-2 && x1<WIDTH-2){
x1++;
} else if (x1==WIDTH-2 && y1>1){
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;
}
3 changes: 3 additions & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ tests = [
'all_optim',
'alpha',
'animated_color_gradient',
'animated_interlaced',
'animated_single_pixel',
'animated_snake',
'animated_stripes_horizontal',
Expand All @@ -22,12 +23,14 @@ tests = [
'more_than_256_colors',
'noise256',
'noise6',
'noise6_interlaced',
'noloop',
'one_full_block',
'only_local_table',
'overlap_everything',
'overlap_everything_only_trans',
'overlap_some_rows',
'stripe_pattern_interlaced',
'trans_inc_initdict',
'user_trans',
'write_fn',
Expand Down
65 changes: 65 additions & 0 deletions tests/noise6_interlaced.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#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_interlaced.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;
fConfig.attrFlags = CGIF_FRAME_ATTR_INTERLACED;
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;
}
57 changes: 57 additions & 0 deletions tests/stripe_pattern_interlaced.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include "cgif.h"

#define WIDTH 1000
#define HEIGHT 1000

/* horizontal stripe pattern with 1pixel linewidth, to test interlaced mode */
int main(void) {
CGIF* pGIF;
CGIF_Config gConfig;
CGIF_FrameConfig fConfig;
uint8_t* pImageData;
cgif_result r;

uint8_t aPalette[] = {
0x00, 0xFF, 0x00, // green
0xFF, 0x00, 0xFF, // purple
};
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.path = "stripe_pattern_interlaced.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 < HEIGHT; ++i){
memset(pImageData + i*WIDTH, i % 2, WIDTH);
}
fConfig.pImageData = pImageData;
fConfig.attrFlags = CGIF_FRAME_ATTR_INTERLACED;
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;
}
3 changes: 3 additions & 0 deletions tests/tests.md5
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
cffd10a00f40b7e9c4a002251e5e9b7e all_optim.gif
3419edce150c2f30dc3979207f0e516d alpha.gif
334dc7f22fb6ad225f7e6c8c164eeaab animated_color_gradient.gif
1733f62baf5491f9ab2e0b85b944d02e animated_interlaced.gif
9996ce895768140e06941161e6fb42ee animated_single_pixel.gif
59234443f5b84b0332c686c3416b6a7b animated_snake.gif
b30bd5a37fbaf2eca0ed00fd0822627a animated_stripe_pattern_2.gif
Expand All @@ -18,12 +19,14 @@ f2bfeb47a7ecc834ae21140f02c60297 min_size.gif
bcf6674e99f31e67da5bc17ad93cb7a7 more_than_256_colors.gif
c6a8e6f6d6d0c969cb300ea7eb18391d noise256.gif
7de24bdbff1dec81aab1840bcf69b19f noise6.gif
06616047c7eba89ea74e0d1783e44c78 noise6_interlaced.gif
92336ea86024feb0a65bc21fde9bee61 noloop.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
aa31b2280914bb30b9b9525f8e0b87d8 stripe_pattern_interlaced.gif
41b1b51c1aebb0ec72b44d0d2a395d58 trans_inc_initdict.gif
327d60a4a703fc6892061bd701ff6e58 user_trans.gif
4c58f4fcd9dbf119385a99cdcd353114 write_fn.gif

0 comments on commit b05de32

Please sign in to comment.