Question

I tried to convert a 32-bit Bitmap to 32-bit PNG using PIL.

from PIL import Image
im = Image.open('example.bmp')
print im.mode
# it prints 'RGB', but expected was 'RGBA'
im.save('output.png', format='PNG')

The expected image mode is 'RGBA', but actually i get 'RGB'.

I also tried the following code, but it does not work.

from PIL import Image
im = Image.open('example.bmp')
im = im.convert('RGBA')
im.save('output.png', format='PNG')
Was it helpful?

Solution

Ok, here is something to get started. Since I don't know specifically which format is your BMP file, I only handled a specific case of BMP with full alpha channel that I happen to have. The kind of BMPs I'm handling here can be obtained by converting, for example, PNG with alpha to BMP using ImageMagick. This will create what is called "BITMAPV5". Given your description, you don't have a BitmapV5 (because PIL would fail to even open it), so we will need an iteration with discussions to solve your specific case.

So, you either need a new file decoder or a patched BmpImagePlugin.py. How to do the former is described in PIL's manual. For the later you will obviously need to send a patch and hope to get it into the next PIL version. My focus is on creating a new decoder:

from PIL import ImageFile, BmpImagePlugin

_i16, _i32 = BmpImagePlugin.i16, BmpImagePlugin.i32

class BmpAlphaImageFile(ImageFile.ImageFile):
    format = "BMP+Alpha"
    format_description = "BMP with full alpha channel"

    def _open(self):
        s = self.fp.read(14)
        if s[:2] != 'BM':
            raise SyntaxError("Not a BMP file")
        offset = _i32(s[10:])

        self._read_bitmap(offset)

    def _read_bitmap(self, offset):

        s = self.fp.read(4)
        s += ImageFile._safe_read(self.fp, _i32(s) - 4)

        if len(s) not in (40, 108, 124):
            # Only accept BMP v3, v4, and v5.
            raise IOError("Unsupported BMP header type (%d)" % len(s))

        bpp = _i16(s[14:])
        if bpp != 32:
            # Only accept BMP with alpha.
            raise IOError("Unsupported BMP pixel depth (%d)" % bpp)

        compression = _i32(s[16:])
        if compression == 3:
            # BI_BITFIELDS compression
            mask = (_i32(self.fp.read(4)), _i32(self.fp.read(4)),
                    _i32(self.fp.read(4)), _i32(self.fp.read(4)))
            # XXX Handle mask.
        elif compression != 0:
            # Only accept uncompressed BMP.
            raise IOError("Unsupported BMP compression (%d)" % compression)

        self.mode, rawmode = 'RGBA', 'BGRA'

        self.size = (_i32(s[4:]), _i32(s[8:]))
        direction = -1
        if s[11] == '\xff':
            # upside-down storage
            self.size = self.size[0], 2**32 - self.size[1]
            direction = 0

        self.info["compression"] = compression

        # data descriptor
        self.tile = [("raw", (0, 0) + self.size, offset,
            (rawmode, 0, direction))]

To properly use this, the canonical way is supposedly to perform:

from PIL import Image
Image.register_open(BmpAlphaImageFile.format, BmpAlphaImageFile)
# XXX register_save

Image.register_extension(BmpAlphaImageFile.format, ".bmp")

The problem is that there is already a plugin for handling ".bmp", and I didn't bother to find out how I could prepend this new extension so it is used before BmpImagePlugin is used (I also don't know if it is possible to do such thing in PIL). Said that, I actually used the code directly, as in:

from BmpAlphaImagePlugin import BmpAlphaImageFile

x = BmpAlphaImageFile('gearscolor.bmp')
print x.mode
x.save('abc1.png')

Where gearscolor.bmp is a sample bitmap with full alpha channel as described earlier. The resulting png is saved with alpha data. If you check BmpImagePlugin.py's code, you will notice I reused most of its code.

OTHER TIPS

PIL is buggy and doesn't work properly with transparent BMP files.

If I remember well wxPython seems to works properly with them. I wrote a small wrapper between the two about an year ago, only if I could find the code.

The code by @mmgp is great for loading BMP files with Alpha, but on Python 3 requires two small changes:

if s[:2] != 'BM':

becomes:

if s[:2] != b'BM':

and self.size needs to change where used, so the end of the code becomes:

self._size = (_i32(s[4:]), _i32(s[8:]))
direction = -1
if s[11] == '\xff':
    # upside-down storage
    self._size = self._size[0], 2**32 - self._size[1]
    direction = 0

self.info["compression"] = compression

# data descriptor
self.tile = [("raw", (0, 0) + self._size, offset,
    (rawmode, 0, direction))]

Additionally, sometimes it will fail - because some of the BMPs aren't in the right format. You can either preselect:

if typename != "alpha":
    img = Image.open(filename)
else:
    img = BmpAlphaImageFile(filename)

Or use a try/catch block as a fall-back:

try:
    img = BmpAlphaImageFile(filename)
except IOError:
    img = Image.open(filename)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top