From b5070cb54f8db752394a74c1fec8c4c55272022b Mon Sep 17 00:00:00 2001 From: Roman Fomin Date: Tue, 27 Aug 2024 09:02:05 +0700 Subject: [PATCH] uniform color quantization (#1868) * rename I_GetPaletteIndex->I_GetNearestColor --- src/am_map.c | 4 +- src/i_video.c | 2 +- src/i_video.h | 2 +- src/mn_font.c | 2 +- src/r_sky.c | 2 +- src/st_stuff.c | 2 +- src/v_flextran.c | 2 +- src/v_fmt.c | 167 ++++++++++++++++++++++++++++++++++++++++------- src/v_trans.c | 2 +- src/v_video.c | 6 +- 10 files changed, 155 insertions(+), 36 deletions(-) diff --git a/src/am_map.c b/src/am_map.c index 1111ad1a..7f523f38 100644 --- a/src/am_map.c +++ b/src/am_map.c @@ -2369,8 +2369,8 @@ void AM_ColorPreset(void) if (mapcolor_preset == AM_PRESET_CRISPY) { byte *playpal = W_CacheLumpName("PLAYPAL", PU_CACHE); - mapcolor_secr = I_GetPaletteIndex(playpal, 255, 0, 255); - mapcolor_revsecr = I_GetPaletteIndex(playpal, 119, 255, 111); + mapcolor_secr = I_GetNearestColor(playpal, 255, 0, 255); + mapcolor_revsecr = I_GetNearestColor(playpal, 119, 255, 111); } } diff --git a/src/i_video.c b/src/i_video.c index 717b94e9..f5232bf6 100644 --- a/src/i_video.c +++ b/src/i_video.c @@ -1025,7 +1025,7 @@ void I_SetPalette(byte *palette) // Taken from Chocolate Doom chocolate-doom/src/i_video.c:L841-867 -byte I_GetPaletteIndex(byte *palette, int r, int g, int b) +byte I_GetNearestColor(byte *palette, int r, int g, int b) { byte best; int best_diff, diff; diff --git a/src/i_video.h b/src/i_video.h index 9b195171..08bde982 100644 --- a/src/i_video.h +++ b/src/i_video.h @@ -82,7 +82,7 @@ extern boolean correct_aspect_ratio; extern boolean screenvisible; extern int gamma2; -byte I_GetPaletteIndex(byte *palette, int r, int g, int b); +byte I_GetNearestColor(byte *palette, int r, int g, int b); boolean I_WritePNGfile(char *filename); // [FG] screenshots in PNG format diff --git a/src/mn_font.c b/src/mn_font.c index 16002b0e..8214647b 100644 --- a/src/mn_font.c +++ b/src/mn_font.c @@ -111,7 +111,7 @@ boolean MN_LoadFon2(const byte *gfx_data, int size) int r = *p++; int g = *p++; int b = *p++; - translate[i] = I_GetPaletteIndex(playpal, r, g, b); + translate[i] = I_GetNearestColor(playpal, r, g, b); } // 0 is transparent, last is border color diff --git a/src/r_sky.c b/src/r_sky.c index 2dbbdff4..6fcfd538 100644 --- a/src/r_sky.c +++ b/src/r_sky.c @@ -111,7 +111,7 @@ static byte R_SkyBlendColor(int tex) b = colors[width/3].b; Z_Free(colors); - return I_GetPaletteIndex(pal, r, g, b); + return I_GetNearestColor(pal, r, g, b); } typedef struct skycolor_s diff --git a/src/st_stuff.c b/src/st_stuff.c index fd10cb99..8ac43eb7 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -370,7 +370,7 @@ static void ST_DrawSolidBackground(int st_x) b /= 2 * depth * (v1 - v0); // [FG] tune down to half saturation (for empiric reasons) - col = I_GetPaletteIndex(pal, r/2, g/2, b/2); + col = I_GetNearestColor(pal, r/2, g/2, b/2); V_FillRect(0, v0, video.unscaledw, v1 - v0, col); } diff --git a/src/v_flextran.c b/src/v_flextran.c index e9782b28..23e7e0d1 100644 --- a/src/v_flextran.c +++ b/src/v_flextran.c @@ -71,7 +71,7 @@ void V_InitFlexTranTable(void) { for (b = 0; b < 32; ++b) { - RGB32k[r][g][b] = I_GetPaletteIndex(palette, MAKECOLOR(r), + RGB32k[r][g][b] = I_GetNearestColor(palette, MAKECOLOR(r), MAKECOLOR(g), MAKECOLOR(b)); } } diff --git a/src/v_fmt.c b/src/v_fmt.c index b87fd69e..c16dc4c3 100644 --- a/src/v_fmt.c +++ b/src/v_fmt.c @@ -268,6 +268,82 @@ static void *DummyFlat(int lump, pu_tag tag) return lumpcache[lump]; } +// Uniform Color Quantization +// +// Each color component axis (red, green and blue) is divided into a few fixed +// segments (8-8-4 in 256 colors). Each found color is placed into a +// corresponding segment slot. After all the colors are added, an average +// color is calculated for each slot. Those are the colors of the palette. + +typedef struct +{ + int value; + int pixel_count; +} color_slot_t; + +static void AddValue(color_slot_t *s, int component) +{ + s->value += component; + s->pixel_count++; +} + +static int GetAverage(color_slot_t *s) +{ + int result = 0; + + if (s->pixel_count > 0) + { + result = (s->pixel_count == 1) ? s->value : (s->value / s->pixel_count); + } + + return result; +} + +typedef struct +{ + color_slot_t red_slots[8]; + color_slot_t green_slots[8]; + color_slot_t blue_slots[4]; + + byte palette[768]; +} uniform_quantizer_t; + +static void AddColor(uniform_quantizer_t *q, int r, int g, int b) +{ + int red_index = r >> 5; + int green_index = g >> 5; + int blue_index = b >> 6; + AddValue(&q->red_slots[red_index], r); + AddValue(&q->green_slots[green_index], g); + AddValue(&q->blue_slots[blue_index], b); +} + +static void GetPalette(uniform_quantizer_t *q) +{ + byte *roller = q->palette; + + for (int rs = 0; rs < arrlen(q->red_slots); ++rs) + { + for (int gs = 0; gs < arrlen(q->green_slots); ++gs) + { + for (int bs = 0; bs < arrlen(q->blue_slots); ++bs) + { + *roller++ = GetAverage(&q->red_slots[rs]); + *roller++ = GetAverage(&q->green_slots[gs]); + *roller++ = GetAverage(&q->blue_slots[bs]); + } + } + } +} + +static int GetPaletteIndex(int r, int g, int b) +{ + int red_index = r >> 5; + int green_index = g >> 5; + int blue_index = b >> 6; + return (red_index << 5) + (green_index << 2) + blue_index; +} + typedef struct { spng_ctx *ctx; @@ -340,8 +416,7 @@ static boolean DecodePNG(png_t *png) if (ret) { - I_Printf(VB_ERROR, "DecodeImage: spng_get_ihdr %s\n", - spng_strerror(ret)); + I_Printf(VB_ERROR, "DecodePNG: spng_get_ihdr %s\n", spng_strerror(ret)); return false; } @@ -368,7 +443,7 @@ static boolean DecodePNG(png_t *png) if (ret) { - I_Printf(VB_ERROR, "DecodeImage: spng_decoded_image_size %s", + I_Printf(VB_ERROR, "DecodePNG: spng_decoded_image_size %s", spng_strerror(ret)); return false; } @@ -378,7 +453,7 @@ static boolean DecodePNG(png_t *png) if (ret) { - I_Printf(VB_ERROR, "DecodeImage: spng_decode_image %s", + I_Printf(VB_ERROR, "DecodePNG: spng_decode_image %s", spng_strerror(ret)); free(image); return false; @@ -391,6 +466,8 @@ static boolean DecodePNG(png_t *png) int indexed_size = image_size / 3; byte *indexed_image = malloc(indexed_size); + uniform_quantizer_t q = {0}; + byte *roller = image; for (int i = 0; i < indexed_size; ++i) @@ -399,7 +476,31 @@ static boolean DecodePNG(png_t *png) int g = *roller++; int b = *roller++; - indexed_image[i] = I_GetPaletteIndex(playpal, r, g, b); + AddColor(&q, r, g, b); + } + + GetPalette(&q); + + byte translate[256]; + byte *palette = q.palette; + for (int i = 0; i < 256; ++i) + { + int r = *palette++; + int g = *palette++; + int b = *palette++; + + translate[i] = I_GetNearestColor(playpal, r, g, b); + } + + roller = image; + + for (int i = 0; i < indexed_size; ++i) + { + int r = *roller++; + int g = *roller++; + int b = *roller++; + + indexed_image[i] = translate[GetPaletteIndex(r, g, b)]; } free(image); @@ -412,6 +513,8 @@ static boolean DecodePNG(png_t *png) int indexed_size = image_size / 4; byte *indexed_image = malloc(indexed_size); + uniform_quantizer_t q = {0}; + byte *roller = image; byte used_colors[256] = {0}; @@ -423,22 +526,34 @@ static boolean DecodePNG(png_t *png) int g = *roller++; int b = *roller++; int a = *roller++; - if (a < 255) { has_alpha = true; continue; } - byte c = I_GetPaletteIndex(playpal, r, g, b); - used_colors[c] = 1; - indexed_image[i] = c; + AddColor(&q, r, g, b); } + GetPalette(&q); + + byte translate[256]; + byte *palette = q.palette; + for (int i = 0; i < 256; ++i) + { + int r = *palette++; + int g = *palette++; + int b = *palette++; + + byte c = I_GetNearestColor(playpal, r, g, b); + used_colors[c] = 1; + translate[i] = c; + } + + int color_key = NO_COLOR_KEY; + if (has_alpha) { - int color_key = NO_COLOR_KEY; - for (int i = 0; i < 256; ++i) { if (used_colors[i] == 0) @@ -447,20 +562,24 @@ static boolean DecodePNG(png_t *png) break; } } + png->color_key = color_key; + } - if (color_key != NO_COLOR_KEY) + roller = image; + + for (int i = 0; i < indexed_size; ++i) + { + int r = *roller++; + int g = *roller++; + int b = *roller++; + int a = *roller++; + if (a < 255) { - roller = image; - for (int i = 0; i < indexed_size; ++i) - { - roller += 3; - if (*roller++ < 255) - { - indexed_image[i] = color_key; - } - } - png->color_key = color_key; + indexed_image[i] = color_key; + continue; } + + indexed_image[i] = translate[GetPaletteIndex(r, g, b)]; } free(image); @@ -475,7 +594,7 @@ static boolean DecodePNG(png_t *png) if (ret) { - I_Printf(VB_ERROR, "DecodeImage: spng_get_plte %s\n", + I_Printf(VB_ERROR, "DecodePNG: spng_get_plte %s\n", spng_strerror(ret)); return false; } @@ -500,7 +619,7 @@ static boolean DecodePNG(png_t *png) need_translation = true; translate[i] = - I_GetPaletteIndex(playpal, e->red, e->green, e->blue); + I_GetNearestColor(playpal, e->red, e->green, e->blue); } if (need_translation) diff --git a/src/v_trans.c b/src/v_trans.c index 1260682e..fc197aba 100644 --- a/src/v_trans.c +++ b/src/v_trans.c @@ -285,5 +285,5 @@ byte V_Colorize(byte *playpal, int cr, byte source) rgb.y *= 255.0; rgb.z *= 255.0; - return I_GetPaletteIndex(playpal, (int)rgb.x, (int)rgb.y, (int)rgb.z); + return I_GetNearestColor(playpal, (int)rgb.x, (int)rgb.y, (int)rgb.z); } diff --git a/src/v_video.c b/src/v_video.c index ce030cf3..e253aa82 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -200,8 +200,8 @@ void V_InitColorTranslation(void) cr_bright[i] = V_Colorize(playpal, CR_BRIGHT, (byte)i); } - v_lightest_color = I_GetPaletteIndex(playpal, 0xFF, 0xFF, 0xFF); - v_darkest_color = I_GetPaletteIndex(playpal, 0x00, 0x00, 0x00); + v_lightest_color = I_GetNearestColor(playpal, 0xFF, 0xFF, 0xFF); + v_darkest_color = I_GetNearestColor(playpal, 0x00, 0x00, 0x00); byte *palsrc = playpal; for (int i = 0; i < 256; ++i) @@ -213,7 +213,7 @@ void V_InitColorTranslation(void) // formula is taken from dcolors.c preseving "Carmack's typo" // https://doomwiki.org/wiki/Carmack%27s_typo int gray = (red * 0.299 + green * 0.587 + blue * 0.144) * 255; - invul_gray[i] = I_GetPaletteIndex(playpal, gray, gray, gray); + invul_gray[i] = I_GetNearestColor(playpal, gray, gray, gray); } }