How to Convert an Image from PNG to JPG or WebP in Memory using Python / Pillow

Share

In Python, it's possible to open an image in one format and save it to another using Pillow, also known as Python's Image Library (PIL). This means we can open a PNG and save it as a JPG or PNG with just Python. However, normally examples to do this show how to do it using files: you open from a filepath, then save to a filepath. Sometimes we might want to avoid this step if, for example, we want to preview how the image will look like before actually saving it.

To do this, we can simply save the image to a buffer in memory instead of to a filepath, then open it from the buffer to see how it looks like. Saving will encode the image using the compression algorithm, and opening will decode it.

from io import BytesIO
from PIL import Image

def convert_in_memory(im: Image, *args, **kwargs) -> Image:
    # create a buffer in memory
    buffer = BytesIO()

    # save to the buffer in memory
    im.save(buffer, *args, **kwargs)
    
    # open from the buffer in memory
    new_im = Image.open(buffer)

    return new_im
    # the buffer is automatically freed
    # after it goes out of scope
    # because it's Python

png_image = Image.open('/home/me/Pictures/example.png')
jpeg_preview = convert_in_memory(png_image, 'jpeg', quality=80)
webp_preview = convert_in_memory(png_image, 'webp', lossless=True)

# if we want to save a preview afterwards
# we have to use the original image or it will re-encode

# right:
png_image.save('/home/me/Pictures/converted.jpg', 'jpeg', quality=80)

# wrong:
jpg_preview.save('/home/me/Pictures/converted.jpg')

Doing this you won't need to use a temporary file to get a preview.

Getting the File Size

To get the file size of the converted image without saving it to disk, you can use the buffer where we saved the file to like this1:

size_in_bytes = buffer.getbuffer().nbytes

We can make this human-readable very easily:

def get_file_size(value: int) -> str:
    if value < 1000:
        format = '%dB'
    else:
        value /= 1024
        if value < 1000:
            format = '%0.1fKiB'
        else:
            value /= 1024
            format = '%0.2fMiB'
    
    return format % (value,)

print("The file size is " + get_file_size(size_in_bytes))

Observations

Currently Pillow doesn't support some settings in some image codecs. Notably, it's missing the sharpyuv setting for the WebP codec, which is useful to make WebP less blurry.

There is a pull request to add it [github.com/python-pillow/Pillow/pull/1263]. It was created in 2015.

ImageMagick and GIMP do have this ability, so one thing that you can do is create a temporary file then use subprocess to convert the image using them.

References

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *