pythonimagepython-imaging-librarybytesio

Issues reading image from BytesIO() object using PIL


I am attempting to save an image in PNG format into a BytesIO object and read the same from buffer, but I get the following error:

ValueError: not enough image data

This is what I am attempting:

i = Image.open('sample_small.png') # Image.fromarray(img_array)
buf = BytesIO()
i = i.resize(size=(int(i.width/2), int(i.height/2)))
i.save(buf, format="PNG", compress_level=9)
buf.seek(0)
i = Image.frombuffer(data=buf.getvalue(), size=i.size, mode=i.mode, decoder_name="raw")
buf.close()

i.show()

could someone point out the right way to do this?

In the real use case, the original image is available as an array, so it is obtained by first loading the array as an Image using:

i = Image.fromarray(img_array)

The objective is to send the image to a zmq receiver as bytes object and hence I need to compress the image as PNG with max. compression, store it into a temporary file-like object as bytes and then publish it using zmq, hence saving the image to a file and then loading the image to use i.tobytes() is also not optimal.

Since the receiver uses the Image.frombuffer()/ Image.frombytes() method to load the image, I want to know the right way to save the image data to a buffer that can be read by the receiver.

Thanks in advance!

The image I used in this example:

enter image description here


Solution

  • frombuffer is expecting pixel data, but you are giving it a compressed png. Since it's already a png file, open it.

    from PIL import Image
    from io import BytesIO
    
    def png_bytes(path:str, scale:float, compress_level:int=9) -> bytes:
        #image setup
        img = Image.open(path)
        img = img.resize(size=(int(img.width*scale), int(img.height*scale)))
    
        with BytesIO() as buff:
            #save png file to buff
            img.save(buff, format="PNG", compress_level=compress_level)
            
            #get bytes
            buff.seek(0) 
            out = buff.read()
            
        return out #return bytes
    
    
    #get bytes
    img = png_bytes('sample_small.png', .5)
    
    #open bytes as BytesIO
    Image.open(BytesIO(img)).show()