Merge pull request #4474 from radarhere/reduce2

Prevent masking Image reduce method in Jpeg2K
This commit is contained in:
Andrew Murray 2020-03-30 07:29:21 +11:00 committed by GitHub
commit 5a511c6a8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 39 additions and 7 deletions

View File

@ -127,10 +127,17 @@ def test_prog_res_rt():
def test_reduce(): def test_reduce():
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
assert callable(im.reduce)
im.reduce = 2 im.reduce = 2
assert im.reduce == 2
im.load() im.load()
assert im.size == (160, 120) assert im.size == (160, 120)
im.thumbnail((40, 40))
assert im.size == (40, 30)
def test_layers_type(tmp_path): def test_layers_type(tmp_path):
outfile = str(tmp_path / "temp_layers.jp2") outfile = str(tmp_path / "temp_layers.jp2")

View File

@ -3,6 +3,9 @@ from PIL import Image, ImageMath, ImageMode
from .helper import convert_to_comparable from .helper import convert_to_comparable
codecs = dir(Image.core)
# There are several internal implementations # There are several internal implementations
remarkable_factors = [ remarkable_factors = [
# special implementations # special implementations
@ -247,3 +250,11 @@ def test_mode_F():
for factor in remarkable_factors: for factor in remarkable_factors:
compare_reduce_with_reference(im, factor, 0, 0) compare_reduce_with_reference(im, factor, 0, 0)
compare_reduce_with_box(im, factor) 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)

View File

@ -1866,7 +1866,11 @@ class Image:
factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1 factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
if factor_x > 1 or factor_y > 1: if factor_x > 1 or factor_y > 1:
reduce_box = self._get_safe_box(size, resample, box) 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 = (
(box[0] - reduce_box[0]) / factor_x, (box[0] - reduce_box[0]) / factor_x,
(box[1] - reduce_box[1]) / factor_y, (box[1] - reduce_box[1]) / factor_y,

View File

@ -176,7 +176,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
if self.size is None or self.mode is None: if self.size is None or self.mode is None:
raise SyntaxError("unable to determine size/mode") raise SyntaxError("unable to determine size/mode")
self.reduce = 0 self._reduce = 0
self.layers = 0 self.layers = 0
fd = -1 fd = -1
@ -200,23 +200,33 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
"jpeg2k", "jpeg2k",
(0, 0) + self.size, (0, 0) + self.size,
0, 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): def load(self):
if self.reduce: if self.tile and self._reduce:
power = 1 << self.reduce power = 1 << self._reduce
adjust = power >> 1 adjust = power >> 1
self._size = ( self._size = (
int((self.size[0] + adjust) / power), int((self.size[0] + adjust) / power),
int((self.size[1] + adjust) / power), int((self.size[1] + adjust) / power),
) )
if self.tile:
# Update the reduce and layers settings # Update the reduce and layers settings
t = self.tile[0] 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)] self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
return ImageFile.ImageFile.load(self) return ImageFile.ImageFile.load(self)