pythonimage-processingpython-imaging-library

How does a Palette work in Python Imaging Library?


Suppose I have an RGB888 image (= 8 bit per color) in which the color of each pixel evenly encodes a (raw) data byte.

Scheme:
Channel red encodes Bit 7:6
(R: 0-63 = 0b00, 64-127 = 0b01, 128-191 = 0b10, 192-255 = 0b11)

Channel green encodes Bit 5:3
(G: 0-31 = 0b000, 32-63 = 0b001, 64-95 = 0b010, 96-127 = 0b011, ...)

Channel blue encodes Bit 2:0
(B: 0-31 = 0b000, 32-63 = 0b001, 64-95 = 0b010, 96-127 = 0b011, ...)

Example:
RGB = [150, 40, 94] represents (raw) data byte 0b10001010.

Question:
I would like to use the Python Imaging Library (PIL) to decode my image. If I got it right, then I would need to define a palette and apply it to my image using the quantize() method (as described here). However, I don't understand how the mapping of the colors does work. How does the palette translate the original 24 Bits of color information into 8 Bits?

Finally: How would I need to set up my palette so that the decoding works according to my scheme?

Edit:

My image does not contain a subject. It contains data. Below is an example ("Data" is what I like to decode using PIL).

Image with encoding scheme


Solution

  • I think your question maybe means something different altogether. I think you want to convert ranges of numbers into small integers, i.e. 0-31 becomes 0, 32-63 becomes one. So you just need to shift your values to the right and you will lose the less significant bits.

    If your image is this:

    import numpy as np
    
    array([102, 220, 225,  95, 179,  61, 234, 203,  92,   3], dtype=uint8)
    

    Then, because 2^6 is 64, you can do get integers in range 0..3, with:

    partA = image >> 6
    

    which will give you:

    array([1, 3, 3, 1, 2, 0, 3, 3, 1, 0], dtype=uint8)
    

    Or , if image is three channels and you want to extract from the Red channel, use:

    partA = image[...,0] >> 6
    

    Similarly, if image is three channels and you want to extract from the Blue channel, use:

    partA = image[...,2] >> 6
    

    And, because 2^5 is 32, you can get integers in range 0..7, with:

    partB = image >> 5
    

    which will give you:

    array([3, 6, 7, 2, 5, 1, 7, 6, 2, 0], dtype=uint8)
    

    Now you can reconstruct your (hidden?) byte by shifting and ORing together the parts:

    reconstructed = (partA << 6) | (partB << 3) | partC
    

    Once you have your data in a Numpy array called reconstructed toy can make it into a PIL Image with:

    from PIL import Image
    pImage = Image.fromarray(reconstructed)