PIL: Create image from blob doesn't work

Hi there,
I’m fairly new to python.
I’m reading images from an sqlite3 database. Unfortunately there is no info about the format. Therefore I need to get the format from the image itself. I came across the image module of PIL and it works fine so far when I create the image object from a file. However I didn’t find out a way to create the image object directly from the blob. This is my code, note the inline remarks:

    def extractPicture(cursor, picture_id):
            sql = "SELECT rowid, Name, Bild FROM `Substanzen - Substanzen` WHERE rowid = :id"
            param = {'id': picture_id}
            cursor.execute(sql, param)
            rowid, name, ablob = cursor.fetchone()
            print(name, isinstance(ablob, (bytes, bytearray)))
            filenamePic = None
            if name is not None and isinstance(ablob, (bytes, bytearray)):
                name = name.replace('/', '_').replace(' ','_')
                filenamePic = picDir + '\\' + name + '.png'
                filename = picLoc + name + '.png'
                # writing to disk is working fine:
                with open(filename, 'wb') as output_file:
                # creating image from path is working fine:
                # img = Image.open(filename)
                # creating image from blob directly doesn't work:
                img = Image.fromarray(ablob)
                imgFormat = img.format.lower()

I keep getting errors, when using Image.fromarray(ablob) the following:

<class ‘AttributeError’>, AttributeError(“‘bytes’ object has no attribute ‘array_interface’”)

I think you need to use frombytes not fromarray according to the pillow docs.

Many thanks for the very quick response! I came across frombytes already, however this requires specifying mode and size whereas I’m having the blob only.
But below in the docs for frombytes there is another hint:

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

I did so and bingo, it works like a charm:
img = Image.open(BytesIO(ablob))
This enabled me to simplify my code a lot.
Many thanks for the support and best regards!

1 Like

Right. Image.frombytes supposes that the data is just raw sample data, without any information about the image mode and dimensions or any other metadata (and also not being compressed etc.) So you have to supply that information. (Image.fromarray is similar, but IIRC it can take a Numpy array and use the array’s shape and dtype to guess an image mode and dimensions.)

Image.open expects to be given a complete image file, which obeys some real-world image format (such as JPEG) - and looks at the header to figure out what the format is, and does all the fancy stuff to interpret the rest.

Since you were having success by writing the data directly to disk in a file with a .png filename extension, I assume that the data represents a PNG image.

io.BytesIO is simply an adapter provided by the standard library, to treat the bytes in a bytes object as if they were bytes in a file on disk. It “reads” the virtual file by maintaining an index position and slicing the underlying bytes appropriately (or at least something equivalent to that).

Unfortunately not there are other types in the DB but there is no info about the type of the image in the DB, only the blob. I used this extension accepting that it may be wrong. Then determined the correct type by use of img.format, then renamed the file if “.png” was wrong. This worked but was ugly and anything but straight forward.

Actually there is info about the type,

The blob will start with unique header.

In the case of PNG that seems to be b’PNG\r\n\x1a\n’.
The other image formats also have signatures you could check for.
The linux file command used libmagic to figure out the file type in this way.

You could use python-libmagic · PyPI maybe to do the hard work for you.

1 Like