Python Pillow – RLE compression of BMP image

Question:

I’m working on a script, which builds an image, combines it with another image and saves it locally as an 8-bit BMP-file.
The image is then read by a ESP32 microcontroller, but the problem is that due to memorylimitations, the allowed file size is somewhat limited.

As a consequence, I made a BMP decoder for the ESP32, which supports RLE. In theory, the allowed number of bytes can still be exceeded, but only text and simple icons are to be read, so it will most likely never happen.

It uses Pillow for image processing, which now supports RLE-compression from version 9.1.0
https://github.com/python-pillow/Pillow/blob/main/docs/handbook/image-file-formats.rst

Pillow reads and writes Windows and OS/2 BMP files containing 1, L, P,
or RGB data. 16-colour images are read as P images. Support for
reading 8-bit run-length encoding was added in Pillow 9.1.0. Support
for reading 4-bit run-length encoding was added in Pillow 9.3.0.

Here’s the part of the code, that combines two existing images into a new one and saves them:

img_buf = io.BytesIO() # Start converting from Matplotlib to PIL
# Supported: eps, jpeg, jpg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff, webp
plt.savefig(img_buf, format='png', transparent=True) 
graph = Image.open(img_buf)
# Create empty, 8-bit canvas
new_image = Image.new('P',(600,448), (255,255,255)) # P = 8-bit indexed
new_image.paste(img,(0,0)) # Insert image 1 into canvas
new_image.paste(graph,(0,200)) # Insert image 2 into canvas at y:200
new_image.save("../output/priceeast.bmp", compression=1) # According to the docs, 1 = RLE

It saves the image, alright, but not RLE-encoded and I can’t work out, how to enable it… or is RLE only supported when reading BMP, not saving?

UPDATE:
I added this line below:

subprocess.call('magick ../output/priceeast.png -type palette -compress RLE ../output/priceeast.bmp ', shell=True)
Asked By: Anders

||

Answers:

Pillow does not support writing BMP files with compression, which can be determined by investigating the source. BmpImagePlugin._write:

    # bitmap info header
    fp.write(
        o32(header)  # info header size
        + o32(im.size[0])  # width
        + o32(im.size[1])  # height
        + o16(1)  # planes
        + o16(bits)  # depth
        + o32(0)  # compression (0=uncompressed)
        + o32(image)  # size of bitmap
        + o32(ppm[0])  # resolution
        + o32(ppm[1])  # resolution
        + o32(colors)  # colors used
        + o32(colors)  # colors important
    )

We can see here that the compression field in the output file header is hard-coded to none, indicating that compression is not supported when writing a file.

Answered By: tari

If you would like a work-around, ImageMagick can convert any image format to 8-bit RLE BMP like this:

magick INPUTIMAGE.xxx -type palette -compress RLE result.bmp     # where XXX is PNG, JPG, TIFF, GIF, TGA etc

Check the result with exiftool like this:

exiftool -filename -filesize -compression result.bmp

File Name                       : result.bmp
File Size                       : 1168 bytes
Compression                     : 8-Bit RLE

Note that there are Python bindings to ImageMagick via wand, so you can achieve the same effect in Python like this:

#!/usr/bin/env python3

# You may need this before running on macOS
# export MAGICK_HOME=/opt/homebrew

from wand.image import Image

# Load image , or create pseudo image
with Image(filename='gradient:red-blue') as img:
    img.type = 'palette'
    img.compression = 'rle'
    img.save(filename='result.bmp')
Answered By: Mark Setchell
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.