diff --git a/CHANGES.rst b/CHANGES.rst index 4c28ef135..e28394831 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,12 +4,63 @@ Changelog (Pillow) 3.4.0 (unreleased) ------------------ +- Resampling lookups, trailing empty coefficients, precision #2008 + [homm] + +- Add (un)packing between RGBA and BGRa #2057 + [arjennienhuis] + +- 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] + +- 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] - Retain a reference to core image object in PyAccess #2009 [homm] +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/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/Image.py b/PIL/Image.py index 839700a0f..8a4aa6ae6 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 [ @@ -1877,7 +1881,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/PIL/ImageOps.py b/PIL/ImageOps.py index f317645b2..8580ec5fb 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -178,6 +178,27 @@ def crop(image, border=0): ) +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 + 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 image.copy() + elif factor <= 0: + raise ValueError("the factor must be greater than 0") + else: + size = (int(round(factor * image.width)), + int(round(factor * image.height))) + return image.resize(size, resample) + + def deform(image, deformer, resample=Image.BILINEAR): """ Deform the image. 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/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 9d4eaabb1..a0d066724 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -583,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/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', 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/helper.py b/Tests/helper.py index f4e8f58fe..0dc39e91f 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/images/imagedraw_bitmap.png b/Tests/images/imagedraw_bitmap.png index 05337b693..2f9c7a937 100644 Binary files a/Tests/images/imagedraw_bitmap.png and b/Tests/images/imagedraw_bitmap.png differ 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_jpeg.py b/Tests/test_file_jpeg.py index d52256ef4..d04218b1a 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -450,6 +450,25 @@ 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): + out = BytesIO() + 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() diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 815215df7..df4814e53 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.assertEqual(px[0,0], (0, 0, 0)) self.assertEqual(im.mode, 'RGB') self.assertEqual(im.size, (640, 480)) self.assertEqual(im.format, 'JPEG2000') 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_pcx.py b/Tests/test_file_pcx.py index db2a526a5..7621c1cc6 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_buffer_overflow(self, im, size=1024): + _last = ImageFile.MAXBLOCK + ImageFile.MAXBLOCK = size + 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_buffer_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_buffer_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_buffer_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_buffer_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_buffer_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_buffer_overflow(im) + 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..52618d541 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") @@ -229,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..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-contigous - i.__array_interface__ = a # patch in new version of attribute - out = Image.fromarray(i) + # 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_image_getdata.py b/Tests/test_image_getdata.py index c5cb32242..ef07844df 100644 --- a/Tests/test_image_getdata.py +++ b/Tests/test_image_getdata.py @@ -20,13 +20,13 @@ class TestImageGetData(PillowTestCase): return data[0], len(data), len(list(data)) self.assertEqual(getdata("1"), (0, 960, 960)) - self.assertEqual(getdata("L"), (25, 960, 960)) - self.assertEqual(getdata("I"), (25, 960, 960)) - self.assertEqual(getdata("F"), (25.0, 960, 960)) - self.assertEqual(getdata("RGB"), (((20, 20, 70), 960, 960))) - self.assertEqual(getdata("RGBA"), ((20, 20, 70, 255), 960, 960)) - self.assertEqual(getdata("CMYK"), ((235, 235, 185, 0), 960, 960)) - self.assertEqual(getdata("YCbCr"), ((25, 153, 123), 960, 960)) + self.assertEqual(getdata("L"), (16, 960, 960)) + self.assertEqual(getdata("I"), (16, 960, 960)) + self.assertEqual(getdata("F"), (16.0, 960, 960)) + self.assertEqual(getdata("RGB"), (((11, 13, 52), 960, 960))) + self.assertEqual(getdata("RGBA"), ((11, 13, 52, 255), 960, 960)) + self.assertEqual(getdata("CMYK"), ((244, 242, 203, 0), 960, 960)) + self.assertEqual(getdata("YCbCr"), ((16, 147, 123), 960, 960)) if __name__ == '__main__': 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/Tests/test_image_transform.py b/Tests/test_image_transform.py index 34b2f4772..a8204eb5c 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 @@ -98,7 +100,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,5 +141,117 @@ class TestImageTransform(PillowTestCase): self.test_mesh() +class TestImageTransformAffine(PillowTestCase): + transform = Image.AFFINE + + def _test_image(self): + im = hopper('RGB') + return im.crop((10, 20, im.width - 10, im.height - 20)) + + def _test_rotate(self, deg, transpose): + im = self._test_image() + + 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] + 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(transposed.size, self.transform, + 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): + self._test_rotate(90, Image.ROTATE_90) + + def test_rotate_180_deg(self): + self._test_rotate(180, Image.ROTATE_180) + + def test_rotate_270_deg(self): + self._test_rotate(270, Image.ROTATE_270) + + def _test_resize(self, scale, epsilonscale): + im = self._test_image() + + size_up = int(round(im.width * scale)), int(round(im.height * scale)) + matrix_up = [ + 1 / scale, 0, 0, + 0, 1 / scale, 0, + 0, 0] + matrix_down = [ + scale, 0, 0, + 0, scale, 0, + 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, 6.9) + + def test_resize_1_5x(self): + self._test_resize(1.5, 5.5) + + def test_resize_2_0x(self): + self._test_resize(2.0, 5.5) + + def test_resize_2_3x(self): + self._test_resize(2.3, 3.7) + + def test_resize_2_5x(self): + 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] + 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, 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_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) + + +class TestImageTransformPerspective(TestImageTransformAffine): + # Repeat all tests for AFFINE transormations with PERSPECTIVE + transform = Image.PERSPECTIVE + + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 18f077c65..3675e7b71 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -78,6 +78,22 @@ 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)) + + 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)) + + newimg = ImageOps.scale(i, 0.5) + self.assertEqual(newimg.size, (25, 25)) + if __name__ == '__main__': unittest.main() 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_lib_pack.py b/Tests/test_lib_pack.py index 461d0bf8e..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]) @@ -122,7 +124,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/Tests/test_numpy.py b/Tests/test_numpy.py index 804ff832f..998bc8510 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 @@ -6,18 +7,29 @@ from PIL import Image try: import site import numpy + assert site # silence warning + assert numpy # silence warning except ImportError: # Skip via setUp() pass TEST_IMAGE_SIZE = (10, 10) +# 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): try: import site import numpy + assert site # silence warning + assert numpy # silence warning except ImportError: self.skipTest("ImportError") @@ -100,6 +112,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() @@ -107,12 +120,22 @@ 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) self._test_img_equals_nparray(img, np_img) self.assertEqual(np_img.dtype, numpy.dtype('= 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); 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/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index b680072ac..aedcdacd7 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -16,6 +16,8 @@ explicitly. Fully supported formats ----------------------- +.. contents:: + BMP ^^^ diff --git a/docs/installation.rst b/docs/installation.rst index ac744e594..996cef517 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 @@ -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. 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 diff --git a/encode.c b/encode.c index 9e45e501e..c7e900d7c 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; } diff --git a/libImaging/Geometry.c b/libImaging/Geometry.c index a11a4eac1..d9750cb10 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; } @@ -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++) {\ diff --git a/libImaging/Pack.c b/libImaging/Pack.c index d83cb8284..7668dd310 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 alpha */ + 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}, diff --git a/libImaging/PcxEncode.c b/libImaging/PcxEncode.c index c73f49a24..163b09b13 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]; @@ -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 @@ -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,33 +154,34 @@ 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; } } - if (bytes < padding) { - 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; } - } + } while (state->x < planes * bytes_per_line); + /* read next line */ state->state = FETCH; break; 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; diff --git a/libImaging/Resample.c b/libImaging/Resample.c index 2c1a38a74..9f03e40da 100644 --- a/libImaging/Resample.c +++ b/libImaging/Resample.c @@ -80,18 +80,52 @@ 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]; } 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; @@ -142,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; } @@ -162,6 +206,32 @@ ImagingPrecompute(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) { @@ -170,28 +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 = ImagingPrecompute(imIn->xsize, xsize, filterp, &xbounds, &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); @@ -282,28 +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 = ImagingPrecompute(imIn->ysize, ysize, filterp, &xbounds, &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); @@ -396,7 +452,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(); } @@ -456,7 +512,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(); } 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}, 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',