From 1e9314b070a237df78c812ee1abf5d4a05658879 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 1 Nov 2022 17:51:24 +1100 Subject: [PATCH] Added signed option when saving JPEG2000 images --- Tests/test_file_jpeg2k.py | 14 ++++++++++++++ docs/handbook/image-file-formats.rst | 5 +++++ src/PIL/Jpeg2KImagePlugin.py | 2 ++ src/encode.c | 5 ++++- src/libImaging/Jpeg2K.h | 3 +++ src/libImaging/Jpeg2KEncode.c | 2 +- 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index cd142e67fc7..0229b224304 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -252,6 +252,20 @@ def test_mct(): assert_image_similar(im, jp2, 1.0e-3) +def test_sgnd(tmp_path): + outfile = str(tmp_path / "temp.jp2") + + im = Image.new("L", (1, 1)) + im.save(outfile) + with Image.open(outfile) as reloaded: + assert reloaded.getpixel((0, 0)) == 0 + + im = Image.new("L", (1, 1)) + im.save(outfile, signed=True) + with Image.open(outfile) as reloaded_signed: + assert reloaded_signed.getpixel((0, 0)) == 128 + + def test_rgba(): # Arrange with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k: diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 1e79db68bcb..93ae1b0543f 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -563,6 +563,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: encoded using RLCP mode will have increasing resolutions decoded as they arrive, and so on. +**signed** + If true, then tell the encoder to save the image as signed. + + .. versionadded:: 9.4.0 + **cinema_mode** Set the encoder to produce output compliant with the digital cinema specifications. The options here are ``"no"`` (the default), diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index c67d8d6bf60..11d1d488a29 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -321,6 +321,7 @@ def _save(im, fp, filename): progression = info.get("progression", "LRCP") cinema_mode = info.get("cinema_mode", "no") mct = info.get("mct", 0) + signed = info.get("signed", False) fd = -1 if hasattr(fp, "fileno"): @@ -342,6 +343,7 @@ def _save(im, fp, filename): progression, cinema_mode, mct, + signed, fd, ) diff --git a/src/encode.c b/src/encode.c index 72c7f64d0a3..aa47fe67194 100644 --- a/src/encode.c +++ b/src/encode.c @@ -1188,11 +1188,12 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { char *cinema_mode = "no"; OPJ_CINEMA_MODE cine_mode; char mct = 0; + int sgnd = 0; Py_ssize_t fd = -1; if (!PyArg_ParseTuple( args, - "ss|OOOsOnOOOssbn", + "ss|OOOsOnOOOssbbn", &mode, &format, &offset, @@ -1207,6 +1208,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { &progression, &cinema_mode, &mct, + &sgnd, &fd)) { return NULL; } @@ -1305,6 +1307,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { context->progression = prog_order; context->cinema_mode = cine_mode; context->mct = mct; + context->sgnd = sgnd; return (PyObject *)encoder; } diff --git a/src/libImaging/Jpeg2K.h b/src/libImaging/Jpeg2K.h index d030b0c439e..b28a0440acc 100644 --- a/src/libImaging/Jpeg2K.h +++ b/src/libImaging/Jpeg2K.h @@ -85,6 +85,9 @@ typedef struct { /* Set multiple component transformation */ char mct; + /* Signed */ + int sgnd; + /* Progression order (LRCP/RLCP/RPCL/PCRL/CPRL) */ OPJ_PROG_ORDER progression; diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index fe5511ba5cb..f5284d911c2 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -343,7 +343,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) { image_params[n].x0 = image_params[n].y0 = 0; image_params[n].prec = prec; image_params[n].bpp = bpp; - image_params[n].sgnd = 0; + image_params[n].sgnd = context->sgnd == 1 ? 1 : 0; } image = opj_image_create(components, image_params, color_space);