8
$\begingroup$

I have seen in some computer graphics software's code bases that sometimes the higher bits of RGB565-format image data are replicated into the lower bits when converting to higher-bit-depth format RGBA8888.

I have found for example the comment by user "eq" in this gamedev.net thread:

I prefer to replicate the higher bits into the undefined lower bits:
R8 = (R5 << 3) | (R5 >> 2);

However I do not understand the reason behind.

What use the purpose of replicating those bits into the converted data?

$\endgroup$
  • 1
    $\begingroup$ Without replicating the bits the LSBs will be 0, so for the maximum value of 0x1f (max for 5 bits) it would expand to 0xf8 when converted to 8 bit. What you want is 0xff so the range of 0x00->0x1f will be mapped to 0x00->0xff instead of 0x00->0xf8. $\endgroup$ – PaulHK Oct 23 '18 at 4:07
  • 2
    $\begingroup$ @PaulHK Please post that as an answer. It's complete as it stands, but as a comment it's not searchable. $\endgroup$ – Dan Hulme Oct 23 '18 at 11:24
  • $\begingroup$ Yes thank you @PaulHK this answers correctly my question $\endgroup$ – wip Oct 24 '18 at 3:33
7
$\begingroup$

Without replicating the bits the LSBs will be 0, so for the maximum value of 0x1f (max for 5 bits) it would expand to 0xf8 when converted to 8 bit. What you want is 0xff so the range of 0x00->0x1f will be mapped to 0x00->0xff instead of 0x00->0xf8. Without merging the LSB you would not be able to convert 0x1f,0x1f,0x1f to white (0xff,0xff,0xff). Incidentally this is the same as N*0xff/0x1f.

Example: 

left shift only (<< 3)
%---00001 -> %00001000     (0x01 -> 0x08)
%---10000 -> %10000000     (0x10 -> 0x80)
%---11111 -> %11111000     (0x1f -> 0xf8)

merging MSB to LSB 
%---00001 -> %00001000     (0x01 -> 0x08)
%---10000 -> %10000100     (0x10 -> 0x84)
$---11111 -> %11111111     (0x1f -> 0xff)
$\endgroup$
8
$\begingroup$

There's actually a reasonably good mathematical reason for doing bit replication:

First note that the n-bit string, $N$, actually represents the value $\frac{N}{2^n-1}$ and we want to produce the m-bit string, $M$, where $n<m$ and $$\frac{N}{2^n-1}\approx\frac{M}{2^m-1}$$

We first scale numerator and denominator with $$\frac{N.(2^n+1)}{(2^n-1)(2^n+1)}\approx \frac{M}{2^m-1}$$ and this simplifies to $$\frac{N.(2^n+1)}{2^{2n}-1}\approx \frac{M}{2^m-1}$$

In your case, $n\in \{5,6\}$ and $m=8$ and we can "stop" here, but but the process can be repeated, (ad nauseum), if m >> n.

We next make the approximation... $$\frac{N.(2^n+1)}{2^{2n}}\approx \frac{M}{2^m}$$ which simplifies to $$\frac{N.(2^n+1)}{2^{2n-m}}\approx M $$

Note that $N.(2^n+1)$ is equivalent to repeating the n-bit string, to create a 2n-bit string, and the division shifts off the $2n-m$ LSBs to leave an M bit result.

QED

Of course, the 'correct' calculation is $M=\lfloor(\frac{(2^m-1) N}{2^n-1}+\frac{1}{2}\rfloor$ but this approximation, in general, works most of the time. Of course there are times when it's inaccurate, but IIRC only by one bit and relatively infrequently.

$\endgroup$
  • 1
    $\begingroup$ Thank you for a detailed explanation with nice formulas. I was curious about the error introduced by the approximation so I made this graph that compares both formulas: desmos.com/calculator/cvaqofyvbf . However I prefer PaulHK's answer since it is easier to understand. $\endgroup$ – wip Oct 25 '18 at 2:12
  • 1
    $\begingroup$ Minor quibble, if m >= 2n then you need to change your "approximation" equation. An extreme, example, is if n=1, then you need to repeat the string 8 times (i.e. perform log2(8)=3 steps). Of course, if you pad with "10...0" instead of all zeros, then on average you'll have a lower error, but lose the extremes. "However I prefer PaulHK's answer" :-) Well, there's no accounting for taste 8P. $\endgroup$ – Simon F Oct 25 '18 at 11:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.