Skip to content

Commit 509a8b8

Browse files
authored
avoid rounding boundary case in color quantization (#125)
* avoid rounding boundary case in color quantization * naming consistency
1 parent 0769718 commit 509a8b8

3 files changed

Lines changed: 124 additions & 13 deletions

File tree

src/cgif_rgb.c

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ typedef struct {
2626
typedef struct treeNode {
2727
struct treeNode* child0; // pointer to child-node (elements smaller or equal than mean)
2828
struct treeNode* child1; // pointer to child-node (elements larger than mean)
29-
float mean[3]; // average of the colors
29+
double mean[3]; // average of the colors
3030
uint32_t idxMin, idxMax; // minimum and maximum index referring to global palette
3131
uint8_t cutDim; // dimension along which the cut (node split) is done
3232
uint8_t colIdx; // color index (only meaningful for leave node)
@@ -43,9 +43,9 @@ typedef struct colHashTable {
4343
uint32_t tableSize; // initial size of the hash table
4444
} colHashTable;
4545

46-
static uint64_t argmax64(float* arry, uint64_t n){
46+
static uint64_t argmax64(double* arry, uint64_t n){
4747
uint64_t imax = 0;
48-
float vmax = 0;
48+
double vmax = 0;
4949

5050
for(uint64_t i = 0; i < n; ++i) {
5151
if(arry[i] > vmax){
@@ -219,13 +219,13 @@ static uint32_t* hash_to_dense(colHashTable* colhash, cgif_chan_fmt fmtChan) {
219219
}
220220

221221
/* get mean of color-cloud along all 3 dimensions (at least one color must be present, otherwise div 0 issue)*/
222-
static void get_mean(const uint8_t* pPalette, const uint32_t* frequ, uint32_t idxMin, uint32_t idxMax, float* mean){
222+
static void get_mean(const uint8_t* pPalette, const uint32_t* frequ, uint32_t idxMin, uint32_t idxMax, double* mean){
223223
// pPalette: RGB color palette
224224
// frequ: frequency of the colors (indexing as pPalette, no hashing)
225225
// idxMin, idxMax: palette range for which the mean is computed
226226
// mean: mean value along all three dimensions
227-
float m[3] = {0,0,0};
228-
float sum[3] = {0,0,0};
227+
double m[3] = {0,0,0};
228+
double sum[3] = {0,0,0};
229229
uint32_t i;
230230
uint8_t dim;
231231

@@ -241,15 +241,15 @@ static void get_mean(const uint8_t* pPalette, const uint32_t* frequ, uint32_t id
241241
}
242242

243243
/* get variance of color-cloud along all 3 dimensions*/
244-
static void get_variance(const uint8_t* pPalette, const uint32_t* frequ, uint32_t idxMin, uint32_t idxMax, float* var, float* mean){
244+
static void get_variance(const uint8_t* pPalette, const uint32_t* frequ, uint32_t idxMin, uint32_t idxMax, double* var, double* mean){
245245
// pPalette: RGB color palette
246246
// frequ: frequency of the colors (indexing as pPalette, no hashing)
247247
// idxMin, idxMax: palette range for which the variance is computed
248248
// var/mean: variance/mean value along all three dimensions (array with 3 entries)
249249
uint32_t i;
250250
uint8_t dim;
251-
float v[3] = {0,0,0};
252-
float sum[3] = {0,0,0};
251+
double v[3] = {0,0,0};
252+
double sum[3] = {0,0,0};
253253

254254
get_mean(pPalette, frequ, idxMin, idxMax, mean);
255255
for(i = idxMin; i <= idxMax; ++i) {
@@ -264,7 +264,7 @@ static void get_variance(const uint8_t* pPalette, const uint32_t* frequ, uint32_
264264
}
265265

266266
static treeNode* new_tree_node(uint8_t* pPalette, uint32_t* frequ, uint16_t* numLeaveNodes, uint32_t idxMin, uint32_t idxMax, uint8_t colIdx) {
267-
float var[3];
267+
double var[3];
268268

269269
treeNode* node = malloc(sizeof(treeNode));
270270
if(node == NULL) {
@@ -326,9 +326,9 @@ static void get_palette_from_decision_tree(const treeNode* root, uint8_t* pPalet
326326
get_palette_from_decision_tree(root->child0, pPalette256);
327327
get_palette_from_decision_tree(root->child1, pPalette256);
328328
} else {
329-
pPalette256[3 * root->colIdx] = (uint8_t)roundf(root->mean[0]);
330-
pPalette256[3 * root->colIdx + 1] = (uint8_t)roundf(root->mean[1]);
331-
pPalette256[3 * root->colIdx + 2] = (uint8_t)roundf(root->mean[2]);
329+
pPalette256[3 * root->colIdx] = (uint8_t)round(root->mean[0]);
330+
pPalette256[3 * root->colIdx + 1] = (uint8_t)round(root->mean[1]);
331+
pPalette256[3 * root->colIdx + 2] = (uint8_t)round(root->mean[2]);
332332
}
333333
}
334334

tests/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ tests_rgb = [
6262
{ 'name' : 'rgb_no_dithering', 'seed_should_fail' : false},
6363
{ 'name' : 'rgb_noise', 'seed_should_fail' : false},
6464
{ 'name' : 'rgb_noise_animated', 'seed_should_fail' : false},
65+
{ 'name' : 'rgb_col_quant_float_issue', 'seed_should_fail' : false},
6566
]
6667

6768
foreach t : tests_index + tests_rgb

tests/rgb_col_quant_float_issue.c

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#include <cgif.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <stdio.h>
5+
#include <stdint.h>
6+
7+
static int writecb(void* pContext, const uint8_t* pData, size_t size) {
8+
(void)pContext; (void)pData; (void)size;
9+
return 0;
10+
}
11+
12+
int main() {
13+
/*
14+
* before treeNode.mean in cgif_rgb.c was changed from float to double: Heap buffer over-read in cgif_rgb.c crawl_decision_tree()
15+
*
16+
* Float precision bug in mean-cut color quantization.
17+
* When dominant color (246,0,0) has freq 545600 and rare color
18+
* (247,0,0) has freq 1, the float mean computation:
19+
* mean = (float)(545600*246 + 1*247) / (float)(545600+1)
20+
* = (float)(134217847) / (float)(545601)
21+
* = 134217840.0 / 545601.0 (numerator lost precision!)
22+
* = 245.999985 < 246
23+
*
24+
* This causes the partition loop in crawl_decision_tree to never
25+
* advance, producing a child node with invalid range
26+
* [idxMin, idxMin-1] = [idxMin, UINT32_MAX], causing a massive
27+
* heap buffer over-read in get_mean/get_variance.
28+
*
29+
* Dimensions: 192 x 2843 = 545856 pixels
30+
* 255 cloud colors + 1 rare + 545600 dominant = 545856
31+
*/
32+
33+
uint16_t w = 192;
34+
uint16_t h = 2843;
35+
uint32_t numPixel = (uint32_t)w * h;
36+
37+
fprintf(stderr, "Image: %ux%u = %u pixels\n", w, h, numPixel);
38+
39+
uint8_t* imgData = malloc(numPixel * 3);
40+
if (!imgData) {
41+
fprintf(stderr, "malloc failed\n");
42+
return 1;
43+
}
44+
45+
uint32_t idx = 0;
46+
47+
/* 255 cloud colors: R=246, G=1..255, B=0 (each appears 1x)
48+
* These share R=246 with the dominant color so they'll be
49+
* in the same partition region along the R dimension.
50+
* Total unique colors = 255 + 1 + 1 = 257 > 255, forcing quantization.
51+
*/
52+
for (int g = 1; g <= 255; g++, idx++) {
53+
imgData[idx * 3 + 0] = 246;
54+
imgData[idx * 3 + 1] = g;
55+
imgData[idx * 3 + 2] = 0;
56+
}
57+
58+
/* 1 pixel of rare color: R=247, G=0, B=0 */
59+
imgData[idx * 3 + 0] = 247;
60+
imgData[idx * 3 + 1] = 0;
61+
imgData[idx * 3 + 2] = 0;
62+
idx++;
63+
64+
/* Remaining 545600 pixels: dominant color R=246, G=0, B=0 */
65+
uint32_t dominant_freq = numPixel - idx;
66+
fprintf(stderr, "Dominant color (246,0,0) freq: %u (need exactly 545600)\n", dominant_freq);
67+
fprintf(stderr, "Rare color (247,0,0) freq: 1\n");
68+
fprintf(stderr, "Cloud colors (246,1-255,0): 255\n");
69+
fprintf(stderr, "Total unique colors: 257\n");
70+
71+
for (; idx < numPixel; idx++) {
72+
imgData[idx * 3 + 0] = 246;
73+
imgData[idx * 3 + 1] = 0;
74+
imgData[idx * 3 + 2] = 0;
75+
}
76+
77+
/* Verify the float precision bug condition */
78+
float num = (float)((uint64_t)545600 * 246 + (uint64_t)1 * 247);
79+
float den = (float)(545600 + 1);
80+
float mean = num / den;
81+
fprintf(stderr, "\nFloat precision check:\n");
82+
fprintf(stderr, " mean = %.15f (should be >= 246.0)\n", mean);
83+
fprintf(stderr, " mean < 246.0? %s\n", mean < 246.0f ? "YES - BUG WILL TRIGGER" : "NO");
84+
85+
/* Create GIF */
86+
CGIFrgb_Config config = {0};
87+
config.width = w;
88+
config.height = h;
89+
config.pWriteFn = writecb;
90+
91+
CGIFrgb* pGIF = cgif_rgb_newgif(&config);
92+
if (!pGIF) {
93+
fprintf(stderr, "cgif_rgb_newgif failed\n");
94+
free(imgData);
95+
return 1;
96+
}
97+
98+
CGIFrgb_FrameConfig fconfig = {0};
99+
fconfig.pImageData = imgData;
100+
fconfig.fmtChan = CGIF_CHAN_FMT_RGB;
101+
fconfig.delay = 10;
102+
103+
fprintf(stderr, "\nCalling cgif_rgb_addframe...\n");
104+
cgif_result r = cgif_rgb_addframe(pGIF, &fconfig);
105+
fprintf(stderr, "Result: %d\n", r);
106+
107+
r = cgif_rgb_close(pGIF);
108+
free(imgData);
109+
return 0;
110+
}

0 commit comments

Comments
 (0)