Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ def test_pclr() -> None:
) as im:
assert im.mode == "P"
assert im.palette is not None
assert im.palette.mode == "CMYK"
assert len(im.palette.colors) == 139
assert im.palette.colors[(0, 0, 0, 0)] == 0

Expand Down
10 changes: 10 additions & 0 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,16 @@ def test_plte_length(self, tmp_path: Path) -> None:
assert reloaded.png.im_palette is not None
assert len(reloaded.png.im_palette[1]) == 3

def test_plte_cmyk(self, tmp_path: Path) -> None:
im = Image.new("P", (1, 1))
im.putpalette((0, 100, 150, 200), "CMYK")

out = tmp_path / "temp.png"
im.save(out)

with Image.open(out) as reloaded:
assert reloaded.convert("CMYK").getpixel((0, 0)) == (200, 222, 232, 0)

def test_getxmp(self) -> None:
with Image.open("Tests/images/color_snakes.png") as im:
if ElementTree is None:
Expand Down
15 changes: 15 additions & 0 deletions Tests/test_image_putpalette.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ def test_rgba_palette(mode: str, palette: tuple[int, ...]) -> None:
assert im.palette.colors == {(1, 2, 3, 4): 0}


@pytest.mark.parametrize(
"mode, palette",
(
("CMYK", (1, 2, 3, 4)),
("CMYKX", (1, 2, 3, 4, 0)),
),
)
def test_cmyk_palette(mode: str, palette: tuple[int, ...]) -> None:
im = Image.new("P", (1, 1))
im.putpalette(palette, mode)
assert im.getpalette() == [250, 249, 248]
assert im.palette is not None
assert im.palette.colors == {(1, 2, 3, 4): 0}


def test_empty_palette() -> None:
im = Image.new("P", (1, 1))
assert im.getpalette() == []
Expand Down
11 changes: 8 additions & 3 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -2145,8 +2145,8 @@ def putpalette(
Alternatively, an 8-bit string may be used instead of an integer sequence.

:param data: A palette sequence (either a list or a string).
:param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a mode
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").
"""
from . import ImagePalette

Expand All @@ -2165,7 +2165,12 @@ def putpalette(
palette = ImagePalette.raw(rawmode, data)
self._mode = "PA" if "A" in self.mode else "P"
self.palette = palette
self.palette.mode = "RGBA" if "A" in rawmode else "RGB"
if rawmode.startswith("CMYK"):
self.palette.mode = "CMYK"
elif "A" in rawmode:
self.palette.mode = "RGBA"
else:
self.palette.mode = "RGB"
self.load() # install new palette

def putpixel(
Expand Down
14 changes: 10 additions & 4 deletions src/PIL/Jpeg2KImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def _parse_jp2_header(
nc = None
dpi = None # 2-tuple of DPI info, or None
palette = None
cmyk = False

while header.has_next_box():
tbox = header.next_box_type()
Expand All @@ -196,10 +197,11 @@ def _parse_jp2_header(
mode = "RGB"
elif nc == 4:
mode = "RGBA"
elif tbox == b"colr" and nc == 4:
elif tbox == b"colr":
meth, _, _, enumcs = header.read_fields(">BBBI")
if meth == 1 and enumcs == 12:
mode = "CMYK"
if cmyk := (meth == 1 and enumcs == 12):
if nc == 4:
mode = "CMYK"
elif tbox == b"pclr" and mode in ("L", "LA"):
ne, npc = header.read_fields(">HB")
assert isinstance(ne, int)
Expand All @@ -210,7 +212,11 @@ def _parse_jp2_header(
if bitdepth > max_bitdepth:
max_bitdepth = bitdepth
if max_bitdepth <= 8:
palette = ImagePalette.ImagePalette("RGBA" if npc == 4 else "RGB")
if npc == 4:
palette_mode = "CMYK" if cmyk else "RGBA"
else:
palette_mode = "RGB"
palette = ImagePalette.ImagePalette(palette_mode)
for i in range(ne):
color: list[int] = []
for value in header.read_fields(">" + ("B" * npc)):
Expand Down
7 changes: 5 additions & 2 deletions src/PIL/PngImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,9 @@ def _save(
mode = im.mode

outmode = mode
palette = []
if im.palette:
palette = im.getpalette() or []
if mode == "P":
#
# attempt to minimize storage requirements for palette images
Expand All @@ -1362,7 +1365,7 @@ def _save(
else:
# check palette contents
if im.palette:
colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1)
colors = max(min(len(palette) // 3, 256), 1)
else:
colors = 256

Expand Down Expand Up @@ -1435,7 +1438,7 @@ def _save(

if im.mode == "P":
palette_byte_number = colors * 3
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
palette_bytes = bytes(palette[:palette_byte_number])
while len(palette_bytes) < palette_byte_number:
palette_bytes += b"\0"
chunk(fp, b"PLTE", palette_bytes)
Expand Down
1 change: 1 addition & 0 deletions src/libImaging/Jpeg2KDecode.c
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ j2ku_sycca_rgba(
static const struct j2k_decode_unpacker j2k_unpackers[] = {
{IMAGING_MODE_L, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l},
{IMAGING_MODE_P, OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l},
{IMAGING_MODE_P, OPJ_CLRSPC_CMYK, 1, 0, j2ku_gray_l},
{IMAGING_MODE_PA, OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la},
{IMAGING_MODE_I_16, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
{IMAGING_MODE_I_16B, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
Expand Down
14 changes: 14 additions & 0 deletions src/libImaging/Pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,19 @@ ImagingPackXBGR(UINT8 *out, const UINT8 *in, int pixels) {
}
}

void
ImagingPackCMYK2RGB(UINT8 *out, const UINT8 *in, int xsize) {
int x, nk, tmp;
for (x = 0; x < xsize; x++) {
nk = 255 - in[3];
out[0] = CLIP8(nk - MULDIV255(in[0], nk, tmp));
out[1] = CLIP8(nk - MULDIV255(in[1], nk, tmp));
out[2] = CLIP8(nk - MULDIV255(in[2], nk, tmp));
out += 3;
in += 4;
}
}

void
ImagingPackBGRA(UINT8 *out, const UINT8 *in, int pixels) {
int i;
Expand Down Expand Up @@ -605,6 +618,7 @@ static struct {
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1},
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2},
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3},
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_RGB, 24, ImagingPackCMYK2RGB},

/* video (YCbCr) */
{IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingPackRGB},
Expand Down
3 changes: 2 additions & 1 deletion src/libImaging/Palette.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ ImagingPaletteNew(const ModeID mode) {
int i;
ImagingPalette palette;

if (mode != IMAGING_MODE_RGB && mode != IMAGING_MODE_RGBA) {
if (mode != IMAGING_MODE_RGB && mode != IMAGING_MODE_RGBA &&
mode != IMAGING_MODE_CMYK) {
return (ImagingPalette)ImagingError_ModeError();
}

Expand Down
Loading