From e7cdcfb97ceb9f1ee6c3b36d30fd1cd81a14032f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 1 Jun 2026 19:51:49 +1000 Subject: [PATCH] Apply XOR mask to 1 and L mode images --- Tests/images/mask_1.cur | Bin 0 -> 102 bytes Tests/images/mask_L.cur | Bin 0 -> 1118 bytes Tests/test_file_cur.py | 16 ++++++++++++++++ src/PIL/CurImagePlugin.py | 34 +++++++++++++++++++++++++++++++--- src/libImaging/Convert.c | 1 + 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 Tests/images/mask_1.cur create mode 100644 Tests/images/mask_L.cur diff --git a/Tests/images/mask_1.cur b/Tests/images/mask_1.cur new file mode 100644 index 0000000000000000000000000000000000000000..0ae2c70597d5736c571c64a8228b3022aee80cc8 GIT binary patch literal 102 zcmZQzU}9ioU}Rwc0}dc52E-ab%m~CFK2R1&GB79r@ew`3{qUI}bV}5C{+w z5<*y57!eT>L`6mE(xnSxVq%Dkiz6W+fuy7)UAuOrTeogVNl76sEsczf46?Gabno7s z9zA*>CnrbGo;~T+s~7U}^7QWAn?8N|prD{Y-@biOR8&MsNeN|TWmHsDP*qh$O-&7T zb#*i}G|<%4L`zGHe*OBoaudP=*Z~hJk?rhK7b185v=0Y>bJC38to|3?Dw65hF%0a^y%xjT*)1(W5alGh@t{ zF^nBMmT}|8F@F4bCQO)sxw$zLCr-q|!U9W6ORTJ{u(r0w#>R$8lO{2F@?@q=nZnem zQ<*kx8n(8!OrJg-J3BjO%$R|_y*&;N4$Pc66Gul!X3d(#?Af!KGiMHS=gwu`ym>e| zIWd3!e4L$~S+HOM3l}cL#l;0zS63D-TEybTi*a*vW66>wxVyWvbm>x-En9|%hX;5}g`b}vt5>gP&6+i=UAvZb>(;S;{dzWR z*uch(8`-pJ6aN1GY~H+?EnBv*b?a6F0s;sO3}oB3ZEWAZogF)Nuyf~5cJ10lP*4!T z!NKg_y_-FI_ON&FUP3}b2n`J-EG&%h@Ngm`B8ZHPBq}P3=;&x-Vq)00Zy)>j?aZa2#!O4>+Id$q3@$vB_BqVV9 z^l1_k6G=)+;>?*doIQJ%Lk)NMWK|uk9g@xR|f1jeFA|5<=z{7_RdGzQJj~_p#xVV^-k`kUg zdBW4DPkHw28PA_Tr?j+`va&MD%gcH3;sq~XzU0-bS5#C~P+3{Y>({S&^X3h2-@fJD zyLVJoRq_7)dp>;lz{igt`Sj@%pFe-*%a<=yS65S0Q$uZSEp>Hu)YsSZ_3Kx@ef!4u z@89|H;|C254Ky}3($v(%&!0bOZf>TfrG?hkR@&Oy`1R`-zkmOxy}h0Ge=p#F^}k(b F^asvIgt7nt literal 0 HcmV?d00001 diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 4b3e3afcb43..58bebb4bb08 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -40,6 +40,22 @@ def test_largest_cursor() -> None: assert im.size == (8, 8) +@pytest.mark.parametrize("mode", ("1", "L")) +def test_mask(mode: str) -> None: + with Image.open("Tests/images/mask_" + mode + ".cur") as im: + assert im.mode == "LA" + + for i, value in enumerate( + [ + (0, 255), # AND 0 XOR 0 is black + (255, 255), # AND 0 XOR 1 is white + (0, 0), # AND 1 XOR 0 is transparent + (0, 0), # AND 1 XOR 1 is transparent + ] + ): + assert im.getpixel((0, i)) == value + + def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index 9c188e08446..b20425040f4 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -17,7 +17,7 @@ # from __future__ import annotations -from . import BmpImagePlugin, Image +from . import BmpImagePlugin, Image, ImageOps from ._binary import i16le as i16 from ._binary import i32le as i32 @@ -61,10 +61,38 @@ def _open(self) -> None: # load as bitmap self._bitmap(i32(m, 12) + offset) + self._masked = self.mode in ("1", "L") + if self._masked: + self._rawmode = self.mode + self._mode = "LA" # patch up the bitmap height - self._size = self.size[0], self.size[1] // 2 - self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)] + self._size = self.width, self.height // 2 + if not self._masked: + self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)] + + def load_prepare(self) -> None: + if self._masked: + self._mode = self._rawmode + self._size = self.width, self.height * 2 + super().load_prepare() + + def load_end(self) -> None: + if not self._masked: + return + self._mode = "LA" + new_height = self.height // 2 + + and_mask = self.im.crop((0, 0, self.width, new_height)) + xor_mask = self.im.crop((0, new_height, self.width, self.height)) + + self._size = self.width, new_height + self._im = Image.core.fill(self.mode, self.size) + self._im.paste( + xor_mask.convert(self.mode), + (0, 0) + self.size, + ImageOps.invert(Image.Image()._new(and_mask)).im, + ) # diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 2c62528c892..1fd14a944ef 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1450,6 +1450,7 @@ static struct { {IMAGING_MODE_1, IMAGING_MODE_L, bit2l}, {IMAGING_MODE_1, IMAGING_MODE_I, bit2i}, {IMAGING_MODE_1, IMAGING_MODE_F, bit2f}, + {IMAGING_MODE_1, IMAGING_MODE_LA, bit2rgb}, {IMAGING_MODE_1, IMAGING_MODE_RGB, bit2rgb}, {IMAGING_MODE_1, IMAGING_MODE_RGBA, bit2rgb}, {IMAGING_MODE_1, IMAGING_MODE_RGBX, bit2rgb},