From a57ebeaaf4b66079ef556c67c87b6555c8514513 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 00:38:49 +0000 Subject: [PATCH] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_jxl.py | 10 +++--- Tests/test_file_jxl_alpha.py | 5 +-- Tests/test_file_jxl_animated.py | 9 +++--- Tests/test_file_jxl_metadata.py | 6 ++-- src/PIL/JxlImagePlugin.py | 57 ++++++++++++++++++++------------- src/PIL/features.py | 3 +- src/_jxl.c | 34 ++++++++++---------- 7 files changed, 65 insertions(+), 59 deletions(-) diff --git a/Tests/test_file_jxl.py b/Tests/test_file_jxl.py index 1ea2c8fce..bd2b1f0bc 100644 --- a/Tests/test_file_jxl.py +++ b/Tests/test_file_jxl.py @@ -1,13 +1,13 @@ +from __future__ import annotations + import re + import pytest from PIL import Image, JxlImagePlugin, features from .helper import ( - assert_image_equal, - assert_image_similar, assert_image_similar_tofile, - hopper, skip_unless_feature, ) @@ -21,6 +21,7 @@ except ImportError: # cjxl v0.9.2 41b8cdab # hopper.jxl: cjxl hopper.png hopper.jxl -q 75 -e 8 + class TestUnsupportedJxl: def test_unsupported(self) -> None: if HAVE_JXL: @@ -35,6 +36,7 @@ class TestUnsupportedJxl: if HAVE_JXL: JxlImagePlugin.SUPPORTED = True + @skip_unless_feature("jxl") class TestFileJxl: def setup_method(self) -> None: @@ -68,5 +70,3 @@ class TestFileJxl: with pytest.raises(TypeError): _jxl.PILJxlDecoder() - - diff --git a/Tests/test_file_jxl_alpha.py b/Tests/test_file_jxl_alpha.py index 710e3b685..d3f950621 100644 --- a/Tests/test_file_jxl_alpha.py +++ b/Tests/test_file_jxl_alpha.py @@ -4,9 +4,7 @@ import pytest from PIL import Image -from .helper import ( - assert_image_similar_tofile -) +from .helper import assert_image_similar_tofile _webp = pytest.importorskip("PIL._jxl", reason="JXL support not installed") @@ -29,4 +27,3 @@ def test_read_rgba() -> None: image.tobytes() assert_image_similar_tofile(image, "Tests/images/transparent.png", 1.0) - diff --git a/Tests/test_file_jxl_animated.py b/Tests/test_file_jxl_animated.py index 8a1b67e9f..e39eabcb0 100644 --- a/Tests/test_file_jxl_animated.py +++ b/Tests/test_file_jxl_animated.py @@ -1,9 +1,8 @@ from __future__ import annotations import pytest -from packaging.version import parse as parse_version -from PIL import Image, features +from PIL import Image from .helper import ( assert_image_equal, @@ -52,9 +51,9 @@ def test_seeking() -> None: im1.load() im2.seek(frame) im2.load() - - assert_image_equal(im1.convert('RGB'), im2.convert('RGB')) - + + assert_image_equal(im1.convert("RGB"), im2.convert("RGB")) + total_dur += im1.info["duration"] assert im1.info["duration"] == im2.info["duration"] assert im1.info["timestamp"] == im1.info["timestamp"] diff --git a/Tests/test_file_jxl_metadata.py b/Tests/test_file_jxl_metadata.py index 8f99bd8f3..0184d4814 100644 --- a/Tests/test_file_jxl_metadata.py +++ b/Tests/test_file_jxl_metadata.py @@ -1,14 +1,12 @@ from __future__ import annotations -from io import BytesIO -from pathlib import Path from types import ModuleType import pytest from PIL import Image -from .helper import mark_if_feature_version, skip_unless_feature +from .helper import skip_unless_feature pytestmark = [ skip_unless_feature("jxl"), @@ -26,6 +24,7 @@ except ImportError: # python -c "from PIL import Image; im=Image.open('Tests/images/flower2.webp'); f=open('/tmp/xmp.xml', 'wb'); f.write(im.info['xmp']); f.close()" # cjxl flower2.jpg flower2.jxl --lossless_jpeg=0 -q 75 -e 8 -x xmp=/tmp/xmp.xml + def test_read_exif_metadata() -> None: file_path = "Tests/images/flower.jxl" with Image.open(file_path) as image: @@ -85,4 +84,3 @@ def test_getxmp() -> None: im.getxmp()["xmpmeta"]["xmptk"] == "Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 " ) - diff --git a/src/PIL/JxlImagePlugin.py b/src/PIL/JxlImagePlugin.py index 8226db096..1372c443f 100644 --- a/src/PIL/JxlImagePlugin.py +++ b/src/PIL/JxlImagePlugin.py @@ -1,6 +1,9 @@ -from io import BytesIO -from . import Image, ImageFile +from __future__ import annotations + import struct +from io import BytesIO + +from . import Image, ImageFile try: from . import _jxl @@ -16,11 +19,14 @@ except ImportError: ## then libjxl decoder is rewinded and we're ready to decode frame by frame ## if OPEN_COUNTS_FRAMES is False, n_frames will be None until the last frame is decoded ## it only applies to animated jpeg xl images -#OPEN_COUNTS_FRAMES = True +# OPEN_COUNTS_FRAMES = True + def _accept(prefix): - is_jxl = prefix[:2] == b'\xff\x0a' \ - or prefix[:12] == b'\x00\x00\x00\x0c\x4a\x58\x4c\x20\x0d\x0a\x87\x0a' + is_jxl = ( + prefix[:2] == b"\xff\x0a" + or prefix[:12] == b"\x00\x00\x00\x0c\x4a\x58\x4c\x20\x0d\x0a\x87\x0a" + ) if is_jxl and not SUPPORTED: return "image file could not be identified because JXL support not installed" return is_jxl @@ -35,7 +41,9 @@ class JxlImageFile(ImageFile.ImageFile): def _open(self): self._decoder = _jxl.PILJxlDecoder(self.fp.read()) - width, height, mode, has_anim, tps_num, tps_denom, n_loops, n_frames = self._decoder.get_info() + width, height, mode, has_anim, tps_num, tps_denom, n_loops, n_frames = ( + self._decoder.get_info() + ) self._size = width, height self.info["loop"] = n_loops self.is_animated = has_anim @@ -46,7 +54,7 @@ class JxlImageFile(ImageFile.ImageFile): self.n_frames = 1 elif n_frames > 0: self.n_frames = n_frames - self._tps_dur_secs = tps_num/tps_denom + self._tps_dur_secs = tps_num / tps_denom # TODO: handle libjxl timecods self.__timestamp = 0 @@ -57,13 +65,17 @@ class JxlImageFile(ImageFile.ImageFile): icc = self._decoder.get_icc() exif = self._decoder.get_exif() xmp = self._decoder.get_xmp() - if icc: self.info["icc_profile"] = icc + if icc: + self.info["icc_profile"] = icc import traceback + try: - if exif: self.info["exif"] = self._fix_exif(exif) + if exif: + self.info["exif"] = self._fix_exif(exif) except: traceback.print_exc() - if xmp: self.info["xmp"] = xmp + if xmp: + self.info["xmp"] = xmp self._rewind() @@ -73,7 +85,7 @@ class JxlImageFile(ImageFile.ImageFile): if len(exif) <= 4: return None exif_start_offset = struct.unpack(">I", exif[:4])[0] - return exif[exif_start_offset+4:] + return exif[exif_start_offset + 4 :] def _getexif(self): if "exif" not in self.info: @@ -82,7 +94,7 @@ class JxlImageFile(ImageFile.ImageFile): def getxmp(self): return self._getxmp(self.info["xmp"]) if "xmp" in self.info else {} - + def _get_next(self): # Get next frame @@ -93,14 +105,14 @@ class JxlImageFile(ImageFile.ImageFile): if next_frame is None: msg = "failed to decode next frame in JXL file" raise EOFError(msg) - + data, tps_duration, is_last = next_frame if is_last and self.n_frames is None: # libjxl said this frame is the last one self.n_frames = self.__physical_frame # duration in miliseconds - duration = 1000 * tps_duration * (1/self._tps_dur_secs) + duration = 1000 * tps_duration * (1 / self._tps_dur_secs) timestamp = self.__timestamp self.__timestamp += duration @@ -115,21 +127,22 @@ class JxlImageFile(ImageFile.ImageFile): def _seek_check(self, frame): # if image is not animated then only the 0th frame is available - if (not self.is_animated and frame != 0) or \ - (self.n_frames is not None and (frame >= self.n_frames or frame < 0)): + if (not self.is_animated and frame != 0) or ( + self.n_frames is not None and (frame >= self.n_frames or frame < 0) + ): msg = "attempt to seek outside sequence" raise EOFError(msg) return self.tell() != frame def _seek(self, frame): - #print("_seek: phy: {}, fr: {}".format(self.__physical_frame, frame)) + # print("_seek: phy: {}, fr: {}".format(self.__physical_frame, frame)) if frame == self.__physical_frame: return # Nothing to do if frame < self.__physical_frame: # also rewind libjxl decoder instance self._rewind(hard=True) - + while self.__physical_frame < frame: self._get_next() # Advance to the requested frame @@ -144,7 +157,7 @@ class JxlImageFile(ImageFile.ImageFile): if self.__loaded != self.__logical_frame: self._seek(self.__logical_frame) - + data, timestamp, duration, is_last = self._get_next() self.info["timestamp"] = timestamp self.info["duration"] = duration @@ -157,9 +170,9 @@ class JxlImageFile(ImageFile.ImageFile): # you need probably 2*(raw image plane) bytes of memory self.fp = BytesIO(data) self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)] - + return super().load() - + def load_seek(self, pos): pass @@ -169,4 +182,4 @@ class JxlImageFile(ImageFile.ImageFile): Image.register_open(JxlImageFile.format, JxlImageFile, _accept) Image.register_extension(JxlImageFile.format, ".jxl") -Image.register_mime(JxlImageFile.format, "image/jxl") \ No newline at end of file +Image.register_mime(JxlImageFile.format, "image/jxl") diff --git a/src/PIL/features.py b/src/PIL/features.py index abd1961a7..e735cb159 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -270,8 +270,7 @@ def pilinfo(out=None, supported_formats=True): ("transp_webp", "WEBP Transparency"), ("webp_mux", "WEBPMUX"), ("webp_anim", "WEBP Animation"), - ("jxl", "JPEG XL") - ("jpg", "JPEG"), + ("jxl", "JPEG XL")("jpg", "JPEG"), ("jpg_2000", "OPENJPEG (JPEG2000)"), ("zlib", "ZLIB (PNG/ZIP)"), ("libtiff", "LIBTIFF"), diff --git a/src/_jxl.c b/src/_jxl.c index 5a0f8eedd..d4b21208a 100644 --- a/src/_jxl.c +++ b/src/_jxl.c @@ -12,9 +12,9 @@ void _pil_jxl_get_pixel_format(JxlPixelFormat *pf, const JxlBasicInfo *bi) { - + pf->num_channels = bi->num_color_channels + bi->num_extra_channels; - + if (bi->exponent_bits_per_sample > 0 || bi->alpha_exponent_bits > 0) { pf->data_type = JXL_TYPE_FLOAT; // not yet supported } else if (bi->bits_per_sample > 8) { @@ -22,7 +22,7 @@ void _pil_jxl_get_pixel_format(JxlPixelFormat *pf, const JxlBasicInfo *bi) { } else { pf->data_type = JXL_TYPE_UINT8; } - + // this *might* cause some issues on Big-Endian systems // would be great to test it pf->endianness = JXL_NATIVE_ENDIAN; @@ -37,7 +37,7 @@ char* _pil_jxl_get_mode(const JxlBasicInfo *bi) { // it will throw an exception but that's for your own good // you wouldn't want to see distorted image if (bi->bits_per_sample != 8) return "uns"; - + // image has transparency if (bi->alpha_bits > 0) { if (bi->num_color_channels == 3) { @@ -62,7 +62,7 @@ char* _pil_jxl_get_mode(const JxlBasicInfo *bi) { typedef struct { PyObject_HEAD JxlDecoder *decoder; void *runner; - + uint8_t *jxl_data; // input jxl bitstream Py_ssize_t jxl_data_len; // length of input jxl bitstream @@ -75,13 +75,13 @@ typedef struct { Py_ssize_t jxl_exif_len; uint8_t *jxl_xmp; Py_ssize_t jxl_xmp_len; - + JxlDecoderStatus status; JxlBasicInfo basic_info; JxlPixelFormat pixel_format; Py_ssize_t n_frames; - + char *mode; } PILJxlDecoderObject; @@ -90,7 +90,7 @@ static PyTypeObject PILJxlDecoder_Type; void _jxl_decoder_dealloc(PyObject *self) { PILJxlDecoderObject *decp = (PILJxlDecoderObject *)self; - + if (decp->jxl_data) { free(decp->jxl_data); decp->jxl_data = NULL; @@ -209,7 +209,7 @@ _jxl_decoder_new(PyObject *self, PyObject *args) { // convert jxl data string to C uint8_t pointer PyBytes_AsStringAndSize((PyObject *)jxl_string, (char **)&_tmp_jxl_data, &_tmp_jxl_data_len); - + // here occurs this copying (inefficiency) decp->jxl_data = malloc(_tmp_jxl_data_len); memcpy(decp->jxl_data, _tmp_jxl_data, _tmp_jxl_data_len); @@ -222,7 +222,7 @@ _jxl_decoder_new(PyObject *self, PyObject *args) { decp->decoder = JxlDecoderCreate(NULL); decp->status = JxlDecoderSetParallelRunner(decp->decoder, - JxlThreadParallelRunner, decp->runner); + JxlThreadParallelRunner, decp->runner); _PIL_JXL_CHECK("JxlDecoderSetParallelRunner") decp->status = JxlDecoderSubscribeEvents(decp->decoder, @@ -241,7 +241,7 @@ _jxl_decoder_new(PyObject *self, PyObject *args) { // decode everything up to the first frame do { - + decp->status = JxlDecoderProcessInput(decp->decoder); //printf("Status: %d\n", decp->status); @@ -255,7 +255,7 @@ _jxl_decoder_new(PyObject *self, PyObject *args) { // got basic info if (decp->status == JXL_DEC_BASIC_INFO) { - + decp->status = JxlDecoderGetBasicInfo(decp->decoder, &decp->basic_info); _PIL_JXL_CHECK("JxlDecoderGetBasicInfo"); @@ -274,7 +274,7 @@ _jxl_decoder_new(PyObject *self, PyObject *args) { // got color encoding if (decp->status == JXL_DEC_COLOR_ENCODING) { - + decp->status = JxlDecoderGetICCProfileSize(decp->decoder, JXL_COLOR_PROFILE_TARGET_DATA, &decp->jxl_icc_len); _PIL_JXL_CHECK("JxlDecoderGetICCProfileSize"); @@ -337,7 +337,7 @@ _jxl_decoder_new(PyObject *self, PyObject *args) { final_jxl_buf_len += (cur_compr_box_size - remaining); } while (decp->status == JXL_DEC_BOX_NEED_MORE_OUTPUT); - + if (is_box_exif) { decp->jxl_exif = final_jxl_buf; decp->jxl_exif_len = final_jxl_buf_len; @@ -444,7 +444,7 @@ _jxl_decoder_get_next(PyObject *self) { size_t new_outbuf_len; decp->status = JxlDecoderImageOutBufferSize(decp->decoder, &decp->pixel_format, &new_outbuf_len); _PIL_JXL_CHECK("JxlDecoderImageOutBufferSize"); - + // only allocate memory when current buffer is too small if (decp->outbuf_len < new_outbuf_len) { @@ -455,7 +455,7 @@ _jxl_decoder_get_next(PyObject *self) { goto end_with_custom_error; } decp->outbuf = _new_outbuf; - + } decp->status = JxlDecoderSetImageOutBuffer(decp->decoder, &decp->pixel_format, decp->outbuf, decp->outbuf_len); @@ -631,4 +631,4 @@ PyInit__jxl(void) { } return m; -} \ No newline at end of file +}