Search code examples
colormapcolor-spacecolor-mapping

Color mapping for 16-bit greyscale values with smooth transitions


I have 16-bits of precision for my data. Trying to visualize this data in greyscale 8-bit leads to significant banding. My thought was that I could store these 16 bit values in colors. For example, the first 8 bits could be in the red channel, and the next 8 bits in the green channel.

However, this will lead to abrupt transitions. Going from 65280 (255,0,0) to 65279 (254,255,0) would cause the color to shift immediately from Red to Yellow. This is really not helpful to human interpretation.

I know that HSB could provide 360 degrees of variation pretty easily, but 360 color values isn't really that much more than the 256 I already have with 8-but greyscale.

What would be the most appropriate way to represent values between 0-65535 in terms of colors that transition in a way that make sense to humans?


Solution

  • It's not clear to me why you are counting the RGB values in a way that (255, 0, 0) follows (254, 255, 0). It seem like it should proceed in some variant of:

    (0, 0, 0) -> (254, 0, 0) -> (254, 1, 0) -> (254, 254, 0) -> (254, 254, 1) -> (254, 254, 254).

    This would represent a transition from black to white through red, orange and yellow without any abrupt changes. Because of the non-linear way our eyes work it may not be perceived as perfectly smooth, but you should be able to do it without significant banding. For example:

    let canvas = document.getElementById('canvas')
    var ctx = canvas.getContext('2d');
    
    let rgb = [0, 0, 0]
    // fill channels fromgre
    for (let j = 0; j < 3; j++){
      for (let i = 0; i < 254; i++) {
        rgb[j]++
        ctx.fillStyle = `rgba(${rgb.join(",")}, 1)`
        ctx.fillRect(i +254*j, 0, 1, 50);
      }
    }
    <canvas id="canvas" width="765"></canvas>

    Or through blue by starting at the other end:

    let canvas = document.getElementById('canvas')
    var ctx = canvas.getContext('2d');
    
    let rgb = [0, 0, 0]
    // fill channels fromgre
    for (let j = 0; j < 3; j++){
      for (let i = 0; i < 254; i++) {
        rgb[2-j]++
        ctx.fillStyle = `rgba(${rgb.join(",")}, 1)`
        ctx.fillRect(i +254*j, 0, 1, 50);
      }
    }
    <canvas id="canvas" width="765"></canvas>