New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Palettes with rawmode RGBA;15 don't work #6027
Comments
Hi. You're calling As noted in https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.putpalette
I've created PR #6031 to add this missing transformation (in Pillow development terms, an "unpacker") from RGBA;15 to RGB. |
Hey, thanks! That looks good. However, does that mean it's impossible to preserve the transparency bit in palettes in my case? TIMs palettes have a single bit signifying if the color is transparent or not, so it should translate to alpha reasonably well when exporting, but I don't know if Pillow lets me do that. |
I expect this code should let you make use of alpha. image = Image.frombytes('P', (image_width * 2, image_height), image_data, 'raw', 'P')
palette = ImagePalette.raw("RGBA;15", clut_data)
palette.mode = "RGBA"
image.palette = palette
name = os.path.splitext('title_gtmode.tim')[0]
image.save(name + '.png') Do you have a palette containing transparency that you can test it on? |
I tried the following example and it throws import sys
import struct
import os
from PIL import Image, ImagePalette
if len(sys.argv) < 3:
exit
mode = sys.argv[1].lower()
if mode == 'unpack':
clut_data = None
image_data = None
image_width = 0
image_height = 0
with open(sys.argv[2], 'rb') as tim:
tag, version = struct.unpack('BB2x', tim.read(4))
if tag == 0x10:
if version != 0:
sys.exit(f'Unknown TIM file version {version}!')
flags = struct.unpack('B3x', tim.read(4))[0]
bpp = flags & 3
clp = (flags & 8) != 0
if clp:
# Parse CLUT
length, x, y, width, height = struct.unpack('IHHHH', tim.read(12))
clut_data = tim.read(length - 12)
# Parse image
length, x, y, width, height = struct.unpack('IHHHH', tim.read(12))
image_data = tim.read(length - 12)
if bpp == 0:
# 4bit, groups of 4 pixels
image_width = width * 4
rawmode = 'P;4'
mode = 'P'
# TODO: Is there a better way to do it in Pillow? Order of nibbles needs to be swapped
image_data = bytes(map(lambda x: ((x & 0xF) << 4) | ((x >> 4) & 0xF), image_data))
elif bpp == 1:
# 8bit, groups of 2 pixels
image_width = width * 2
rawmode = 'P'
mode = 'P'
elif bpp == 2:
# 16bit, each pixel separate
image_width = width
rawmode = 'RGB;15' # TODO: Alpha?
mode = 'RGB'
elif bpp == 3:
# 24bit, 3-byte groups
# TODO: Verify this
image_width = (width * 3) / 2
rawmode = 'RGB'
mode = 'RGB'
image_height = height
if image_data:
image = Image.frombytes(mode, (image_width, image_height), image_data, 'raw', rawmode)
palette = ImagePalette.raw('RGBA;15', clut_data)
palette.mode = 'RGBA'
image.palette = palette
name = os.path.splitext(sys.argv[2])[0]
image.save(name + '.bmp') |
Ok, I've created PR #6054 to resolve this. With that addition, your code (without the change from my earlier comment) produces this image. |
Thanks, that looks very good! That is indeed the image you should get when interpreting the transparency bit as alpha. Slightly offtopic but still on the topic of palettes with alpha, how should I go about retrieving such a palette back from an image, e.g. after a conversion? (my current toolchain works on RGBA images after all due to poor support of indexed PNGs with alpha from GIMP) My current approach is to use In an ideal scenario, I feel like the best way would be to retrieve the palette the same way I retrieve image bytes, i.e. through a decoder like That is probably a feature request separate to this ticket, but I'm asking in case I am missing something obvious in Pillow. EDIT: |
If you're after RGBA values, If you're after RGBA;15 values, then if we added a "packer" from RGBA to RGBA;15 (almost the inverse of #6031), then you could do I'm not sure why in your last edit you mentioned guessing the returned format of the palette - I would have thought that was exactly why the first return value of |
You're absolutely right, although arguably it's not the most user friendly way of dealing with the palettes. with Image.open(os.path.join(dir_name, rid)) as org_im:
if org_im.getcolors(maxcolors=16) is None:
print(f'WARNING: {rid} has more than 16 unique colors! The image will be quantized down to 16 colors when packing, but quality may suffer.')
im = org_im.quantize(colors=16)
imageData = im.tobytes('raw', 'P;4')
imagePalette = im.palette.getdata() Now depending on whether EDIT: |
What did you do?
In an attempt to convert a PlayStation TIM image format to a bitmap, I turned to Pillow to handle image processing for me. This proved to be reasonably straightforward, but when operating on 8-bit palettized TIM files, I seem to be unable to preserve transparency. As per THIS DOCUMENTATION palette entries are RGBA5551 entries, but attempting to use this format with Pillow results in an exception (see the sample code below for more detail).
Since palettes with format
'RGBA;15'
(RGBA5551) are reported as supported, while both'RGBA'
(RGBA8888) and'RGB;15'
(RGB555) are accepted, I believe this might be a bug.What did you expect to happen?
I expected
image.putpalette(clut_data, 'RGBA;15')
to succeed and produce a correct image with the alpha channel used.What actually happened?
image.putpalette(clut_data, 'RGBA;15')
throwsunrecognized raw mode
.What are your OS, Python and Pillow versions?
pip
For a relatively small reproduction, refer to the attached
.zip
with a.tim
file to convert and run the following example withpython code.py unpack title_gtmode.tim
. Noticeimage.putpalette(clut_data, 'RGBA;15')
throwsunrecognized raw mode
, while running it with modeRGB;15
works as expected and produces a correct image, albeit with transparency data discarded.title_gtmode.zip
The text was updated successfully, but these errors were encountered: