diff --git a/.gitignore b/.gitignore index 861b801b5..ef7520c0d 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,9 @@ docs/_build/ \#*# .#* +#VS Code +.vscode + #Komodo *.komodoproject diff --git a/.travis/script.sh b/.travis/script.sh index d6e02f01d..ae2e8aad4 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -7,7 +7,7 @@ make clean make install-coverage python selftest.py -python -m pytest -vx --cov PIL --cov-report term Tests +python -m pytest -v -x --cov PIL --cov-report term Tests pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd diff --git a/CHANGES.rst b/CHANGES.rst index 599eaa345..03c878dc9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,36 @@ Changelog (Pillow) 6.1.0 (unreleased) ------------------ +- Create GIF deltas from background colour of GIF frames if disposal mode is 2 #3708 + [sircinnamon, radarhere] + +- Added ImageSequence all_frames #3778 + [radarhere] + +- Use unsigned int to store TIFF IFD offsets #3923 + [cgohlke] + +- Include CPPFLAGS when searching for libraries #3819 + [jefferyto] + +- Updated TIFF tile descriptors to match current decoding functionality #3795 + [dmnisson] + +- Added an `image.entropy()` method (second revision) #3608 + [fish2000] + +- Pass the correct types to PyArg_ParseTuple #3880 + [QuLogic] + +- Fixed crash when loading non-font bytes #3912 + [radarhere] + +- Fix SPARC memory alignment issues in Pack/Unpack functions #3858 + [kulikjak] + +- Added CMYK;16B and CMYK;16N unpackers #3913 + [radarhere] + - Fixed bugs in calculating text size #3864 [radarhere] diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index d5e358f31..c8ba4b4d5 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -105,6 +105,8 @@ class TestCoreMemory(PillowTestCase): Image.new("RGB", (10, 10)) self.assertRaises(ValueError, Image.core.set_blocks_max, -1) + if sys.maxsize < 2 ** 32: + self.assertRaises(ValueError, Image.core.set_blocks_max, 2 ** 29) @unittest.skipIf(is_pypy, "images are not collected") def test_set_blocks_max_stats(self): diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 69a1ee5be..45409cbc6 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,6 +1,6 @@ from .helper import unittest, PillowTestCase, hopper, netpbm_available -from PIL import Image, ImagePalette, GifImagePlugin +from PIL import Image, ImagePalette, GifImagePlugin, ImageDraw from io import BytesIO @@ -59,7 +59,7 @@ class TestFileGif(PillowTestCase): return len(test_file.getvalue()) self.assertEqual(test_grayscale(0), 800) - self.assertEqual(test_grayscale(1), 38) + self.assertEqual(test_grayscale(1), 44) self.assertEqual(test_bilevel(0), 800) self.assertEqual(test_bilevel(1), 800) @@ -318,6 +318,103 @@ class TestFileGif(PillowTestCase): img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, i + 1) + def test_dispose2_palette(self): + out = self.tempfile("temp.gif") + + # 4 backgrounds: White, Grey, Black, Red + circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)] + + im_list = [] + for circle in circles: + img = Image.new("RGB", (100, 100), (255, 0, 0)) + + # Red circle in center of each frame + d = ImageDraw.Draw(img) + d.ellipse([(40, 40), (60, 60)], fill=circle) + + im_list.append(img) + + im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2) + + img = Image.open(out) + + for i, circle in enumerate(circles): + img.seek(i) + rgb_img = img.convert("RGB") + + # Check top left pixel matches background + self.assertEqual(rgb_img.getpixel((0, 0)), (255, 0, 0)) + + # Center remains red every frame + self.assertEqual(rgb_img.getpixel((50, 50)), circle) + + def test_dispose2_diff(self): + out = self.tempfile("temp.gif") + + # 4 frames: red/blue, red/red, blue/blue, red/blue + circles = [ + ((255, 0, 0, 255), (0, 0, 255, 255)), + ((255, 0, 0, 255), (255, 0, 0, 255)), + ((0, 0, 255, 255), (0, 0, 255, 255)), + ((255, 0, 0, 255), (0, 0, 255, 255)), + ] + + im_list = [] + for i in range(len(circles)): + # Transparent BG + img = Image.new("RGBA", (100, 100), (255, 255, 255, 0)) + + # Two circles per frame + d = ImageDraw.Draw(img) + d.ellipse([(0, 30), (40, 70)], fill=circles[i][0]) + d.ellipse([(60, 30), (100, 70)], fill=circles[i][1]) + + im_list.append(img) + + im_list[0].save( + out, save_all=True, append_images=im_list[1:], disposal=2, transparency=0 + ) + + img = Image.open(out) + + for i, colours in enumerate(circles): + img.seek(i) + rgb_img = img.convert("RGBA") + + # Check left circle is correct colour + self.assertEqual(rgb_img.getpixel((20, 50)), colours[0]) + + # Check right circle is correct colour + self.assertEqual(rgb_img.getpixel((80, 50)), colours[1]) + + # Check BG is correct colour + self.assertEqual(rgb_img.getpixel((1, 1)), (255, 255, 255, 0)) + + def test_dispose2_background(self): + out = self.tempfile("temp.gif") + + im_list = [] + + im = Image.new("P", (100, 100)) + d = ImageDraw.Draw(im) + d.rectangle([(50, 0), (100, 100)], fill="#f00") + d.rectangle([(0, 0), (50, 100)], fill="#0f0") + im_list.append(im) + + im = Image.new("P", (100, 100)) + d = ImageDraw.Draw(im) + d.rectangle([(0, 0), (100, 50)], fill="#f00") + d.rectangle([(0, 50), (100, 100)], fill="#0f0") + im_list.append(im) + + im_list[0].save( + out, save_all=True, append_images=im_list[1:], disposal=[0, 2], background=1 + ) + + im = Image.open(out) + im.seek(1) + self.assertEqual(im.getpixel((0, 0)), 0) + def test_iss634(self): img = Image.open("Tests/images/iss634.gif") # seek to the second frame diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 236ce70f6..29fc592e2 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -122,7 +122,7 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (278, 374)) - self.assertEqual(im.tile[0][:3], ("tiff_adobe_deflate", (0, 0, 278, 374), 0)) + self.assertEqual(im.tile[0][:3], ("libtiff", (0, 0, 278, 374), 0)) im.load() self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") @@ -710,10 +710,10 @@ class TestFileLibTiff(LibTiffTestCase): im.tile, [ ( - "tiff_adobe_deflate", + "libtiff", (0, 0, 100, 40), 0, - ("RGB;16N", "tiff_adobe_deflate", False), + ("RGB;16N", "tiff_adobe_deflate", False, 8), ) ], ) @@ -727,7 +727,8 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.mode, "RGBA") self.assertEqual(im.size, (100, 40)) self.assertEqual( - im.tile, [("tiff_lzw", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False))] + im.tile, + [("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236))], ) im.load() @@ -746,7 +747,7 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (256, 256)) self.assertEqual( - im.tile, [("jpeg", (0, 0, 256, 256), 0, ("RGB", "jpeg", False))] + im.tile, [("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))] ) im.load() diff --git a/Tests/test_image_entropy.py b/Tests/test_image_entropy.py new file mode 100644 index 000000000..bc792bca6 --- /dev/null +++ b/Tests/test_image_entropy.py @@ -0,0 +1,17 @@ +from .helper import PillowTestCase, hopper + + +class TestImageEntropy(PillowTestCase): + def test_entropy(self): + def entropy(mode): + return hopper(mode).entropy() + + self.assertAlmostEqual(entropy("1"), 0.9138803254693582) + self.assertAlmostEqual(entropy("L"), 7.06650513081286) + self.assertAlmostEqual(entropy("I"), 7.06650513081286) + self.assertAlmostEqual(entropy("F"), 7.06650513081286) + self.assertAlmostEqual(entropy("P"), 5.0530452472519745) + self.assertAlmostEqual(entropy("RGB"), 8.821286587714319) + self.assertAlmostEqual(entropy("RGBA"), 7.42724306524488) + self.assertAlmostEqual(entropy("CMYK"), 7.4272430652448795) + self.assertAlmostEqual(entropy("YCbCr"), 7.698360534903628) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 3388c2055..0ee3b979e 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -420,6 +420,10 @@ class TestImageFont(PillowTestCase): self.assertRaises(IOError, ImageFont.load_path, filename) self.assertRaises(IOError, ImageFont.truetype, filename) + def test_load_non_font_bytes(self): + with open("Tests/images/hopper.jpg", "rb") as f: + self.assertRaises(IOError, ImageFont.truetype, f) + def test_default_font(self): # Arrange txt = 'This is a "better than nothing" default font.' diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 38645f133..5d90dc4c5 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -74,3 +74,25 @@ class TestImageSequence(PillowTestCase): im.seek(0) color2 = im.getpalette()[0:3] self.assertEqual(color1, color2) + + def test_all_frames(self): + # Test a single image + im = Image.open("Tests/images/iss634.gif") + ims = ImageSequence.all_frames(im) + + self.assertEqual(len(ims), 42) + for i, im_frame in enumerate(ims): + self.assertFalse(im_frame is im) + + im.seek(i) + self.assert_image_equal(im, im_frame) + + # Test a series of images + ims = ImageSequence.all_frames([im, hopper(), im]) + self.assertEqual(len(ims), 85) + + # Test an operation + ims = ImageSequence.all_frames(im, lambda im_frame: im_frame.rotate(90)) + for i, im_frame in enumerate(ims): + im.seek(i) + self.assert_image_equal(im.rotate(90), im_frame) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 598e73abd..cfbcb8b6b 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -16,7 +16,7 @@ Open, rotate, and display an image (using the default viewer) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The following script loads an image, rotates it 45 degrees, and displays it -using an external viewer (usually xv on Unix, and the paint program on +using an external viewer (usually xv on Unix, and the Paint program on Windows). .. code-block:: python @@ -116,15 +116,66 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space: rgb2xyz = ( 0.412453, 0.357580, 0.180423, 0, 0.212671, 0.715160, 0.072169, 0, - 0.019334, 0.119193, 0.950227, 0 ) + 0.019334, 0.119193, 0.950227, 0) out = im.convert("RGB", rgb2xyz) .. automethod:: PIL.Image.Image.copy .. automethod:: PIL.Image.Image.crop + +This crops the input image with the provided coordinates: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + + # The crop method from the Image module takes four coordinates as input. + # The right can also be represented as (left+width) + # and lower can be represented as (upper+height). + (left, upper, right, lower) = (20, 20, 100, 100) + + # Here the image "im" is cropped and assigned to new variable im_crop + im_crop = im.crop((left, upper, right, lower)) + + .. automethod:: PIL.Image.Image.draft .. automethod:: PIL.Image.Image.filter + +This blurs the input image using a filter from the ``ImageFilter`` module: + +.. code-block:: python + + from PIL import Image, ImageFilter + + im = Image.open("hopper.jpg") + + # Blur the input image using the filter ImageFilter.BLUR + im_blurred = im.filter(filter=ImageFilter.BLUR) + .. automethod:: PIL.Image.Image.getbands + +This helps to get the bands of the input image: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + print(im.getbands()) # Returns ('R', 'G', 'B') + .. automethod:: PIL.Image.Image.getbbox + +This helps to get the bounding box coordinates of the input image: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + print(im.getbbox()) + # Returns four coordinates in the format (left, upper, right, lower) + .. automethod:: PIL.Image.Image.getcolors .. automethod:: PIL.Image.Image.getdata .. automethod:: PIL.Image.Image.getextrema @@ -140,8 +191,35 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space: .. automethod:: PIL.Image.Image.putpixel .. automethod:: PIL.Image.Image.quantize .. automethod:: PIL.Image.Image.resize + +This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + + # Provide the target width and height of the image + (width, height) = (im.width // 2, im.height // 2) + im_resized = im.resize((width, height)) + .. automethod:: PIL.Image.Image.remap_palette .. automethod:: PIL.Image.Image.rotate + +This rotates the input image by ``theta`` degrees counter clockwise: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + + # Rotate the image by 60 degrees counter clockwise + theta = 60 + # Angle is in degrees counter clockwise + im_rotated = im.rotate(angle=theta) + .. automethod:: PIL.Image.Image.save .. automethod:: PIL.Image.Image.seek .. automethod:: PIL.Image.Image.show @@ -154,6 +232,21 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space: .. automethod:: PIL.Image.Image.tostring .. automethod:: PIL.Image.Image.transform .. automethod:: PIL.Image.Image.transpose + +This flips the input image by using the ``Image.FLIP_LEFT_RIGHT`` method. + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + + # Flip the image from left to right + im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT) + # To flip the image from top to bottom, + # use the method "Image.FLIP_TOP_BOTTOM" + + .. automethod:: PIL.Image.Image.verify .. automethod:: PIL.Image.Image.fromstring diff --git a/docs/releasenotes/6.1.0.rst b/docs/releasenotes/6.1.0.rst index 98568fb52..851dcb2d0 100644 --- a/docs/releasenotes/6.1.0.rst +++ b/docs/releasenotes/6.1.0.rst @@ -11,6 +11,14 @@ An optional ``include_layered_windows`` parameter has been added to ``ImageGrab. defaulting to ``False``. If true, layered windows will be included in the resulting image on Windows. +ImageSequence.all_frames +^^^^^^^^^^^^^^^^^^^^^^^^ + +A new method to facilitate applying a given function to all frames in an image, or to +all frames in a list of images. The frames are returned as a list of separate images. +For example, ``ImageSequence.all_frames(im, lambda im_frame: im_frame.rotate(90))`` +could be used to return all frames from an image, each rotated 90 degrees. + Variation fonts ^^^^^^^^^^^^^^^ diff --git a/selftest.py b/selftest.py index 3e4112ffe..4dea3363c 100755 --- a/selftest.py +++ b/selftest.py @@ -90,6 +90,8 @@ def testimage(): 2 >>> len(im.histogram()) 768 + >>> '%.7f' % im.entropy() + '8.8212866' >>> _info(im.point(list(range(256))*3)) (None, 'RGB', (128, 128)) >>> _info(im.resize((64, 64))) diff --git a/setup.py b/setup.py index 5ccf7133d..2c25543a9 100755 --- a/setup.py +++ b/setup.py @@ -386,8 +386,8 @@ class pil_build_ext(build_ext): _add_directory(library_dirs, lib_root) _add_directory(include_dirs, include_root) - # respect CFLAGS/LDFLAGS - for k in ("CFLAGS", "LDFLAGS"): + # respect CFLAGS/CPPFLAGS/LDFLAGS + for k in ("CFLAGS", "CPPFLAGS", "LDFLAGS"): if k in os.environ: for match in re.finditer(r"-I([^\s]+)", os.environ[k]): _add_directory(include_dirs, match.group(1)) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index b35420328..bbf1c603f 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -426,6 +426,7 @@ def _write_multiple_frames(im, fp, palette): im_frames = [] frame_count = 0 + background_im = None for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): for im_frame in ImageSequence.Iterator(imSequence): # a copy is required here since seek can still mutate the image @@ -445,11 +446,22 @@ def _write_multiple_frames(im, fp, palette): if im_frames: # delta frame previous = im_frames[-1] - if _get_palette_bytes(im_frame) == _get_palette_bytes(previous["im"]): - delta = ImageChops.subtract_modulo(im_frame, previous["im"]) + if encoderinfo.get("disposal") == 2: + if background_im is None: + background = _get_background( + im, + im.encoderinfo.get("background", im.info.get("background")), + ) + background_im = Image.new("P", im_frame.size, background) + background_im.putpalette(im_frames[0]["im"].palette) + base_im = background_im + else: + base_im = previous["im"] + if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im): + delta = ImageChops.subtract_modulo(im_frame, base_im) else: delta = ImageChops.subtract_modulo( - im_frame.convert("RGB"), previous["im"].convert("RGB") + im_frame.convert("RGB"), base_im.convert("RGB") ) bbox = delta.getbbox() if not bbox: @@ -683,10 +695,12 @@ def _get_color_table_size(palette_bytes): # calculate the palette size for the header import math - color_table_size = int(math.ceil(math.log(len(palette_bytes) // 3, 2))) - 1 - if color_table_size < 0: - color_table_size = 0 - return color_table_size + if not palette_bytes: + return 0 + elif len(palette_bytes) < 9: + return 1 + else: + return int(math.ceil(math.log(len(palette_bytes) // 3, 2))) - 1 def _get_header_palette(palette_bytes): @@ -717,6 +731,18 @@ def _get_palette_bytes(im): return im.palette.palette +def _get_background(im, infoBackground): + background = 0 + if infoBackground: + background = infoBackground + if isinstance(background, tuple): + # WebPImagePlugin stores an RGBA value in info["background"] + # So it must be converted to the same format as GifImagePlugin's + # info["background"] - a global color table index + background = im.palette.getcolor(background) + return background + + def _get_global_header(im, info): """Return a list of strings representing a GIF header""" @@ -736,14 +762,7 @@ def _get_global_header(im, info): if im.info.get("version") == b"89a": version = b"89a" - background = 0 - if "background" in info: - background = info["background"] - if isinstance(background, tuple): - # WebPImagePlugin stores an RGBA value in info["background"] - # So it must be converted to the same format as GifImagePlugin's - # info["background"] - a global color table index - background = im.palette.getcolor(background) + background = _get_background(im, info.get("background")) palette_bytes = _get_palette_bytes(im) color_table_size = _get_color_table_size(palette_bytes) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ee7d2c523..973325d8b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1405,6 +1405,7 @@ class Image(object): bi-level image (mode "1") or a greyscale image ("L"). :param mask: An optional mask. + :param extrema: An optional tuple of manually-specified extrema. :returns: A list containing pixel counts. """ self.load() @@ -1417,6 +1418,32 @@ class Image(object): return self.im.histogram(extrema) return self.im.histogram() + def entropy(self, mask=None, extrema=None): + """ + Calculates and returns the entropy for the image. + + A bilevel image (mode "1") is treated as a greyscale ("L") + image by this method. + + If a mask is provided, the method employs the histogram for + those parts of the image where the mask image is non-zero. + The mask image must have the same size as the image, and be + either a bi-level image (mode "1") or a greyscale image ("L"). + + :param mask: An optional mask. + :param extrema: An optional tuple of manually-specified extrema. + :returns: A float value representing the image entropy + """ + self.load() + if mask: + mask.load() + return self.im.entropy((0, 0), mask.im) + if self.mode in ("I", "F"): + if extrema is None: + extrema = self.getextrema() + return self.im.entropy(extrema) + return self.im.entropy() + def offset(self, xoffset, yoffset=None): raise NotImplementedError( "offset() has been removed. Please call ImageChops.offset() instead." diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 7074a70c0..f43f95b9a 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -545,6 +545,8 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): try: return freetype(font) except IOError: + if not isPath(font): + raise ttf_filename = os.path.basename(font) dirs = [] diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py index 84199fe27..f9be92d48 100644 --- a/src/PIL/ImageSequence.py +++ b/src/PIL/ImageSequence.py @@ -54,3 +54,25 @@ class Iterator(object): def next(self): return self.__next__() + + +def all_frames(im, func=None): + """ + Applies a given function to all frames in an image or a list of images. + The frames are returned as a list of separate images. + + :param im: An image, or a list of images. + :param func: The function to apply to all of the image frames. + :returns: A list of images. + """ + if not isinstance(im, list): + im = [im] + + ims = [] + for imSequence in im: + current = imSequence.tell() + + ims += [im_frame.copy() for im_frame in Iterator(imSequence)] + + imSequence.seek(current) + return [func(im) for im in ims] if func else ims diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index d8634ba8a..3a26f739b 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1124,7 +1124,7 @@ class TiffImageFile(ImageFile.ImageFile): # (self._compression, (extents tuple), # 0, (rawmode, self._compression, fp)) extents = self.tile[0][1] - args = list(self.tile[0][3]) + [self.tag_v2.offset] + args = list(self.tile[0][3]) # To be nice on memory footprint, if there's a # file descriptor, use that instead of reading @@ -1331,8 +1331,8 @@ class TiffImageFile(ImageFile.ImageFile): # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds - a = (rawmode, self._compression, False) - self.tile.append((self._compression, (0, 0, xsize, ysize), 0, a)) + a = (rawmode, self._compression, False, self.tag_v2.offset) + self.tile.append(("libtiff", (0, 0, xsize, ysize), 0, a)) elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2: # striped image diff --git a/src/PIL/_binary.py b/src/PIL/_binary.py index 80c86dab8..e5ee0bf28 100644 --- a/src/PIL/_binary.py +++ b/src/PIL/_binary.py @@ -37,8 +37,8 @@ def i16le(c, o=0): """ Converts a 2-bytes (16 bits) string to an unsigned integer. - c: string containing bytes to convert - o: offset of bytes to convert in string + :param c: string containing bytes to convert + :param o: offset of bytes to convert in string """ return unpack_from(" + /* Configuration stuff. Feel free to undef things you don't need. */ #define WITH_IMAGECHOPS /* ImageChops support */ #define WITH_IMAGEDRAW /* ImageDraw support */ @@ -1176,59 +1179,68 @@ _getpixel(ImagingObject* self, PyObject* args) return getpixel(self->image, self->access, x, y); } +union hist_extrema { + UINT8 u[2]; + INT32 i[2]; + FLOAT32 f[2]; +}; + +static union hist_extrema* +parse_histogram_extremap(ImagingObject* self, PyObject* extremap, + union hist_extrema* ep) +{ + int i0, i1; + double f0, f1; + + if (extremap) { + switch (self->image->type) { + case IMAGING_TYPE_UINT8: + if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) + return NULL; + ep->u[0] = CLIP8(i0); + ep->u[1] = CLIP8(i1); + break; + case IMAGING_TYPE_INT32: + if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) + return NULL; + ep->i[0] = i0; + ep->i[1] = i1; + break; + case IMAGING_TYPE_FLOAT32: + if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1)) + return NULL; + ep->f[0] = (FLOAT32) f0; + ep->f[1] = (FLOAT32) f1; + break; + default: + return NULL; + } + } else { + return NULL; + } + return ep; +} + static PyObject* _histogram(ImagingObject* self, PyObject* args) { ImagingHistogram h; PyObject* list; int i; - union { - UINT8 u[2]; - INT32 i[2]; - FLOAT32 f[2]; - } extrema; - void* ep; - int i0, i1; - double f0, f1; + union hist_extrema extrema; + union hist_extrema* ep; PyObject* extremap = NULL; ImagingObject* maskp = NULL; if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) - return NULL; - - if (extremap) { - ep = &extrema; - switch (self->image->type) { - case IMAGING_TYPE_UINT8: - if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) - return NULL; - /* FIXME: clip */ - extrema.u[0] = i0; - extrema.u[1] = i1; - break; - case IMAGING_TYPE_INT32: - if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) - return NULL; - extrema.i[0] = i0; - extrema.i[1] = i1; - break; - case IMAGING_TYPE_FLOAT32: - if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1)) - return NULL; - extrema.f[0] = (FLOAT32) f0; - extrema.f[1] = (FLOAT32) f1; - break; - default: - ep = NULL; - break; - } - } else - ep = NULL; + return NULL; + /* Using a var to avoid allocations. */ + ep = parse_histogram_extremap(self, extremap, &extrema); h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep); if (!h) - return NULL; + return NULL; /* Build an integer list containing the histogram */ list = PyList_New(h->bands * 256); @@ -1243,11 +1255,59 @@ _histogram(ImagingObject* self, PyObject* args) PyList_SetItem(list, i, item); } + /* Destroy the histogram structure */ ImagingHistogramDelete(h); return list; } +static PyObject* +_entropy(ImagingObject* self, PyObject* args) +{ + ImagingHistogram h; + int idx, length; + long sum; + double entropy, fsum, p; + union hist_extrema extrema; + union hist_extrema* ep; + + PyObject* extremap = NULL; + ImagingObject* maskp = NULL; + if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) + return NULL; + + /* Using a local var to avoid allocations. */ + ep = parse_histogram_extremap(self, extremap, &extrema); + h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep); + + if (!h) + return NULL; + + /* Calculate the histogram entropy */ + /* First, sum the histogram data */ + length = h->bands * 256; + sum = 0; + for (idx = 0; idx < length; idx++) { + sum += h->histogram[idx]; + } + + /* Next, normalize the histogram data, */ + /* using the histogram sum value */ + fsum = (double)sum; + entropy = 0.0; + for (idx = 0; idx < length; idx++) { + p = (double)h->histogram[idx] / fsum; + if (p != 0.0) { + entropy += p * log(p) * M_LOG2E; + } + } + + /* Destroy the histogram structure */ + ImagingHistogramDelete(h); + + return PyFloat_FromDouble(-entropy); +} + #ifdef WITH_MODEFILTER static PyObject* _modefilter(ImagingObject* self, PyObject* args) @@ -3193,6 +3253,7 @@ static struct PyMethodDef methods[] = { {"expand", (PyCFunction)_expand_image, 1}, {"filter", (PyCFunction)_filter, 1}, {"histogram", (PyCFunction)_histogram, 1}, + {"entropy", (PyCFunction)_entropy, 1}, #ifdef WITH_MODEFILTER {"modefilter", (PyCFunction)_modefilter, 1}, #endif @@ -3625,6 +3686,12 @@ _set_blocks_max(PyObject* self, PyObject* args) "blocks_max should be greater than 0"); return NULL; } + else if ( blocks_max > SIZE_MAX/sizeof(ImagingDefaultArena.blocks_pool[0])) { + PyErr_SetString(PyExc_ValueError, + "blocks_max is too large"); + return NULL; + } + if ( ! ImagingMemorySetBlocksMax(&ImagingDefaultArena, blocks_max)) { ImagingError_MemoryError(); @@ -3912,4 +3979,3 @@ init_imaging(void) setup_module(m); } #endif - diff --git a/src/_imagingft.c b/src/_imagingft.c index f6bd787ef..2567e30d1 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -265,7 +265,7 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) return NULL; } - if (!PyArg_ParseTupleAndKeywords(args, kw, "eti|is"PY_ARG_BYTES_LENGTH"i", + if (!PyArg_ParseTupleAndKeywords(args, kw, "etn|ns"PY_ARG_BYTES_LENGTH"n", kwlist, Py_FileSystemDefaultEncoding, &filename, &size, &index, &encoding, &font_bytes, @@ -315,6 +315,7 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) if (error) { if (self->font_bytes) { PyMem_Free(self->font_bytes); + self->font_bytes = NULL; } Py_DECREF(self); return geterror(error); @@ -1006,6 +1007,9 @@ font_render(FontObject* self, PyObject* args) num_coords = PyObject_Length(axes); coords = malloc(2 * sizeof(coords)); + if (coords == NULL) { + return PyErr_NoMemory(); + } for (i = 0; i < num_coords; i++) { item = PyList_GET_ITEM(axes, i); if (PyFloat_Check(item)) @@ -1015,6 +1019,7 @@ font_render(FontObject* self, PyObject* args) else if (PyNumber_Check(item)) coord = PyFloat_AsDouble(item); else { + free(coords); PyErr_SetString(PyExc_TypeError, "list must contain numbers"); return NULL; } @@ -1022,6 +1027,7 @@ font_render(FontObject* self, PyObject* args) } error = FT_Set_Var_Design_Coordinates(self->face, num_coords, coords); + free(coords); if (error) return geterror(error); diff --git a/src/decode.c b/src/decode.c index 10e740aaf..79133f48f 100644 --- a/src/decode.c +++ b/src/decode.c @@ -503,9 +503,9 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args) char* rawmode; char* compname; int fp; - int ifdoffset; + uint32 ifdoffset; - if (! PyArg_ParseTuple(args, "sssii", &mode, &rawmode, &compname, &fp, &ifdoffset)) + if (! PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset)) return NULL; TRACE(("new tiff decoder %s\n", compname)); diff --git a/src/encode.c b/src/encode.c index 4db20e5c2..c6c46ec5a 100644 --- a/src/encode.c +++ b/src/encode.c @@ -126,7 +126,7 @@ _encode(ImagingEncoderObject* encoder, PyObject* args) Py_ssize_t bufsize = 16384; - if (!PyArg_ParseTuple(args, "|i", &bufsize)) + if (!PyArg_ParseTuple(args, "|n", &bufsize)) return NULL; buf = PyBytes_FromStringAndSize(NULL, bufsize); @@ -180,7 +180,7 @@ _encode_to_file(ImagingEncoderObject* encoder, PyObject* args) Py_ssize_t fh; Py_ssize_t bufsize = 16384; - if (!PyArg_ParseTuple(args, "i|i", &fh, &bufsize)) + if (!PyArg_ParseTuple(args, "n|n", &fh, &bufsize)) return NULL; /* Allocate an encoder buffer */ @@ -229,7 +229,7 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args) x0 = y0 = x1 = y1 = 0; /* FIXME: should publish the ImagingType descriptor */ - if (!PyArg_ParseTuple(args, "O|(iiii)", &op, &x0, &y0, &x1, &y1)) + if (!PyArg_ParseTuple(args, "O|(nnnn)", &op, &x0, &y0, &x1, &y1)) return NULL; im = PyImaging_AsImaging(op); if (!im) @@ -409,7 +409,7 @@ PyImaging_GifEncoderNew(PyObject* self, PyObject* args) char *rawmode; Py_ssize_t bits = 8; Py_ssize_t interlace = 0; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits, &interlace)) + if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace)) return NULL; encoder = PyImaging_EncoderNew(sizeof(GIFENCODERSTATE)); @@ -441,7 +441,7 @@ PyImaging_PcxEncoderNew(PyObject* self, PyObject* args) char *rawmode; Py_ssize_t bits = 8; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits)) { + if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &bits)) { return NULL; } @@ -474,7 +474,7 @@ PyImaging_RawEncoderNew(PyObject* self, PyObject* args) Py_ssize_t stride = 0; Py_ssize_t ystep = 1; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) + if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep)) return NULL; encoder = PyImaging_EncoderNew(0); @@ -506,7 +506,7 @@ PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args) char *rawmode; Py_ssize_t ystep = 1; - if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &ystep)) + if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep)) return NULL; encoder = PyImaging_EncoderNew(0); @@ -567,7 +567,7 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args) Py_ssize_t compress_type = -1; char* dictionary = NULL; Py_ssize_t dictionary_size = 0; - if (!PyArg_ParseTuple(args, "ss|iii"PY_ARG_BYTES_LENGTH, &mode, &rawmode, + if (!PyArg_ParseTuple(args, "ss|nnn"PY_ARG_BYTES_LENGTH, &mode, &rawmode, &optimize, &compress_level, &compress_type, &dictionary, &dictionary_size)) @@ -650,7 +650,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) PyObject *item; - if (! PyArg_ParseTuple(args, "sssisOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types)) { + if (! PyArg_ParseTuple(args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types)) { return NULL; } @@ -1025,7 +1025,7 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) char* rawExif = NULL; Py_ssize_t rawExifLen = 0; - if (!PyArg_ParseTuple(args, "ss|iiiiiiiiO"PY_ARG_BYTES_LENGTH""PY_ARG_BYTES_LENGTH, + if (!PyArg_ParseTuple(args, "ss|nnnnnnnnO"PY_ARG_BYTES_LENGTH""PY_ARG_BYTES_LENGTH, &mode, &rawmode, &quality, &progressive, &smooth, &optimize, &streamtype, &xdpi, &ydpi, &subsampling, &qtables, &extra, &extra_size, @@ -1095,7 +1095,6 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) #endif - /* -------------------------------------------------------------------- */ /* JPEG 2000 */ /* -------------------------------------------------------------------- */ @@ -1141,7 +1140,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) OPJ_CINEMA_MODE cine_mode; Py_ssize_t fd = -1; - if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format, + if (!PyArg_ParseTuple(args, "ss|OOOsOnOOOssn", &mode, &format, &offset, &tile_offset, &tile_size, &quality_mode, &quality_layers, &num_resolutions, &cblk_size, &precinct_size, diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index 56aebf58e..000ee384a 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -251,6 +251,15 @@ ImagingPackRGB(UINT8* out, const UINT8* in, int pixels) { int i = 0; /* RGB triplets */ +#ifdef __sparc + /* SPARC CPUs cannot read integers from nonaligned addresses. */ + for (; i < pixels; i++) { + out[0] = in[R]; + out[1] = in[G]; + out[2] = in[B]; + out += 3; in += 4; + } +#else for (; i < pixels-1; i++) { ((UINT32*)out)[0] = ((UINT32*)in)[i]; out += 3; @@ -261,6 +270,7 @@ ImagingPackRGB(UINT8* out, const UINT8* in, int pixels) out[2] = in[i*4+B]; out += 3; } +#endif } void diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index e73467fab..3ade7377c 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -147,7 +147,7 @@ void _tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) { (void) hdata; (void) base; (void) size; } -int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset) { +int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { TIFFSTATE *clientstate = (TIFFSTATE *)state->context; TRACE(("initing libtiff\n")); @@ -194,6 +194,9 @@ int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) { } swap_line = (UINT32*)malloc(swap_line_size); + if (swap_line == NULL) { + return -1; + } /* * For some reason the TIFFReadRGBATile() function chooses the * lower left corner as the origin. Vertically mirror scanlines. diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h index 6cb7e53a2..08ef35cfd 100644 --- a/src/libImaging/TiffDecode.h +++ b/src/libImaging/TiffDecode.h @@ -43,7 +43,7 @@ typedef struct { -extern int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset); +extern int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset); extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); extern int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length); extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 9b73dd60d..c1e8f25d0 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -480,6 +480,16 @@ void ImagingUnpackRGB(UINT8* _out, const UINT8* in, int pixels) { int i = 0; +#ifdef __sparc + /* SPARC CPUs cannot read integers from nonaligned addresses. */ + for (; i < pixels; i++) { + _out[R] = in[0]; + _out[G] = in[1]; + _out[B] = in[2]; + _out[A] = 255; + _out += 4; in += 3; + } +#else UINT32* out = (UINT32*) _out; /* RGB triplets */ for (; i < pixels-1; i++) { @@ -490,6 +500,7 @@ ImagingUnpackRGB(UINT8* _out, const UINT8* in, int pixels) out[i] = MAKE_UINT32(in[0], in[1], in[2], 255); in += 3; } +#endif } void @@ -1085,22 +1096,44 @@ static void copy4skip1(UINT8* _out, const UINT8* in, int pixels) { int i; +#ifdef __sparc + /* SPARC CPUs cannot read integers from nonaligned addresses. */ + for (i = 0; i < pixels; i++) { + _out[0] = in[0]; + _out[1] = in[1]; + _out[2] = in[2]; + _out[3] = in[3]; + _out += 4; in += 5; + } +#else UINT32* out = (UINT32*) _out; for (i = 0; i < pixels; i++) { out[i] = *(UINT32*)&in[0]; in += 5; } +#endif } static void copy4skip2(UINT8* _out, const UINT8* in, int pixels) { int i; +#ifdef __sparc + /* SPARC CPUs cannot read integers from nonaligned addresses. */ + for (i = 0; i < pixels; i++) { + _out[0] = in[0]; + _out[1] = in[1]; + _out[2] = in[2]; + _out[3] = in[3]; + _out += 4; in += 6; + } +#else UINT32* out = (UINT32*) _out; for (i = 0; i < pixels; i++) { out[i] = *(UINT32*)&in[0]; in += 6; } +#endif } diff --git a/src/path.c b/src/path.c index eb1e065f9..5f0541b0b 100644 --- a/src/path.c +++ b/src/path.c @@ -133,8 +133,8 @@ PyPath_Flatten(PyObject* data, double **pxy) /* Assume the buffer contains floats */ Py_buffer buffer; if (PyImaging_GetBuffer(data, &buffer) == 0) { - int n = buffer.len / (2 * sizeof(float)); float *ptr = (float*) buffer.buf; + n = buffer.len / (2 * sizeof(float)); xy = alloc_array(n); if (!xy) return -1;