I'm writing PNG Decoder and I'm having problems with grayscale images with bit depths < 8.
I am not sure what to do to get full color range.
For example, given that image is using 4 bit depth, and data byte is: 10110010
I have 2 pixels, 1011
and 0010
. Now, I shift second pixel 4 bits left to occupy high order bits. And here is the problem. Should I duplicate bits so that 2 pixels will be 10111011
and 00100010
or should I just extend last bit so that pixels will be 10111111
and 00100000
. I could not find information about this in PNG spec.
Why would this be in the specifications? You are converting your output values, and the specs should not care what you do with your output data. Still, as Glenn Randers-Pehrson observed, the official specification contains relevant advice. Concepts: 4.3.6 Sample depth scaling introduces the basics. Recommendations for encoders and decoders are discussed in 12.5 Sample depth scaling for encoders (scaling external data to a supported PNG format), and 13.12 Sample depth rescaling for decoders (scaling a PNG format to a desired output format).
That said, assuming you want a 4 bit value 1111
linearly converted to an 8-bit value of 1111.1111
, then the formula is
output = 255 * input / 15
Applying the formula to the range 0..15, you get
0: 0000 -> 00000000
1: 0001 -> 00010001
2: 0010 -> 00100010
3: 0011 -> 00110011
4: 0100 -> 01000100
5: 0101 -> 01010101
6: 0110 -> 01100110
7: 0111 -> 01110111
8: 1000 -> 10001000
9: 1001 -> 10011001
10: 1010 -> 10101010
11: 1011 -> 10111011
12: 1100 -> 11001100
13: 1101 -> 11011101
14: 1110 -> 11101110
15: 1111 -> 11111111
As you can see, the first four bits are always repeated in the lower bits. That is exactly what the formula above does!
255 * input / 15
= input * 255 / 15
= input * 17
= input + input * 16
and we end up with
output = (input<<4) + input
This can be extended to upsample (and downsample) any value to another base:
newvalue = (oldvalue * max_new) / max_old
As max_new
typically will be odd for bit values, to get proper rounding you can use
newvalue = (2 * oldvalue * max_new + max_old) / (2*max_old)