Convert HEX to closest X11 Color Number

后端 未结 5 1720
醉梦人生
醉梦人生 2021-02-01 08:43

I\'m looking for a Algorithm/Way to convert given HEX (e.g. #111111 R:0x11, G:0x11, B:0x11) to the closest X11 color number (Terminal is either 88 or 256 colors) using either Py

5条回答
  •  星月不相逢
    2021-02-01 09:23

    This is a C implementation, though it should be easy to convert it to other languages. There is a JavaScript implementation at the end of this post. Do note that the C implementation uses integer arithmetic:

    // 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 the
    // input is the exact middle:
    // - The r/g/b channels and the gray value: the higher value output is chosen.
    // - If the gray and color have same distance from the input - color is chosen.
    static int rgb_to_x256(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 evaluation */
    
        // 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;
    }
    

    This is the same implementation which is currently used by tmux and mpv. You can find some explanation of the algorithm at the tmux pull request on github.

    JavaScript implementation

    // 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 the
    // input is the exact middle:
    // - The r/g/b channels and the gray value: the higher value output is chosen.
    // - If the gray and color have same distance from the input - color is chosen.
    function RgbToX256(r, g, b) {
      // Calculate the nearest 0-based color index at 16 .. 231
      const v2ci = (v) => {
        if (v < 48) {
          return 0;
        } else if (v < 115) {
          return 1;
        } else {
          return Math.trunc((v - 35) / 40);
        }
      };
    
      const ir = v2ci(r);
      const ig = v2ci(g);
      const ib = v2ci(b);
      const colorIndex = (36 * ir + 6 * ig + ib);
    
      // Calculate the nearest 0-based gray index at 232 .. 255
      const average = Math.trunc((r + g + b) / 3);
      const grayIndex = average > 238 ? 23 : Math.trunc((average - 3) / 10);
    
      // Calculate the represented colors back from the index
      const i2cv = [0, 0x5F, 0x87, 0xAF, 0xD7, 0xFF];
      const cr = i2cv[ir];
      const cg = i2cv[ig];
      const cb = i2cv[ib];
      const gv = 8 + 10 * grayIndex;
    
      // Return the one which is nearer to the original input rgb value
      const distSquare = (A, B, C, a, b, c) => {
        return ((A - a) * (A - a) + (B - b) * (B - b) + (C - c) * (C - c));
      }
      const colorErr = distSquare(cr, cg, cb, r, g, b);
      const grayErr = distSquare(gv, gv, gv, r, g, b);
    
      return colorErr <= grayErr ? 16 + colorIndex : 232 + grayIndex;
    }
    
    const assertEq = (result, expect) => {
      if (result !== expect) {
        return console.log("[FAIL]", `expected: ${expect}, got ${result}`);
      }
    
      return console.log("[PASS]", `expected: ${expect}, got ${result}`);
    }
    
    const tests = [{
        expect: 16,
        rgb: [0, 0, 0]
      },
      {
        expect: 17,
        rgb: [0, 0, 95]
      },
      {
        expect: 18,
        rgb: [0, 0, 135]
      },
      {
        expect: 19,
        rgb: [0, 0, 175]
      },
      {
        expect: 20,
        rgb: [0, 0, 215]
      },
      {
        expect: 21,
        rgb: [0, 0, 255]
      },
      {
        expect: 22,
        rgb: [0, 95, 0]
      },
      {
        expect: 23,
        rgb: [0, 95, 95]
      },
      {
        expect: 24,
        rgb: [0, 95, 135]
      },
      {
        expect: 25,
        rgb: [0, 95, 175]
      },
      {
        expect: 26,
        rgb: [0, 95, 215]
      },
      {
        expect: 27,
        rgb: [0, 95, 255]
      },
      {
        expect: 28,
        rgb: [0, 135, 0]
      },
      {
        expect: 29,
        rgb: [0, 135, 95]
      },
      {
        expect: 30,
        rgb: [0, 135, 135]
      },
      {
        expect: 31,
        rgb: [0, 135, 175]
      },
      {
        expect: 32,
        rgb: [0, 135, 215]
      },
      {
        expect: 33,
        rgb: [0, 135, 255]
      },
      {
        expect: 34,
        rgb: [0, 175, 0]
      },
      {
        expect: 35,
        rgb: [0, 175, 95]
      },
      {
        expect: 36,
        rgb: [0, 175, 135]
      },
      {
        expect: 37,
        rgb: [0, 175, 175]
      },
      {
        expect: 38,
        rgb: [0, 175, 215]
      },
      {
        expect: 39,
        rgb: [0, 175, 255]
      },
      {
        expect: 40,
        rgb: [0, 215, 0]
      },
      {
        expect: 41,
        rgb: [0, 215, 95]
      },
      {
        expect: 42,
        rgb: [0, 215, 135]
      },
      {
        expect: 43,
        rgb: [0, 215, 175]
      },
      {
        expect: 44,
        rgb: [0, 215, 215]
      },
      {
        expect: 45,
        rgb: [0, 215, 255]
      },
      {
        expect: 46,
        rgb: [0, 255, 0]
      },
      {
        expect: 47,
        rgb: [0, 255, 95]
      },
      {
        expect: 48,
        rgb: [0, 255, 135]
      },
      {
        expect: 49,
        rgb: [0, 255, 175]
      },
      {
        expect: 50,
        rgb: [0, 255, 215]
      },
      {
        expect: 51,
        rgb: [0, 255, 255]
      },
      {
        expect: 52,
        rgb: [95, 0, 0]
      },
      {
        expect: 53,
        rgb: [95, 0, 95]
      },
      {
        expect: 54,
        rgb: [95, 0, 135]
      },
      {
        expect: 55,
        rgb: [95, 0, 175]
      },
      {
        expect: 56,
        rgb: [95, 0, 215]
      },
      {
        expect: 57,
        rgb: [95, 0, 255]
      },
      {
        expect: 58,
        rgb: [95, 95, 0]
      },
      {
        expect: 59,
        rgb: [95, 95, 95]
      },
      {
        expect: 60,
        rgb: [95, 95, 135]
      },
      {
        expect: 61,
        rgb: [95, 95, 175]
      },
      {
        expect: 62,
        rgb: [95, 95, 215]
      },
      {
        expect: 63,
        rgb: [95, 95, 255]
      },
      {
        expect: 64,
        rgb: [95, 135, 0]
      },
      {
        expect: 65,
        rgb: [95, 135, 95]
      },
      {
        expect: 66,
        rgb: [95, 135, 135]
      },
      {
        expect: 67,
        rgb: [95, 135, 175]
      },
      {
        expect: 68,
        rgb: [95, 135, 215]
      },
      {
        expect: 69,
        rgb: [95, 135, 255]
      },
      {
        expect: 70,
        rgb: [95, 175, 0]
      },
      {
        expect: 71,
        rgb: [95, 175, 95]
      },
      {
        expect: 72,
        rgb: [95, 175, 135]
      },
      {
        expect: 73,
        rgb: [95, 175, 175]
      },
      {
        expect: 74,
        rgb: [95, 175, 215]
      },
      {
        expect: 75,
        rgb: [95, 175, 255]
      },
      {
        expect: 76,
        rgb: [95, 215, 0]
      },
      {
        expect: 77,
        rgb: [95, 215, 95]
      },
      {
        expect: 78,
        rgb: [95, 215, 135]
      },
      {
        expect: 79,
        rgb: [95, 215, 175]
      },
      {
        expect: 80,
        rgb: [95, 215, 215]
      },
      {
        expect: 81,
        rgb: [95, 215, 255]
      },
      {
        expect: 82,
        rgb: [95, 255, 0]
      },
      {
        expect: 83,
        rgb: [95, 255, 95]
      },
      {
        expect: 84,
        rgb: [95, 255, 135]
      },
      {
        expect: 85,
        rgb: [95, 255, 175]
      },
      {
        expect: 86,
        rgb: [95, 255, 215]
      },
      {
        expect: 87,
        rgb: [95, 255, 255]
      },
      {
        expect: 88,
        rgb: [135, 0, 0]
      },
      {
        expect: 89,
        rgb: [135, 0, 95]
      },
      {
        expect: 90,
        rgb: [135, 0, 135]
      },
      {
        expect: 91,
        rgb: [135, 0, 175]
      },
      {
        expect: 92,
        rgb: [135, 0, 215]
      },
      {
        expect: 93,
        rgb: [135, 0, 255]
      },
      {
        expect: 94,
        rgb: [135, 95, 0]
      },
      {
        expect: 95,
        rgb: [135, 95, 95]
      },
      {
        expect: 96,
        rgb: [135, 95, 135]
      },
      {
        expect: 97,
        rgb: [135, 95, 175]
      },
      {
        expect: 98,
        rgb: [135, 95, 215]
      },
      {
        expect: 99,
        rgb: [135, 95, 255]
      },
      {
        expect: 100,
        rgb: [135, 135, 0]
      },
      {
        expect: 101,
        rgb: [135, 135, 95]
      },
      {
        expect: 102,
        rgb: [135, 135, 135]
      },
      {
        expect: 103,
        rgb: [135, 135, 175]
      },
      {
        expect: 104,
        rgb: [135, 135, 215]
      },
      {
        expect: 105,
        rgb: [135, 135, 255]
      },
      {
        expect: 106,
        rgb: [135, 175, 0]
      },
      {
        expect: 107,
        rgb: [135, 175, 95]
      },
      {
        expect: 108,
        rgb: [135, 175, 135]
      },
      {
        expect: 109,
        rgb: [135, 175, 175]
      },
      {
        expect: 110,
        rgb: [135, 175, 215]
      },
      {
        expect: 111,
        rgb: [135, 175, 255]
      },
      {
        expect: 112,
        rgb: [135, 215, 0]
      },
      {
        expect: 113,
        rgb: [135, 215, 95]
      },
      {
        expect: 114,
        rgb: [135, 215, 135]
      },
      {
        expect: 115,
        rgb: [135, 215, 175]
      },
      {
        expect: 116,
        rgb: [135, 215, 215]
      },
      {
        expect: 117,
        rgb: [135, 215, 255]
      },
      {
        expect: 118,
        rgb: [135, 255, 0]
      },
      {
        expect: 119,
        rgb: [135, 255, 95]
      },
      {
        expect: 120,
        rgb: [135, 255, 135]
      },
      {
        expect: 121,
        rgb: [135, 255, 175]
      },
      {
        expect: 122,
        rgb: [135, 255, 215]
      },
      {
        expect: 123,
        rgb: [135, 255, 255]
      },
      {
        expect: 124,
        rgb: [175, 0, 0]
      },
      {
        expect: 125,
        rgb: [175, 0, 95]
      },
      {
        expect: 126,
        rgb: [175, 0, 135]
      },
      {
        expect: 127,
        rgb: [175, 0, 175]
      },
      {
        expect: 128,
        rgb: [175, 0, 215]
      },
      {
        expect: 129,
        rgb: [175, 0, 255]
      },
      {
        expect: 130,
        rgb: [175, 95, 0]
      },
      {
        expect: 131,
        rgb: [175, 95, 95]
      },
      {
        expect: 132,
        rgb: [175, 95, 135]
      },
      {
        expect: 133,
        rgb: [175, 95, 175]
      },
      {
        expect: 134,
        rgb: [175, 95, 215]
      },
      {
        expect: 135,
        rgb: [175, 95, 255]
      },
      {
        expect: 136,
        rgb: [175, 135, 0]
      },
      {
        expect: 137,
        rgb: [175, 135, 95]
      },
      {
        expect: 138,
        rgb: [175, 135, 135]
      },
      {
        expect: 139,
        rgb: [175, 135, 175]
      },
      {
        expect: 140,
        rgb: [175, 135, 215]
      },
      {
        expect: 141,
        rgb: [175, 135, 255]
      },
      {
        expect: 142,
        rgb: [175, 175, 0]
      },
      {
        expect: 143,
        rgb: [175, 175, 95]
      },
      {
        expect: 144,
        rgb: [175, 175, 135]
      },
      {
        expect: 145,
        rgb: [175, 175, 175]
      },
      {
        expect: 146,
        rgb: [175, 175, 215]
      },
      {
        expect: 147,
        rgb: [175, 175, 255]
      },
      {
        expect: 148,
        rgb: [175, 215, 0]
      },
      {
        expect: 149,
        rgb: [175, 215, 95]
      },
      {
        expect: 150,
        rgb: [175, 215, 135]
      },
      {
        expect: 151,
        rgb: [175, 215, 175]
      },
      {
        expect: 152,
        rgb: [175, 215, 215]
      },
      {
        expect: 153,
        rgb: [175, 215, 255]
      },
      {
        expect: 154,
        rgb: [175, 255, 0]
      },
      {
        expect: 155,
        rgb: [175, 255, 95]
      },
      {
        expect: 156,
        rgb: [175, 255, 135]
      },
      {
        expect: 157,
        rgb: [175, 255, 175]
      },
      {
        expect: 158,
        rgb: [175, 255, 215]
      },
      {
        expect: 159,
        rgb: [175, 255, 255]
      },
      {
        expect: 160,
        rgb: [215, 0, 0]
      },
      {
        expect: 161,
        rgb: [215, 0, 95]
      },
      {
        expect: 162,
        rgb: [215, 0, 135]
      },
      {
        expect: 163,
        rgb: [215, 0, 175]
      },
      {
        expect: 164,
        rgb: [215, 0, 215]
      },
      {
        expect: 165,
        rgb: [215, 0, 255]
      },
      {
        expect: 166,
        rgb: [215, 95, 0]
      },
      {
        expect: 167,
        rgb: [215, 95, 95]
      },
      {
        expect: 168,
        rgb: [215, 95, 135]
      },
      {
        expect: 169,
        rgb: [215, 95, 175]
      },
      {
        expect: 170,
        rgb: [215, 95, 215]
      },
      {
        expect: 171,
        rgb: [215, 95, 255]
      },
      {
        expect: 172,
        rgb: [215, 135, 0]
      },
      {
        expect: 173,
        rgb: [215, 135, 95]
      },
      {
        expect: 174,
        rgb: [215, 135, 135]
      },
      {
        expect: 175,
        rgb: [215, 135, 175]
      },
      {
        expect: 176,
        rgb: [215, 135, 215]
      },
      {
        expect: 177,
        rgb: [215, 135, 255]
      },
      {
        expect: 178,
        rgb: [215, 175, 0]
      },
      {
        expect: 179,
        rgb: [215, 175, 95]
      },
      {
        expect: 180,
        rgb: [215, 175, 135]
      },
      {
        expect: 181,
        rgb: [215, 175, 175]
      },
      {
        expect: 182,
        rgb: [215, 175, 215]
      },
      {
        expect: 183,
        rgb: [215, 175, 255]
      },
      {
        expect: 184,
        rgb: [215, 215, 0]
      },
      {
        expect: 185,
        rgb: [215, 215, 95]
      },
      {
        expect: 186,
        rgb: [215, 215, 135]
      },
      {
        expect: 187,
        rgb: [215, 215, 175]
      },
      {
        expect: 188,
        rgb: [215, 215, 215]
      },
      {
        expect: 189,
        rgb: [215, 215, 255]
      },
      {
        expect: 190,
        rgb: [215, 255, 0]
      },
      {
        expect: 191,
        rgb: [215, 255, 95]
      },
      {
        expect: 192,
        rgb: [215, 255, 135]
      },
      {
        expect: 193,
        rgb: [215, 255, 175]
      },
      {
        expect: 194,
        rgb: [215, 255, 215]
      },
      {
        expect: 195,
        rgb: [215, 255, 255]
      },
      {
        expect: 196,
        rgb: [255, 0, 0]
      },
      {
        expect: 197,
        rgb: [255, 0, 95]
      },
      {
        expect: 198,
        rgb: [255, 0, 135]
      },
      {
        expect: 199,
        rgb: [255, 0, 175]
      },
      {
        expect: 200,
        rgb: [255, 0, 215]
      },
      {
        expect: 201,
        rgb: [255, 0, 255]
      },
      {
        expect: 202,
        rgb: [255, 95, 0]
      },
      {
        expect: 203,
        rgb: [255, 95, 95]
      },
      {
        expect: 204,
        rgb: [255, 95, 135]
      },
      {
        expect: 205,
        rgb: [255, 95, 175]
      },
      {
        expect: 206,
        rgb: [255, 95, 215]
      },
      {
        expect: 207,
        rgb: [255, 95, 255]
      },
      {
        expect: 208,
        rgb: [255, 135, 0]
      },
      {
        expect: 209,
        rgb: [255, 135, 95]
      },
      {
        expect: 210,
        rgb: [255, 135, 135]
      },
      {
        expect: 211,
        rgb: [255, 135, 175]
      },
      {
        expect: 212,
        rgb: [255, 135, 215]
      },
      {
        expect: 213,
        rgb: [255, 135, 255]
      },
      {
        expect: 214,
        rgb: [255, 175, 0]
      },
      {
        expect: 215,
        rgb: [255, 175, 95]
      },
      {
        expect: 216,
        rgb: [255, 175, 135]
      },
      {
        expect: 217,
        rgb: [255, 175, 175]
      },
      {
        expect: 218,
        rgb: [255, 175, 215]
      },
      {
        expect: 219,
        rgb: [255, 175, 255]
      },
      {
        expect: 220,
        rgb: [255, 215, 0]
      },
      {
        expect: 221,
        rgb: [255, 215, 95]
      },
      {
        expect: 222,
        rgb: [255, 215, 135]
      },
      {
        expect: 223,
        rgb: [255, 215, 175]
      },
      {
        expect: 224,
        rgb: [255, 215, 215]
      },
      {
        expect: 225,
        rgb: [255, 215, 255]
      },
      {
        expect: 226,
        rgb: [255, 255, 0]
      },
      {
        expect: 227,
        rgb: [255, 255, 95]
      },
      {
        expect: 228,
        rgb: [255, 255, 135]
      },
      {
        expect: 229,
        rgb: [255, 255, 175]
      },
      {
        expect: 230,
        rgb: [255, 255, 215]
      },
      {
        expect: 231,
        rgb: [255, 255, 255]
      },
      {
        expect: 232,
        rgb: [8, 8, 8]
      },
      {
        expect: 233,
        rgb: [18, 18, 18]
      },
      {
        expect: 234,
        rgb: [28, 28, 28]
      },
      {
        expect: 235,
        rgb: [38, 38, 38]
      },
      {
        expect: 236,
        rgb: [48, 48, 48]
      },
      {
        expect: 237,
        rgb: [58, 58, 58]
      },
      {
        expect: 238,
        rgb: [68, 68, 68]
      },
      {
        expect: 239,
        rgb: [78, 78, 78]
      },
      {
        expect: 240,
        rgb: [88, 88, 88]
      },
      {
        expect: 241,
        rgb: [98, 98, 98]
      },
      {
        expect: 242,
        rgb: [108, 108, 108]
      },
      {
        expect: 243,
        rgb: [118, 118, 118]
      },
      {
        expect: 244,
        rgb: [128, 128, 128]
      },
      {
        expect: 245,
        rgb: [138, 138, 138]
      },
      {
        expect: 246,
        rgb: [148, 148, 148]
      },
      {
        expect: 247,
        rgb: [158, 158, 158]
      },
      {
        expect: 248,
        rgb: [168, 168, 168]
      },
      {
        expect: 249,
        rgb: [178, 178, 178]
      },
      {
        expect: 250,
        rgb: [188, 188, 188]
      },
      {
        expect: 251,
        rgb: [198, 198, 198]
      },
      {
        expect: 252,
        rgb: [208, 208, 208]
      },
      {
        expect: 253,
        rgb: [218, 218, 218]
      },
      {
        expect: 254,
        rgb: [228, 228, 228]
      },
      {
        expect: 255,
        rgb: [238, 238, 238]
      }
    ];
    
    
    for (const {
        expect,
        rgb: [r, g, b]
      } of tests) {
      assertEq(expect, RgbToX256(r, g, b));
    }

提交回复
热议问题