Search code examples
pythonimagepython-imaging-libraryraw

How would I read a raw RGBA4444 image using Pillow?


I'm trying to read a '.waltex' image, which is a 'walaber image'. It's basically just in raw image format. The problem is, it uses 'RGBA8888', 'RGBA4444', 'RGB565' and 'RGB5551' (all of which can be determined from the header), and I could not find a way to use these color specs in PIL.

I've tried doing this

from PIL import Image

with open('Carl.waltex', 'rb') as file:
    rawdata = file.read()

image = Image.frombytes('RGBA', (1024,1024), rawdata, 'raw', 'RGBA;4B')
image.show()

I've tried all the 16-bit raw modes in the last input, and I couldn't find a single one that worked. To be clear, this file is specifically 'RGBA4444' with little endian, 8 bytes per pixel.

If you need the file, then I can link it.


Solution

  • Now that I have a better idea of how your Waltex files work, I attempted to write a custom PIL Plugin for them - a new experience for me. I've put it as a different answer because the approach is very different.

    You use it very simply like this:

    from PIL import Image
    import WaltexImagePlugin
    
    im = Image.open('objects.waltex')
    im.show()
    

    You need to save the following as WaltexImagePlugin.py in the directory beside your main Python program:

    from PIL import Image, ImageFile
    import struct
    
    def _accept(prefix):
        return prefix[:4] == b"WALT"
    
    class WaltexImageFile(ImageFile.ImageFile):
    
        format = "Waltex"
        format_description = "Waltex texture image"
    
        def _open(self):
            header = self.fp.read(HEADER_LENGTH)
            magic, vers, fmt, w, h, _ = struct.unpack('4sBBHH6s', header)
    
            # size in pixels (width, height)
            self._size = w, h
    
            # mode setting
            self.mode = 'RGBA'
    
            # Decoder
            if fmt == 0:
                # RGBA8888
                # Just use built-in raw decoder
                self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, (self.mode, 
    0, 1))]
            elif fmt == 3:
                # RGBA4444
                # Use raw decoder with custom RGBA;4B unpacker
                self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, ('RGBA;4B', 
    0, 1))]
    
    
    Image.register_open(WaltexImageFile.format, WaltexImageFile, _accept)
    
    Image.register_extensions(
        WaltexImageFile.format,
        [
            ".waltex"
        ],
    )
    
    HEADER_LENGTH = 16
    

    It works perfectly for your RGBA888 images, but cannot quite handle the byte ordering of your RGBA444 file, so you need to reverse it for those images. I used this:

    ...
    ...
    im = Image.open(...)
    
    # Split channels and recombine in correct order
    a, b, c, d = im.split()
    im = Image.merge((c,d,a,b))
    

    If anyone knows how to use something in the Unpack.c file to do this correctly, please ping me. Thank you.