From c1da18e0ad541fd3f80de17a766b32e0e032b0ef Mon Sep 17 00:00:00 2001 From: homm Date: Sun, 3 Jul 2016 05:40:34 +0300 Subject: [PATCH 01/57] do not allow to save images discarding alpha channel --- PIL/JpegImagePlugin.py | 1 - Tests/test_file_jpeg.py | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 9d4eaabb1..a08e932ab 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -534,7 +534,6 @@ RAWMODE = { "1": "L", "L": "L", "RGB": "RGB", - "RGBA": "RGB", "RGBX": "RGB", "CMYK": "CMYK;I", # assume adobe conventions "YCbCr": "YCbCr", diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 54f8b9fd1..692dc5279 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -450,6 +450,19 @@ class TestFileJpeg(PillowTestCase): # Assert self.assertEqual(im.format, "JPEG") + def test_save_correct_modes(self): + out = BytesIO() + for mode in ['1', 'L', 'RGB', 'RGBX', 'CMYK', 'YCbCr']: + img = Image.new(mode, (20, 20)) + img.save(out, "JPEG") + + def test_save_wrong_modes(self): + # ref https://github.com/python-pillow/Pillow/issues/2005 + out = BytesIO() + for mode in ['LA', 'La', 'RGBA', 'RGBa', 'P']: + img = Image.new(mode, (20, 20)) + self.assertRaises(IOError, img.save, out, "JPEG") + if __name__ == '__main__': unittest.main() From ea0071309919cf1848fc46f9137165659049a701 Mon Sep 17 00:00:00 2001 From: Marco De Donno Date: Sun, 3 Jul 2016 21:38:36 +0200 Subject: [PATCH 02/57] Add a scale function to expand or contract a PIL image by a factor `factor`passed in argument. --- PIL/Image.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/PIL/Image.py b/PIL/Image.py index 64f461358..d0152b52b 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1547,6 +1547,26 @@ class Image(object): return self._new(self.im.resize(size, resample)) + def scale(self, factor, resample = NEAREST): + """ + Returns a rescaled image by a specific factor given in parameter. + A factor greater than 1 expands the image, between 0 and 1 contracts the + image. + + :param factor: The expansion factor, as a float. + :param resample: An optional resampling filter. Same values possible as + in the PIL.Image.resize function. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + if factor == 1: + return self._new(self.im) + elif factor <= 0: + raise ValueError("the factor must be greater than 0") + else: + size = (int(round(factor * self.width)), + int(round(factor * self.height))) + return self.resize(size, resample) + def rotate(self, angle, resample=NEAREST, expand=0): """ Returns a rotated copy of this image. This method returns a From c0eb87cac32a359222df8aa40e5ca33ac05d9a63 Mon Sep 17 00:00:00 2001 From: Marco De Donno Date: Tue, 5 Jul 2016 01:32:06 +0200 Subject: [PATCH 03/57] Move the scale function from Image to ImageOps --- PIL/Image.py | 20 -------------------- PIL/ImageOps.py | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index d0152b52b..64f461358 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1547,26 +1547,6 @@ class Image(object): return self._new(self.im.resize(size, resample)) - def scale(self, factor, resample = NEAREST): - """ - Returns a rescaled image by a specific factor given in parameter. - A factor greater than 1 expands the image, between 0 and 1 contracts the - image. - - :param factor: The expansion factor, as a float. - :param resample: An optional resampling filter. Same values possible as - in the PIL.Image.resize function. - :returns: An :py:class:`~PIL.Image.Image` object. - """ - if factor == 1: - return self._new(self.im) - elif factor <= 0: - raise ValueError("the factor must be greater than 0") - else: - size = (int(round(factor * self.width)), - int(round(factor * self.height))) - return self.resize(size, resample) - def rotate(self, angle, resample=NEAREST, expand=0): """ Returns a rotated copy of this image. This method returns a diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py index f317645b2..a0fa9e41d 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -178,6 +178,27 @@ def crop(image, border=0): ) +def scale(self, factor, resample=Image.NEAREST): + """ + Returns a rescaled image by a specific factor given in parameter. + A factor greater than 1 expands the image, between 0 and 1 contracts the + image. + + :param factor: The expansion factor, as a float. + :param resample: An optional resampling filter. Same values possible as + in the PIL.Image.resize function. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + if factor == 1: + return self._new(self.im) + elif factor <= 0: + raise ValueError("the factor must be greater than 0") + else: + size = (int(round(factor * self.width)), + int(round(factor * self.height))) + return self.resize(size, resample) + + def deform(image, deformer, resample=Image.BILINEAR): """ Deform the image. From 7af3c4c3bcde86e2bfe31b3d2859f96275810546 Mon Sep 17 00:00:00 2001 From: Marco De Donno Date: Tue, 5 Jul 2016 20:15:14 +0200 Subject: [PATCH 04/57] Add test for the ImageOps.scale function --- Tests/test_imageops.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 396f0da6c..47daef334 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -78,6 +78,16 @@ class TestImageOps(PillowTestCase): ImageOps.equalize(i.convert("P")) ImageOps.equalize(i.convert("RGB")) + def test_scale(self): + # Test the scaling function + i = hopper("L").resize((50,50)) + + newimg = ImageOps.scale(i,2) + self.assertEqual(newimg.size, (100, 100)) + + newimg = ImageOps.scale(i,0.5) + self.assertEqual(newimg.size, (25, 25)) + if __name__ == '__main__': unittest.main() From 4d51a410d8b237c9e284a6ccc62ad84226fd4c50 Mon Sep 17 00:00:00 2001 From: Marco De Donno Date: Tue, 5 Jul 2016 20:46:47 +0200 Subject: [PATCH 05/57] Add the test for factor = 1 and -1 --- Tests/test_imageops.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 47daef334..6873b6e11 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -81,7 +81,13 @@ class TestImageOps(PillowTestCase): def test_scale(self): # Test the scaling function i = hopper("L").resize((50,50)) - + + with self.assertRaises(ValueError): + ImageOps.scale(i,-1) + + newimg = ImageOps.scale(i,1) + self.assertEqual(newimg.size, (50, 50)) + newimg = ImageOps.scale(i,2) self.assertEqual(newimg.size, (100, 100)) From 7d8fea012be342fad0c74eb465583ce69f557eb7 Mon Sep 17 00:00:00 2001 From: Marco De Donno Date: Wed, 6 Jul 2016 01:32:16 +0200 Subject: [PATCH 06/57] Code style update --- PIL/ImageOps.py | 34 +++++++++++++++++----------------- Tests/test_imageops.py | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py index a0fa9e41d..2b3496e56 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -179,24 +179,24 @@ def crop(image, border=0): def scale(self, factor, resample=Image.NEAREST): - """ - Returns a rescaled image by a specific factor given in parameter. - A factor greater than 1 expands the image, between 0 and 1 contracts the - image. + """ + Returns a rescaled image by a specific factor given in parameter. + A factor greater than 1 expands the image, between 0 and 1 contracts the + image. - :param factor: The expansion factor, as a float. - :param resample: An optional resampling filter. Same values possible as - in the PIL.Image.resize function. - :returns: An :py:class:`~PIL.Image.Image` object. - """ - if factor == 1: - return self._new(self.im) - elif factor <= 0: - raise ValueError("the factor must be greater than 0") - else: - size = (int(round(factor * self.width)), - int(round(factor * self.height))) - return self.resize(size, resample) + :param factor: The expansion factor, as a float. + :param resample: An optional resampling filter. Same values possible as + in the PIL.Image.resize function. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + if factor == 1: + return self._new(self.im) + elif factor <= 0: + raise ValueError("the factor must be greater than 0") + else: + size = (int(round(factor * self.width)), + int(round(factor * self.height))) + return self.resize(size, resample) def deform(image, deformer, resample=Image.BILINEAR): diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 6873b6e11..fa763e09b 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -80,7 +80,7 @@ class TestImageOps(PillowTestCase): def test_scale(self): # Test the scaling function - i = hopper("L").resize((50,50)) + i = hopper("L").resize((50, 50)) with self.assertRaises(ValueError): ImageOps.scale(i,-1) From f19c52b5d5fd4b240e524e100b9358c88e7fd969 Mon Sep 17 00:00:00 2001 From: Marco De Donno Date: Wed, 6 Jul 2016 10:21:00 +0200 Subject: [PATCH 07/57] Code style update --- Tests/test_imageops.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index fa763e09b..f06f284e6 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -83,15 +83,15 @@ class TestImageOps(PillowTestCase): i = hopper("L").resize((50, 50)) with self.assertRaises(ValueError): - ImageOps.scale(i,-1) + ImageOps.scale(i, -1) - newimg = ImageOps.scale(i,1) + newimg = ImageOps.scale(i, 1) self.assertEqual(newimg.size, (50, 50)) - newimg = ImageOps.scale(i,2) + newimg = ImageOps.scale(i, 2) self.assertEqual(newimg.size, (100, 100)) - newimg = ImageOps.scale(i,0.5) + newimg = ImageOps.scale(i, 0.5) self.assertEqual(newimg.size, (25, 25)) From 5232361718bae0f0ccda76bfd5b390ebf9179b18 Mon Sep 17 00:00:00 2001 From: homm Date: Thu, 23 Jun 2016 14:24:31 +0300 Subject: [PATCH 08/57] fix errors with pixel center coordinates --- libImaging/Geometry.c | 56 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index a11a4eac1..79a78c006 100644 --- a/libImaging/Geometry.c +++ b/libImaging/Geometry.c @@ -240,7 +240,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn) /* transform primitives (ImagingTransformMap) */ static int -affine_transform(double* xin, double* yin, int x, int y, void* data) +affine_transform(double* xout, double* yout, int x, int y, void* data) { /* full moon tonight. your compiler will generate bogus code for simple expressions, unless you reorganize the code, or @@ -250,28 +250,34 @@ affine_transform(double* xin, double* yin, int x, int y, void* data) double a0 = a[0]; double a1 = a[1]; double a2 = a[2]; double a3 = a[3]; double a4 = a[4]; double a5 = a[5]; - xin[0] = a0*x + a1*y + a2; - yin[0] = a3*x + a4*y + a5; + double xin = x + 0.5; + double yin = y + 0.5; + + xout[0] = a0*xin + a1*yin + a2; + yout[0] = a3*xin + a4*yin + a5; return 1; } static int -perspective_transform(double* xin, double* yin, int x, int y, void* data) +perspective_transform(double* xout, double* yout, int x, int y, void* data) { double* a = (double*) data; double a0 = a[0]; double a1 = a[1]; double a2 = a[2]; double a3 = a[3]; double a4 = a[4]; double a5 = a[5]; double a6 = a[6]; double a7 = a[7]; - xin[0] = (a0*x + a1*y + a2) / (a6*x + a7*y + 1); - yin[0] = (a3*x + a4*y + a5) / (a6*x + a7*y + 1); + double xin = x + 0.5; + double yin = y + 0.5; + + xout[0] = (a0*xin + a1*yin + a2) / (a6*xin + a7*yin + 1); + yout[0] = (a3*xin + a4*yin + a5) / (a6*xin + a7*yin + 1); return 1; } static int -quad_transform(double* xin, double* yin, int x, int y, void* data) +quad_transform(double* xout, double* yout, int x, int y, void* data) { /* quad warp: map quadrilateral to rectangle */ @@ -279,8 +285,11 @@ quad_transform(double* xin, double* yin, int x, int y, void* data) double a0 = a[0]; double a1 = a[1]; double a2 = a[2]; double a3 = a[3]; double a4 = a[4]; double a5 = a[5]; double a6 = a[6]; double a7 = a[7]; - xin[0] = a0 + a1*x + a2*y + a3*x*y; - yin[0] = a4 + a5*x + a6*y + a7*x*y; + double xin = x + 0.5; + double yin = y + 0.5; + + xout[0] = a0 + a1*xin + a2*yin + a3*xin*yin; + yout[0] = a4 + a5*xin + a6*yin + a7*xin*yin; return 1; } @@ -290,8 +299,8 @@ quad_transform(double* xin, double* yin, int x, int y, void* data) static int nearest_filter8(void* out, Imaging im, double xin, double yin) { - int x = COORD(xin); - int y = COORD(yin); + int x = COORD(xin + 0.5); + int y = COORD(yin + 0.5); if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) return 0; ((UINT8*)out)[0] = im->image8[y][x]; @@ -301,8 +310,8 @@ nearest_filter8(void* out, Imaging im, double xin, double yin) static int nearest_filter16(void* out, Imaging im, double xin, double yin) { - int x = COORD(xin); - int y = COORD(yin); + int x = COORD(xin + 0.5); + int y = COORD(yin + 0.5); if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) return 0; ((INT16*)out)[0] = ((INT16*)(im->image8[y]))[x]; @@ -312,8 +321,8 @@ nearest_filter16(void* out, Imaging im, double xin, double yin) static int nearest_filter32(void* out, Imaging im, double xin, double yin) { - int x = COORD(xin); - int y = COORD(yin); + int x = COORD(xin + 0.5); + int y = COORD(yin + 0.5); if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) return 0; ((INT32*)out)[0] = im->image32[y][x]; @@ -691,8 +700,8 @@ ImagingScaleAffine(Imaging imOut, Imaging imIn, return (Imaging) ImagingError_MemoryError(); } - xo = a[2]; - yo = a[5]; + xo = a[2] + a[0] * 0.5; + yo = a[5] + a[4] * 0.5; xmin = x1; xmax = x0; @@ -771,8 +780,10 @@ affine_fixed(Imaging imOut, Imaging imIn, /* use 16.16 fixed point arithmetics */ #define FIX(v) FLOOR((v)*65536.0 + 0.5) - a0 = FIX(a[0]); a1 = FIX(a[1]); a2 = FIX(a[2]); - a3 = FIX(a[3]); a4 = FIX(a[4]); a5 = FIX(a[5]); + a0 = FIX(a[0]); a1 = FIX(a[1]); + a3 = FIX(a[3]); a4 = FIX(a[4]); + a2 = FIX(a[2] + a[0] * 0.5 + a[1] * 0.5); + a5 = FIX(a[5] + a[3] * 0.5 + a[4] * 0.5); #undef FIX @@ -835,9 +846,10 @@ ImagingTransformAffine(Imaging imOut, Imaging imIn, filterid, fill); } - if (a[1] == 0 && a[3] == 0) + if (a[1] == 0 && a[3] == 0) { /* Scaling */ return ImagingScaleAffine(imOut, imIn, x0, y0, x1, y1, a, fill); + } if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) return (Imaging) ImagingError_ModeError(); @@ -867,8 +879,8 @@ ImagingTransformAffine(Imaging imOut, Imaging imIn, xsize = (int) imIn->xsize; ysize = (int) imIn->ysize; - xo = a[2]; - yo = a[5]; + xo = a[2] + a[1] * 0.5 + a[0] * 0.5; + yo = a[5] + a[4] * 0.5 + a[3] * 0.5; #define AFFINE_TRANSFORM(pixel, image)\ for (y = y0; y < y1; y++) {\ From 2522101ed76fb100563e9a690b4598da50684ed7 Mon Sep 17 00:00:00 2001 From: homm Date: Sun, 10 Jul 2016 14:59:36 +0300 Subject: [PATCH 09/57] tests for 90 degree transformation --- Tests/test_image_transform.py | 55 ++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 26ff8822c..0d6b91276 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -98,7 +98,7 @@ class TestImageTransform(PillowTestCase): def test_alpha_premult_resize(self): def op(im, sz): - return im.resize(sz, Image.LINEAR) + return im.resize(sz, Image.BILINEAR) self._test_alpha_premult(op) @@ -139,6 +139,59 @@ class TestImageTransform(PillowTestCase): self.test_mesh() +class TestImageTransformAffine(PillowTestCase): + def _test_image(self): + im = hopper('RGB') + return im.crop((10, 20, im.width - 10, im.height - 20)) + + def test_rotate_0_deg(self): + im = self._test_image() + transform_data = [ + 1, 0, 0, + 0, 1, 0, + ] + for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: + transformed = im.transform(im.size, Image.AFFINE, + transform_data, resample) + self.assert_image_equal(im, transformed) + + def test_rotate_90_deg(self): + im = self._test_image() + transform_data = [ + 0, -1, im.width, + 1, 0, 0, + ] + transposed = im.transpose(Image.ROTATE_90) + for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: + transformed = im.transform(transposed.size, Image.AFFINE, + transform_data, resample) + self.assert_image_equal(transposed, transformed) + + def test_rotate_180_deg(self): + im = self._test_image() + transform_data = [ + -1, 0, im.width, + 0, -1, im.height, + ] + transposed = im.transpose(Image.ROTATE_180) + for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: + transformed = im.transform(transposed.size, Image.AFFINE, + transform_data, resample) + self.assert_image_equal(transposed, transformed) + + def test_rotate_270_deg(self): + im = self._test_image() + transform_data = [ + 0, 1, 0, + -1, 0, im.height, + ] + transposed = im.transpose(Image.ROTATE_270) + for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: + transformed = im.transform(transposed.size, Image.AFFINE, + transform_data, resample) + self.assert_image_equal(transposed, transformed) + + if __name__ == '__main__': unittest.main() From ac747d22904ed60c1a84e01b364ab79327f42d26 Mon Sep 17 00:00:00 2001 From: homm Date: Sun, 10 Jul 2016 16:07:31 +0300 Subject: [PATCH 10/57] resize transform tests --- Tests/test_image_transform.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 0d6b91276..c2eec93fc 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -191,6 +191,38 @@ class TestImageTransformAffine(PillowTestCase): transform_data, resample) self.assert_image_equal(transposed, transformed) + def _test_resize_x(self, x): + im = self._test_image() + transform_data = [ + 1/x, 0, 0, + 0, 1/x, 0, + ] + size = (int(round(im.width * x)), int(round(im.height * x))) + transformed = im.transform( + size, Image.AFFINE, transform_data, Image.NEAREST) + transform_data = [ + x, 0, 0, + 0, x, 0, + ] + transformed = transformed.transform( + im.size, Image.AFFINE, transform_data, Image.NEAREST) + self.assert_image_equal(im, transformed) + + def test_resize_1_1x(self): + self._test_resize_x(1.1) + + def test_resize_1_5x(self): + self._test_resize_x(1.5) + + def test_resize_2_0x(self): + self._test_resize_x(2.0) + + def test_resize_2_3x(self): + self._test_resize_x(2.3) + + def test_resize_2_5x(self): + self._test_resize_x(2.5) + if __name__ == '__main__': unittest.main() From ad3f7238d2f49ae3fc0718bf4dcfbdee3dd7bd97 Mon Sep 17 00:00:00 2001 From: homm Date: Sun, 10 Jul 2016 21:47:59 +0300 Subject: [PATCH 11/57] add translate tests --- Tests/test_image_transform.py | 119 +++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index c2eec93fc..b658ece5e 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,3 +1,5 @@ +import math + from helper import unittest, PillowTestCase, hopper from PIL import Image @@ -144,84 +146,97 @@ class TestImageTransformAffine(PillowTestCase): im = hopper('RGB') return im.crop((10, 20, im.width - 10, im.height - 20)) - def test_rotate_0_deg(self): + def _test_rotate(self, angle, transpose): im = self._test_image() - transform_data = [ - 1, 0, 0, - 0, 1, 0, + + angle = - math.radians(angle) + matrix = [ + round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0, + round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0 ] + matrix[2] = (1 - matrix[0] - matrix[1]) * im.width / 2 + matrix[5] = (1 - matrix[3] - matrix[4]) * im.height / 2 + + if transpose is not None: + transposed = im.transpose(transpose) + else: + transposed = im for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: - transformed = im.transform(im.size, Image.AFFINE, - transform_data, resample) - self.assert_image_equal(im, transformed) + transformed = im.transform(transposed.size, Image.AFFINE, + matrix, resample) + self.assert_image_equal(transposed, transformed) + + def test_rotate_0_deg(self): + self._test_rotate(0, None) def test_rotate_90_deg(self): - im = self._test_image() - transform_data = [ - 0, -1, im.width, - 1, 0, 0, - ] - transposed = im.transpose(Image.ROTATE_90) - for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: - transformed = im.transform(transposed.size, Image.AFFINE, - transform_data, resample) - self.assert_image_equal(transposed, transformed) + self._test_rotate(90, Image.ROTATE_90) def test_rotate_180_deg(self): - im = self._test_image() - transform_data = [ - -1, 0, im.width, - 0, -1, im.height, - ] - transposed = im.transpose(Image.ROTATE_180) - for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: - transformed = im.transform(transposed.size, Image.AFFINE, - transform_data, resample) - self.assert_image_equal(transposed, transformed) + self._test_rotate(180, Image.ROTATE_180) def test_rotate_270_deg(self): - im = self._test_image() - transform_data = [ - 0, 1, 0, - -1, 0, im.height, - ] - transposed = im.transpose(Image.ROTATE_270) - for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: - transformed = im.transform(transposed.size, Image.AFFINE, - transform_data, resample) - self.assert_image_equal(transposed, transformed) + self._test_rotate(270, Image.ROTATE_270) - def _test_resize_x(self, x): + def _test_resize(self, scale): im = self._test_image() - transform_data = [ - 1/x, 0, 0, - 0, 1/x, 0, + matrix = [ + 1 / scale, 0, 0, + 0, 1 / scale, 0, ] - size = (int(round(im.width * x)), int(round(im.height * x))) + size = int(round(im.width * scale)), int(round(im.height * scale)) transformed = im.transform( - size, Image.AFFINE, transform_data, Image.NEAREST) - transform_data = [ - x, 0, 0, - 0, x, 0, + size, Image.AFFINE, matrix, Image.NEAREST) + matrix = [ + scale, 0, 0, + 0, scale, 0, ] transformed = transformed.transform( - im.size, Image.AFFINE, transform_data, Image.NEAREST) + im.size, Image.AFFINE, matrix, Image.NEAREST) self.assert_image_equal(im, transformed) def test_resize_1_1x(self): - self._test_resize_x(1.1) + self._test_resize(1.1) def test_resize_1_5x(self): - self._test_resize_x(1.5) + self._test_resize(1.5) def test_resize_2_0x(self): - self._test_resize_x(2.0) + self._test_resize(2.0) def test_resize_2_3x(self): - self._test_resize_x(2.3) + self._test_resize(2.3) def test_resize_2_5x(self): - self._test_resize_x(2.5) + self._test_resize(2.5) + + def _test_translate(self, x, y, epsilonscale): + im = self._test_image() + size_up = int(round(im.width + x)), int(round(im.height + y)) + matrix_up = [ + 1, 0, -x, + 0, 1, -y, + ] + matrix_down = [ + 1, 0, x, + 0, 1, y, + ] + for resample, epsilon in [(Image.NEAREST, 0), + (Image.BILINEAR, 1.5), (Image.BICUBIC, 1)]: + transformed = im.transform( + size_up, Image.AFFINE, matrix_up, resample) + transformed = transformed.transform( + im.size, Image.AFFINE, matrix_down, resample) + self.assert_image_similar(transformed, im, epsilon * epsilonscale) + + def test_translate_0_1(self): + self._test_translate(.1, 0, 3.7) + + def test_translate_0_6(self): + self._test_translate(.6, 0, 9.1) + + def test_translate_50(self): + self._test_translate(50, 50, 0) if __name__ == '__main__': From 1321713688dc8fd3c8a5265e4bca413f15b97609 Mon Sep 17 00:00:00 2001 From: homm Date: Mon, 11 Jul 2016 00:26:12 +0300 Subject: [PATCH 12/57] repeat all affine tests with PERSPECTIVE --- Tests/test_image_transform.py | 24 ++++++++++++++++++------ libImaging/Geometry.c | 12 ++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index b658ece5e..8ff25b90b 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -142,6 +142,8 @@ class TestImageTransform(PillowTestCase): class TestImageTransformAffine(PillowTestCase): + transform = Image.AFFINE + def _test_image(self): im = hopper('RGB') return im.crop((10, 20, im.width - 10, im.height - 20)) @@ -152,7 +154,8 @@ class TestImageTransformAffine(PillowTestCase): angle = - math.radians(angle) matrix = [ round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0, - round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0 + round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0, + 0, 0, ] matrix[2] = (1 - matrix[0] - matrix[1]) * im.width / 2 matrix[5] = (1 - matrix[3] - matrix[4]) * im.height / 2 @@ -162,7 +165,7 @@ class TestImageTransformAffine(PillowTestCase): else: transposed = im for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: - transformed = im.transform(transposed.size, Image.AFFINE, + transformed = im.transform(transposed.size, self.transform, matrix, resample) self.assert_image_equal(transposed, transformed) @@ -183,16 +186,18 @@ class TestImageTransformAffine(PillowTestCase): matrix = [ 1 / scale, 0, 0, 0, 1 / scale, 0, + 0, 0, ] size = int(round(im.width * scale)), int(round(im.height * scale)) transformed = im.transform( - size, Image.AFFINE, matrix, Image.NEAREST) + size, self.transform, matrix, Image.NEAREST) matrix = [ scale, 0, 0, 0, scale, 0, + 0, 0, ] transformed = transformed.transform( - im.size, Image.AFFINE, matrix, Image.NEAREST) + im.size, self.transform, matrix, Image.NEAREST) self.assert_image_equal(im, transformed) def test_resize_1_1x(self): @@ -216,17 +221,19 @@ class TestImageTransformAffine(PillowTestCase): matrix_up = [ 1, 0, -x, 0, 1, -y, + 0, 0, ] matrix_down = [ 1, 0, x, 0, 1, y, + 0, 0, ] for resample, epsilon in [(Image.NEAREST, 0), (Image.BILINEAR, 1.5), (Image.BICUBIC, 1)]: transformed = im.transform( - size_up, Image.AFFINE, matrix_up, resample) + size_up, self.transform, matrix_up, resample) transformed = transformed.transform( - im.size, Image.AFFINE, matrix_down, resample) + im.size, self.transform, matrix_down, resample) self.assert_image_similar(transformed, im, epsilon * epsilonscale) def test_translate_0_1(self): @@ -239,6 +246,11 @@ class TestImageTransformAffine(PillowTestCase): self._test_translate(50, 50, 0) +class TestImageTransformPerspective(TestImageTransformAffine): + # Repeat all tests for AFFINE transormations with PERSPECTIVE + transform = Image.PERSPECTIVE + + if __name__ == '__main__': unittest.main() diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index 79a78c006..d9750cb10 100644 --- a/libImaging/Geometry.c +++ b/libImaging/Geometry.c @@ -299,8 +299,8 @@ quad_transform(double* xout, double* yout, int x, int y, void* data) static int nearest_filter8(void* out, Imaging im, double xin, double yin) { - int x = COORD(xin + 0.5); - int y = COORD(yin + 0.5); + int x = COORD(xin); + int y = COORD(yin); if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) return 0; ((UINT8*)out)[0] = im->image8[y][x]; @@ -310,8 +310,8 @@ nearest_filter8(void* out, Imaging im, double xin, double yin) static int nearest_filter16(void* out, Imaging im, double xin, double yin) { - int x = COORD(xin + 0.5); - int y = COORD(yin + 0.5); + int x = COORD(xin); + int y = COORD(yin); if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) return 0; ((INT16*)out)[0] = ((INT16*)(im->image8[y]))[x]; @@ -321,8 +321,8 @@ nearest_filter16(void* out, Imaging im, double xin, double yin) static int nearest_filter32(void* out, Imaging im, double xin, double yin) { - int x = COORD(xin + 0.5); - int y = COORD(yin + 0.5); + int x = COORD(xin); + int y = COORD(yin); if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) return 0; ((INT32*)out)[0] = im->image32[y][x]; From 5b8c8aa389b967071a19e6b1cae5ad36537ddc9c Mon Sep 17 00:00:00 2001 From: homm Date: Mon, 11 Jul 2016 00:47:58 +0300 Subject: [PATCH 13/57] improve resize test --- Tests/test_image_transform.py | 54 ++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 8ff25b90b..cd05f2d7c 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -148,15 +148,14 @@ class TestImageTransformAffine(PillowTestCase): im = hopper('RGB') return im.crop((10, 20, im.width - 10, im.height - 20)) - def _test_rotate(self, angle, transpose): + def _test_rotate(self, deg, transpose): im = self._test_image() - angle = - math.radians(angle) + angle = - math.radians(deg) matrix = [ round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0, round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0, - 0, 0, - ] + 0, 0] matrix[2] = (1 - matrix[0] - matrix[1]) * im.width / 2 matrix[5] = (1 - matrix[3] - matrix[4]) * im.height / 2 @@ -164,6 +163,7 @@ class TestImageTransformAffine(PillowTestCase): transposed = im.transpose(transpose) else: transposed = im + for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: transformed = im.transform(transposed.size, self.transform, matrix, resample) @@ -181,53 +181,55 @@ class TestImageTransformAffine(PillowTestCase): def test_rotate_270_deg(self): self._test_rotate(270, Image.ROTATE_270) - def _test_resize(self, scale): + def _test_resize(self, scale, epsilonscale): im = self._test_image() - matrix = [ + + size_up = int(round(im.width * scale)), int(round(im.height * scale)) + matrix_up = [ 1 / scale, 0, 0, 0, 1 / scale, 0, - 0, 0, - ] - size = int(round(im.width * scale)), int(round(im.height * scale)) - transformed = im.transform( - size, self.transform, matrix, Image.NEAREST) - matrix = [ + 0, 0] + matrix_down = [ scale, 0, 0, 0, scale, 0, - 0, 0, - ] - transformed = transformed.transform( - im.size, self.transform, matrix, Image.NEAREST) - self.assert_image_equal(im, transformed) + 0, 0] + + for resample, epsilon in [(Image.NEAREST, 0), + (Image.BILINEAR, 2), (Image.BICUBIC, 1)]: + transformed = im.transform( + size_up, self.transform, matrix_up, resample) + transformed = transformed.transform( + im.size, self.transform, matrix_down, resample) + self.assert_image_similar(transformed, im, epsilon * epsilonscale) def test_resize_1_1x(self): - self._test_resize(1.1) + self._test_resize(1.1, 6.9) def test_resize_1_5x(self): - self._test_resize(1.5) + self._test_resize(1.5, 5.5) def test_resize_2_0x(self): - self._test_resize(2.0) + self._test_resize(2.0, 5.5) def test_resize_2_3x(self): - self._test_resize(2.3) + self._test_resize(2.3, 3.7) def test_resize_2_5x(self): - self._test_resize(2.5) + self._test_resize(2.5, 3.7) def _test_translate(self, x, y, epsilonscale): im = self._test_image() + size_up = int(round(im.width + x)), int(round(im.height + y)) matrix_up = [ 1, 0, -x, 0, 1, -y, - 0, 0, - ] + 0, 0] matrix_down = [ 1, 0, x, 0, 1, y, - 0, 0, - ] + 0, 0] + for resample, epsilon in [(Image.NEAREST, 0), (Image.BILINEAR, 1.5), (Image.BICUBIC, 1)]: transformed = im.transform( From 2221a6467cc20685d6110bc25884642af83e2038 Mon Sep 17 00:00:00 2001 From: homm Date: Mon, 11 Jul 2016 01:47:25 +0300 Subject: [PATCH 14/57] fix test which relied on old bugs --- PIL/Image.py | 2 +- Tests/images/imagedraw_bitmap.png | Bin 2127 -> 2133 bytes Tests/test_image_getdata.py | 14 +++++++------- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 2f9c2666e..61efce06d 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1877,7 +1877,7 @@ class Image(object): xs = float(x1 - x0) / w ys = float(y1 - y0) / h method = AFFINE - data = (xs, 0, x0 + xs/2, 0, ys, y0 + ys/2) + data = (xs, 0, x0, 0, ys, y0) elif method == PERSPECTIVE: data = data[0:8] diff --git a/Tests/images/imagedraw_bitmap.png b/Tests/images/imagedraw_bitmap.png index 05337b693928aa27ede94ddf8aa1b4afc534ff8c..2f9c7a93770ca0785358d452d499f0202804f904 100644 GIT binary patch delta 2120 zcmah~eLT|(AKgAqMJe1-oEj-pZTwB+M5F~hI2$3Ai>^<$W#0;@ zl`ptR79GSxa4W4mMJAg-se)0k4^NwT^F|Z1n6e%q?;;g1IP%5&Nqfe^^ykl?r>3UJ zdgfZ5S9`GfT`%Cpn;DACCSpCu9yRpp)vK|wv6Yo3t`b^=i+udx%D}uvh)Sh45hEBl z3NDE}mQ9(D|cJ7>YNc}LGyoCfNoupT%wiL8h)%+jD~X#A8&d(~-MTHW2qCr85tLyss3p2^lqd z`A;uKic)My1u;H!Tk*jtlf!7>0z?tLxONZX^2f8vpCe6A$pO}DryT7}bIL=gL zG7DcDc<>zjLP8>CyHT8A`^NAAry_M{{8hyZ<9eKnM?z&^Y|f-;_Ls7_$D?rl&1aRu zq;Z7V_S$4xmf-~cq*(b2KQ zcQ@pExaHfBXDkun zDDI@kw9)Xuc5L!z$j}@diI0t~?&=g9CQUS_%=VXh?evtctB<{nRo_1B;jv$w3IIS` zl#Z3lYl+Ln52i?;km1x8eW)A7>?NW9i#qe$*UY+LOACwUOX~=H#0hl}q<`+$pt|7p zoHc%HYf*A7o4xXtbv99(26}B=f8kf9g>k1vLIZH7rE}3uvZ&uhdKO`{KW<*Uw6r9x zJ@H)u%GS6U@sPuD5VHwCJO{1Joa{-3D2D4phvOSh!onEKQ-5AD*%Q#J(Yx9AZZR`b zc4<&-?qQA|_S4 zQO4f9VEdWWmgVK;8&@xmlxWgL>fXBUS3{4nF!kQCjV!*kU)~=jTMmE77>H9YYuZPt zB6aKh`Ibrs=YtvInMA}Ne14Vk2pW)XY;5oyYERYK=*BmOyH`qPQp?yaT&iHA(Cx3v zK`UNVNA6Dt4h-h%f$%{7cUD%BQBer=h3xEX0)apz4m1%dRaseCX_3~JmK2ekP4J~x z!zegROntYtlVohWmz|y6!BYRmaN_Hoq=TjXwB(APvdy6Hgj}w&`Oy{t`ozI6@OO=q znwpw3`}T+rB5IELVU8$l7-R#TkNgTUnQWuWjr_xm+5-aVvPMTom9hqbfCZzl=X`93 zn8Yvb$rc52mx7pXc}lA;Bsv->+i=vAd8c0?*5cgda(Vlw2*&bs54E(kw6719=;_){ zR@Pth+WEtEfh=B=|080A^hs9RJ``Q<0)6seqbJ_#9li|V>|EHe;D1KeqX-3EX0f1? zsyhR5b-~XRi+Xx`W$eS2#Swy}M%9YML*0$##cDj-MGSYFt(NaVS}^av38+0#FA%7P zYWyla0Bv>9v&Giq_jo+s!u!Ep6WZ3}aLeM7l9EwibQH9iONPw=Ta zQ&CE`*`{OopxXhzm?3o&7XF#>W8hM>FAH|9_S^mD^A}$qbQi{nYS1iu+K~$18aIh6 z@a^mLG`7Cp$CmD6`&kJWatjZ+&7*$C2AcMyKuC{5+(pk%HOQQuoxKCUF33e~*{a^& zUVvuh+Pj!zYy6`bwK3eWB0w*HO33AK@@*zM0QJ(GlowVcZ_dm6mnYb=_&8otnJ%rv zt+h8Og)qu+{0`)Oj=gtamy1tFl5-NsJ-1C^tA8h9?gY} zjErbhgX&Dhd`;(X_JujbVNTjKn6GkgPlfaC)goM4N{)R?I z@`kCe-y?mb@HhHNNl7#sZDnc6Nx#d<-|Yv{{&@1C@=vqQShE$^N!0?kSdr-o*6(_H z4#QOR@r$k>f}qMmUAtKDR*HiaF|~4`|Z=t-KmkG0KHnXnu!SFI07wo z(ob}6F2-R>e0!RR%JF-vt?s&; z%Bg~L<8br^h=}iyi%wG=G{5FW?~6Tq)|!?sx delta 2114 zcmZvdeK^y5AIE>B>^RCpv)qw~wxaIQVzD&op2!>zp|Mq(r^!Rhp*&1Zzp!vNCWK75 zM<@?db2V8xl8142YvIhW+LWi5lr!n7Jlwm^b>H`2_xqpEb$$N%en0Q)_4&Ld`eOZC z2oQ!p`HwRxcR#RwqjnCO2rnj&csqdX{SUTNA5C%?&Jd$#DSkbfyW(&^E&J>H%C{PI z1q@}TAR#a<>Xltv^&U@lxjxFUfd#+^JNK6-CAXH34{nr?D?(AzQfbVD<71^LZAWNw zlKru-OZH-{3afDg$2n~c)w%gsVSp~n6U6i9u?ue(O=cgOPtxQcwYyBep#+-OQi6B> zxbVrRgtC5!^iQSp@4UMn2&Z;ccedX2a}}0;8f(^u6vdn7#!F90j09_TV65kyxGLYh zroaPXy5owe5T%RjN9jUBJ1zu4YXckqmi1O}IazzuNN`~E&~2yWrPs?-LNMUL88VrS zFf`CgpQDvfGyvo95p-g35aIFJW^PMMsc6Q%CS8Bw-IxF;t>Yj?E>tO+nwpwQqkXL^ z<@5O$I#S(ob3%<-Qy5kN!3Y<{uG8yXU{ey`i@@L4*4C8LmSE{)>N#ky|ARA)60eap z$6w60oq`A2mbOyA%fXIodG6h)+{U1m3r~K2`|Y#RI9$ZDpl@6~2YMj=po-sqkwu{q z_(BApAW7|zuW8U9j$dNeMUzUR*{mrG`dVb*Bnp?E1cSibeaGVIcahZ&H z*d1bEMUnBjN&z>P4a;*kz)ctGeRj$rR?c-?`@-#@v{+wFNGLSkh3n@b?L_@n@m|dV z85GiW*6gv}MRT;~p`G@Y^+-`6aUlLKIc>d{xl8{=LOW8#tBcmhK{yQ!gOMXVq%?7l z&=Rbhrd28hCy$u-EmttMUWfeQG>fr?>im!~Dd1LHcP@oSMn=+T?L;IWy|(~r011D+ z^zxj1FmBNmSwb0zI}P1e?CG|*AU!=j15O^N7JZif8^OE%H zbSu;ik1%X*bGEm?a>LODi*2&jp}(pE1DbX^8UDS@i{CUkQtz8@=Ir#m$fpvEpA9-* zB}ETrGvCC`TUSYrllz21z14g3^Ya1qJ{8A*`8HVY$T|H!q{7rD+vGOi9*w^3Q|~=u zYGRV9pHa2XLl#unY4j0gNowxx?G0qcji|m8w{IfSPu!qyJgsKb%;;bFq#Y6sM*lkZ zm540ytlg%qT{IA34c!~xVvf-iu`4RpH`c%R^<`N=4v~M;0-Ctovn5B49FbgdILcBg zl_tiPH!J4`ip1Z7`OasU_ci~fIs%IxGCTW#i z|)LXa4H`7(F>QVct=QKDWB zwUUyO((JUm3W%`2F;wn(+fwmd?Blbo^ z3BGqvA*F2Ww(q}muEy^6@$!nWD;pXbYP~4d1cv8VhKGkQIKt@`^kUE2v&}fj;XVP^ z6#`O7`)|6*`1peoJ)e$>Em zIj5@iA83;;X6CqM!1oRc^Ud+_vUmvRqPTK!aIkhH+Fxqt-Map?sHg}LYrG>Hf5&IS zS=>r}Iuv!Lk)Rkwu3C+j9wGyP6yyfO>nrD)TUuPejc185GCl%d>^U<~WEBt)z^-#n z`4sX6P72hgr(kU%n!@-GrKrApcC%u})l*>wfA+k2nrwbNEsDKgtO5YT3kMnMBhyd4 zC$|zM*p{9nlRK}>vDAx*2yRc9*)$vAWmw$ve;}93PlU9MAVjr_XP^-W{>Ve3oFAz- zo*Ew~0YE&OT{n}^UR+!}KOYS|UtM(|b@JQVq@$yOfq^3kHs2)A~F9ANlcLs5N_7)j!r2)kboCk0DYSi=}K=AN}UM%}%F+Z7t>Bg&|L z(%xR?S*w{2jQ5~)Rpu*dM|0zAhOA*su5whFNWaBZnhNIyK4~RWn9r%sV3oM&*uaD@ z0#25qn~6yG8oywvjp$&hsG;E->f{)6y@OCWcP%v))faIJSM6z-vjs5DdP_TQmcIq) z@61V}Dlx3s;z;k2KhWL;La=lb*0E{@D?8FT_S#O@6<6Pplw>Mg#+pO4`Zx2^o@lJ> q0Sl_C|ChkW{+q=A3FQA*vc;P~PNx6SN$(m Date: Mon, 11 Jul 2016 05:25:47 +0300 Subject: [PATCH 15/57] temporary disable PCX P mode test due to errors in codec --- Tests/test_file_pcx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index f6342bed9..0c44ddd17 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -32,6 +32,8 @@ class TestFilePcx(PillowTestCase): for mode in ('1', 'L', 'P', 'RGB'): # larger, odd sized images are better here to ensure that # we handle interrupted scan lines properly. + if mode == 'P': + continue self._roundtrip(hopper(mode).resize((511, 511))) def test_pil184(self): From 47ebf695acdd600f0026b181fd8fdfaced6ea41b Mon Sep 17 00:00:00 2001 From: Marco De Donno Date: Mon, 11 Jul 2016 12:00:44 +0200 Subject: [PATCH 16/57] Patch : Change from self to image --- PIL/ImageOps.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py index 2b3496e56..8bf6e7a44 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -178,7 +178,7 @@ def crop(image, border=0): ) -def scale(self, factor, resample=Image.NEAREST): +def scale(image, factor, resample=Image.NEAREST): """ Returns a rescaled image by a specific factor given in parameter. A factor greater than 1 expands the image, between 0 and 1 contracts the @@ -190,13 +190,13 @@ def scale(self, factor, resample=Image.NEAREST): :returns: An :py:class:`~PIL.Image.Image` object. """ if factor == 1: - return self._new(self.im) + return Image.Image._new(image.im) elif factor <= 0: raise ValueError("the factor must be greater than 0") else: - size = (int(round(factor * self.width)), - int(round(factor * self.height))) - return self.resize(size, resample) + size = (int(round(factor * image.width)), + int(round(factor * image.height))) + return image.resize(size, resample) def deform(image, deformer, resample=Image.BILINEAR): From 8355a34c1469e6b81eb7e29dde1376b96cfbd577 Mon Sep 17 00:00:00 2001 From: Marco De Donno Date: Mon, 11 Jul 2016 12:18:07 +0200 Subject: [PATCH 17/57] patch : image copy --- PIL/ImageOps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py index 8bf6e7a44..8580ec5fb 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -190,7 +190,7 @@ def scale(image, factor, resample=Image.NEAREST): :returns: An :py:class:`~PIL.Image.Image` object. """ if factor == 1: - return Image.Image._new(image.im) + return image.copy() elif factor <= 0: raise ValueError("the factor must be greater than 0") else: From f01c2a37dab0fb6022ee64ce9040dcae203cc6ac Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Fri, 15 Jul 2016 10:51:00 +0300 Subject: [PATCH 18/57] installation.rst: Windows console prompts are > --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 41122901c..0c6a3fabf 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -51,11 +51,11 @@ supported Pythons in both 32 and 64-bit versions in wheel, egg, and executable installers. These binaries have all of the optional libraries included:: - $ pip install Pillow + > pip install Pillow or:: - $ easy_install Pillow + > easy_install Pillow OS X Installation From 7bfb2527bb0b832f830f87c37bd165028118e442 Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Fri, 15 Jul 2016 11:00:55 +0300 Subject: [PATCH 19/57] Expose Pillow package version as PIL.__version__ --- PIL/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PIL/__init__.py b/PIL/__init__.py index d8cc20e53..561a13a67 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -14,6 +14,8 @@ VERSION = '1.1.7' # PIL version PILLOW_VERSION = '3.4.0.dev0' # Pillow +__version__ = PILLOW_VERSION + _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', 'CurImagePlugin', From f3565f33003b6a1f5b64320cba7d5f29ed998a85 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sun, 17 Jul 2016 17:58:53 +0000 Subject: [PATCH 20/57] BUG: fix C90 compilation error for new Tk method New Tk code that picks up libraries at run-time causes complaints and errors; fix. See: https://github.com/python-pillow/Pillow/issues/2017 --- Tk/tkImaging.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tk/tkImaging.c b/Tk/tkImaging.c index 7f030bf89..5999a140a 100644 --- a/Tk/tkImaging.c +++ b/Tk/tkImaging.c @@ -371,7 +371,8 @@ int load_tkinter_funcs(void) #if PY_VERSION_HEX >= 0x03000000 char *fname2char(PyObject *fname) { - PyObject *bytes = PyUnicode_EncodeFSDefault(fname); + PyObject* bytes; + bytes = PyUnicode_EncodeFSDefault(fname); if (bytes == NULL) { return NULL; } @@ -391,9 +392,10 @@ void *_dfunc(void *lib_handle, const char *func_name) * Returns function pointer or NULL if not present. */ + void* func; /* Reset errors. */ dlerror(); - void *func = dlsym(lib_handle, func_name); + func = dlsym(lib_handle, func_name); if (func == NULL) { const char *error = dlerror(); PyErr_SetString(PyExc_RuntimeError, error); From 07209e9b9848d9dc87fb54087d80e46469ea5e1e Mon Sep 17 00:00:00 2001 From: homm Date: Mon, 18 Jul 2016 02:39:29 +0300 Subject: [PATCH 21/57] release note about new filters --- docs/releasenotes/3.4.0.rst | 15 +++++++++++++++ docs/releasenotes/index.rst | 1 + 2 files changed, 16 insertions(+) create mode 100644 docs/releasenotes/3.4.0.rst diff --git a/docs/releasenotes/3.4.0.rst b/docs/releasenotes/3.4.0.rst new file mode 100644 index 000000000..eb158c713 --- /dev/null +++ b/docs/releasenotes/3.4.0.rst @@ -0,0 +1,15 @@ + +3.4.0 +----- + +New resizing filters +==================== + +Two new filters available for ``Image.resize()`` and ``Image.thumbnail()`` +functions: ``BOX`` and ``HAMMING``. ``BOX`` is the high-performance filter with +two times shorter window than ``BILINEAR``. It can be used for image reduction +3 and more times and produces a more sharp result than ``BILINEAR``. + +``HAMMING`` filter has the same performance as ``BILINEAR`` filter while +providing the image downscaling quality comparable to ``BICUBIC``. +Both new filters don't show good quality for the image upscaling. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index f38b9fbfa..2ffe37980 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -6,6 +6,7 @@ Release Notes .. toctree:: :maxdepth: 2 + 3.4.0 3.3.0 3.2.0 3.1.2 From 53ffd26e624d015a2df2dff66a574154a7b77193 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Jul 2016 20:19:34 +1000 Subject: [PATCH 22/57] Updated freetype to 2.6.5 --- winbuild/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index 070986e9a..275063af9 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -28,9 +28,9 @@ libs = { 'dir': 'tiff-4.0.6', }, 'freetype': { - 'url': 'http://download.savannah.gnu.org/releases/freetype/freetype-2.6.3.tar.gz', - 'hash': 'md5:8b0c80b64042b7c39b9fa9debe6305c3', - 'dir': 'freetype-2.6.3', + 'url': 'http://download.savannah.gnu.org/releases/freetype/freetype-2.6.5.tar.gz', + 'hash': 'md5:31b2276515d9ee1c7f37d9c9f4f3145a', + 'dir': 'freetype-2.6.5', }, 'lcms': { 'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip', From f8912a73e07ba5f7821ca1d61284fc28f4c879fa Mon Sep 17 00:00:00 2001 From: homm Date: Wed, 27 Jul 2016 14:41:04 +0300 Subject: [PATCH 23/57] code style --- libImaging/PcxEncode.c | 47 +++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/libImaging/PcxEncode.c b/libImaging/PcxEncode.c index c73f49a24..b17a09184 100644 --- a/libImaging/PcxEncode.c +++ b/libImaging/PcxEncode.c @@ -75,7 +75,7 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) (UINT8*) im->image[state->y + state->yoff] + state->xoff * im->pixelsize, state->xsize); - state->y++; + state->y += 1; state->count = 1; state->LAST = state->buffer[0]; @@ -103,22 +103,23 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) if (state->count == 63) { /* this run is full; flush it */ - if (bytes < 2) + if (bytes < 2) { return ptr - buf; - *ptr++ = 0xff; - *ptr++ = state->LAST; + } + ptr[0] = 0xff; + ptr[1] = state->LAST; + ptr += 2; bytes -= 2; state->count = 0; - } this = state->buffer[state->x]; if (this == state->LAST) { /* extend the current run */ - state->x++; - state->count++; + state->x += 1; + state->count += 1; } else { /* start a new run */ @@ -126,15 +127,17 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) if (bytes < 1) { return ptr - buf; } - *ptr++ = state->LAST; - bytes--; + ptr[0] = state->LAST; + ptr += 1; + bytes -= 1; } else { if (state->count > 0) { if (bytes < 2) { return ptr - buf; } - *ptr++ = 0xc0 | state->count; - *ptr++ = state->LAST; + ptr[0] = 0xc0 | state->count; + ptr[1] = state->LAST; + ptr += 2; bytes -= 2; } } @@ -142,8 +145,7 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) state->LAST = this; state->count = 1; - state->x++; - + state->x += 1; } } @@ -152,15 +154,17 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) if (bytes < 1 + padding) { return ptr - buf; } - *ptr++ = state->LAST; - bytes--; + ptr[0] = state->LAST; + ptr += 1; + bytes -= 1; } else { if (state->count > 0) { if (bytes < 2 + padding) { return ptr - buf; } - *ptr++ = 0xc0 | state->count; - *ptr++ = state->LAST; + ptr[0] = 0xc0 | state->count; + ptr[1] = state->LAST; + ptr += 2; bytes -= 2; } } @@ -168,15 +172,16 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) return ptr - buf; } /* add the padding */ - for (i=0;ix < planes * bytes_per_line) { state->count = 1; state->LAST = state->buffer[state->x]; - state->x++; + state->x += 1; } } /* read next line */ From 978c37d699f24d5bdcadf7617e8313433ca0dfdb Mon Sep 17 00:00:00 2001 From: homm Date: Thu, 28 Jul 2016 05:29:24 +0300 Subject: [PATCH 24/57] add tests for different PCX encoding cases --- Tests/test_file_pcx.py | 80 +++++++++++++++++++++++++++++++++++++++++- libImaging/PcxEncode.c | 3 -- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index db2a526a5..6b15b2fb6 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase, hopper -from PIL import Image, PcxImagePlugin +from PIL import Image, ImageFile, PcxImagePlugin class TestFilePcx(PillowTestCase): @@ -46,6 +46,84 @@ class TestFilePcx(PillowTestCase): # Make sure all pixels are either 0 or 255. self.assertEqual(im.histogram()[0] + im.histogram()[255], 447*144) + def test_1px_width(self): + im = Image.new('L', (1, 256)) + px = im.load() + for y in range(256): + px[0, y] = y + self._roundtrip(im) + + def test_large_count(self): + im = Image.new('L', (256, 1)) + px = im.load() + for x in range(256): + px[x, 0] = x // 67 * 67 + self._roundtrip(im) + + def _test_overflow(self, im): + _last = ImageFile.MAXBLOCK + ImageFile.MAXBLOCK = 1024 + try: + self._roundtrip(im) + finally: + ImageFile.MAXBLOCK = _last + + def test_break_in_count_overflow(self): + im = Image.new('L', (256, 5)) + px = im.load() + for y in range(4): + for x in range(256): + px[x, y] = x % 128 + self._test_overflow(im) + + def test_break_one_in_loop(self): + im = Image.new('L', (256, 5)) + px = im.load() + for y in range(5): + for x in range(256): + px[x, y] = x % 128 + self._test_overflow(im) + + def test_break_many_in_loop(self): + im = Image.new('L', (256, 5)) + px = im.load() + for y in range(4): + for x in range(256): + px[x, y] = x % 128 + for x in range(8): + px[x, 4] = 16 + self._test_overflow(im) + + def test_break_one_at_end(self): + im = Image.new('L', (256, 5)) + px = im.load() + for y in range(5): + for x in range(256): + px[x, y] = x % 128 + px[0, 3] = 128 + 64 + self._test_overflow(im) + + def test_break_many_at_end(self): + im = Image.new('L', (256, 5)) + px = im.load() + for y in range(5): + for x in range(256): + px[x, y] = x % 128 + for x in range(4): + px[x * 2, 3] = 128 + 64 + px[x + 256 - 4, 3] = 0 + self._test_overflow(im) + + def test_break_padding(self): + im = Image.new('L', (257, 5)) + px = im.load() + for y in range(5): + for x in range(257): + px[x, y] = x % 128 + for x in range(5): + px[x, 3] = 0 + self._test_overflow(im) + if __name__ == '__main__': unittest.main() diff --git a/libImaging/PcxEncode.c b/libImaging/PcxEncode.c index b17a09184..1dd5836e2 100644 --- a/libImaging/PcxEncode.c +++ b/libImaging/PcxEncode.c @@ -168,9 +168,6 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) bytes -= 2; } } - if (bytes < padding) { - return ptr - buf; - } /* add the padding */ for (i = 0; i < padding; i++) { ptr[0] = 0; From 87b20389d85344f67db875b18886e6586048edbd Mon Sep 17 00:00:00 2001 From: homm Date: Thu, 28 Jul 2016 05:30:27 +0300 Subject: [PATCH 25/57] fix any errors --- libImaging/PcxEncode.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libImaging/PcxEncode.c b/libImaging/PcxEncode.c index 1dd5836e2..163b09b13 100644 --- a/libImaging/PcxEncode.c +++ b/libImaging/PcxEncode.c @@ -91,7 +91,7 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) /* when we arrive here, "count" contains the number of bytes having the value of "LAST" that we've already seen */ - while (state->x < planes * bytes_per_line) { + do { /* If we're encoding an odd width file, and we've got more than one plane, we need to pad each color row with padding bytes at the end. Since @@ -180,7 +180,8 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) state->LAST = state->buffer[state->x]; state->x += 1; } - } + } while (state->x < planes * bytes_per_line); + /* read next line */ state->state = FETCH; break; From 467f6cfcbb649a555455753c6719b8197ab29683 Mon Sep 17 00:00:00 2001 From: homm Date: Fri, 29 Jul 2016 12:47:36 +0300 Subject: [PATCH 26/57] rename test --- Tests/test_file_pcx.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 6b15b2fb6..7621c1cc6 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -60,9 +60,9 @@ class TestFilePcx(PillowTestCase): px[x, 0] = x // 67 * 67 self._roundtrip(im) - def _test_overflow(self, im): + def _test_buffer_overflow(self, im, size=1024): _last = ImageFile.MAXBLOCK - ImageFile.MAXBLOCK = 1024 + ImageFile.MAXBLOCK = size try: self._roundtrip(im) finally: @@ -74,7 +74,7 @@ class TestFilePcx(PillowTestCase): for y in range(4): for x in range(256): px[x, y] = x % 128 - self._test_overflow(im) + self._test_buffer_overflow(im) def test_break_one_in_loop(self): im = Image.new('L', (256, 5)) @@ -82,7 +82,7 @@ class TestFilePcx(PillowTestCase): for y in range(5): for x in range(256): px[x, y] = x % 128 - self._test_overflow(im) + self._test_buffer_overflow(im) def test_break_many_in_loop(self): im = Image.new('L', (256, 5)) @@ -92,7 +92,7 @@ class TestFilePcx(PillowTestCase): px[x, y] = x % 128 for x in range(8): px[x, 4] = 16 - self._test_overflow(im) + self._test_buffer_overflow(im) def test_break_one_at_end(self): im = Image.new('L', (256, 5)) @@ -101,7 +101,7 @@ class TestFilePcx(PillowTestCase): for x in range(256): px[x, y] = x % 128 px[0, 3] = 128 + 64 - self._test_overflow(im) + self._test_buffer_overflow(im) def test_break_many_at_end(self): im = Image.new('L', (256, 5)) @@ -112,7 +112,7 @@ class TestFilePcx(PillowTestCase): for x in range(4): px[x * 2, 3] = 128 + 64 px[x + 256 - 4, 3] = 0 - self._test_overflow(im) + self._test_buffer_overflow(im) def test_break_padding(self): im = Image.new('L', (257, 5)) @@ -122,7 +122,7 @@ class TestFilePcx(PillowTestCase): px[x, y] = x % 128 for x in range(5): px[x, 3] = 0 - self._test_overflow(im) + self._test_buffer_overflow(im) if __name__ == '__main__': From 2c4a1209f456b28220787c1aa62efdabc9783d0c Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 4 Aug 2016 09:40:12 +0300 Subject: [PATCH 27/57] flake8 --- Tests/check_j2k_overflow.py | 3 ++- Tests/test_file_bmp.py | 1 - Tests/test_file_libtiff.py | 15 ++++++++------- Tests/test_file_png.py | 3 +-- Tests/test_image_access.py | 3 +++ Tests/test_imagepath.py | 2 +- Tests/test_imagesequence.py | 2 +- Tests/test_imagetk.py | 3 ++- Tests/test_imagewin_pointers.py | 8 ++++++-- Tests/test_numpy.py | 4 ++++ Tests/test_tiff_ifdrational.py | 2 +- 11 files changed, 29 insertions(+), 17 deletions(-) diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index de671a53f..474b49948 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -1,6 +1,7 @@ from PIL import Image from helper import unittest, PillowTestCase + class TestJ2kEncodeOverflow(PillowTestCase): def test_j2k_overflow(self): @@ -12,7 +13,7 @@ class TestJ2kEncodeOverflow(PillowTestCase): except IOError as err: self.assertTrue(True, "IOError is expected") except Exception as err: - self.assertTrue(False, "Expected IOError, got %s" %type(err)) + self.assertTrue(False, "Expected IOError, got %s" % type(err)) if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 5d6b30e79..36bc84d84 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -77,6 +77,5 @@ class TestFileBmp(PillowTestCase): self.assert_image_equal(im, target) - if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index ef86a1136..378bd6377 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -170,7 +170,8 @@ class TestFileLibTiff(LibTiffTestCase): 'RowsPerStrip', 'StripOffsets'] for field in requested_fields: - self.assertTrue(field in reloaded, "%s not in metadata" % field) + self.assertTrue(field in reloaded, + "%s not in metadata" % field) def test_additional_metadata(self): # these should not crash. Seriously dummy data, most of it doesn't make @@ -183,7 +184,8 @@ class TestFileLibTiff(LibTiffTestCase): in TiffTags.LIBTIFF_CORE] if info.type is not None) - # Exclude ones that have special meaning that we're already testing them + # Exclude ones that have special meaning + # that we're already testing them im = Image.open('Tests/images/hopper_g4.tif') for tag in im.tag_v2.keys(): try: @@ -422,8 +424,8 @@ class TestFileLibTiff(LibTiffTestCase): def test_gray_semibyte_per_pixel(self): test_files = ( ( - 24.8,#epsilon - (#group + 24.8, # epsilon + ( # group "Tests/images/tiff_gray_2_4_bpp/hopper2.tif", "Tests/images/tiff_gray_2_4_bpp/hopper2I.tif", "Tests/images/tiff_gray_2_4_bpp/hopper2R.tif", @@ -431,8 +433,8 @@ class TestFileLibTiff(LibTiffTestCase): ) ), ( - 7.3,#epsilon - (#group + 7.3, # epsilon + ( # group "Tests/images/tiff_gray_2_4_bpp/hopper4.tif", "Tests/images/tiff_gray_2_4_bpp/hopper4I.tif", "Tests/images/tiff_gray_2_4_bpp/hopper4R.tif", @@ -504,6 +506,5 @@ class TestFileLibTiff(LibTiffTestCase): im.save(outfile) - if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 4bdfb603b..418aaf41e 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -313,7 +313,7 @@ class TestFilePng(PillowTestCase): # -14: malformed chunk for offset in (-10, -13, -14): - with open(TEST_PNG_FILE,'rb') as f: + with open(TEST_PNG_FILE, 'rb') as f: test_file = f.read()[:offset] im = Image.open(BytesIO(test_file)) @@ -347,7 +347,6 @@ class TestFilePng(PillowTestCase): finally: ImageFile.LOAD_TRUNCATED_IMAGES = False - def test_roundtrip_dpi(self): # Check dpi roundtripping diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 40f648f27..be7457bb7 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -105,6 +105,7 @@ class TestCffiPutPixel(TestImagePutPixel): def setUp(self): try: import cffi + assert cffi # silence warning except ImportError: self.skipTest("No cffi") @@ -115,6 +116,7 @@ class TestCffiGetPixel(TestImageGetPixel): def setUp(self): try: import cffi + assert cffi # silence warning except ImportError: self.skipTest("No cffi") @@ -125,6 +127,7 @@ class TestCffi(AccessTest): def setUp(self): try: import cffi + assert cffi # silence warning except ImportError: self.skipTest("No cffi") diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 1df9715e0..d5ec5dfaa 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -5,6 +5,7 @@ from PIL import ImagePath, Image import array import struct + class TestImagePath(PillowTestCase): def test_path(self): @@ -61,7 +62,6 @@ class TestImagePath(PillowTestCase): p = ImagePath.Path(arr.tostring()) self.assertEqual(list(p), [(0.0, 1.0)]) - def test_overflow_segfault(self): try: # post patch, this fails with a memory error diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index f79c5fe87..8c65e6358 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -56,7 +56,7 @@ class TestImageSequence(PillowTestCase): im = Image.open('Tests/images/multipage.tiff') firstFrame = None for frame in ImageSequence.Iterator(im): - if firstFrame == None: + if firstFrame is None: firstFrame = frame.copy() pass for frame in ImageSequence.Iterator(im): diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index d29953453..f56333a59 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -8,6 +8,7 @@ except (OSError, ImportError) as v: # Skipped via setUp() pass + class TestImageTk(PillowTestCase): def setUp(self): @@ -24,7 +25,7 @@ class TestImageTk(PillowTestCase): im2 = Image.open(TEST_PNG) with open(TEST_PNG, 'rb') as fp: data = fp.read() - kw = {"file":TEST_JPG, "data":data} + kw = {"file": TEST_JPG, "data": data} # Test "file" im = ImageTk._get_image_from_kw(kw) diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index 692868bdf..f37c67eac 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -57,7 +57,10 @@ if sys.platform.startswith('win32'): DeleteObject.argtypes = [ctypes.wintypes.HGDIOBJ] CreateDIBSection = ctypes.windll.gdi32.CreateDIBSection - CreateDIBSection.argtypes = [ctypes.wintypes.HDC, ctypes.c_void_p, ctypes.c_uint, ctypes.POINTER(ctypes.c_void_p), ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD] + CreateDIBSection.argtypes = [ctypes.wintypes.HDC, ctypes.c_void_p, + ctypes.c_uint, + ctypes.POINTER(ctypes.c_void_p), + ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD] CreateDIBSection.restype = ctypes.wintypes.HBITMAP def serialize_dib(bi, pixels): @@ -99,7 +102,8 @@ if sys.platform.startswith('win32'): hdc = CreateCompatibleDC(None) # print('hdc:',hex(hdc)) pixels = ctypes.c_void_p() - dib = CreateDIBSection(hdc, ctypes.byref(hdr), DIB_RGB_COLORS, ctypes.byref(pixels), None, 0) + dib = CreateDIBSection(hdc, ctypes.byref(hdr), DIB_RGB_COLORS, + ctypes.byref(pixels), None, 0) SelectObject(hdc, dib) imdib.expose(hdc) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 804ff832f..40b7c64e3 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -6,6 +6,8 @@ from PIL import Image try: import site import numpy + assert site # silence warning + assert numpy # silence warning except ImportError: # Skip via setUp() pass @@ -18,6 +20,8 @@ class TestNumpy(PillowTestCase): try: import site import numpy + assert site # silence warning + assert numpy # silence warning except ImportError: self.skipTest("ImportError") diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index 3a38f2237..dd3ad1b3d 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -48,7 +48,7 @@ class Test_IFDRational(PillowTestCase): methods = (True, False) if 'libtiff_encoder' not in dir(Image.core): methods = (False) - + for libtiff in methods: TiffImagePlugin.WRITE_LIBTIFF = libtiff From ccba5572dcfc39385497112779ed1856f5f2d86c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 6 Aug 2016 22:30:02 +0100 Subject: [PATCH 28/57] Update CHANGES.rst [ci skip] --- CHANGES.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4c28ef135..4a03eccb1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,27 @@ Changelog (Pillow) 3.4.0 (unreleased) ------------------ +- Fix image loading when rotating by 0 deg #2052 + [homm] + +- Add ImageOps.scale to expand or contract a PIL image by a factor #2011 + [vlmath] + +- Flake8 fixes #2050 + [hugovk] + +- Updated freetype to 2.6.5 on Appveyor builds #2035 + [radarhere] + +- PCX encoder fixes #2023, pr #2041 + [homm] + +- Docs: Windows console prompts are > #2031 + [techtonik] + +- Expose Pillow package version as PIL.__version__ #2027 + [techtonik] + - Add Box and Hamming filters for resampling #1959 [homm] From a08514d7ba6ee9a9ea5f24a6046ab4144e4a2377 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 6 Aug 2016 22:57:02 +0100 Subject: [PATCH 29/57] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4a03eccb1..fee7ab05a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.4.0 (unreleased) ------------------ +- Fix C90 compilation error for Tcl / Tk rewrite #2033 + [matthew-brett] + - Fix image loading when rotating by 0 deg #2052 [homm] From 2ecbcce4156c21b8a40a0c85c89c1b9f7cd33610 Mon Sep 17 00:00:00 2001 From: Arjen Nienhuis Date: Sat, 6 Aug 2016 10:54:58 +0200 Subject: [PATCH 30/57] add unpacking from BRGa --- Tests/test_lib_pack.py | 2 ++ libImaging/Unpack.c | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 461d0bf8e..5c2af8d04 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -122,7 +122,9 @@ class TestLibPack(PillowTestCase): self.assertEqual(unpack("RGB", "XBGR", 4), (4, 3, 2)) self.assertEqual(unpack("RGBA", "RGBA", 4), (1, 2, 3, 4)) + self.assertEqual(unpack("RGBA", "RGBa", 4), (63, 127, 191, 4)) self.assertEqual(unpack("RGBA", "BGRA", 4), (3, 2, 1, 4)) + self.assertEqual(unpack("RGBA", "BGRa", 4), (191, 127, 63, 4)) self.assertEqual(unpack("RGBA", "ARGB", 4), (2, 3, 4, 1)) self.assertEqual(unpack("RGBA", "ABGR", 4), (4, 3, 2, 1)) self.assertEqual(unpack("RGBA", "RGBA;15", 2), (8, 131, 0, 0)) diff --git a/libImaging/Unpack.c b/libImaging/Unpack.c index 702bc9f1f..8112afd8d 100644 --- a/libImaging/Unpack.c +++ b/libImaging/Unpack.c @@ -747,6 +747,30 @@ unpackRGBa(UINT8* out, const UINT8* in, int pixels) } } +static void +unpackBGRa(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* premultiplied BGRA */ + for (i = 0; i < pixels; i++) { + int a = in[3]; + if (!a) + out[R] = out[G] = out[B] = out[A] = 0; + else if (a == 255) { + out[R] = in[2]; + out[G] = in[1]; + out[B] = in[0]; + out[A] = a; + } else { + out[R] = CLIP(in[2] * 255 / a); + out[G] = CLIP(in[1] * 255 / a); + out[B] = CLIP(in[0] * 255 / a); + out[A] = a; + } + out += 4; in += 4; + } +} + static void unpackRGBAI(UINT8* out, const UINT8* in, int pixels) { @@ -1206,6 +1230,7 @@ static struct { {"RGBA", "LA;16B", 32, unpackRGBALA16B}, {"RGBA", "RGBA", 32, copy4}, {"RGBA", "RGBa", 32, unpackRGBa}, + {"RGBA", "BGRa", 32, unpackBGRa}, {"RGBA", "RGBA;I", 32, unpackRGBAI}, {"RGBA", "RGBA;L", 32, unpackRGBAL}, {"RGBA", "RGBA;15", 16, ImagingUnpackRGBA15}, From 1b54e0173654829f8658d59e606f6a3704a2c500 Mon Sep 17 00:00:00 2001 From: homm Date: Sun, 7 Aug 2016 13:48:01 +0300 Subject: [PATCH 31/57] Revert "temporary disable PCX P mode test due to errors in codec" This reverts commit afff487084f97fb8bc2f724ecf03e927287a80ab. --- Tests/test_file_pcx.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 63d1f5dd1..7621c1cc6 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -32,8 +32,6 @@ class TestFilePcx(PillowTestCase): for mode in ('1', 'L', 'P', 'RGB'): # larger, odd sized images are better here to ensure that # we handle interrupted scan lines properly. - if mode == 'P': - continue self._roundtrip(hopper(mode).resize((511, 511))) def test_pil184(self): From 608a7776b2ca1b910e27f627c8c0447efb3a4f03 Mon Sep 17 00:00:00 2001 From: Paul Korir Date: Sun, 7 Aug 2016 12:31:09 +0100 Subject: [PATCH 32/57] Table of contents Added a table of contents of the available support file formats --- docs/handbook/image-file-formats.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index b680072ac..926e33244 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -16,6 +16,8 @@ explicitly. Fully supported formats ----------------------- +.. contents:: Formats + BMP ^^^ From 5a8ecc6d603b4b7239e4ed39b9c278f08578810b Mon Sep 17 00:00:00 2001 From: Paul Korir Date: Sun, 7 Aug 2016 12:31:56 +0100 Subject: [PATCH 33/57] Update image-file-formats.rst --- docs/handbook/image-file-formats.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 926e33244..aedcdacd7 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -16,7 +16,7 @@ explicitly. Fully supported formats ----------------------- -.. contents:: Formats +.. contents:: BMP ^^^ From 124e42ade36c06931057789f4b2cf646c9cc65b3 Mon Sep 17 00:00:00 2001 From: Arjen Nienhuis Date: Sun, 7 Aug 2016 02:17:16 +0200 Subject: [PATCH 34/57] Improved error message for missing packer It's not that the raw mode does not exist. There is just no direct conversion. --- encode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encode.c b/encode.c index b08a9aa57..9a0dd2446 100644 --- a/encode.c +++ b/encode.c @@ -365,7 +365,7 @@ get_packer(ImagingEncoderObject* encoder, const char* mode, pack = ImagingFindPacker(mode, rawmode, &bits); if (!pack) { Py_DECREF(encoder); - PyErr_SetString(PyExc_SystemError, "unknown raw mode"); + PyErr_Format(PyExc_ValueError, "No packer found from %s to %s", mode, rawmode); return -1; } From 916ea9405278b2fb979da184c9ea65f8a96548f7 Mon Sep 17 00:00:00 2001 From: Arjen Nienhuis Date: Sun, 7 Aug 2016 15:34:45 +0200 Subject: [PATCH 35/57] Add packing from RGBA to BGRa --- Tests/test_mode_bgra.py | 23 +++++++++++++++++++++++ libImaging/Pack.c | 20 ++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 Tests/test_mode_bgra.py diff --git a/Tests/test_mode_bgra.py b/Tests/test_mode_bgra.py new file mode 100644 index 000000000..331be4d93 --- /dev/null +++ b/Tests/test_mode_bgra.py @@ -0,0 +1,23 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + + +class TestBGRa(PillowTestCase): + + def test_bgra(self): + RGBA_RED_50 = b'\xff\x00\x00\x80' # 50% red RGBA + BGRa_RED_50 = b'\x00\x00\x80\x80' # 50% red BGRa + RGBa_RED_50 = b'\x80\x00\x00\x80' # 50% red RGBa + + im = Image.frombuffer("RGBA", (1, 1), BGRa_RED_50, "raw", "BGRa", 4, 1) + self.assertEqual(im.tobytes(), RGBA_RED_50) + self.assertEqual(im.tobytes('raw', 'BGRa'), BGRa_RED_50) + + im = Image.frombuffer("RGBa", (1, 1), BGRa_RED_50, "raw", "BGRa", 4, 1) + self.assertEqual(im.tobytes(), RGBa_RED_50) + self.assertEqual(im.tobytes('raw', 'BGRa'), BGRa_RED_50) + + +if __name__ == '__main__': + unittest.main() diff --git a/libImaging/Pack.c b/libImaging/Pack.c index d83cb8284..acbfc8143 100644 --- a/libImaging/Pack.c +++ b/libImaging/Pack.c @@ -72,6 +72,10 @@ #define C64L C64N #endif +/* like (a * b + 127) / 255), but much faster on most platforms */ +#define MULDIV255(a, b, tmp)\ + (tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8)) + static void pack1(UINT8* out, const UINT8* in, int pixels) { @@ -315,6 +319,21 @@ ImagingPackABGR(UINT8* out, const UINT8* in, int pixels) } } +void +ImagingPackBGRa(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* BGRa, reversed bytes with premultiplied alplha */ + for (i = 0; i < pixels; i++) { + int alpha = out[3] = in[A]; + int tmp; + out[0] = MULDIV255(in[B], alpha, tmp); + out[1] = MULDIV255(in[G], alpha, tmp); + out[2] = MULDIV255(in[R], alpha, tmp); + out += 4; in += 4; + } +} + static void packRGBL(UINT8* out, const UINT8* in, int pixels) { @@ -525,6 +544,7 @@ static struct { {"RGBA", "BGR", 24, ImagingPackBGR}, {"RGBA", "BGRA", 32, ImagingPackBGRA}, {"RGBA", "ABGR", 32, ImagingPackABGR}, + {"RGBA", "BGRa", 32, ImagingPackBGRa}, {"RGBA", "R", 8, band0}, {"RGBA", "G", 8, band1}, {"RGBA", "B", 8, band2}, From 28ede3a3272126907626d6c4d0a8ce00ccea39d5 Mon Sep 17 00:00:00 2001 From: Arjen Nienhuis Date: Mon, 8 Aug 2016 00:07:08 +0200 Subject: [PATCH 36/57] Merged BGRa test into Tests/test_lib_pack.py --- Tests/test_lib_pack.py | 8 +++++--- Tests/test_mode_bgra.py | 23 ----------------------- 2 files changed, 5 insertions(+), 26 deletions(-) delete mode 100644 Tests/test_mode_bgra.py diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 5c2af8d04..83872c5d1 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -10,11 +10,11 @@ class TestLibPack(PillowTestCase): def test_pack(self): - def pack(mode, rawmode): + def pack(mode, rawmode, in_data=(1, 2, 3, 4)): if len(mode) == 1: - im = Image.new(mode, (1, 1), 1) + im = Image.new(mode, (1, 1), in_data[0]) else: - im = Image.new(mode, (1, 1), (1, 2, 3, 4)[:len(mode)]) + im = Image.new(mode, (1, 1), in_data[:len(mode)]) if py3: return list(im.tobytes("raw", rawmode)) @@ -47,6 +47,8 @@ class TestLibPack(PillowTestCase): self.assertEqual(pack("RGBX", "RGBX"), [1, 2, 3, 4]) # 4->255? self.assertEqual(pack("RGBA", "RGBA"), [1, 2, 3, 4]) + self.assertEqual(pack("RGBA", "BGRa"), [0, 0, 0, 4]) + self.assertEqual(pack("RGBA", "BGRa", in_data=(20, 30, 40, 50)), [8, 6, 4, 50]) self.assertEqual(pack("RGBa", "RGBa"), [1, 2, 3, 4]) self.assertEqual(pack("RGBa", "BGRa"), [3, 2, 1, 4]) diff --git a/Tests/test_mode_bgra.py b/Tests/test_mode_bgra.py deleted file mode 100644 index 331be4d93..000000000 --- a/Tests/test_mode_bgra.py +++ /dev/null @@ -1,23 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - - -class TestBGRa(PillowTestCase): - - def test_bgra(self): - RGBA_RED_50 = b'\xff\x00\x00\x80' # 50% red RGBA - BGRa_RED_50 = b'\x00\x00\x80\x80' # 50% red BGRa - RGBa_RED_50 = b'\x80\x00\x00\x80' # 50% red RGBa - - im = Image.frombuffer("RGBA", (1, 1), BGRa_RED_50, "raw", "BGRa", 4, 1) - self.assertEqual(im.tobytes(), RGBA_RED_50) - self.assertEqual(im.tobytes('raw', 'BGRa'), BGRa_RED_50) - - im = Image.frombuffer("RGBa", (1, 1), BGRa_RED_50, "raw", "BGRa", 4, 1) - self.assertEqual(im.tobytes(), RGBa_RED_50) - self.assertEqual(im.tobytes('raw', 'BGRa'), BGRa_RED_50) - - -if __name__ == '__main__': - unittest.main() From 299713313ae80e62f62d1b83fa35bb9a9c4b0b84 Mon Sep 17 00:00:00 2001 From: Arjen Nienhuis Date: Mon, 8 Aug 2016 00:09:55 +0200 Subject: [PATCH 37/57] Fixed whitespace issue --- libImaging/Pack.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libImaging/Pack.c b/libImaging/Pack.c index acbfc8143..a19252e88 100644 --- a/libImaging/Pack.c +++ b/libImaging/Pack.c @@ -325,12 +325,12 @@ ImagingPackBGRa(UINT8* out, const UINT8* in, int pixels) int i; /* BGRa, reversed bytes with premultiplied alplha */ for (i = 0; i < pixels; i++) { - int alpha = out[3] = in[A]; - int tmp; - out[0] = MULDIV255(in[B], alpha, tmp); - out[1] = MULDIV255(in[G], alpha, tmp); - out[2] = MULDIV255(in[R], alpha, tmp); - out += 4; in += 4; + int alpha = out[3] = in[A]; + int tmp; + out[0] = MULDIV255(in[B], alpha, tmp); + out[1] = MULDIV255(in[G], alpha, tmp); + out[2] = MULDIV255(in[R], alpha, tmp); + out += 4; in += 4; } } From cf27e03dcd445998ae205a5c59747326a536e593 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 8 Aug 2016 07:36:34 -0700 Subject: [PATCH 38/57] Added return for J2k (and fpx) Load to return a pixel access object --- PIL/FpxImagePlugin.py | 2 +- PIL/Jpeg2KImagePlugin.py | 2 +- Tests/test_file_jpeg2k.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/PIL/FpxImagePlugin.py b/PIL/FpxImagePlugin.py index aefc57420..a4a9098a7 100644 --- a/PIL/FpxImagePlugin.py +++ b/PIL/FpxImagePlugin.py @@ -216,7 +216,7 @@ class FpxImageFile(ImageFile.ImageFile): self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"]) - ImageFile.ImageFile.load(self) + return ImageFile.ImageFile.load(self) # # -------------------------------------------------------------------- diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 02b1e53f5..d54ee0ca4 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -207,7 +207,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): 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)] - ImageFile.ImageFile.load(self) + return ImageFile.ImageFile.load(self) def _accept(prefix): diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 815215df7..29a6d5f73 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -34,7 +34,8 @@ class TestFileJpeg2k(PillowTestCase): self.assertRegexpMatches(Image.core.jp2klib_version, '\d+\.\d+\.\d+$') im = Image.open('Tests/images/test-card-lossless.jp2') - im.load() + px = im.load() + self.assertTrue(px) self.assertEqual(im.mode, 'RGB') self.assertEqual(im.size, (640, 480)) self.assertEqual(im.format, 'JPEG2000') From 193c7561392fd12c3bd93bc232d9041c89bec4f6 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 9 Aug 2016 03:11:35 +0300 Subject: [PATCH 39/57] return implicit RGBA to JPEG save, raise warning --- PIL/JpegImagePlugin.py | 9 +++++++++ Tests/helper.py | 4 ++-- Tests/test_file_jpeg.py | 10 ++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index a08e932ab..a0d066724 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -534,6 +534,7 @@ RAWMODE = { "1": "L", "L": "L", "RGB": "RGB", + "RGBA": "RGB", "RGBX": "RGB", "CMYK": "CMYK;I", # assume adobe conventions "YCbCr": "YCbCr", @@ -582,6 +583,14 @@ def _save(im, fp, filename): except KeyError: raise IOError("cannot write mode %s as JPEG" % im.mode) + if im.mode == 'RGBA': + warnings.warn( + 'You are saving RGBA image as JPEG. The alpha channel will be ' + 'discarded. This conversion is deprecated and will be disabled ' + 'in Pillow 3.7. Please, convert the image to RGB explicitly.', + DeprecationWarning + ) + info = im.encoderinfo dpi = info.get("dpi", (0, 0)) diff --git a/Tests/helper.py b/Tests/helper.py index abb2fbd6d..99d102e6c 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -99,7 +99,7 @@ class PillowTestCase(unittest.TestCase): " average pixel value difference %.4f > epsilon %.4f" % ( ave_diff, epsilon)) - def assert_warning(self, warn_class, func): + def assert_warning(self, warn_class, func, *args, **kwargs): import warnings result = None @@ -108,7 +108,7 @@ class PillowTestCase(unittest.TestCase): warnings.simplefilter("always") # Hopefully trigger a warning. - result = func() + result = func(*args, **kwargs) # Verify some things. self.assertGreaterEqual(len(w), 1) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 692dc5279..0a07df2fa 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -457,12 +457,18 @@ class TestFileJpeg(PillowTestCase): img.save(out, "JPEG") def test_save_wrong_modes(self): - # ref https://github.com/python-pillow/Pillow/issues/2005 out = BytesIO() - for mode in ['LA', 'La', 'RGBA', 'RGBa', 'P']: + for mode in ['LA', 'La', 'RGBa', 'P']: img = Image.new(mode, (20, 20)) self.assertRaises(IOError, img.save, out, "JPEG") + def test_save_modes_with_warnings(self): + # ref https://github.com/python-pillow/Pillow/issues/2005 + out = BytesIO() + for mode in ['RGBA']: + img = Image.new(mode, (20, 20)) + self.assert_warning(DeprecationWarning, img.save, out, "JPEG") + if __name__ == '__main__': unittest.main() From 61972f03eca4e9282cc715d37945d8a5874666a4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 Aug 2016 12:32:13 +1000 Subject: [PATCH 40/57] Fixed typos [ci skip] --- Tests/test_image_access.py | 2 +- Tests/test_image_array.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index be7457bb7..52618d541 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -232,7 +232,7 @@ class TestCffi(AccessTest): # Do not save references to the image, only to the access object px = Image.new('L', (size, 1), 0).load() for i in range(size): - # pixels can contain garbarge if image is released + # pixels can contain garbage if image is released self.assertEqual(px[i, 0], 0) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 655fcc228..e17e6c2e5 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -28,7 +28,7 @@ class TestImageArray(PillowTestCase): def test(mode): i = im.convert(mode) a = i.__array_interface__ - a["strides"] = 1 # pretend it's non-contigous + a["strides"] = 1 # pretend it's non-contiguous i.__array_interface__ = a # patch in new version of attribute out = Image.fromarray(i) return out.mode, out.size, list(i.getdata()) == list(out.getdata()) From aad079a6690dcc24d685423625766a47d243b36f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Aug 2016 16:44:56 +1000 Subject: [PATCH 41/57] Fixed typos --- _imagingcms.c | 2 +- decode.c | 6 +++--- libImaging/QuantOctree.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/_imagingcms.c b/_imagingcms.c index a05e5cdc6..830a03721 100644 --- a/_imagingcms.c +++ b/_imagingcms.c @@ -729,7 +729,7 @@ static cmsBool _calculate_rgb_primaries(CmsProfileObject* self, cmsCIEXYZTRIPLE* /* http://littlecms2.blogspot.com/2009/07/less-is-more.html */ - // double array of RGB values with max on each identitiy + // double array of RGB values with max on each identity hXYZ = cmsCreateXYZProfile(); if (hXYZ == NULL) return 0; diff --git a/decode.c b/decode.c index 7bb88d94e..482449c85 100644 --- a/decode.c +++ b/decode.c @@ -204,7 +204,7 @@ _setimage(ImagingDecoderObject* decoder, PyObject* args) } state->bytes = (state->bits * state->xsize+7)/8; } - /* malloc check ok, oveflow checked above */ + /* malloc check ok, overflow checked above */ state->buffer = (UINT8*) malloc(state->bytes); if (!state->buffer) return PyErr_NoMemory(); @@ -233,7 +233,7 @@ _setfd(ImagingDecoderObject* decoder, PyObject* args) Py_XINCREF(fd); state->fd = fd; - + Py_INCREF(Py_None); return Py_None; } @@ -874,7 +874,7 @@ PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) decoder->cleanup = ImagingJpeg2KDecodeCleanup; context = (JPEG2KDECODESTATE *)decoder->state.context; - + context->fd = fd; context->length = (off_t)length; context->format = codec_format; diff --git a/libImaging/QuantOctree.c b/libImaging/QuantOctree.c index ede3ad634..e18ab3c65 100644 --- a/libImaging/QuantOctree.c +++ b/libImaging/QuantOctree.c @@ -311,7 +311,7 @@ static Pixel * create_palette_array(const ColorBucket palette, unsigned int paletteLength) { Pixel *paletteArray; unsigned int i; - + /* malloc check ok, calloc for overflow */ paletteArray = calloc(paletteLength, sizeof(Pixel)); if (!paletteArray) return NULL; @@ -407,7 +407,7 @@ int quantize_octree(Pixel *pixelData, /* remove the used fine colors from the coarse cube */ subtract_color_buckets(coarseCube, paletteBucketsFine, nFineColors); - /* did the substraction cleared one or more coarse bucket? */ + /* did the subtraction cleared one or more coarse bucket? */ while (nCoarseColors > count_used_color_buckets(coarseCube)) { /* then we can use the free buckets for fine colors */ nAlreadySubtracted = nFineColors; From 4ab18e2b50a1ce550b1bcf97ba42a82c6494f46e Mon Sep 17 00:00:00 2001 From: Manoj Mohan Date: Mon, 15 Aug 2016 08:12:47 +0530 Subject: [PATCH 42/57] Installation instructions on Fedora Python3 Added installations instructions of Pillow on Fedora Python3 --- docs/installation.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 5c90eecac..996cef517 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -282,7 +282,9 @@ Building on Linux ^^^^^^^^^^^^^^^^^ If you didn't build Python from source, make sure you have Python's -development libraries installed. In Debian or Ubuntu:: +development libraries installed. + +In Debian or Ubuntu:: $ sudo apt-get install python-dev python-setuptools @@ -293,6 +295,10 @@ Or for Python 3:: In Fedora, the command is:: $ sudo dnf install python-devel redhat-rpm-config + +Or for Python 3:: + + $ sudo dnf install python3-devel redhat-rpm-config .. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions. From 9fb400ac420def380e8f22069586a0fe8bf13e49 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sun, 17 Jul 2016 17:58:53 +0000 Subject: [PATCH 43/57] BUG: fix C90 compilation error for new Tk method New Tk code that picks up libraries at run-time causes complaints and errors; fix. See: https://github.com/python-pillow/Pillow/issues/2017 --- Tk/tkImaging.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tk/tkImaging.c b/Tk/tkImaging.c index 7f030bf89..5999a140a 100644 --- a/Tk/tkImaging.c +++ b/Tk/tkImaging.c @@ -371,7 +371,8 @@ int load_tkinter_funcs(void) #if PY_VERSION_HEX >= 0x03000000 char *fname2char(PyObject *fname) { - PyObject *bytes = PyUnicode_EncodeFSDefault(fname); + PyObject* bytes; + bytes = PyUnicode_EncodeFSDefault(fname); if (bytes == NULL) { return NULL; } @@ -391,9 +392,10 @@ void *_dfunc(void *lib_handle, const char *func_name) * Returns function pointer or NULL if not present. */ + void* func; /* Reset errors. */ dlerror(); - void *func = dlsym(lib_handle, func_name); + func = dlsym(lib_handle, func_name); if (func == NULL) { const char *error = dlerror(); PyErr_SetString(PyExc_RuntimeError, error); From 57addf02b692c15f0db6eef03ea8d3307d92bc32 Mon Sep 17 00:00:00 2001 From: homm Date: Fri, 5 Aug 2016 19:20:02 +0300 Subject: [PATCH 44/57] fix image loading when rotating by 0 deg --- PIL/Image.py | 2 +- Tests/test_image_rotate.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 64f461358..4283b640c 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1572,7 +1572,7 @@ class Image(object): # Fast paths regardless of filter if angle == 0: - return self._new(self.im) + return self.copy() if angle == 180: return self.transpose(ROTATE_180) if angle == 90 and expand: diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 26c0bd729..3b2319fb0 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -11,11 +11,14 @@ class TestImageRotate(PillowTestCase): self.assertEqual(out.size, im.size) # default rotate clips output out = im.rotate(angle, expand=1) self.assertEqual(out.mode, mode) - self.assertNotEqual(out.size, im.size) + if angle % 180 == 0: + self.assertEqual(out.size, im.size) + else: + self.assertNotEqual(out.size, im.size) for mode in "1", "P", "L", "RGB", "I", "F": im = hopper(mode) rotate(im, mode, 45) - for angle in 90, 270: + for angle in 0, 90, 180, 270: im = Image.open('Tests/images/test-card.png') rotate(im, im.mode, angle) From aab33141f381d5577cdef9033b7fdff79122a10c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 17 Aug 2016 21:33:33 +0100 Subject: [PATCH 45/57] 3.3.1 Release Version bump --- CHANGES.rst | 10 ++++++++++ PIL/__init__.py | 2 +- _imaging.c | 2 +- appveyor.yml | 2 +- setup.py | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0d43ab1ae..ca852f1de 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,16 @@ Changelog (Pillow) ================== +3.3.1 (2016-08-18) +------------------ + +- Fix C90 compilation error for Tcl / Tk rewrite #2033 + [matthew-brett] + +- Fix image loading when rotating by 0 deg #2052 + [homm] + + 3.3.0 (2016-07-01) ------------------ diff --git a/PIL/__init__.py b/PIL/__init__.py index e5dcf4369..6b87744ed 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '3.3.0' # Pillow +PILLOW_VERSION = '3.3.1' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index 52dc3b3fa..7dd8f6993 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "3.3.0" +#define PILLOW_VERSION "3.3.1" #include "Python.h" diff --git a/appveyor.yml b/appveyor.yml index 8adb9ba6d..4b434377f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.3.0.{build} +version: 3.3.1.{build} clone_folder: c:\pillow init: - ECHO %PYTHON% diff --git a/setup.py b/setup.py index 53c7227b8..6f150382f 100644 --- a/setup.py +++ b/setup.py @@ -110,7 +110,7 @@ except (ImportError, OSError): _tkinter = None NAME = 'Pillow' -PILLOW_VERSION = '3.3.0' +PILLOW_VERSION = '3.3.1' JPEG_ROOT = None JPEG2K_ROOT = None ZLIB_ROOT = None From 67eb7a3dc2a73a5d381ae8a6ee63e82173c01ab8 Mon Sep 17 00:00:00 2001 From: Arjen Nienhuis Date: Wed, 17 Aug 2016 23:25:52 +0200 Subject: [PATCH 46/57] Ignore PyPy numpy errors --- Tests/test_numpy.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 40b7c64e3..96b0f4371 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,4 +1,5 @@ from __future__ import print_function +import sys from helper import unittest, PillowTestCase, hopper from PIL import Image @@ -14,6 +15,8 @@ except ImportError: TEST_IMAGE_SIZE = (10, 10) +@unittest.skipIf(hasattr(sys, 'pypy_version_info'), + "numpy is flaky on PyPy") class TestNumpy(PillowTestCase): def setUp(self): @@ -104,6 +107,7 @@ class TestNumpy(PillowTestCase): self.assert_image(Image.fromarray(a[:, :, 1]), "L", TEST_IMAGE_SIZE) def _test_img_equals_nparray(self, img, np): + self.assertGreaterEqual(len(np.shape), 2) np_size = np.shape[1], np.shape[0] self.assertEqual(img.size, np_size) px = img.load() From 824a0c232c375b308197a5bbebded8963aabaeb8 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sun, 7 Aug 2016 15:25:43 -0700 Subject: [PATCH 47/57] BF: fix conversion of bit images to numpy arrays Numpy cannot form arrays from bits. To convert bit images to numpy, convert bits to bytes. From suggestion by Alexander Karpinsky, with thanks. Fixes gh-350. --- PIL/Image.py | 26 +++++++++++++++----------- Tests/test_image_array.py | 19 ++++++++++++++++--- Tests/test_numpy.py | 9 +++++++++ 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 839700a0f..7c95b18f1 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -246,7 +246,7 @@ else: _MODE_CONV = { # official modes - "1": ('|b1', None), # broken + "1": ('|b1', None), # Bits need to be extended to bytes "L": ('|u1', None), "LA": ('|u1', 2), "I": (_ENDIAN + 'i4', None), @@ -615,17 +615,21 @@ class Image(object): self.save(b, 'PNG') return b.getvalue() - def __getattr__(self, name): - if name == "__array_interface__": - # numpy array interface support - new = {} - shape, typestr = _conv_type_shape(self) - new['shape'] = shape - new['typestr'] = typestr + @property + def __array_interface__(self): + # numpy array interface support + new = {} + shape, typestr = _conv_type_shape(self) + new['shape'] = shape + new['typestr'] = typestr + new['version'] = 3 + if self.mode == '1': + # Binary images need to be extended from bits to bytes + # See: https://github.com/python-pillow/Pillow/issues/350 + new['data'] = self.tobytes('raw', 'L') + else: new['data'] = self.tobytes() - new['version'] = 3 - return new - raise AttributeError(name) + return new def __getstate__(self): return [ diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index e17e6c2e5..0d9bf626d 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -25,13 +25,26 @@ class TestImageArray(PillowTestCase): self.assertEqual(test("RGBX"), (3, (100, 128, 4), '|u1', 51200)) def test_fromarray(self): + + class Wrapper(object): + """ Class with API matching Image.fromarray """ + + def __init__(self, img, arr_params): + self.img = img + self.__array_interface__ = arr_params + + def tobytes(self): + return self.img.tobytes() + def test(mode): i = im.convert(mode) a = i.__array_interface__ - a["strides"] = 1 # pretend it's non-contiguous - i.__array_interface__ = a # patch in new version of attribute - out = Image.fromarray(i) + a["strides"] = 1 # pretend it's non-contigous + # Make wrapper instance for image, new array interface + wrapped = Wrapper(i, a) + out = Image.fromarray(wrapped) return out.mode, out.size, list(i.getdata()) == list(out.getdata()) + # self.assertEqual(test("1"), ("1", (128, 100), True)) self.assertEqual(test("L"), ("L", (128, 100), True)) self.assertEqual(test("I"), ("I", (128, 100), True)) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 40b7c64e3..076e71b42 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -117,6 +117,15 @@ class TestNumpy(PillowTestCase): self._test_img_equals_nparray(img, np_img) self.assertEqual(np_img.dtype, numpy.dtype(' Date: Thu, 25 Aug 2016 03:22:20 -0700 Subject: [PATCH 48/57] More specific version of pr #2083 --- Tests/test_numpy.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 96b0f4371..1a44d3c8b 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -15,8 +15,13 @@ except ImportError: TEST_IMAGE_SIZE = (10, 10) -@unittest.skipIf(hasattr(sys, 'pypy_version_info'), - "numpy is flaky on PyPy") +# Numpy on pypy as of pypy 5.3.1 is corrupting the numpy.array(Image) +# call such that it's returning a object of type numpy.ndarray, but +# the repr is that of a PIL.Image. Size and shape are 1 and (), not the +# size and shape of the array. This causes failures in several tests. +SKIP_NUMPY_ON_PYPY = hasattr(sys, 'pypy_version_info') and ( + sys.pypy_version_info <= (5,3,1,'final',0)) + class TestNumpy(PillowTestCase): def setUp(self): @@ -115,6 +120,7 @@ class TestNumpy(PillowTestCase): for y in range(0, img.size[1], int(img.size[1]/10)): self.assert_deep_equal(px[x, y], np[y, x]) + @unittest.skipIf(SKIP_NUMPY_ON_PYPY, "numpy.array(Image) is flaky on PyPy") def test_16bit(self): img = Image.open('Tests/images/16bit.cropped.tif') np_img = numpy.array(img) @@ -136,6 +142,7 @@ class TestNumpy(PillowTestCase): im_good = Image.open(filename) self.assert_image_equal(im_good, im_test) + @unittest.skipIf(SKIP_NUMPY_ON_PYPY, "numpy.array(Image) is flaky on PyPy") def test_to_array(self): def _to_array(mode, dtype): From 21209769bd226bc88496932dfd0c95a0c39daf34 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 25 Aug 2016 03:55:50 -0700 Subject: [PATCH 49/57] More specific test --- Tests/test_file_jpeg2k.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 29a6d5f73..df4814e53 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -35,7 +35,7 @@ class TestFileJpeg2k(PillowTestCase): im = Image.open('Tests/images/test-card-lossless.jp2') px = im.load() - self.assertTrue(px) + self.assertEqual(px[0,0], (0, 0, 0)) self.assertEqual(im.mode, 'RGB') self.assertEqual(im.size, (640, 480)) self.assertEqual(im.format, 'JPEG2000') From 62388199fa4caa150a8c952e875ee2b42238cf49 Mon Sep 17 00:00:00 2001 From: homm Date: Sat, 2 Jul 2016 18:13:56 +0300 Subject: [PATCH 50/57] use lookups table instead of two conditions for each channel --- libImaging/Resample.c | 44 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/libImaging/Resample.c b/libImaging/Resample.c index 2c1a38a74..732d245dd 100644 --- a/libImaging/Resample.c +++ b/libImaging/Resample.c @@ -80,13 +80,47 @@ static struct filter LANCZOS = { lanczos_filter, 3.0 }; #define PRECISION_BITS (32 - 8 - 2) +UINT8 _lookups[512] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 +}; + +UINT8 *lookups = &_lookups[128]; + + static inline UINT8 clip8(int in) { - if (in >= (1 << PRECISION_BITS << 8)) - return 255; - if (in <= 0) - return 0; - return (UINT8) (in >> PRECISION_BITS); + return lookups[in >> PRECISION_BITS]; } From 7d3db1f02a9e8ac61861b14d21f6d1ab288edaef Mon Sep 17 00:00:00 2001 From: homm Date: Sat, 2 Jul 2016 18:24:08 +0300 Subject: [PATCH 51/57] truncate zero coefficients --- libImaging/Resample.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libImaging/Resample.c b/libImaging/Resample.c index 732d245dd..ccd1d4d0a 100644 --- a/libImaging/Resample.c +++ b/libImaging/Resample.c @@ -176,6 +176,16 @@ ImagingPrecompute(int inSize, int outSize, struct filter *filterp, k = &kk[xx * kmax]; for (x = 0; x < xmax; x++) { double w = filterp->filter((x + xmin - center + 0.5) * ss); + if (w == 0) { + if (x == 0) { + x -= 1; + xmin += 1; + xmax -= 1; + } else if (x == xmax - 1) { + xmax -= 1; + } + continue; + } k[x] = w; ww += w; } From 788810f313b923b739a67a5c7c95e17df18b0d4a Mon Sep 17 00:00:00 2001 From: homm Date: Sun, 3 Jul 2016 02:06:54 +0300 Subject: [PATCH 52/57] =?UTF-8?q?ImagingPrecompute=20=E2=86=92=20precomput?= =?UTF-8?q?e=5Fcoeffs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libImaging/Resample.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/libImaging/Resample.c b/libImaging/Resample.c index ccd1d4d0a..f4d30f25f 100644 --- a/libImaging/Resample.c +++ b/libImaging/Resample.c @@ -125,7 +125,7 @@ static inline UINT8 clip8(int in) int -ImagingPrecompute(int inSize, int outSize, struct filter *filterp, +precompute_coeffs(int inSize, int outSize, struct filter *filterp, int **xboundsp, double **kkp) { double support, scale, filterscale; double center, ww, ss; @@ -217,8 +217,7 @@ ImagingResampleHorizontal_8bpc(Imaging imIn, int xsize, struct filter *filterp) int *k, *kk; double *prekk; - - kmax = ImagingPrecompute(imIn->xsize, xsize, filterp, &xbounds, &prekk); + kmax = precompute_coeffs(imIn->xsize, xsize, filterp, &xbounds, &prekk); if ( ! kmax) { return (Imaging) ImagingError_MemoryError(); } @@ -329,8 +328,7 @@ ImagingResampleVertical_8bpc(Imaging imIn, int ysize, struct filter *filterp) int *k, *kk; double *prekk; - - kmax = ImagingPrecompute(imIn->ysize, ysize, filterp, &xbounds, &prekk); + kmax = precompute_coeffs(imIn->ysize, ysize, filterp, &xbounds, &prekk); if ( ! kmax) { return (Imaging) ImagingError_MemoryError(); } @@ -440,7 +438,7 @@ ImagingResampleHorizontal_32bpc(Imaging imIn, int xsize, struct filter *filterp) int *xbounds; double *k, *kk; - kmax = ImagingPrecompute(imIn->xsize, xsize, filterp, &xbounds, &kk); + kmax = precompute_coeffs(imIn->xsize, xsize, filterp, &xbounds, &kk); if ( ! kmax) { return (Imaging) ImagingError_MemoryError(); } @@ -500,7 +498,7 @@ ImagingResampleVertical_32bpc(Imaging imIn, int ysize, struct filter *filterp) int *xbounds; double *k, *kk; - kmax = ImagingPrecompute(imIn->ysize, ysize, filterp, &xbounds, &kk); + kmax = precompute_coeffs(imIn->ysize, ysize, filterp, &xbounds, &kk); if ( ! kmax) { return (Imaging) ImagingError_MemoryError(); } From 5c0eb2c365ebd2afe767dedea6f1a679aff79e57 Mon Sep 17 00:00:00 2001 From: homm Date: Sun, 3 Jul 2016 02:09:39 +0300 Subject: [PATCH 53/57] normalize coefficients in normalize_coeffs_8bpc increase precision of negative filter lobes. Add test --- Tests/test_image_resample.py | 17 +++++++++++ libImaging/Resample.c | 58 ++++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 464e127f8..edcf00c5e 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -320,5 +320,22 @@ class CoreResamplePassesTest(PillowTestCase): self.assertEqual(Image.core.getcount(), count + 2) +class CoreResampleCoefficientsTest(PillowTestCase): + def test_reduce(self): + test_color = 254 + # print '' + + for size in range(400000, 400010, 2): + # print '\r', size, + i = Image.new('L', (size, 1), 0) + draw = ImageDraw.Draw(i) + draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color) + + px = i.resize((5, i.size[1]), Image.BICUBIC).load() + if px[2, 0] != test_color // 2: + self.assertEqual(test_color // 2, px[2, 0]) + # print '\r>', size, test_color // 2, px[2, 0] + + if __name__ == '__main__': unittest.main() diff --git a/libImaging/Resample.c b/libImaging/Resample.c index f4d30f25f..9f03e40da 100644 --- a/libImaging/Resample.c +++ b/libImaging/Resample.c @@ -206,6 +206,32 @@ precompute_coeffs(int inSize, int outSize, struct filter *filterp, } +int +normalize_coeffs_8bpc(int outSize, int kmax, double *prekk, INT32 **kkp) +{ + int x; + INT32 *kk; + + /* malloc check ok, overflow checked in precompute_coeffs */ + kk = malloc(outSize * kmax * sizeof(INT32)); + if ( ! kk) { + return 0; + } + + for (x = 0; x < outSize * kmax; x++) { + if (prekk[x] < 0) { + kk[x] = (int) (-0.5 + prekk[x] * (1 << PRECISION_BITS)); + } else { + kk[x] = (int) (0.5 + prekk[x] * (1 << PRECISION_BITS)); + } + } + + *kkp = kk; + return kmax; +} + + + Imaging ImagingResampleHorizontal_8bpc(Imaging imIn, int xsize, struct filter *filterp) { @@ -214,27 +240,21 @@ ImagingResampleHorizontal_8bpc(Imaging imIn, int xsize, struct filter *filterp) int ss0, ss1, ss2, ss3; int xx, yy, x, kmax, xmin, xmax; int *xbounds; - int *k, *kk; + INT32 *k, *kk; double *prekk; kmax = precompute_coeffs(imIn->xsize, xsize, filterp, &xbounds, &prekk); if ( ! kmax) { return (Imaging) ImagingError_MemoryError(); } - - kk = malloc(xsize * kmax * sizeof(int)); - if ( ! kk) { + + kmax = normalize_coeffs_8bpc(xsize, kmax, prekk, &kk); + free(prekk); + if ( ! kmax) { free(xbounds); - free(prekk); return (Imaging) ImagingError_MemoryError(); } - for (x = 0; x < xsize * kmax; x++) { - kk[x] = (int) (0.5 + prekk[x] * (1 << PRECISION_BITS)); - } - - free(prekk); - imOut = ImagingNew(imIn->mode, xsize, imIn->ysize); if ( ! imOut) { free(kk); @@ -325,27 +345,21 @@ ImagingResampleVertical_8bpc(Imaging imIn, int ysize, struct filter *filterp) int ss0, ss1, ss2, ss3; int xx, yy, y, kmax, ymin, ymax; int *xbounds; - int *k, *kk; + INT32 *k, *kk; double *prekk; kmax = precompute_coeffs(imIn->ysize, ysize, filterp, &xbounds, &prekk); if ( ! kmax) { return (Imaging) ImagingError_MemoryError(); } - - kk = malloc(ysize * kmax * sizeof(int)); - if ( ! kk) { + + kmax = normalize_coeffs_8bpc(ysize, kmax, prekk, &kk); + free(prekk); + if ( ! kmax) { free(xbounds); - free(prekk); return (Imaging) ImagingError_MemoryError(); } - for (y = 0; y < ysize * kmax; y++) { - kk[y] = (int) (0.5 + prekk[y] * (1 << PRECISION_BITS)); - } - - free(prekk); - imOut = ImagingNew(imIn->mode, imIn->xsize, ysize); if ( ! imOut) { free(kk); From 581014e68db065e5ef2672a856d5e86fe3ab7ab8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 25 Aug 2016 12:26:04 +0100 Subject: [PATCH 54/57] Updated Changes.rst [ci skip] --- CHANGES.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f89cccbfd..023445d11 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,24 @@ Changelog (Pillow) 3.4.0 (unreleased) ------------------ +- Added return for J2k (and fpx) Load to return a pixel access object #2061 + [wiredfool] + +- Skip failing numpy tests on Pypy <= 5.3.1 #2090 + [arjennienhuis] + +- Show warning when trying to save RGBA image as JPEG #2010 + [homm] + +- Respect pixel centers during transform #2022 + [homm] + +- TOC for supported file formats #2056 + [polarize] + +- Fix conversion of bit images to numpy arrays Fixes #350, #2058 + [matthew-brett] + - Add ImageOps.scale to expand or contract a PIL image by a factor #2011 [vlmath] From 9bcb5e455a32756e015acf2e371f2f88e70697b0 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 25 Aug 2016 12:38:22 +0100 Subject: [PATCH 55/57] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 023445d11..eaf49fe83 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.4.0 (unreleased) ------------------ +- Add (un)packing between RGBA and BGRa #2057 + [arjennienhuis] + - Added return for J2k (and fpx) Load to return a pixel access object #2061 [wiredfool] From e0612ab14f43ec070825921c1af11d95a43d55aa Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Aug 2016 21:43:31 +1000 Subject: [PATCH 56/57] Fixed typo --- libImaging/Pack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libImaging/Pack.c b/libImaging/Pack.c index a19252e88..7668dd310 100644 --- a/libImaging/Pack.c +++ b/libImaging/Pack.c @@ -323,7 +323,7 @@ void ImagingPackBGRa(UINT8* out, const UINT8* in, int pixels) { int i; - /* BGRa, reversed bytes with premultiplied alplha */ + /* BGRa, reversed bytes with premultiplied alpha */ for (i = 0; i < pixels; i++) { int alpha = out[3] = in[A]; int tmp; From 8846e99e8437f55fd160b0b887b6d5a2bd4db2fb Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 25 Aug 2016 12:59:31 +0100 Subject: [PATCH 57/57] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index eaf49fe83..e28394831 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.4.0 (unreleased) ------------------ +- Resampling lookups, trailing empty coefficients, precision #2008 + [homm] + - Add (un)packing between RGBA and BGRa #2057 [arjennienhuis]