Search code examples
javascriptcolorsrgbpalette

Why this color conversion maps to gray so often?


I want to convert the RGB Color to an indexed color, here is my code, it works. But although some colors are converted to blue or red, but others that look like blue or red got converted to gray. How to fix this? You can check my snippet, click the color from big table and see the results down there.

var palette = [0, 0xff0000, 0xff00, 0xffff00, 0xff, 0xff00ff, 0xffff, 0x808080]

function rgb(c) {
    return [c & 0xff, (c >> 8) & 0xff, (c >> 16) & 0xff]
  }
  // http://alienryderflex.com/hsp.html

function rgb2lum(r, g, b) {
  return Math.sqrt(Pr * r * r + Pg * g * g + Pb * b * b)
}
var Pr = .2126,
  Pg = .7152,
  Pb = .0722;
var rd = 255,
  gd = 255,
  bd = 255,
  maxDist = Math.sqrt(Pr * rd * rd + Pg * gd * gd + Pb * bd * bd);

function colorDist(a, b) {
  a = rgb(a), b = rgb(b)
  var rd = b[0] - a[0],
    gd = b[1] - a[1],
    bd = b[2] - a[2]
  return Math.sqrt(Pr * rd * rd + Pg * gd * gd + Pb * bd * bd) / maxDist
}

function randomColor() {
  var r = Math.floor(Math.random() * 256)
  var g = Math.floor(Math.random() * 256)
  var b = Math.floor(Math.random() * 256)
  var c = (r + (g << 8) + (b << 16))
  return c
}

function hex(c) {
  c = c.toString(16)
  while (c.length < 6) c = '0' + c
  return '#' + c
}

function f(a) {
  var id = -1,
    val = 2
  for (var i = 0; i < palette.length; i++) {
    var c = palette[i],
      d = colorDist(a, c)
    if (d < val) id = i, val = d
  }
  out.innerHTML += ('<span style="border:1px solid black"><span style=background:' + hex(a) + '>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp</span><span style=background:' + hex(palette[id]) + '>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp</span></span>, ')
}
var W = 60,
  H = 10
var s = '<table border=0 cellspacing=0 style=cursor:pointer>'
for (var y = 0; y < H; y++) {
  s += '<tr>'
  for (var x = 0; x < W; x++) {
    var c = randomColor()
    s += '<td style="background:' + hex(c) + '" onclick=f(' + c + ')>&nbsp;&nbsp;</td>'
  }
  s += '</tr>'
}
s += '</table>'
s += 'palette:<table border=1><tr>'
for (var x = 0; x < palette.length; x++) {
  s += '<td style="background:' + hex(palette[x]) + '" onclick=f(' + palette[x] + ')>&nbsp;&nbsp;</td>'
}
s += '</tr></table>'
out.innerHTML = s
<span id=out></span>


Solution

  • It looks like you have your rgb arrays reversed.

    function rgb(c) {
        return [c & 0xff, (c >> 8) & 0xff, (c >> 16) & 0xff]
    }
    

    rgb() produces an array of [b,g,r] and your HSP math assumes the returned array is [r,g,b]:

    function colorDist(a, b) {
      a = rgb(a), b = rgb(b) // <--- produces [b,g,r] arrays
      var rd = b[0] - a[0], // <--- red is index 0
        gd = b[1] - a[1],
        bd = b[2] - a[2]
      return Math.sqrt(Pr * rd * rd + Pg * gd * gd + Pb * bd * bd) / maxDist
    }
    

    This would seem to throw off the intended brightness calculations.