Search code examples
pythonimagepython-imaging-librarykeil

Create 8 bit compressed RGB with Pillow


For my college project, I need to convert a 4-byte colored png file to a 1-byte colored png file using Keil.

I've converted a png file to a hex file using Python, then loaded a hex file in Keil, progressed the data, and got the output.

In Keil, a hex file with a 4-byte RGBA was changed to a 1-byte RGB(RRR_GGG_BB in each byte) by removing the alpha channel and concatenating 3 or 2 MSB of each color.

Compressing process MUST be done in keil.

So the output hex file has 1-byte RGB data.

I put this data in python, reshape the array, but I can't make it to PNG file.

Here's the code that I tried.

from intelhex import IntelHex
from PIL import Image
import numpy as np

ih = IntelHex()
ih.loadhex('memoutput.hex')
pydict=ih.todict()
rgbcomp = np.fromiter(pydict.values(), dtype=int)
rgbcomp = np.reshape(rgbcomp,(640,960))
img_2 = Image.fromarray(rgbcomp, 'P')
# https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes
img_2.show()

Each data in rgbcomp array has 1-byte RGB data(R[7:5]+G[7:5]+B[7:6]).

Does anyone knows how to make reshaped array to 256 color PNG using pillow?

It should be look like true-color image converted into 256 color gif image.

Please help.

++ Added at 14:15, May 23, 2024
Here's the file of keil's output memoutput.hex

And also I've almost done that make pltePix - targetting the number of palette, plte - palette that decompose RRRGGGBB to R[7:0], G[7:0], B[7:0]

from intelhex import IntelHex
from PIL import Image
import numpy as np

ih = IntelHex()
ih.loadhex('memoutput.hex')
pydict=ih.todict() #hex to dict
rgbcomp = np.fromiter(pydict.values(), dtype=int) #array of values in dict
uniqRgbComp = np.unique(rgbcomp) #int list of uniq colors
plte = np.zeros((len(uniqRgbComp),3)) #plte(24bit rgb) = uniqRgbComp(8bit rgb)
for i in range(len(uniqRgbComp)):
   plte[i] = np.array([ int(uniqRgbComp[i]&0b11100000), int( ((uniqRgbComp[i])&0b00011100) << 3 ), int( (uniqRgbComp[i]&0b00000011) << 6 ) ])
pltePix = np.zeros(len(rgbcomp)) #Pixel data points palette
for i in range(len(rgbcomp)):
    pltePix[i] = np.argwhere(uniqRgbComp==rgbcomp[i])[0]
pltePix = np.reshape(pltePix, (640, 960))

The last, I just need to write PNG file using pltePix(pixel data), and plte(palette).

Can I get help to write 1-byte RGB image with these two arrays?

The original image is: original_image

The 256 color image will looks like(generated by photoshop, 3:3:2 color will looks different): 8bit_image

++ Added at 14:50, May 23, 2024
FINALLY I MADE IT!!! Thanks for your all help.

And the code:

from intelhex import IntelHex
from PIL import Image
import numpy as np

# data=[None]*256
# for i in range(256):
#     data[i] = i
    #data[i] = f'ob{i:08b}'

ih = IntelHex()
ih.loadhex('memoutput.hex')
pydict=ih.todict() #hex to dict
rgbcomp = np.fromiter(pydict.values(), dtype=int) #array of values in dict
uniqRgbComp = np.unique(rgbcomp) #int list of uniq colors
plte = np.zeros((len(uniqRgbComp),3)) #plte(24bit rgb) = uniqRgbComp(8bit rgb)
for i in range(len(uniqRgbComp)):
   plte[i] = np.array([ int(uniqRgbComp[i]&0b11100000), int( ((uniqRgbComp[i])&0b00011100) << 3 ), int( (uniqRgbComp[i]&0b00000011) << 6 ) ])
pltePix = np.zeros(len(rgbcomp)) #Pixel data points palette
for i in range(len(rgbcomp)):
    pltePix[i] = np.argwhere(uniqRgbComp==rgbcomp[i])[0]
pltePix = np.reshape(pltePix, (640, 960))
im = Image.new(mode="P", size=(960,640))

plte = np.ravel(plte)
plte = np.uint8(plte).tolist()
im.putpalette(plte, rawmode="RGB")
pltePix = np.ravel(pltePix)
im.putdata(pltePix)
im.show()

Output: OutputImage


Solution

  • Updated Answer

    Thank you for providing your Intel hex file. I converted it to a straight binary file with just 61,4400 bytes (960*640) using:

    objcopy --input-target=ihex --output-target=binary image.hex image.bin
    

    objcopy should be in any compiler toolchain. I actually used Alpine Linux on docker and installed the binutils package to get it.

    You can then do the following:

    • read the file into a Numpy array and reshape
    • convert to a PIL Image
    • create a palette, per my original answer
    • push palette into image and save

    #!/usr/bin/env python3
    
    import numpy as np
    from PIL import Image
    
    # Convert your Intel hex file to 'image.bin' with:
    # objcopy --input-target=ihex --output-target=binary image.hex image.bin
    
    # Load binary file and reshape to 960x640
    pixels = np.fromfile('image.bin', dtype=np.uint8).reshape((640,960))
    
    # Make into 'PIL Image'
    im = Image.fromarray(pixels)
    
    # Generate palette
    pal = [ [i & 0x70, (i & 0x1c)<<3, (i & 0x3)<<6] for i in range(256)]
    
    # Flatten palette and push into image and save
    flat = [x for xs in pal for x in xs]
    im.putpalette(flat)
    im.save('result.png')
    

    enter image description here

    Original Answer

    You should really share your 1-byte RGB file so folks can test their solutions, but I'll try and answer blind.

    You have 2 options:

    • read the 1-byte RGB file into Numpy, promote the colours to full RGB88, convert to PIL Image and let PIL deduce a palette for you
    • read the 1-byte RGB file and formulate a palette for it, and push that palette into the image.

    I think the first option is not "in the spirit" of your assignment, so let's go with the second.


    At each pixel location, you will have 1-byte, with a value in the range 0..255. You need to determine what RGB colour that byte must become in order to make a palette. Let's look at the first few:

    0  => rgb(0,0,0)
    1  => rgb(0,0,64)
    2  => rgb(0,0,128)
    3  => rgb(0,0,192)
    4  => rgb(0,32,0)
    

    So you need something like this to generate your palette.

    pal = [ [i & 0x70, (i & 0x1c)<<3, (i & 0x3)<<6] for i in range(256)]
    

    Then you'll need to call putpalette(pal) to force that palette into your P mode image.

    All code sadly untested due to lack of input data in question.