From 6f28a0c009affcfb8f67c4bde921aa7d6684a7ac Mon Sep 17 00:00:00 2001 From: Yay295 Date: Thu, 11 Apr 2024 00:40:11 -0500 Subject: [PATCH 1/7] Fix TGA rawmode BGR;15 --- src/PIL/TgaImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index f16f075df05..ebf9a1b80cd 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -36,7 +36,7 @@ (3, 1): "1", (3, 8): "L", (3, 16): "LA", - (2, 16): "BGR;5", + (2, 16): "BGR;15", (2, 24): "BGR", (2, 32): "BGRA", } From 872ff125fcc9182fcc4cbcbf3d1bc5632a21860b Mon Sep 17 00:00:00 2001 From: Yay295 Date: Thu, 11 Apr 2024 00:53:06 -0500 Subject: [PATCH 2/7] "BGR;15" -> "BGRA;15" --- src/PIL/TgaImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index ebf9a1b80cd..6a6bceacaba 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -36,7 +36,7 @@ (3, 1): "1", (3, 8): "L", (3, 16): "LA", - (2, 16): "BGR;15", + (2, 16): "BGRA;15", (2, 24): "BGR", (2, 32): "BGRA", } @@ -118,7 +118,7 @@ def _open(self) -> None: start, size, mapdepth = i16(s, 3), i16(s, 5), s[7] if mapdepth == 16: self.palette = ImagePalette.raw( - "BGR;15", b"\0" * 2 * start + self.fp.read(2 * size) + "BGRA;15", b"\0" * 2 * start + self.fp.read(2 * size) ) elif mapdepth == 24: self.palette = ImagePalette.raw( From e5c6d883d44aab0ba27ec8faf384f9a3e3b5aa01 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Thu, 11 Apr 2024 07:56:29 -0500 Subject: [PATCH 3/7] set palette mode for BGRA;15 Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/TgaImagePlugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 6a6bceacaba..9bc483fc5b7 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -120,6 +120,7 @@ def _open(self) -> None: self.palette = ImagePalette.raw( "BGRA;15", b"\0" * 2 * start + self.fp.read(2 * size) ) + self.palette.mode = "RGBA" elif mapdepth == 24: self.palette = ImagePalette.raw( "BGR", b"\0" * 3 * start + self.fp.read(3 * size) From 38402554869bdb2af6ac81bcb90070e939a41c38 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 25 Jun 2024 20:50:17 +1000 Subject: [PATCH 4/7] Invert alpha bit for map depth 16 --- Tests/images/p_16.png | Bin 378 -> 414 bytes Tests/test_file_tga.py | 5 +++-- src/PIL/TgaImagePlugin.py | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Tests/images/p_16.png b/Tests/images/p_16.png index e3588641277b804c0100707146a46a506f33d0d6..458f7138e8026159cb9e52a18088db59358e4c44 100644 GIT binary patch literal 414 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU`+LNaSW-L^Y)S>UxNY}4gE|G$2o{oCumKWmr&|6dn*{vnHidV>Yy5l#g^ ohC-$eVTU;kk4A;CheS1lgtNdOv)R!Vz;I{qboFyt=akR{0D$9pJpcdz literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V083!aSW-L^Y-FF&I1Yp3 None: def test_palette_depth_16(tmp_path: Path) -> None: with Image.open("Tests/images/p_16.tga") as im: - assert_image_equal_tofile(im.convert("RGB"), "Tests/images/p_16.png") + assert im.palette.mode == "RGBA" + assert_image_equal_tofile(im.convert("RGBA"), "Tests/images/p_16.png") out = str(tmp_path / "temp.png") im.save(out) with Image.open(out) as reloaded: - assert_image_equal_tofile(reloaded.convert("RGB"), "Tests/images/p_16.png") + assert_image_equal_tofile(reloaded.convert("RGBA"), "Tests/images/p_16.png") def test_id_field() -> None: diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 9bc483fc5b7..d158fe49203 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -117,9 +117,11 @@ def _open(self) -> None: # read palette start, size, mapdepth = i16(s, 3), i16(s, 5), s[7] if mapdepth == 16: - self.palette = ImagePalette.raw( - "BGRA;15", b"\0" * 2 * start + self.fp.read(2 * size) - ) + colormap = self.fp.read(2 * size) + palette_data = bytearray(b"\0" * 2 * start) + for a, b in zip(colormap[::2], colormap[1::2]): + palette_data += bytearray((a, b ^ 128)) + self.palette = ImagePalette.raw("BGRA;15", bytes(palette_data)) self.palette.mode = "RGBA" elif mapdepth == 24: self.palette = ImagePalette.raw( From 4cadf5c99f690b82cf281b601f96bf5711e52c58 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Tue, 25 Jun 2024 09:17:40 -0500 Subject: [PATCH 5/7] use bytes/bytearray int constructor instead of string multiplication --- src/PIL/TgaImagePlugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index d158fe49203..c41b31d6852 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -118,18 +118,18 @@ def _open(self) -> None: start, size, mapdepth = i16(s, 3), i16(s, 5), s[7] if mapdepth == 16: colormap = self.fp.read(2 * size) - palette_data = bytearray(b"\0" * 2 * start) + palette_data = bytearray(2 * start) for a, b in zip(colormap[::2], colormap[1::2]): palette_data += bytearray((a, b ^ 128)) self.palette = ImagePalette.raw("BGRA;15", bytes(palette_data)) self.palette.mode = "RGBA" elif mapdepth == 24: self.palette = ImagePalette.raw( - "BGR", b"\0" * 3 * start + self.fp.read(3 * size) + "BGR", bytes(3 * start) + self.fp.read(3 * size) ) elif mapdepth == 32: self.palette = ImagePalette.raw( - "BGRA", b"\0" * 4 * start + self.fp.read(4 * size) + "BGRA", bytes(4 * start) + self.fp.read(4 * size) ) else: msg = "unknown TGA map depth" From 6ee41897e277a6e05f3673255b21333d7279d9fa Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Jun 2024 07:19:44 +1000 Subject: [PATCH 6/7] Added dedicated unpacker for inverted alpha --- src/PIL/TgaImagePlugin.py | 14 +++++--------- src/libImaging/Unpack.c | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index c41b31d6852..39104aeced9 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -36,7 +36,7 @@ (3, 1): "1", (3, 8): "L", (3, 16): "LA", - (2, 16): "BGRA;15", + (2, 16): "BGRA;15Z", (2, 24): "BGR", (2, 32): "BGRA", } @@ -87,9 +87,7 @@ def _open(self) -> None: elif imagetype in (1, 9): self._mode = "P" if colormaptype else "L" elif imagetype in (2, 10): - self._mode = "RGB" - if depth == 32: - self._mode = "RGBA" + self._mode = "RGB" if depth == 24 else "RGBA" else: msg = "unknown TGA mode" raise SyntaxError(msg) @@ -117,11 +115,9 @@ def _open(self) -> None: # read palette start, size, mapdepth = i16(s, 3), i16(s, 5), s[7] if mapdepth == 16: - colormap = self.fp.read(2 * size) - palette_data = bytearray(2 * start) - for a, b in zip(colormap[::2], colormap[1::2]): - palette_data += bytearray((a, b ^ 128)) - self.palette = ImagePalette.raw("BGRA;15", bytes(palette_data)) + self.palette = ImagePalette.raw( + "BGRA;15Z", bytes(2 * start) + self.fp.read(2 * size) + ) self.palette.mode = "RGBA" elif mapdepth == 24: self.palette = ImagePalette.raw( diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 063bcbbcc48..eaa4374e3e9 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -718,6 +718,21 @@ ImagingUnpackBGRA15(UINT8 *out, const UINT8 *in, int pixels) { } } +void +ImagingUnpackBGRA15Z(UINT8 *out, const UINT8 *in, int pixels) { + int i, pixel; + /* RGB, rearranged channels, 5/5/5/1 bits per pixel, inverted alpha */ + for (i = 0; i < pixels; i++) { + pixel = in[0] + (in[1] << 8); + out[B] = (pixel & 31) * 255 / 31; + out[G] = ((pixel >> 5) & 31) * 255 / 31; + out[R] = ((pixel >> 10) & 31) * 255 / 31; + out[A] = ~((pixel >> 15) * 255); + out += 4; + in += 2; + } +} + void ImagingUnpackRGB16(UINT8 *out, const UINT8 *in, int pixels) { int i, pixel; @@ -1538,7 +1553,7 @@ static struct { /* flags: "I" inverted data; "R" reversed bit order; "B" big endian byte order (default is little endian); "L" line - interleave, "S" signed, "F" floating point */ + interleave, "S" signed, "F" floating point, "Z" inverted alpha */ /* exception: rawmodes "I" and "F" are always native endian byte order */ @@ -1646,6 +1661,7 @@ static struct { {"RGBA", "RGBA;L", 32, unpackRGBAL}, {"RGBA", "RGBA;15", 16, ImagingUnpackRGBA15}, {"RGBA", "BGRA;15", 16, ImagingUnpackBGRA15}, + {"RGBA", "BGRA;15Z", 16, ImagingUnpackBGRA15Z}, {"RGBA", "RGBA;4B", 16, ImagingUnpackRGBA4B}, {"RGBA", "RGBA;16L", 64, unpackRGBA16L}, {"RGBA", "RGBA;16B", 64, unpackRGBA16B}, From 6863c87c0151c9330c4ef1db1bb9058b8d34f703 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Jun 2024 18:06:47 +1000 Subject: [PATCH 7/7] Added test for non-colormap 16-bit image --- Tests/images/rgba16.tga | Bin 0 -> 48 bytes Tests/test_file_tga.py | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 Tests/images/rgba16.tga diff --git a/Tests/images/rgba16.tga b/Tests/images/rgba16.tga new file mode 100644 index 0000000000000000000000000000000000000000..3918647a23cf31628e0beb8a14084ef7383582a1 GIT binary patch literal 48 qcmZQzU}As)CI&_Z0S@_)SIsbykf2c4FwbC5e?Q#_H%}i|Jq7@4vIgk@ literal 0 HcmV?d00001 diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 15c7bbdb89e..a03a6a6e10a 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -81,6 +81,14 @@ def test_palette_depth_16(tmp_path: Path) -> None: assert_image_equal_tofile(reloaded.convert("RGBA"), "Tests/images/p_16.png") +def test_rgba_16() -> None: + with Image.open("Tests/images/rgba16.tga") as im: + assert im.mode == "RGBA" + + assert im.getpixel((0, 0)) == (172, 0, 255, 255) + assert im.getpixel((1, 0)) == (0, 255, 82, 0) + + def test_id_field() -> None: # tga file with id field test_file = "Tests/images/tga_id_field.tga"