Skip to content

Commit cfa51ad

Browse files
committed
Populate single band
1 parent 68ac337 commit cfa51ad

File tree

2 files changed

+85
-17
lines changed

2 files changed

+85
-17
lines changed

Tests/test_file_iptc.py

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,87 @@
22

33
from io import BytesIO
44

5+
import pytest
6+
57
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
68

79
from .helper import assert_image_equal, hopper
810

911
TEST_FILE = "Tests/images/iptc.jpg"
1012

1113

14+
def create_iptc_image(info: dict[str, int] = {}) -> BytesIO:
15+
def field(tag, value):
16+
return bytes((0x1C,) + tag + (0, len(value))) + value
17+
18+
data = field((3, 60), bytes((info.get("layers", 1), info.get("component", 0))))
19+
data += field((3, 120), bytes((info.get("compression", 1),)))
20+
if "band" in info:
21+
data += field((3, 65), bytes((info["band"] + 1,)))
22+
data += field((3, 20), b"\x01") # width
23+
data += field((3, 30), b"\x01") # height
24+
data += field(
25+
(8, 10),
26+
bytes((info.get("data", 0),)),
27+
)
28+
29+
return BytesIO(data)
30+
31+
1232
def test_open() -> None:
1333
expected = Image.new("L", (1, 1))
1434

15-
f = BytesIO(
16-
b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c\x03\x14\x00\x01\x01"
17-
b"\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x01\x00"
18-
)
35+
f = create_iptc_image()
1936
with Image.open(f) as im:
20-
assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
37+
assert im.tile == [("iptc", (0, 0, 1, 1), 25, ("raw", None))]
2138
assert_image_equal(im, expected)
2239

2340
with Image.open(f) as im:
2441
assert im.load() is not None
2542

2643

44+
def test_field_length() -> None:
45+
f = create_iptc_image()
46+
f.seek(28)
47+
f.write(b"\xff")
48+
with pytest.raises(OSError, match="illegal field length in IPTC/NAA file"):
49+
with Image.open(f):
50+
pass
51+
52+
53+
@pytest.mark.parametrize("layers, mode", ((3, "RGB"), (4, "CMYK")))
54+
def test_layers(layers: int, mode: str) -> None:
55+
for band in range(-1, layers):
56+
info = {"layers": layers, "component": 1, "data": 5}
57+
if band != -1:
58+
info["band"] = band
59+
f = create_iptc_image(info)
60+
with Image.open(f) as im:
61+
assert im.mode == mode
62+
63+
data = [0] * layers
64+
data[max(band, 0)] = 5
65+
assert im.getpixel((0, 0)) == tuple(data)
66+
67+
68+
def test_unknown_compression() -> None:
69+
f = create_iptc_image({"compression": 2})
70+
with pytest.raises(OSError, match="Unknown IPTC image compression"):
71+
with Image.open(f):
72+
pass
73+
74+
75+
def test_getiptcinfo() -> None:
76+
f = create_iptc_image()
77+
with Image.open(f) as im:
78+
assert IptcImagePlugin.getiptcinfo(im) == {
79+
(3, 60): b"\x01\x00",
80+
(3, 120): b"\x01",
81+
(3, 20): b"\x01",
82+
(3, 30): b"\x01",
83+
}
84+
85+
2786
def test_getiptcinfo_jpg_none() -> None:
2887
# Arrange
2988
with hopper() as im:

src/PIL/IptcImagePlugin.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,18 @@ def _open(self) -> None:
9696
# mode
9797
layers = self.info[(3, 60)][0]
9898
component = self.info[(3, 60)][1]
99-
if (3, 65) in self.info:
100-
id = self.info[(3, 65)][0] - 1
101-
else:
102-
id = 0
10399
if layers == 1 and not component:
104100
self._mode = "L"
105-
elif layers == 3 and component:
106-
self._mode = "RGB"[id]
107-
elif layers == 4 and component:
108-
self._mode = "CMYK"[id]
101+
band = None
102+
else:
103+
if layers == 3 and component:
104+
self._mode = "RGB"
105+
elif layers == 4 and component:
106+
self._mode = "CMYK"
107+
if (3, 65) in self.info:
108+
band = self.info[(3, 65)][0] - 1
109+
else:
110+
band = 0
109111

110112
# size
111113
self._size = self.getint((3, 20)), self.getint((3, 30))
@@ -120,14 +122,16 @@ def _open(self) -> None:
120122
# tile
121123
if tag == (8, 10):
122124
self.tile = [
123-
ImageFile._Tile("iptc", (0, 0) + self.size, offset, compression)
125+
ImageFile._Tile("iptc", (0, 0) + self.size, offset, (compression, band))
124126
]
125127

126128
def load(self) -> Image.core.PixelAccess | None:
127129
if self.tile:
128-
offset, compression = self.tile[0][2:]
130+
args = self.tile[0].args
131+
assert isinstance(args, tuple)
132+
compression, band = args
129133

130-
self.fp.seek(offset)
134+
self.fp.seek(self.tile[0].offset)
131135

132136
# Copy image data to temporary file
133137
o = BytesIO()
@@ -147,7 +151,12 @@ def load(self) -> Image.core.PixelAccess | None:
147151
size -= len(s)
148152

149153
with Image.open(o) as _im:
150-
_im.load()
154+
if band is not None:
155+
bands = [Image.new("L", _im.size)] * Image.getmodebands(self.mode)
156+
bands[band] = _im
157+
_im = Image.merge(self.mode, bands)
158+
else:
159+
_im.load()
151160
self.im = _im.im
152161
self.tile = []
153162
return ImageFile.ImageFile.load(self)

0 commit comments

Comments
 (0)