diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index e37b46a41d3..72bc7df672f 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -127,10 +127,17 @@ def test_prog_res_rt(): def test_reduce(): with Image.open("Tests/images/test-card-lossless.jp2") as im: + assert callable(im.reduce) + im.reduce = 2 + assert im.reduce == 2 + im.load() assert im.size == (160, 120) + im.thumbnail((40, 40)) + assert im.size == (40, 30) + def test_layers_type(tmp_path): outfile = str(tmp_path / "temp_layers.jp2") diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 658a0f5134e..729645a0b2b 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -3,6 +3,9 @@ from .helper import convert_to_comparable +codecs = dir(Image.core) + + # There are several internal implementations remarkable_factors = [ # special implementations @@ -247,3 +250,11 @@ def test_mode_F(): for factor in remarkable_factors: compare_reduce_with_reference(im, factor, 0, 0) compare_reduce_with_box(im, factor) + + +@pytest.mark.skipif( + "jpeg2k_decoder" not in codecs, reason="JPEG 2000 support not available" +) +def test_jpeg2k(): + with Image.open("Tests/images/test-card-lossless.jp2") as im: + assert im.reduce(2).size == (320, 240) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b1e8ad3ea06..f296ff86bbf 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1866,7 +1866,11 @@ def resize(self, size, resample=BICUBIC, box=None, reducing_gap=None): factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1 if factor_x > 1 or factor_y > 1: reduce_box = self._get_safe_box(size, resample, box) - self = self.reduce((factor_x, factor_y), box=reduce_box) + factor = (factor_x, factor_y) + if callable(self.reduce): + self = self.reduce(factor, box=reduce_box) + else: + self = Image.reduce(self, factor, box=reduce_box) box = ( (box[0] - reduce_box[0]) / factor_x, (box[1] - reduce_box[1]) / factor_y, diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 2c51d36780a..0b0d433db41 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -176,7 +176,7 @@ def _open(self): if self.size is None or self.mode is None: raise SyntaxError("unable to determine size/mode") - self.reduce = 0 + self._reduce = 0 self.layers = 0 fd = -1 @@ -200,23 +200,33 @@ def _open(self): "jpeg2k", (0, 0) + self.size, 0, - (self.codec, self.reduce, self.layers, fd, length), + (self.codec, self._reduce, self.layers, fd, length), ) ] + @property + def reduce(self): + # https://github.com/python-pillow/Pillow/issues/4343 found that the + # new Image 'reduce' method was shadowed by this plugin's 'reduce' + # property. This attempts to allow for both scenarios + return self._reduce or super().reduce + + @reduce.setter + def reduce(self, value): + self._reduce = value + def load(self): - if self.reduce: - power = 1 << self.reduce + if self.tile and self._reduce: + power = 1 << self._reduce adjust = power >> 1 self._size = ( int((self.size[0] + adjust) / power), int((self.size[1] + adjust) / power), ) - if self.tile: # Update the reduce and layers settings t = self.tile[0] - t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4]) + t3 = (t[3][0], self._reduce, self.layers, t[3][3], t[3][4]) self.tile = [(t[0], (0, 0) + self.size, t[2], t3)] return ImageFile.ImageFile.load(self)