Merge branch 'main' into jpeg2000_cmyk_save

This commit is contained in:
Andrew Murray 2024-12-28 22:08:08 +11:00
commit 973cd6481a
7 changed files with 29 additions and 42 deletions

View File

@ -436,8 +436,9 @@ def test_pclr() -> None:
def test_comment() -> None: def test_comment() -> None:
with Image.open("Tests/images/comment.jp2") as im: for path in ("Tests/images/9bit.j2k", "Tests/images/comment.jp2"):
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0" with Image.open(path) as im:
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
# Test an image that is truncated partway through a codestream # Test an image that is truncated partway through a codestream
with open("Tests/images/comment.jp2", "rb") as fp: with open("Tests/images/comment.jp2", "rb") as fp:

View File

@ -772,22 +772,18 @@ class TestFilePng:
im.seek(1) im.seek(1)
@pytest.mark.parametrize("buffer", (True, False)) @pytest.mark.parametrize("buffer", (True, False))
def test_save_stdout(self, buffer: bool) -> None: def test_save_stdout(self, buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
old_stdout = sys.stdout
class MyStdOut: class MyStdOut:
buffer = BytesIO() buffer = BytesIO()
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
sys.stdout = mystdout monkeypatch.setattr(sys, "stdout", mystdout)
with Image.open(TEST_PNG_FILE) as im: with Image.open(TEST_PNG_FILE) as im:
im.save(sys.stdout, "PNG") im.save(sys.stdout, "PNG")
# Reset stdout
sys.stdout = old_stdout
if isinstance(mystdout, MyStdOut): if isinstance(mystdout, MyStdOut):
mystdout = mystdout.buffer mystdout = mystdout.buffer
with Image.open(mystdout) as reloaded: with Image.open(mystdout) as reloaded:

View File

@ -367,22 +367,18 @@ def test_mimetypes(tmp_path: Path) -> None:
@pytest.mark.parametrize("buffer", (True, False)) @pytest.mark.parametrize("buffer", (True, False))
def test_save_stdout(buffer: bool) -> None: def test_save_stdout(buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
old_stdout = sys.stdout
class MyStdOut: class MyStdOut:
buffer = BytesIO() buffer = BytesIO()
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
sys.stdout = mystdout monkeypatch.setattr(sys, "stdout", mystdout)
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
im.save(sys.stdout, "PPM") im.save(sys.stdout, "PPM")
# Reset stdout
sys.stdout = old_stdout
if isinstance(mystdout, MyStdOut): if isinstance(mystdout, MyStdOut):
mystdout = mystdout.buffer mystdout = mystdout.buffer
with Image.open(mystdout) as reloaded: with Image.open(mystdout) as reloaded:

View File

@ -52,6 +52,12 @@ zlib library, and what version of zlib-ng is being used::
Other Changes Other Changes
============= =============
Reading JPEG 2000 comments
^^^^^^^^^^^^^^^^^^^^^^^^^^
When opening a JPEG 2000 image, the comment may now be read into
:py:attr:`~PIL.Image.Image.info` for J2K images, not just JP2 images.
Saving JPEG 2000 CMYK images Saving JPEG 2000 CMYK images
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -104,28 +104,17 @@ def grab(
def grabclipboard() -> Image.Image | list[str] | None: def grabclipboard() -> Image.Image | list[str] | None:
if sys.platform == "darwin": if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp(".png") p = subprocess.run(
os.close(fh) ["osascript", "-e", "get the clipboard as «class PNGf»"],
commands = [ capture_output=True,
'set theFile to (open for access POSIX file "' )
+ filepath if p.returncode != 0:
+ '" with write permission)', return None
"try",
" write (the clipboard as «class PNGf») to theFile",
"end try",
"close access theFile",
]
script = ["osascript"]
for command in commands:
script += ["-e", command]
subprocess.call(script)
im = None import binascii
if os.stat(filepath).st_size != 0:
im = Image.open(filepath) data = io.BytesIO(binascii.unhexlify(p.stdout[11:-3]))
im.load() return Image.open(data)
os.unlink(filepath)
return im
elif sys.platform == "win32": elif sys.platform == "win32":
fmt, data = Image.core.grabclipboard_win32() fmt, data = Image.core.grabclipboard_win32()
if fmt == "file": # CF_HDROP if fmt == "file": # CF_HDROP

View File

@ -252,6 +252,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
if sig == b"\xff\x4f\xff\x51": if sig == b"\xff\x4f\xff\x51":
self.codec = "j2k" self.codec = "j2k"
self._size, self._mode = _parse_codestream(self.fp) self._size, self._mode = _parse_codestream(self.fp)
self._parse_comment()
else: else:
sig = sig + self.fp.read(8) sig = sig + self.fp.read(8)
@ -262,6 +263,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
if dpi is not None: if dpi is not None:
self.info["dpi"] = dpi self.info["dpi"] = dpi
if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"): if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"):
hdr = self.fp.read(2)
length = _binary.i16be(hdr)
self.fp.seek(length - 2, os.SEEK_CUR)
self._parse_comment() self._parse_comment()
else: else:
msg = "not a JPEG 2000 file" msg = "not a JPEG 2000 file"
@ -296,10 +300,6 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
] ]
def _parse_comment(self) -> None: def _parse_comment(self) -> None:
hdr = self.fp.read(2)
length = _binary.i16be(hdr)
self.fp.seek(length - 2, os.SEEK_CUR)
while True: while True:
marker = self.fp.read(2) marker = self.fp.read(2)
if not marker: if not marker:

View File

@ -640,7 +640,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
opj_dparameters_t params; opj_dparameters_t params;
OPJ_COLOR_SPACE color_space; OPJ_COLOR_SPACE color_space;
j2k_unpacker_t unpack = NULL; j2k_unpacker_t unpack = NULL;
size_t buffer_size = 0, tile_bytes = 0; size_t tile_bytes = 0;
unsigned n, tile_height, tile_width; unsigned n, tile_height, tile_width;
int subsampling; int subsampling;
int total_component_width = 0; int total_component_width = 0;
@ -870,7 +870,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
tile_info.data_size = tile_bytes; tile_info.data_size = tile_bytes;
} }
if (buffer_size < tile_info.data_size) { if (tile_info.data_size > 0) {
/* malloc check ok, overflow and tile size sanity check above */ /* malloc check ok, overflow and tile size sanity check above */
UINT8 *new = realloc(state->buffer, tile_info.data_size); UINT8 *new = realloc(state->buffer, tile_info.data_size);
if (!new) { if (!new) {
@ -883,7 +883,6 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
to valgrind errors. */ to valgrind errors. */
memset(new, 0, tile_info.data_size); memset(new, 0, tile_info.data_size);
state->buffer = new; state->buffer = new;
buffer_size = tile_info.data_size;
} }
if (!opj_decode_tile_data( if (!opj_decode_tile_data(