From aba798af259718e5a164aa42460d905fa87fb215 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 4 Nov 2014 12:31:36 +0300 Subject: [PATCH 01/10] replace resize implementation --- PIL/Image.py | 13 ++----------- _imaging.c | 23 ++++++++++++++++++++--- libImaging/Geometry.c | 24 ------------------------ libImaging/Imaging.h | 1 - 4 files changed, 22 insertions(+), 39 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 922fe36a8..8e7140847 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -876,7 +876,7 @@ class Image: elif self.mode == 'P' and mode == 'RGBA': t = self.info['transparency'] delete_trns = True - + if isinstance(t, bytes): self.im.putpalettealphas(t) elif isinstance(t, int): @@ -1539,16 +1539,7 @@ class Image: if self.mode == 'RGBA': return self.convert('RGBa').resize(size, resample).convert('RGBA') - if resample == ANTIALIAS: - # requires stretch support (imToolkit & PIL 1.1.3) - try: - im = self.im.stretch(size, resample) - except AttributeError: - raise ValueError("unsupported resampling filter") - else: - im = self.im.resize(size, resample) - - return self._new(im) + return self._new(self.im.resize(size, resample)) def rotate(self, angle, resample=NEAREST, expand=0): """ diff --git a/_imaging.c b/_imaging.c index f7ea510bc..280db9fdb 100644 --- a/_imaging.c +++ b/_imaging.c @@ -1513,9 +1513,26 @@ _resize(ImagingObject* self, PyObject* args) imIn = self->image; - imOut = ImagingNew(imIn->mode, xsize, ysize); - if (imOut) - (void) ImagingResize(imOut, imIn, filter); + if (imIn->xsize == xsize && imIn->ysize == ysize) { + imOut = ImagingCopy(imIn); + } + else if ( ! filter) { + double a[6]; + + memset(a, 0, sizeof a); + a[1] = (double) imIn->xsize / xsize; + a[5] = (double) imIn->ysize / ysize; + + imOut = ImagingNew(imIn->mode, xsize, ysize); + + imOut = ImagingTransformAffine( + imOut, imIn, + 0, 0, xsize, ysize, + a, filter, 1); + } + else { + imOut = ImagingStretch(imIn, xsize, ysize, filter); + } return PyImagingNew(imOut); } diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index b987a5616..f586974c3 100644 --- a/libImaging/Geometry.c +++ b/libImaging/Geometry.c @@ -979,30 +979,6 @@ ImagingTransformQuad(Imaging imOut, Imaging imIn, /* -------------------------------------------------------------------- */ /* Convenience functions */ -Imaging -ImagingResize(Imaging imOut, Imaging imIn, int filterid) -{ - double a[6]; - - if (imOut->xsize == imIn->xsize && imOut->ysize == imIn->ysize) - return ImagingCopy2(imOut, imIn); - - memset(a, 0, sizeof a); - a[1] = (double) imIn->xsize / imOut->xsize; - a[5] = (double) imIn->ysize / imOut->ysize; - - if (!filterid && imIn->type != IMAGING_TYPE_SPECIAL) - return ImagingScaleAffine( - imOut, imIn, - 0, 0, imOut->xsize, imOut->ysize, - a, 1); - - return ImagingTransformAffine( - imOut, imIn, - 0, 0, imOut->xsize, imOut->ysize, - a, filterid, 1); -} - Imaging ImagingRotate(Imaging imOut, Imaging imIn, double theta, int filterid) { diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index 6c91958ec..694a15c21 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -285,7 +285,6 @@ extern Imaging ImagingPointTransform( Imaging imIn, double scale, double offset); extern Imaging ImagingPutBand(Imaging im, Imaging imIn, int band); extern Imaging ImagingRankFilter(Imaging im, int size, int rank); -extern Imaging ImagingResize(Imaging imOut, Imaging imIn, int filter); extern Imaging ImagingRotate( Imaging imOut, Imaging imIn, double theta, int filter); extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn); From 693aff7ee1403c1184ff6a500587077cd1195004 Mon Sep 17 00:00:00 2001 From: homm Date: Sun, 9 Nov 2014 04:20:54 +0300 Subject: [PATCH 02/10] remove thumbnail fallback --- PIL/Image.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 8e7140847..82bed82ad 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1789,14 +1789,7 @@ class Image: self.draft(None, size) - self.load() - - try: - im = self.resize(size, resample) - except ValueError: - if resample != ANTIALIAS: - raise - im = self.resize(size, NEAREST) # fallback + im = self.resize(size, resample) self.im = im.im self.mode = im.mode From f88878c20bc36c10894ba6d5b8d2940e00046fe7 Mon Sep 17 00:00:00 2001 From: homm Date: Sun, 9 Nov 2014 04:26:53 +0300 Subject: [PATCH 03/10] reflect changes in documentation --- PIL/Image.py | 19 ++++++------------- docs/handbook/concepts.rst | 22 +++++++++------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 82bed82ad..e3434dc56 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1515,9 +1515,8 @@ class Image: (width, height). :param resample: An optional resampling filter. This can be one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), - :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 - environment), :py:attr:`PIL.Image.BICUBIC` (cubic spline - interpolation in a 4x4 environment), or + :py:attr:`PIL.Image.BILINEAR` (linear interpolation), + :py:attr:`PIL.Image.BICUBIC` (cubic spline interpolation), or :py:attr:`PIL.Image.ANTIALIAS` (a high-quality downsampling filter). If omitted, or if the image has mode "1" or "P", it is set :py:attr:`PIL.Image.NEAREST`. @@ -1754,12 +1753,7 @@ class Image: :py:meth:`~PIL.Image.Image.draft` method to configure the file reader (where applicable), and finally resizes the image. - Note that the bilinear and bicubic filters in the current - version of PIL are not well-suited for thumbnail generation. - You should use :py:attr:`PIL.Image.ANTIALIAS` unless speed is much more - important than quality. - - Also note that this function modifies the :py:class:`~PIL.Image.Image` + Note that this function modifies the :py:class:`~PIL.Image.Image` object in place. If you need to use the full resolution image as well, apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original image. @@ -1767,10 +1761,9 @@ class Image: :param size: Requested size. :param resample: Optional resampling filter. This can be one of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`, - :py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS` - (best quality). If omitted, it defaults to - :py:attr:`PIL.Image.ANTIALIAS`. (was :py:attr:`PIL.Image.NEAREST` - prior to version 2.5.0) + :py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS`. + If omitted, it defaults to :py:attr:`PIL.Image.ANTIALIAS`. + (was :py:attr:`PIL.Image.NEAREST` prior to version 2.5.0) :returns: None """ diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index b5e5e44c1..492ec5457 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -87,25 +87,21 @@ pixel, the Python Imaging Library provides four different resampling *filters*. Pick the nearest pixel from the input image. Ignore all other input pixels. ``BILINEAR`` - Use linear interpolation over a 2x2 environment in the input image. Note - that in the current version of PIL, this filter uses a fixed input - environment when downsampling. + For resize calculate the output pixel value using linear interpolation + on all pixels that may contribute to the output value. + For other transformations linear interpolation over a 2x2 environment + in the input image is used. ``BICUBIC`` - Use cubic interpolation over a 4x4 environment in the input image. Note - that in the current version of PIL, this filter uses a fixed input - environment when downsampling. + For resize calculate the output pixel value using cubic interpolation + on all pixels that may contribute to the output value. + For other transformations cubic interpolation over a 4x4 environment + in the input image is used. ``ANTIALIAS`` - Calculate the output pixel value using a high-quality resampling filter (a + Calculate the output pixel value using a high-quality Lanczos filter (a truncated sinc) on all pixels that may contribute to the output value. In the current version of PIL, this filter can only be used with the resize and thumbnail methods. .. versionadded:: 1.1.3 - -Note that in the current version of PIL, the ``ANTIALIAS`` filter is the only -filter that behaves properly when downsampling (that is, when converting a -large image to a small one). The ``BILINEAR`` and ``BICUBIC`` filters use a -fixed input environment, and are best used for scale-preserving geometric -transforms and upsamping. From aabf66a7a419ac10df71b600e37801c2cdb58ea7 Mon Sep 17 00:00:00 2001 From: homm Date: Mon, 10 Nov 2014 02:11:12 +0300 Subject: [PATCH 04/10] fix tests: use `im.transform` directly when result is compared with other transform operations. --- Tests/test_image_transform.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index c4111d0b6..4306e9b43 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -44,7 +44,9 @@ class TestImageTransform(PillowTestCase): w//2, h//2, w//2, 0), Image.BILINEAR) - scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0, 0, w, h)) + scaled = im.transform((w, h), Image.AFFINE, + (.5, 0, 0, 0, .5, 0), + Image.BILINEAR) self.assert_image_equal(transformed, scaled) @@ -61,9 +63,9 @@ class TestImageTransform(PillowTestCase): w, h, w, 0))], # ul -> ccw around quad Image.BILINEAR) - # transformed.save('transformed.png') - - scaled = im.resize((w//2, h//2), Image.BILINEAR) + scaled = im.transform((w//2, h//2), Image.AFFINE, + (2, 0, 0, 0, 2, 0), + Image.BILINEAR) checker = Image.new('RGBA', im.size) checker.paste(scaled, (0, 0)) @@ -128,7 +130,8 @@ class TestImageTransform(PillowTestCase): foo = [ Image.new('RGBA', (1024, 1024), (a, a, a, a)) - for a in range(1, 65)] + for a in range(1, 65) + ] # Yeah. Watch some JIT optimize this out. foo = None From 513e2a12dd5108b37b9789f52d0710206a32cf28 Mon Sep 17 00:00:00 2001 From: homm Date: Wed, 19 Nov 2014 03:03:49 +0300 Subject: [PATCH 05/10] resize test rework --- Tests/test_imaging_stretch.py | 73 +++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/Tests/test_imaging_stretch.py b/Tests/test_imaging_stretch.py index aaf1a4d1d..b4e66433d 100644 --- a/Tests/test_imaging_stretch.py +++ b/Tests/test_imaging_stretch.py @@ -1,51 +1,58 @@ """ -Tests for ImagingCore.stretch functionality. +Tests for resize functionality. """ from itertools import permutations -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, hopper from PIL import Image -im = Image.open("Tests/images/hopper.ppm").copy() +class TestImagingCoreResize(PillowTestCase): + def resize(self, im, size, f): + # Image class independend version of resize. + im.load() + return im._new(im.im.resize(size, f)) -class TestImagingStretch(PillowTestCase): - - def stretch(self, im, size, f): - return im._new(im.im.stretch(size, f)) - - def test_modes(self): - self.assertRaises(ValueError, im.convert("1").im.stretch, - (15, 12), Image.ANTIALIAS) - self.assertRaises(ValueError, im.convert("P").im.stretch, - (15, 12), Image.ANTIALIAS) - for mode in ["L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr"]: - s = im.convert(mode).im - r = s.stretch((15, 12), Image.ANTIALIAS) + def test_nearest_mode(self): + for mode in ["1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", + "I;16"]: # exotic mode + im = hopper(mode) + r = self.resize(im, (15, 12), Image.NEAREST) self.assertEqual(r.mode, mode) - self.assertEqual(r.size, (15, 12)) - self.assertEqual(r.bands, s.bands) + self.assertEqual(r.size, (15, 12) ) + self.assertEqual(r.im.bands, im.im.bands) + + def test_convolution_modes(self): + self.assertRaises(ValueError, self.resize, hopper("1"), + (15, 12), Image.BILINEAR) + self.assertRaises(ValueError, self.resize, hopper("P"), + (15, 12), Image.BILINEAR) + self.assertRaises(ValueError, self.resize, hopper("I;16"), + (15, 12), Image.BILINEAR) + for mode in ["L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr"]: + im = hopper(mode) + r = self.resize(im, (15, 12), Image.BILINEAR) + self.assertEqual(r.mode, mode) + self.assertEqual(r.size, (15, 12) ) + self.assertEqual(r.im.bands, im.im.bands) def test_reduce_filters(self): - # There is no Image.NEAREST because im.stretch implementation - # is not NEAREST for reduction. It should be removed - # or renamed to supersampling. - for f in [Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: - r = im.im.stretch((15, 12), f) + for f in [Image.LINEAR, Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: + r = self.resize(hopper("RGB"), (15, 12), f) self.assertEqual(r.mode, "RGB") self.assertEqual(r.size, (15, 12)) def test_enlarge_filters(self): - for f in [Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: - r = im.im.stretch((764, 414), f) + for f in [Image.LINEAR, Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: + r = self.resize(hopper("RGB"), (212, 195), f) self.assertEqual(r.mode, "RGB") - self.assertEqual(r.size, (764, 414)) + self.assertEqual(r.size, (212, 195)) def test_endianness(self): # Make an image with one colored pixel, in one channel. - # When stretched, that channel should be the same as a GS image. + # When resized, that channel should be the same as a GS image. # Other channels should be unaffected. # The R and A channels should not swap, which is indicitive of # an endianness issues. @@ -57,10 +64,10 @@ class TestImagingStretch(PillowTestCase): } samples['dirty'].putpixel((1, 1), 128) - for f in [Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: + for f in [Image.LINEAR, Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: # samples resized with current filter - resized = dict( - (name, self.stretch(ch, (4, 4), f)) + references = dict( + (name, self.resize(ch, (4, 4), f)) for name, ch in samples.items() ) @@ -72,12 +79,12 @@ class TestImagingStretch(PillowTestCase): for channels in set(permutations(channels_set)): # compile image from different channels permutations im = Image.merge(mode, [samples[ch] for ch in channels]) - stretched = self.stretch(im, (4, 4), f) + resized = self.resize(im, (4, 4), f) - for i, ch in enumerate(stretched.split()): + for i, ch in enumerate(resized.split()): # check what resized channel in image is the same # as separately resized channel - self.assert_image_equal(ch, resized[channels[i]]) + self.assert_image_equal(ch, references[channels[i]]) if __name__ == '__main__': From 24ed800fae0257c242e7c93bb3ec4e939bcf4792 Mon Sep 17 00:00:00 2001 From: homm Date: Wed, 19 Nov 2014 03:05:39 +0300 Subject: [PATCH 06/10] merge imaging and imaging core resize tests --- Tests/test_image_resize.py | 90 ++++++++++++++++++++++++++++++++- Tests/test_imaging_stretch.py | 93 ----------------------------------- 2 files changed, 88 insertions(+), 95 deletions(-) delete mode 100644 Tests/test_imaging_stretch.py diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 603f598d8..e14f403a3 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -1,5 +1,91 @@ +""" +Tests for resize functionality. +""" +from itertools import permutations + from helper import unittest, PillowTestCase, hopper +from PIL import Image + + +class TestImagingCoreResize(PillowTestCase): + + def resize(self, im, size, f): + # Image class independend version of resize. + im.load() + return im._new(im.im.resize(size, f)) + + def test_nearest_mode(self): + for mode in ["1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", + "I;16"]: # exotic mode + im = hopper(mode) + r = self.resize(im, (15, 12), Image.NEAREST) + self.assertEqual(r.mode, mode) + self.assertEqual(r.size, (15, 12) ) + self.assertEqual(r.im.bands, im.im.bands) + + def test_convolution_modes(self): + self.assertRaises(ValueError, self.resize, hopper("1"), + (15, 12), Image.BILINEAR) + self.assertRaises(ValueError, self.resize, hopper("P"), + (15, 12), Image.BILINEAR) + self.assertRaises(ValueError, self.resize, hopper("I;16"), + (15, 12), Image.BILINEAR) + for mode in ["L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr"]: + im = hopper(mode) + r = self.resize(im, (15, 12), Image.BILINEAR) + self.assertEqual(r.mode, mode) + self.assertEqual(r.size, (15, 12) ) + self.assertEqual(r.im.bands, im.im.bands) + + def test_reduce_filters(self): + for f in [Image.LINEAR, Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: + r = self.resize(hopper("RGB"), (15, 12), f) + self.assertEqual(r.mode, "RGB") + self.assertEqual(r.size, (15, 12)) + + def test_enlarge_filters(self): + for f in [Image.LINEAR, Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: + r = self.resize(hopper("RGB"), (212, 195), f) + self.assertEqual(r.mode, "RGB") + self.assertEqual(r.size, (212, 195)) + + def test_endianness(self): + # Make an image with one colored pixel, in one channel. + # When resized, that channel should be the same as a GS image. + # Other channels should be unaffected. + # The R and A channels should not swap, which is indicitive of + # an endianness issues. + + samples = { + 'blank': Image.new('L', (2, 2), 0), + 'filled': Image.new('L', (2, 2), 255), + 'dirty': Image.new('L', (2, 2), 0), + } + samples['dirty'].putpixel((1, 1), 128) + + for f in [Image.LINEAR, Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: + # samples resized with current filter + references = dict( + (name, self.resize(ch, (4, 4), f)) + for name, ch in samples.items() + ) + + for mode, channels_set in [ + ('RGB', ('blank', 'filled', 'dirty')), + ('RGBA', ('blank', 'blank', 'filled', 'dirty')), + ('LA', ('filled', 'dirty')), + ]: + for channels in set(permutations(channels_set)): + # compile image from different channels permutations + im = Image.merge(mode, [samples[ch] for ch in channels]) + resized = self.resize(im, (4, 4), f) + + for i, ch in enumerate(resized.split()): + # check what resized channel in image is the same + # as separately resized channel + self.assert_image_equal(ch, references[channels[i]]) + class TestImageResize(PillowTestCase): @@ -9,8 +95,8 @@ class TestImageResize(PillowTestCase): self.assertEqual(out.mode, mode) self.assertEqual(out.size, size) for mode in "1", "P", "L", "RGB", "I", "F": - resize(mode, (100, 100)) - resize(mode, (200, 200)) + resize(mode, (112, 103)) + resize(mode, (188, 214)) if __name__ == '__main__': diff --git a/Tests/test_imaging_stretch.py b/Tests/test_imaging_stretch.py deleted file mode 100644 index b4e66433d..000000000 --- a/Tests/test_imaging_stretch.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Tests for resize functionality. -""" -from itertools import permutations - -from helper import unittest, PillowTestCase, hopper - -from PIL import Image - - -class TestImagingCoreResize(PillowTestCase): - - def resize(self, im, size, f): - # Image class independend version of resize. - im.load() - return im._new(im.im.resize(size, f)) - - def test_nearest_mode(self): - for mode in ["1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", - "I;16"]: # exotic mode - im = hopper(mode) - r = self.resize(im, (15, 12), Image.NEAREST) - self.assertEqual(r.mode, mode) - self.assertEqual(r.size, (15, 12) ) - self.assertEqual(r.im.bands, im.im.bands) - - def test_convolution_modes(self): - self.assertRaises(ValueError, self.resize, hopper("1"), - (15, 12), Image.BILINEAR) - self.assertRaises(ValueError, self.resize, hopper("P"), - (15, 12), Image.BILINEAR) - self.assertRaises(ValueError, self.resize, hopper("I;16"), - (15, 12), Image.BILINEAR) - for mode in ["L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr"]: - im = hopper(mode) - r = self.resize(im, (15, 12), Image.BILINEAR) - self.assertEqual(r.mode, mode) - self.assertEqual(r.size, (15, 12) ) - self.assertEqual(r.im.bands, im.im.bands) - - def test_reduce_filters(self): - for f in [Image.LINEAR, Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: - r = self.resize(hopper("RGB"), (15, 12), f) - self.assertEqual(r.mode, "RGB") - self.assertEqual(r.size, (15, 12)) - - def test_enlarge_filters(self): - for f in [Image.LINEAR, Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: - r = self.resize(hopper("RGB"), (212, 195), f) - self.assertEqual(r.mode, "RGB") - self.assertEqual(r.size, (212, 195)) - - def test_endianness(self): - # Make an image with one colored pixel, in one channel. - # When resized, that channel should be the same as a GS image. - # Other channels should be unaffected. - # The R and A channels should not swap, which is indicitive of - # an endianness issues. - - samples = { - 'blank': Image.new('L', (2, 2), 0), - 'filled': Image.new('L', (2, 2), 255), - 'dirty': Image.new('L', (2, 2), 0), - } - samples['dirty'].putpixel((1, 1), 128) - - for f in [Image.LINEAR, Image.BILINEAR, Image.BICUBIC, Image.ANTIALIAS]: - # samples resized with current filter - references = dict( - (name, self.resize(ch, (4, 4), f)) - for name, ch in samples.items() - ) - - for mode, channels_set in [ - ('RGB', ('blank', 'filled', 'dirty')), - ('RGBA', ('blank', 'blank', 'filled', 'dirty')), - ('LA', ('filled', 'dirty')), - ]: - for channels in set(permutations(channels_set)): - # compile image from different channels permutations - im = Image.merge(mode, [samples[ch] for ch in channels]) - resized = self.resize(im, (4, 4), f) - - for i, ch in enumerate(resized.split()): - # check what resized channel in image is the same - # as separately resized channel - self.assert_image_equal(ch, references[channels[i]]) - - -if __name__ == '__main__': - unittest.main() - -# End of file From 6078e6e1a74c423ad0cf61d6ac11e2aac1aa01ee Mon Sep 17 00:00:00 2001 From: homm Date: Wed, 19 Nov 2014 03:15:04 +0300 Subject: [PATCH 07/10] this bug actually was in previous implementation too: type switch default case was never achieved because special images is in image8 pointers, not in image32 --- libImaging/Antialias.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/libImaging/Antialias.c b/libImaging/Antialias.c index 5c9514103..a5f0fde83 100644 --- a/libImaging/Antialias.c +++ b/libImaging/Antialias.c @@ -208,7 +208,7 @@ ImagingStretchHorizontal(Imaging imIn, int xsize, int filter) ss += i2f(imIn->image8[yy][x]) * k[x - xmin]; imOut->image8[yy][xx] = clip8(ss); } - } else + } else { switch(imIn->type) { case IMAGING_TYPE_UINT8: /* n-bit grayscale */ @@ -283,13 +283,8 @@ ImagingStretchHorizontal(Imaging imIn, int xsize, int filter) IMAGING_PIXEL_F(imOut, xx, yy) = ss; } break; - default: - ImagingSectionLeave(&cookie); - ImagingDelete(imOut); - free(kk); - free(xbounds); - return (Imaging) ImagingError_ModeError(); } + } } ImagingSectionLeave(&cookie); free(kk); @@ -307,6 +302,9 @@ ImagingStretch(Imaging imIn, int xsize, int ysize, int filter) if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) return (Imaging) ImagingError_ModeError(); + if (imIn->type == IMAGING_TYPE_SPECIAL) + return (Imaging) ImagingError_ModeError(); + /* two-pass resize, first pass */ imTemp1 = ImagingStretchHorizontal(imIn, xsize, filter); if ( ! imTemp1) From 41029f0149cb91813a55b8c6217257398c53e4f8 Mon Sep 17 00:00:00 2001 From: homm Date: Wed, 19 Nov 2014 03:26:17 +0300 Subject: [PATCH 08/10] make im.stretch strong alias to im.resize. mark im.stretch as deprecated. --- _imaging.c | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/_imaging.c b/_imaging.c index 280db9fdb..fd28ef451 100644 --- a/_imaging.c +++ b/_imaging.c @@ -1626,25 +1626,6 @@ im_setmode(ImagingObject* self, PyObject* args) return Py_None; } -static PyObject* -_stretch(ImagingObject* self, PyObject* args) -{ - Imaging imIn, imOut; - - int xsize, ysize; - int filter = IMAGING_TRANSFORM_NEAREST; - if (!PyArg_ParseTuple(args, "(ii)|i", &xsize, &ysize, &filter)) - return NULL; - - imIn = self->image; - - imOut = ImagingStretch(imIn, xsize, ysize, filter); - if ( ! imOut) { - return NULL; - } - - return PyImagingNew(imOut); -} static PyObject* _transform2(ImagingObject* self, PyObject* args) @@ -3026,8 +3007,10 @@ static struct PyMethodDef methods[] = { {"rankfilter", (PyCFunction)_rankfilter, 1}, #endif {"resize", (PyCFunction)_resize, 1}, + // There was two methods for image resize before. + // Starting from 2.7 stretch is obsolete. + {"stretch", (PyCFunction)_resize, 1}, {"rotate", (PyCFunction)_rotate, 1}, - {"stretch", (PyCFunction)_stretch, 1}, {"transpose", (PyCFunction)_transpose, 1}, {"transform2", (PyCFunction)_transform2, 1}, From 814dd3123e70eb0faff0d149a5565483b42340fa Mon Sep 17 00:00:00 2001 From: homm Date: Wed, 19 Nov 2014 11:26:02 +0300 Subject: [PATCH 09/10] fix comments --- Tests/test_image_resize.py | 2 +- _imaging.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index e14f403a3..79816e450 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -11,7 +11,7 @@ from PIL import Image class TestImagingCoreResize(PillowTestCase): def resize(self, im, size, f): - # Image class independend version of resize. + # Image class independent version of resize. im.load() return im._new(im.im.resize(size, f)) diff --git a/_imaging.c b/_imaging.c index fd28ef451..97349f187 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3007,8 +3007,8 @@ static struct PyMethodDef methods[] = { {"rankfilter", (PyCFunction)_rankfilter, 1}, #endif {"resize", (PyCFunction)_resize, 1}, - // There was two methods for image resize before. - // Starting from 2.7 stretch is obsolete. + // There were two methods for image resize before. + // Starting from Pillow 2.7.0 stretch is depreciated. {"stretch", (PyCFunction)_resize, 1}, {"rotate", (PyCFunction)_rotate, 1}, {"transpose", (PyCFunction)_transpose, 1}, From bc0f896a47d7b2dcd6f9fc1fff88f6a25b248f8a Mon Sep 17 00:00:00 2001 From: homm Date: Wed, 19 Nov 2014 13:59:11 +0300 Subject: [PATCH 10/10] rename Antialias and stretch to resample --- CHANGES.rst | 2 +- _imaging.c | 2 +- libImaging/Imaging.h | 2 +- libImaging/{Antialias.c => Resample.c} | 26 ++++++-------------------- setup.py | 2 +- 5 files changed, 10 insertions(+), 24 deletions(-) rename libImaging/{Antialias.c => Resample.c} (93%) diff --git a/CHANGES.rst b/CHANGES.rst index 1b4826451..45589c7d6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,7 +18,7 @@ Changelog (Pillow) - Use PyQt4 if it has already been imported, otherwise prefer PyQt5. #1003 [AurelienBallier] -- Speedup stretch implementation up to 2.5 times. #977 +- Speedup resample implementation up to 2.5 times. #977 [homm] - Speed up rotation by using cache aware loops, added transpose to rotations. #994 diff --git a/_imaging.c b/_imaging.c index a84350cf2..299e88662 100644 --- a/_imaging.c +++ b/_imaging.c @@ -1531,7 +1531,7 @@ _resize(ImagingObject* self, PyObject* args) a, filter, 1); } else { - imOut = ImagingStretch(imIn, xsize, ysize, filter); + imOut = ImagingResample(imIn, xsize, ysize, filter); } return PyImagingNew(imOut); diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index 694a15c21..95957bb9d 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -290,7 +290,7 @@ extern Imaging ImagingRotate( extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn); extern Imaging ImagingRotate180(Imaging imOut, Imaging imIn); extern Imaging ImagingRotate270(Imaging imOut, Imaging imIn); -extern Imaging ImagingStretch(Imaging imIn, int xsize, int ysize, int filter); +extern Imaging ImagingResample(Imaging imIn, int xsize, int ysize, int filter); extern Imaging ImagingTranspose(Imaging imOut, Imaging imIn); extern Imaging ImagingTransposeToNew(Imaging imIn); extern Imaging ImagingTransformPerspective( diff --git a/libImaging/Antialias.c b/libImaging/Resample.c similarity index 93% rename from libImaging/Antialias.c rename to libImaging/Resample.c index d6b10c431..69c32beba 100644 --- a/libImaging/Antialias.c +++ b/libImaging/Resample.c @@ -2,7 +2,7 @@ * The Python Imaging Library * $Id$ * - * pilopen antialiasing support + * Pillow image resamling support * * history: * 2002-03-09 fl Created (for PIL 1.1.3) @@ -17,8 +17,6 @@ #include -/* resampling filters (from antialias.py) */ - struct filter { float (*filter)(float x); float support; @@ -42,15 +40,6 @@ static inline float antialias_filter(float x) static struct filter ANTIALIAS = { antialias_filter, 3.0 }; -static inline float nearest_filter(float x) -{ - if (-0.5 <= x && x < 0.5) - return 1.0; - return 0.0; -} - -static struct filter NEAREST = { nearest_filter, 0.5 }; - static inline float bilinear_filter(float x) { if (x < 0.0) @@ -106,7 +95,7 @@ static float inline i2f(int v) { return (float) v; } Imaging -ImagingStretchHorizontal(Imaging imIn, int xsize, int filter) +ImagingResampleHorizontal(Imaging imIn, int xsize, int filter) { ImagingSectionCookie cookie; Imaging imOut; @@ -119,9 +108,6 @@ ImagingStretchHorizontal(Imaging imIn, int xsize, int filter) /* check filter */ switch (filter) { - case IMAGING_TRANSFORM_NEAREST: - filterp = &NEAREST; - break; case IMAGING_TRANSFORM_ANTIALIAS: filterp = &ANTIALIAS; break; @@ -152,7 +138,7 @@ ImagingStretchHorizontal(Imaging imIn, int xsize, int filter) /* maximum number of coofs */ kmax = (int) ceil(support) * 2 + 1; - /* coefficient buffer (with rounding safety margin) */ + /* coefficient buffer */ kk = malloc(xsize * kmax * sizeof(float)); if ( ! kk) return (Imaging) ImagingError_MemoryError(); @@ -294,7 +280,7 @@ ImagingStretchHorizontal(Imaging imIn, int xsize, int filter) Imaging -ImagingStretch(Imaging imIn, int xsize, int ysize, int filter) +ImagingResample(Imaging imIn, int xsize, int ysize, int filter) { Imaging imTemp1, imTemp2, imTemp3; Imaging imOut; @@ -306,7 +292,7 @@ ImagingStretch(Imaging imIn, int xsize, int ysize, int filter) return (Imaging) ImagingError_ModeError(); /* two-pass resize, first pass */ - imTemp1 = ImagingStretchHorizontal(imIn, xsize, filter); + imTemp1 = ImagingResampleHorizontal(imIn, xsize, filter); if ( ! imTemp1) return NULL; @@ -317,7 +303,7 @@ ImagingStretch(Imaging imIn, int xsize, int ysize, int filter) return NULL; /* second pass */ - imTemp3 = ImagingStretchHorizontal(imTemp2, ysize, filter); + imTemp3 = ImagingResampleHorizontal(imTemp2, ysize, filter); ImagingDelete(imTemp2); if ( ! imTemp3) return NULL; diff --git a/setup.py b/setup.py index 2d8cafa34..ff50867d5 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ _IMAGING = ( "decode", "encode", "map", "display", "outline", "path") _LIB_IMAGING = ( - "Access", "AlphaComposite", "Antialias", "Bands", "BitDecode", "Blend", + "Access", "AlphaComposite", "Resample", "Bands", "BitDecode", "Blend", "Chops", "Convert", "ConvertYCbCr", "Copy", "Crc32", "Crop", "Dib", "Draw", "Effects", "EpsEncode", "File", "Fill", "Filter", "FliDecode", "Geometry", "GetBBox", "GifDecode", "GifEncode", "HexDecode",