Issues reading image from BytesIO() object using PIL

522 Views Asked by At

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

3

There are 3 best solutions below

1
OysterShucker On BEST ANSWER

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()
0
Mark Setchell On

If the recipient is going to use frombytes() to read the image, you need to use tobytes() to create it.

And the recipient will need to know, or be told, the image mode and size.

3
Mark Ransom On

Image.frombuffer is exactly the wrong function to use for this; it says so right in the docs.

Note that this function decodes pixel data only, not entire images. If you have an entire image file in a string, wrap it in a BytesIO object, and use open() to load it.

Note that open can take an open file instead of a filename, and a BytesIO object qualifies as a file..