cosmopolitan/dsp/tty/rgb2xterm256.c

72 lines
4.1 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "dsp/tty/rgb2xterm256.h"
/* 1bc */
forceinline int sqr(int x) { return x * x; }
/* forceinline int dst6(int x) { return x * x; } */
int rgb2xterm256v2(int r, int g, int b) {
static const int i2cv[] = {0, 0137, 0207, 0257, 0327, 0377, 0377};
#define v2ci(v) (v < 060 ? 0 : v < 0163 ? 01 : (v - 043) / 050)
#define dst6(A, B, C, a, b, c) (sqr(A - a) + sqr(B - b) + sqr(C - c))
int ir = v2ci(r);
int ig = v2ci(g);
int ib = v2ci(b);
int avg = (r + g + b) / 3;
int cr = i2cv[ir];
int cg = i2cv[ig];
int cb = i2cv[ib];
int gidx = avg > 238 ? 23 : (avg - 3) / 10;
int gv = 8 + 10 * gidx;
int cerr = dst6(cr, cg, cb, r, g, b);
int gerr = dst6(gv, gv, gv, r, g, b);
return cerr <= gerr ? 16 + (36 * ir + 6 * ig + ib) : 232 + gidx;
#undef dst6
#undef cidx
#undef v2ci
}
/* 1e3 */
// Convert RGB24 to xterm-256 8-bit value
// For simplicity, assume RGB space is perceptually uniform.
// There are 5 places where one of two outputs needs to be chosen when
// input is the exact middle:
// - The r/g/b channels and the gray value: choose higher value output
// - If gray and color have same distance from input - choose color
int rgb2xterm256(uint8_t r, uint8_t g, uint8_t b) {
// Calculate the nearest 0-based color index at 16 .. 231
#define v2ci(v) (v < 48 ? 0 : v < 115 ? 1 : (v - 35) / 40)
int ir = v2ci(r), ig = v2ci(g), ib = v2ci(b); // 0..5 each
#define color_index() (36 * ir + 6 * ig + ib) /* 0..215, lazy eval */
// Calculate the nearest 0-based gray index at 232 .. 255
int average = (r + g + b) / 3;
int gray_index = average > 238 ? 23 : (average - 3) / 10; // 0..23
// Calculate the represented colors back from the index
static const int i2cv[6] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
int cr = i2cv[ir], cg = i2cv[ig], cb = i2cv[ib]; // r/g/b, 0..255 each
int gv = 8 + 10 * gray_index; // same value for r/g/b, 0..255
// Return the one which is nearer to the original input rgb value
#define dist_square(A, B, C, a, b, c) \
((A - a) * (A - a) + (B - b) * (B - b) + (C - c) * (C - c))
int color_err = dist_square(cr, cg, cb, r, g, b);
int gray_err = dist_square(gv, gv, gv, r, g, b);
return color_err <= gray_err ? 16 + color_index() : 232 + gray_index;
}