Search code examples
bitmapbit-manipulationpngpixel

PNG Bit extension


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.


Solution

  • 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)