From d5d0734169ac4c593caac4f5769e60a01574fa09 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 16 Mar 2026 20:48:25 +1100 Subject: [PATCH] Add CMYK palettes --- Tests/test_image_putpalette.py | 15 +++++++++++++++ src/PIL/Image.py | 11 ++++++++--- src/libImaging/Pack.c | 14 ++++++++++++++ src/libImaging/Palette.c | 3 ++- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 661764b60..237de6330 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -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() == [] diff --git a/src/PIL/Image.py b/src/PIL/Image.py index cc431a86a..c9db97319 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2145,8 +2145,8 @@ class Image: 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 @@ -2165,7 +2165,12 @@ class Image: 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( diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index fdf5a72aa..161d82f2e 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -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; @@ -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}, diff --git a/src/libImaging/Palette.c b/src/libImaging/Palette.c index 371ba644b..b2dacf656 100644 --- a/src/libImaging/Palette.c +++ b/src/libImaging/Palette.c @@ -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(); }