We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
There was an error while loading. Please reload this page.
2 parents 3b1f70d + 8442a85 commit a67ce7fCopy full SHA for a67ce7f
9 files changed
Tests/test_file_jpeg2k.py
@@ -445,6 +445,7 @@ def test_pclr() -> None:
445
) as im:
446
assert im.mode == "P"
447
assert im.palette is not None
448
+ assert im.palette.mode == "CMYK"
449
assert len(im.palette.colors) == 139
450
assert im.palette.colors[(0, 0, 0, 0)] == 0
451
Tests/test_file_png.py
@@ -707,6 +707,16 @@ def test_plte_length(self, tmp_path: Path) -> None:
707
assert reloaded.png.im_palette is not None
708
assert len(reloaded.png.im_palette[1]) == 3
709
710
+ def test_plte_cmyk(self, tmp_path: Path) -> None:
711
+ im = Image.new("P", (1, 1))
712
+ im.putpalette((0, 100, 150, 200), "CMYK")
713
+
714
+ out = tmp_path / "temp.png"
715
+ im.save(out)
716
717
+ with Image.open(out) as reloaded:
718
+ assert reloaded.convert("CMYK").getpixel((0, 0)) == (200, 222, 232, 0)
719
720
def test_getxmp(self) -> None:
721
with Image.open("Tests/images/color_snakes.png") as im:
722
if ElementTree is None:
Tests/test_image_putpalette.py
@@ -91,6 +91,21 @@ def test_rgba_palette(mode: str, palette: tuple[int, ...]) -> None:
91
assert im.palette.colors == {(1, 2, 3, 4): 0}
92
93
94
+@pytest.mark.parametrize(
95
+ "mode, palette",
96
+ (
97
+ ("CMYK", (1, 2, 3, 4)),
98
+ ("CMYKX", (1, 2, 3, 4, 0)),
99
+ ),
100
+)
101
+def test_cmyk_palette(mode: str, palette: tuple[int, ...]) -> None:
102
103
+ im.putpalette(palette, mode)
104
+ assert im.getpalette() == [250, 249, 248]
105
+ assert im.palette is not None
106
+ assert im.palette.colors == {(1, 2, 3, 4): 0}
107
108
109
def test_empty_palette() -> None:
110
im = Image.new("P", (1, 1))
111
assert im.getpalette() == []
src/PIL/Image.py
@@ -2145,8 +2145,8 @@ def putpalette(
2145
Alternatively, an 8-bit string may be used instead of an integer sequence.
2146
2147
:param data: A palette sequence (either a list or a string).
2148
- :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a mode
2149
- that can be transformed to "RGB" or "RGBA" (e.g. "R", "BGR;15", "RGBA;L").
+ :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", "CMYK", or a
+ mode that can be transformed to one of those modes (e.g. "R", "RGBA;L").
2150
"""
2151
from . import ImagePalette
2152
@@ -2165,7 +2165,12 @@ def putpalette(
2165
palette = ImagePalette.raw(rawmode, data)
2166
self._mode = "PA" if "A" in self.mode else "P"
2167
self.palette = palette
2168
- self.palette.mode = "RGBA" if "A" in rawmode else "RGB"
+ if rawmode.startswith("CMYK"):
2169
+ self.palette.mode = "CMYK"
2170
+ elif "A" in rawmode:
2171
+ self.palette.mode = "RGBA"
2172
+ else:
2173
+ self.palette.mode = "RGB"
2174
self.load() # install new palette
2175
2176
def putpixel(
src/PIL/Jpeg2KImagePlugin.py
@@ -176,6 +176,7 @@ def _parse_jp2_header(
176
nc = None
177
dpi = None # 2-tuple of DPI info, or None
178
palette = None
179
+ cmyk = False
180
181
while header.has_next_box():
182
tbox = header.next_box_type()
@@ -196,10 +197,11 @@ def _parse_jp2_header(
196
197
mode = "RGB"
198
elif nc == 4:
199
mode = "RGBA"
- elif tbox == b"colr" and nc == 4:
200
+ elif tbox == b"colr":
201
meth, _, _, enumcs = header.read_fields(">BBBI")
- if meth == 1 and enumcs == 12:
202
- mode = "CMYK"
+ if cmyk := (meth == 1 and enumcs == 12):
203
+ if nc == 4:
204
+ mode = "CMYK"
205
elif tbox == b"pclr" and mode in ("L", "LA"):
206
ne, npc = header.read_fields(">HB")
207
assert isinstance(ne, int)
@@ -210,7 +212,11 @@ def _parse_jp2_header(
210
212
if bitdepth > max_bitdepth:
211
213
max_bitdepth = bitdepth
214
if max_bitdepth <= 8:
- palette = ImagePalette.ImagePalette("RGBA" if npc == 4 else "RGB")
215
+ if npc == 4:
216
+ palette_mode = "CMYK" if cmyk else "RGBA"
217
218
+ palette_mode = "RGB"
219
+ palette = ImagePalette.ImagePalette(palette_mode)
220
for i in range(ne):
221
color: list[int] = []
222
for value in header.read_fields(">" + ("B" * npc)):
src/PIL/PngImagePlugin.py
@@ -1353,6 +1353,9 @@ def _save(
1353
mode = im.mode
1354
1355
outmode = mode
1356
+ palette = []
1357
+ if im.palette:
1358
+ palette = im.getpalette() or []
1359
if mode == "P":
1360
#
1361
# attempt to minimize storage requirements for palette images
@@ -1362,7 +1365,7 @@ def _save(
1362
1365
else:
1363
1366
# check palette contents
1364
1367
if im.palette:
- colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1)
1368
+ colors = max(min(len(palette) // 3, 256), 1)
1369
1370
colors = 256
1371
@@ -1435,7 +1438,7 @@ def _save(
1435
1438
1436
1439
if im.mode == "P":
1437
1440
palette_byte_number = colors * 3
- palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
1441
+ palette_bytes = bytes(palette[:palette_byte_number])
1442
while len(palette_bytes) < palette_byte_number:
1443
palette_bytes += b"\0"
1444
chunk(fp, b"PLTE", palette_bytes)
src/libImaging/Jpeg2KDecode.c
@@ -601,6 +601,7 @@ j2ku_sycca_rgba(
601
static const struct j2k_decode_unpacker j2k_unpackers[] = {
602
{IMAGING_MODE_L, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l},
603
{IMAGING_MODE_P, OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l},
604
+ {IMAGING_MODE_P, OPJ_CLRSPC_CMYK, 1, 0, j2ku_gray_l},
605
{IMAGING_MODE_PA, OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la},
606
{IMAGING_MODE_I_16, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
607
{IMAGING_MODE_I_16B, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
src/libImaging/Pack.c
@@ -325,6 +325,19 @@ ImagingPackXBGR(UINT8 *out, const UINT8 *in, int pixels) {
325
}
326
327
328
+void
329
+ImagingPackCMYK2RGB(UINT8 *out, const UINT8 *in, int xsize) {
330
+ int x, nk, tmp;
331
+ for (x = 0; x < xsize; x++) {
332
+ nk = 255 - in[3];
333
+ out[0] = CLIP8(nk - MULDIV255(in[0], nk, tmp));
334
+ out[1] = CLIP8(nk - MULDIV255(in[1], nk, tmp));
335
+ out[2] = CLIP8(nk - MULDIV255(in[2], nk, tmp));
336
+ out += 3;
337
+ in += 4;
338
+ }
339
+}
340
341
void
342
ImagingPackBGRA(UINT8 *out, const UINT8 *in, int pixels) {
343
int i;
@@ -605,6 +618,7 @@ static struct {
618
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1},
619
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2},
620
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3},
621
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_RGB, 24, ImagingPackCMYK2RGB},
608
622
609
623
/* video (YCbCr) */
610
624
{IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingPackRGB},
src/libImaging/Palette.c
@@ -27,7 +27,8 @@ ImagingPaletteNew(const ModeID mode) {
27
28
ImagingPalette palette;
29
30
- if (mode != IMAGING_MODE_RGB && mode != IMAGING_MODE_RGBA) {
+ if (mode != IMAGING_MODE_RGB && mode != IMAGING_MODE_RGBA &&
31
+ mode != IMAGING_MODE_CMYK) {
32
return (ImagingPalette)ImagingError_ModeError();
33
34
0 commit comments