diff --git a/src/bin/termptyesc.c b/src/bin/termptyesc.c index 906f30a0..3cb64ca3 100644 --- a/src/bin/termptyesc.c +++ b/src/bin/termptyesc.c @@ -654,12 +654,77 @@ _csi_truecolor_arg_get(Termpty *ty, Eina_Unicode **ptr) return sum; } +#if !defined(ENABLE_FUZZING) +/********************************* + * cache true color approximations + ********************************* + * Approximating true colors is costly since it needs to compare 256 colors. + * The following considers that a few colors are used a lot. For example, one + * can consider that a text editor with syntax highlighting would only use a + * small palette. + * Given that, using the cache needs to be efficient: it needs to speed up the + * approximation when the color is already in the cache but not knowing the + * color requested is not in the cache needs to be efficient too. + * + * The cache is an array of @TCC_LEN uint32_t. + * Each entry has on the MSB the color as 3 uint8_t (R,G,B) and the LSB is + * the approximated color. + * Searching a color is simply going through the array and testing the mask on + * the 3 most significant bytes. + * When a color is found, it is bubbled up towards the start of the array by + * swapping this entry with the one above. This way the array is ordered to + * get most used colors as fast as possible. + * When a color is not found, the slow process of comparing it with the 256 + * colors is used. Then the result is inserted in the lower half of the + * array. This ensures that new entries can live a bit and not be removed by + * the next new color. Using a modulo with a prime number makes for a + * nice-enough random generator to figure out where to insert. + */ +#define TCC_LEN 32 +#define TCC_PRIME 17 /* smallest prime number larger than @TCC_LEN/2 */ +static struct { + uint32_t colors[TCC_LEN]; +} _truecolor_cache; +static int _tcc_random_pos = 0; -static int +static Eina_Bool +_tcc_find(const uint32_t color_msb, uint8_t *chosen_color) +{ + int i; + for (i = 0; i < TCC_LEN; i++) + { + if ((_truecolor_cache.colors[i] & 0xffffff00) == color_msb) + { + *chosen_color = _truecolor_cache.colors[i] & 0xff; + /* bubble up this result */ + if (i > 0) + { + uint32_t prev = _truecolor_cache.colors[i - 1]; + _truecolor_cache.colors[i - 1] = _truecolor_cache.colors[i]; + _truecolor_cache.colors[i] = prev; + } + return EINA_TRUE; + } + } + return EINA_FALSE; +} + +static void +_tcc_insert(const uint32_t color_msb, const uint8_t approximated) +{ + uint32_t c = color_msb | approximated; + int i; + + i = (TCC_LEN / 2) + ((_tcc_random_pos + TCC_PRIME) % (TCC_LEN / 2)); + + _truecolor_cache.colors[i] = c; +} +#endif + +static uint8_t _approximate_truecolor_rgb(Termpty *ty, int r0, int g0, int b0) { - int chosen_color = COL_DEF; -/* TODO: use the function in tests */ + uint8_t chosen_color = COL_DEF; #if defined(ENABLE_FUZZING) (void) ty; (void) r0; @@ -669,14 +734,16 @@ _approximate_truecolor_rgb(Termpty *ty, int r0, int g0, int b0) int c; int distance_min = INT_MAX; Evas_Object *textgrid; + const uint32_t color_msb = (r0 << 24) | (g0 << 16) | (b0 << 8); - DBG("approximating r:%d g:%d b:%d", r0, g0, b0); + if (_tcc_find(color_msb, &chosen_color)) + return chosen_color; textgrid = termio_textgrid_get(ty->obj); for (c = 0; c < 256; c++) { int r1 = 0, g1 = 0, b1 = 0, a1 = 0; - int delta_red_sq, delta_green_sq, delta_blue_sq, middle_red; + int delta_red_sq, delta_green_sq, delta_blue_sq, red_mean; int distance; evas_object_textgrid_palette_get(textgrid, @@ -684,23 +751,34 @@ _approximate_truecolor_rgb(Termpty *ty, int r0, int g0, int b0) c, &r1, &g1, &b1, &a1); /* Compute the color distance * XXX: this is inacurate but should give good enough results. - * See * https://en.wikipedia.org/wiki/Color_difference + * See https://en.wikipedia.org/wiki/Color_difference */ - middle_red = (r0 + r1) / 2; + red_mean = (r0 + r1) / 2; delta_red_sq = (r0 - r1) * (r0 - r1); delta_green_sq = (g0 - g1) * (g0 - g1); delta_blue_sq = (b0 - b1) * (b0 - b1); +#if 1 distance = 2 * delta_red_sq + 4 * delta_green_sq + 3 * delta_blue_sq - + ((middle_red) * (delta_red_sq - delta_blue_sq) / 256); + + ((red_mean) * (delta_red_sq - delta_blue_sq) / 256); +#else + /* from https://www.compuphase.com/cmetric.htm */ + distance = (((512 + red_mean) * delta_red_sq) >> 8) + + 4 * delta_green_sq + + (((767 - red_mean) * delta_blue_sq) >> 8); + /* euclidian distance */ + distance = delta_red_sq + delta_green_sq + delta_blue_sq; + (void)red_mean; +#endif if (distance < distance_min) { distance_min = distance; chosen_color = c; } } + _tcc_insert(color_msb, chosen_color); #endif return chosen_color; }