1

I have three integer values (rgb) between 0 and 255. I want to store this information in a single float value between 0 and 1 without information loss.

My approach was to bitshift the values into a new integer meaning:

int rgb = (r << 16) + (g << 8) + b;

and then divide the result by the 2^24 to get it between 0 and 1.

This works fine so far because you can recover each color value.

The problem i have is that i need each color value to be evenly weighted in the solution.

The resulting float value is changing a lot when the red part is being modified, whereas the changes are small when tweaking the blue part.

Any ideas how to solve this issue?

Tom B.
  • 307
  • 1
  • 11
  • 2
    You could redistribute the bits of each color component individually so that the relative "weights" aren't quite so extremely different. But fundamentally you are trying to take a three-dimensional array of points and label them sequentially. There will be "jumps" in the sequence _somewhere._ – David K Oct 21 '17 at 21:45
  • If you interleaved the bytes from rgb (one bit from red, one bit from green, one bit from blue, ...) you would get closer to your desired behaviour. Or if you transform to HSV, instead of rgb, equal changes to red green an blue would change the float very little, but unequal changes would change the hue and therefore change the float a lot. – HugoRune Oct 21 '17 at 21:50
  • 1
    When you say "a single float value," does that mean single-precision IEEE-754, or are you allowed to use double precision? – David K Oct 21 '17 at 21:55
  • 1
    @DavidK i mean "one 32bit float value" – Tom B. Oct 21 '17 at 22:03

1 Answers1

0

Some ideas:

int lightness = r + g + b;
int exp1 = lightness / 8;
int man1 = lightness % 8;

This sets lightness to a value in the range 0..765, exp1 to a value in the range 0..95, and man1 to a value in the range 0..7.

int mantissa_bits = (1 << 23) + (man1 << 20) + (r << 8) + g;

This sets mantissa to a non-negative integer less than 2^24, but not less than 2^23.

float multiplier = pow(2, exp1 - 119);

This sets multiplier to a power of 2 no less than 2^-119 and no greater than 2^-24.

float rgb_encoding = mantissa * multiplier;

This sets rgb_encoding to a number less than 2^24 * 2^-24 = 1, but no less than 2^23 * 2^-119 = 2^-96. The leading bit of mantissa_bits, which is always 1 << 23, becomes the implicit most significant bit of the floating-point value (which is not actually stored in the bits of rgb_encoding) while the next 23 bits of mantissa_bits become the actual stored mantissa of rgb_encoding with no loss of information.

Now if you increase or decrease any of the values of r, g, or b by 1, you change the value of rgb_encoding by at least 6.6%. The change in rgb_encoding is very slightly larger if you change r than if you change b. For example, if r = 127, g = 15, b = 95, then mantissa_bits = 13664015 (0x00d07f0f hexadecimal). Increasing b by 1 changes mantissa_bits to 14712591 (an increase of 1048576), but increasing r by 1 changes mantissa_bits to 14712847 (an increase of 1048832).

To decode the RGB data from rgb_encoding, get its exponent using double frexp(double value, int *exp) (as in this answer), use the value of exp to multiply rgb_encoding by a power of 2 that produces an integer value between 2^23 and 2^24, cast it to an int, extract bits 20 through 22 and add these to 8 * (exp + 119). This gives the value of r + g + b. Get the values of r and g from the least significant 16 bits of the integer, and subtract r + g from r + g + b to get b.

The trick here is that we've stored just 23 bits of information in the mantissa of rgb_encoding, but almost 7 bits of information in the 8-bit exponent of rgb_encoding. As a result, the encoded value represents the lightness of the original RGB color in a non-linear way, and darker colors map to very small values of rgb_encoding, although still in the range of normal positive float values. We've made use of those 30 bits by storing the completely symmetric information of r + g + b in the 10 bits that have the greatest impact on the encoded value, and the asymmetric information of r and g in the 16 bits that have the least impact on the encoded value. We've also left 4 bits in the middle at constant zeros in order to amplify the significance of the symmetric information over the asymmetric information.

David K
  • 3,147
  • 2
  • 13
  • 19