Skip to content

Commit

Permalink
Merge pull request #72 from tlsa/tlsa/perf-optimise
Browse files Browse the repository at this point in the history
Optimize frame comparison
  • Loading branch information
dloebl authored Aug 13, 2024
2 parents 6945c56 + 94a67d1 commit c234bc2
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 21 deletions.
144 changes: 123 additions & 21 deletions src/cgif.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,8 @@ static int cmpPixel(const CGIF* pGIF, const CGIF_FrameConfig* pCur, const CGIF_F
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;
// compare given frames; returns 0 if frames are equal and 1 if they differ. If they differ, pResult returns area of difference
static int getDiffArea(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
const uint8_t* pCurImageData;
const uint8_t* pBefImageData;
uint16_t i, x;
Expand All @@ -181,13 +180,8 @@ static uint8_t* doWidthHeightOptim(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_Fram
++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;
if(i == height) {
return 0;
}
newTop = i;

Expand All @@ -212,7 +206,7 @@ static uint8_t* doWidthHeightOptim(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_Fram
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)
++x; //(x==width cannot happen as return 0 is trigged in the only possible case before)
i = newTop;
}
}
Expand All @@ -224,25 +218,126 @@ static uint8_t* doWidthHeightOptim(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_Fram
while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
++i;
if(i > (newTop + newHeight - 1)) {
--x; //(x<newLeft cannot happen as goto Done is triggered in the only possible case before)
--x; //(x<newLeft cannot happen as return 0 is trigged in the only possible case before)
i = newTop;
}
}
newWidth = (x + 1) - newLeft;

Done:
pResult->width = newWidth;
pResult->height = newHeight;
pResult->top = newTop;
pResult->left = newLeft;
return 1;
}

// compare given global palette frames; returns 0 if frames are equal and 1 if they differ. If they differ, pResult returns area of difference
static int getDiffAreaGlobalPalette(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
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;
size_t offset;

// create new image data
pNewImageData = malloc(MULU16(newWidth, newHeight)); // TBD check return value of malloc
for (i = 0; i < newHeight; ++i) {
memcpy(pNewImageData + MULU16(i, newWidth), pCurImageData + MULU16((i + newTop), width) + newLeft, newWidth);
pCurImageData = pCur->pImageData;
pBefImageData = pBef->pImageData;
// find top
i = 0;
offset = 0;
while(i < height) {
if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
break;
}
++i;
offset += width;
}

if(i == height) {
return 0;
}
newTop = i;

// find actual height
i = height - 1;
offset = i * width;
while(i > newTop) {
if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
break;
}
--i;
offset -= width;
}
newHeight = (i + 1) - newTop;

// find left
i = newTop;
x = 0;
offset = i * width;
while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
++i;
offset += width;
if(i > (newTop + newHeight - 1)) {
++x; //(x==width cannot happen as return 0 is triggered in the only possible case before)
i = newTop;
offset = i * width;
}
}
newLeft = x;

// find actual width
i = newTop;
x = width - 1;
offset = i * width;
while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
++i;
offset += width;
if(i > (newTop + newHeight - 1)) {
--x; //(x<newLeft cannot happen as return 0 is triggered in the only possible case before)
i = newTop;
offset = i * width;
}
}
newWidth = (x + 1) - newLeft;

// set new width, height, top, left in DimResult struct
pResult->width = newWidth;
pResult->height = newHeight;
pResult->top = newTop;
pResult->left = newLeft;
return 1;
}

/* 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) {
uint16_t i;
uint8_t* pNewImageData;
const uint16_t width = pGIF->config.width;
const uint8_t* pCurImageData = pCur->pImageData;
int diffFrame;

if ((pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0 && (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0
&& (pBef->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0 && (pCur->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0) {
// Both frames use global palette; use fast comparison.
diffFrame = getDiffAreaGlobalPalette(pGIF, pCur, pBef, pResult);
} else {
diffFrame = getDiffArea(pGIF, pCur, pBef, pResult);
}

if (diffFrame == 0) { // need dummy pixel (frame is identical with one before)
// TBD we might make it possible to merge identical frames in the future
pResult->width = 1;
pResult->height = 1;
pResult->left = 0;
pResult->top = 0;
}

// create new image data
pNewImageData = malloc(MULU16(pResult->width, pResult->height)); // TBD check return value of malloc
for (i = 0; i < pResult->height; ++i) {
memcpy(pNewImageData + MULU16(i, pResult->width), pCurImageData + MULU16((i + pResult->top), width) + pResult->left, pResult->width);
}

return pNewImageData;
}

Expand Down Expand Up @@ -401,10 +496,17 @@ int cgif_addframe(CGIF* pGIF, CGIF_FrameConfig* pConfig) {
const uint32_t frameDelay = pConfig->delay + pGIF->aFrames[pGIF->iHEAD]->config.delay;
if(frameDelay <= 0xFFFF && !(pGIF->config.genFlags & CGIF_GEN_KEEP_IDENT_FRAMES)) {
int sameFrame = 1;
for(i = 0; i < pGIF->config.width * pGIF->config.height; i++) {
if(cmpPixel(pGIF, pConfig, &pGIF->aFrames[pGIF->iHEAD]->config, pConfig->pImageData[i], pGIF->aFrames[pGIF->iHEAD]->config.pImageData[i])) {
if ((pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0 && (pGIF->aFrames[pGIF->iHEAD]->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0
&& (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0 && (pGIF->aFrames[pGIF->iHEAD]->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0) {
if (memcmp(pConfig->pImageData, pGIF->aFrames[pGIF->iHEAD]->config.pImageData, pGIF->config.width * pGIF->config.height)) {
sameFrame = 0;
break;
}
} else {
for(i = 0; i < pGIF->config.width * pGIF->config.height; i++) {
if(cmpPixel(pGIF, pConfig, &pGIF->aFrames[pGIF->iHEAD]->config, pConfig->pImageData[i], pGIF->aFrames[pGIF->iHEAD]->config.pImageData[i])) {
sameFrame = 0;
break;
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ tests = [
{ 'name' : 'stripe_pattern_interlaced', 'seed_should_fail' : false},
{ 'name' : 'switchpattern', 'seed_should_fail' : false},
{ 'name' : 'trans_inc_initdict', 'seed_should_fail' : false},
{ 'name' : 'user_trans_diff_area', 'seed_should_fail' : false},
{ 'name' : 'user_trans_merge', 'seed_should_fail' : false},
{ 'name' : 'user_trans', 'seed_should_fail' : false},
{ 'name' : 'write_fn', 'seed_should_fail' : false},
]
Expand Down
2 changes: 2 additions & 0 deletions tests/tests.sha256
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ ff1da19b0448af07d8561f0ee62b184f99ac7dcb75f1a2215474190ec6648901 noise6.gif
161a132972bfbc060ea0359d8c40513d2e22e65b27a7c11553f883fe3856fe30 stripe_pattern_interlaced.gif
b8a7e72024a1263229e85f27168800400489cf993f7b90f45f00d39323763261 switchpattern.gif
1eb29910b6633bc1c6be49fc85ba7f5c24f415083d81f35c366ea50d55bcdf8d trans_inc_initdict.gif
0e02f2440b2db58268f6ef7906e41b088177e9fcdb2cb37e9540f4bfc9a7fa17 user_trans_diff_area.gif
55b64d9c9a359f9daeecdf55c83e485aca3f90d2a6dd29f86d5b14a0e1396770 user_trans.gif
86a08337540a8332dea3cb092394c3aac04fbbe98d9d884eb169a6c85d20f9a6 user_trans_merge.gif
5d301094a0b54287cabcc71c6972f7be417a5bea87cde01d85089a8893fd17fd write_fn.gif
62 changes: 62 additions & 0 deletions tests/user_trans_diff_area.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include "cgif.h"

#define WIDTH 2
#define HEIGHT 2

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.genFlags = CGIF_GEN_KEEP_IDENT_FRAMES;
gConfig.width = WIDTH;
gConfig.height = HEIGHT;
gConfig.pGlobalPalette = aPalette;
gConfig.numGlobalPaletteEntries = 2;
gConfig.path = "user_trans_diff_area.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 = 500;
cgif_addframe(pGIF, &fConfig);
fConfig.attrFlags = CGIF_FRAME_ATTR_HAS_SET_TRANS;
fConfig.genFlags = CGIF_FRAME_GEN_USE_DIFF_WINDOW;
fConfig.transIndex = 1;
// set everything to transparent (frame from before shines through)
memset(pImageData, 1, WIDTH * HEIGHT);
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;
}
61 changes: 61 additions & 0 deletions tests/user_trans_merge.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include "cgif.h"

#define WIDTH 2
#define HEIGHT 2

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_merge.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 = 500;
cgif_addframe(pGIF, &fConfig);
fConfig.attrFlags = CGIF_FRAME_ATTR_HAS_SET_TRANS;
fConfig.genFlags = CGIF_FRAME_GEN_USE_DIFF_WINDOW;
fConfig.transIndex = 1;
// set everything to transparent (frame from before shines through)
memset(pImageData, 1, WIDTH * HEIGHT);
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;
}

0 comments on commit c234bc2

Please sign in to comment.