From 8f6b7b258b11fc99ffaa7084ab200b175e14b627 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 22 Nov 2016 04:00:49 +0300 Subject: [PATCH 1/2] use minimal scale for jpeg downscaling --- PIL/JpegImagePlugin.py | 2 +- Tests/test_image_draft.py | 70 +++++++++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 6d4e588c017..d44b345fdc7 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -349,7 +349,7 @@ def draft(self, mode, size): a = mode, "" if size: - scale = max(self.size[0] // size[0], self.size[1] // size[1]) + scale = min(self.size[0] // size[0], self.size[1] // size[1]) for s in [8, 4, 2, 1]: if scale >= s: break diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 0a8cc023c77..409d7059808 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -2,35 +2,63 @@ from PIL import Image -CODECS = dir(Image.core) -FILENAME = "Tests/images/hopper.jpg" -DATA = tostring(Image.open(FILENAME).resize((512, 512)), "JPEG") - - -def draft(mode, size): - im = fromstring(DATA) - im.draft(mode, size) - return im - class TestImageDraft(PillowTestCase): - def setUp(self): - if "jpeg_encoder" not in CODECS or "jpeg_decoder" not in CODECS: + codecs = dir(Image.core) + if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: self.skipTest("jpeg support not available") + def draft_roundtrip(self, in_mode, in_size, req_mode, req_size): + im = Image.new(in_mode, in_size) + data = tostring(im, 'JPEG') + im = fromstring(data) + im.draft(req_mode, req_size) + return im + def test_size(self): - self.assertEqual(draft("RGB", (512, 512)).size, (512, 512)) - self.assertEqual(draft("RGB", (256, 256)).size, (256, 256)) - self.assertEqual(draft("RGB", (128, 128)).size, (128, 128)) - self.assertEqual(draft("RGB", (64, 64)).size, (64, 64)) - self.assertEqual(draft("RGB", (32, 32)).size, (64, 64)) + for in_size, req_size, out_size in [ + ((435, 361), (2048, 2048), (435, 361)), # bigger + ((435, 361), (435, 361), (435, 361)), # same + + # large requested width + ((435, 361), (218, 128), (435, 361)), # almost 2x + ((435, 361), (217, 128), (218, 181)), # more than 2x + ((435, 361), (109, 64), (218, 181)), # almost 4x + ((435, 361), (108, 64), (109, 91)), # more than 4x + ((435, 361), (55, 32), (109, 91)), # almost 8x + ((435, 361), (54, 32), (55, 46)), # more than 8x + ((435, 361), (27, 16), (55, 46)), # more than 16x + + # and vice versa + ((435, 361), (128, 181), (435, 361)), # almost 2x + ((435, 361), (128, 180), (218, 181)), # more than 2x + ((435, 361), (64, 91), (218, 181)), # almost 4x + ((435, 361), (64, 90), (109, 91)), # more than 4x + ((435, 361), (32, 46), (109, 91)), # almost 8x + ((435, 361), (32, 45), (55, 46)), # more than 8x + ((435, 361), (16, 22), (55, 46)), # more than 16x + ]: + im = self.draft_roundtrip('L', in_size, None, req_size) + self.assertEqual(im.size, out_size) def test_mode(self): - self.assertEqual(draft("1", (512, 512)).mode, "RGB") - self.assertEqual(draft("L", (512, 512)).mode, "L") - self.assertEqual(draft("RGB", (512, 512)).mode, "RGB") - self.assertEqual(draft("YCbCr", (512, 512)).mode, "YCbCr") + for in_mode, req_mode, out_mode in [ + ("RGB", "1", "RGB"), + ("RGB", "L", "L"), + ("RGB", "RGB", "RGB"), + ("RGB", "YCbCr", "YCbCr"), + ("L", "1", "L"), + ("L", "L", "L"), + ("L", "RGB", "L"), + ("L", "YCbCr", "L"), + ("CMYK", "1", "CMYK"), + ("CMYK", "L", "CMYK"), + ("CMYK", "RGB", "CMYK"), + ("CMYK", "YCbCr", "CMYK"), + ]: + im = self.draft_roundtrip(in_mode, (64, 64), req_mode, None) + self.assertEqual(im.mode, out_mode) if __name__ == '__main__': From 55fca4857ce97a6e97abcf4eee2228b4046488b8 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 22 Nov 2016 04:28:04 +0300 Subject: [PATCH 2/2] protect .draft() from second call --- PIL/JpegImagePlugin.py | 4 ++++ Tests/test_image_draft.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index d44b345fdc7..2728fb9f89f 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -341,6 +341,10 @@ def draft(self, mode, size): if len(self.tile) != 1: return + # Protect from second call + if self.decoderconfig: + return + d, e, o, a = self.tile[0] scale = 0 diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 409d7059808..12f5e0e9f38 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -20,6 +20,9 @@ def test_size(self): for in_size, req_size, out_size in [ ((435, 361), (2048, 2048), (435, 361)), # bigger ((435, 361), (435, 361), (435, 361)), # same + ((128, 128), (64, 64), (64, 64)), + ((128, 128), (32, 32), (32, 32)), + ((128, 128), (16, 16), (16, 16)), # large requested width ((435, 361), (218, 128), (435, 361)), # almost 2x @@ -40,6 +43,7 @@ def test_size(self): ((435, 361), (16, 22), (55, 46)), # more than 16x ]: im = self.draft_roundtrip('L', in_size, None, req_size) + im.load() self.assertEqual(im.size, out_size) def test_mode(self): @@ -58,8 +62,13 @@ def test_mode(self): ("CMYK", "YCbCr", "CMYK"), ]: im = self.draft_roundtrip(in_mode, (64, 64), req_mode, None) + im.load() self.assertEqual(im.mode, out_mode) + def test_several_drafts(self): + im = self.draft_roundtrip('L', (128, 128), None, (64, 64)) + im.draft(None, (64, 64)) + im.load() if __name__ == '__main__': unittest.main()