Skip to content

Commit e7cdcfb

Browse files
committed
Apply XOR mask to 1 and L mode images
1 parent ee9e761 commit e7cdcfb

5 files changed

Lines changed: 48 additions & 3 deletions

File tree

Tests/images/mask_1.cur

102 Bytes
Binary file not shown.

Tests/images/mask_L.cur

1.09 KB
Binary file not shown.

Tests/test_file_cur.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ def test_largest_cursor() -> None:
4040
assert im.size == (8, 8)
4141

4242

43+
@pytest.mark.parametrize("mode", ("1", "L"))
44+
def test_mask(mode: str) -> None:
45+
with Image.open("Tests/images/mask_" + mode + ".cur") as im:
46+
assert im.mode == "LA"
47+
48+
for i, value in enumerate(
49+
[
50+
(0, 255), # AND 0 XOR 0 is black
51+
(255, 255), # AND 0 XOR 1 is white
52+
(0, 0), # AND 1 XOR 0 is transparent
53+
(0, 0), # AND 1 XOR 1 is transparent
54+
]
55+
):
56+
assert im.getpixel((0, i)) == value
57+
58+
4359
def test_invalid_file() -> None:
4460
invalid_file = "Tests/images/flower.jpg"
4561

src/PIL/CurImagePlugin.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#
1818
from __future__ import annotations
1919

20-
from . import BmpImagePlugin, Image
20+
from . import BmpImagePlugin, Image, ImageOps
2121
from ._binary import i16le as i16
2222
from ._binary import i32le as i32
2323

@@ -61,10 +61,38 @@ def _open(self) -> None:
6161

6262
# load as bitmap
6363
self._bitmap(i32(m, 12) + offset)
64+
self._masked = self.mode in ("1", "L")
65+
if self._masked:
66+
self._rawmode = self.mode
67+
self._mode = "LA"
6468

6569
# patch up the bitmap height
66-
self._size = self.size[0], self.size[1] // 2
67-
self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)]
70+
self._size = self.width, self.height // 2
71+
if not self._masked:
72+
self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)]
73+
74+
def load_prepare(self) -> None:
75+
if self._masked:
76+
self._mode = self._rawmode
77+
self._size = self.width, self.height * 2
78+
super().load_prepare()
79+
80+
def load_end(self) -> None:
81+
if not self._masked:
82+
return
83+
self._mode = "LA"
84+
new_height = self.height // 2
85+
86+
and_mask = self.im.crop((0, 0, self.width, new_height))
87+
xor_mask = self.im.crop((0, new_height, self.width, self.height))
88+
89+
self._size = self.width, new_height
90+
self._im = Image.core.fill(self.mode, self.size)
91+
self._im.paste(
92+
xor_mask.convert(self.mode),
93+
(0, 0) + self.size,
94+
ImageOps.invert(Image.Image()._new(and_mask)).im,
95+
)
6896

6997

7098
#

src/libImaging/Convert.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,6 +1450,7 @@ static struct {
14501450
{IMAGING_MODE_1, IMAGING_MODE_L, bit2l},
14511451
{IMAGING_MODE_1, IMAGING_MODE_I, bit2i},
14521452
{IMAGING_MODE_1, IMAGING_MODE_F, bit2f},
1453+
{IMAGING_MODE_1, IMAGING_MODE_LA, bit2rgb},
14531454
{IMAGING_MODE_1, IMAGING_MODE_RGB, bit2rgb},
14541455
{IMAGING_MODE_1, IMAGING_MODE_RGBA, bit2rgb},
14551456
{IMAGING_MODE_1, IMAGING_MODE_RGBX, bit2rgb},

0 commit comments

Comments
 (0)