diff --git a/.coveragerc b/.coveragerc index bd93c4749..87e3e968f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -9,3 +9,6 @@ exclude_lines = # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: + # Don't complain about debug code + if Image.DEBUG: + if DEBUG: \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index d4880847c..57c2cb94b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: python notifications: irc: "chat.freenode.net#pil" -env: MAX_CONCURRENCY=4 +env: MAX_CONCURRENCY=4 NOSE_PROCESSES=4 NOSE_PROCESS_TIMEOUT=30 python: - "pypy" @@ -14,9 +14,9 @@ python: - 3.4 install: - - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake" + - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "pip install cffi" - - "pip install coveralls nose pyroma" + - "pip install coveralls nose pyroma nose-cov" - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp @@ -35,10 +35,13 @@ script: - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests Tests/test_*.py; fi # Cover the others - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose Tests/test_*.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --parallel-mode --include=PIL/* selftest.py; fi + # write html report, then ignore. Coverage needs to be combined first + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py; fi after_success: + - ls -l .coverage* + - coverage combine - coverage report - coveralls - pip install pep8 pyflakes diff --git a/CHANGES.rst b/CHANGES.rst index ea5f56b95..f19ae0683 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Imagedraw rewrite + [terseus, wiredfool] + +- Add support for multithreaded test execution + [wiredfool] + - Prevent shell injection #748 [mbrown1413, wiredfool] @@ -495,6 +501,10 @@ Changelog (Pillow) 2.0.0 (2013-03-15) ------------------ +.. Note:: Special thanks to Christoph Gohlke and Eric Soroos for assisting with a pre-PyCon 2013 release! + +- Many other bug fixes and enhancements by many other people. + - Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.) [fluggo] @@ -518,10 +528,6 @@ Changelog (Pillow) - Added support for PNG images with transparency palette. [d-schmidt] -- Many other bug fixes and enhancements by many other people (see commit log and/or docs/CONTRIBUTORS.txt). - -- Special thanks to Christoph Gohlke and Eric Soroos for rallying around the effort to get a release out for PyCon 2013. - 1.7.8 (2012-11-01) ------------------ @@ -594,44 +600,55 @@ Changelog (Pillow) [elro] - Doc fixes + [aclark] 1.5 (11/28/2010) ---------------- - Module and package fixes + [aclark] 1.4 (11/28/2010) ---------------- - Doc fixes + [aclark] 1.3 (11/28/2010) ---------------- - Add support for /lib64 and /usr/lib64 library directories on Linux + [aclark] + - Doc fixes + [aclark] 1.2 (08/02/2010) ---------------- -- On OS X also check for freetype2 in the X11 path [jezdez] -- Doc fixes [aclark] +- On OS X also check for freetype2 in the X11 path + [jezdez] + +- Doc fixes + [aclark] 1.1 (07/31/2010) ---------------- - Removed setuptools_hg requirement + [aclark] + - Doc fixes + [aclark] 1.0 (07/30/2010) ---------------- -- Forked PIL based on Hanno Schlichting's re-packaging - (http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz) +- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required. +- Forked PIL based on `Hanno Schlichting's re-packaging `_ + [aclark] -- Remove support for importing from the standard namespace - -.. Note:: What follows is the original PIL 1.1.7 CHANGES file contents +.. Note:: What follows is the original PIL 1.1.7 CHANGES :: diff --git a/Makefile b/Makefile index 4f6a00711..0637e901f 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ + + pre: virtualenv . bin/pip install -r requirements.txt @@ -9,3 +11,32 @@ pre: check-manifest pyroma . viewdoc + +clean: + python setup.py clean + rm PIL/*.so || true + find . -name __pycache__ | xargs rm -r + +install: + python setup.py install + python selftest.py --installed + +test: install + python test-installed.py + +inplace: clean + python setup.py build_ext --inplace + +coverage: +# requires nose-cov + coverage erase + coverage run --parallel-mode --include=PIL/* selftest.py + nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py +# doesn't combine properly before report, +# writing report instead of displaying invalid report + rm -r htmlcov || true + coverage combine + coverage report + +test-dep: + pip install coveralls nose nose-cov pep8 pyflakes diff --git a/PIL/__init__.py b/PIL/__init__.py index 14daee5ca..d446aa19b 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.4.0' # Pillow +PILLOW_VERSION = '2.5.0' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/README.rst b/README.rst index 0831a6b81..224f98f03 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Pillow *Python Imaging Library (Fork)* -Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. +Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation `_. .. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master :target: https://travis-ci.org/python-pillow/Pillow @@ -20,4 +20,3 @@ Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Pyt .. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master :target: https://coveralls.io/r/python-pillow/Pillow?branch=master -The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details, and more. diff --git a/Tests/helper.py b/Tests/helper.py index 416586c78..1c9851e25 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -3,39 +3,30 @@ Helper functions. """ from __future__ import print_function import sys +import tempfile +import os +import glob if sys.version_info[:2] <= (2, 6): import unittest2 as unittest else: import unittest - -# This should be imported into every test_XXX.py file to report -# any remaining temp files at the end of the run. def tearDownModule(): - import glob - import os - import tempfile - temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - tempfiles = glob.glob(os.path.join(temp_root, "temp_*")) - if tempfiles: - print("===", "remaining temporary files") - for file in tempfiles: - print(file) - print("-"*68) - + #remove me later + pass class PillowTestCase(unittest.TestCase): - currentResult = None # holds last result object passed to run method - _tempfiles = [] + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + self.currentResult = None # holds last result object passed to run method def run(self, result=None): - self.addCleanup(self.delete_tempfiles) self.currentResult = result # remember result for use later unittest.TestCase.run(self, result) # call superclass run method - def delete_tempfiles(self): + def delete_tempfile(self, path): try: ok = self.currentResult.wasSuccessful() except AttributeError: # for nosetests @@ -44,19 +35,12 @@ class PillowTestCase(unittest.TestCase): if ok: # only clean out tempfiles if test passed - import os - import os.path - import tempfile - for file in self._tempfiles: - try: - os.remove(file) - except OSError: - pass # report? - temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') try: - os.rmdir(temp_root) + os.remove(path) except OSError: - pass + pass # report? + else: + print("=== orphaned temp file: %s" %path) def assert_almost_equal(self, a, b, msg=None, eps=1e-6): self.assertLess( @@ -139,27 +123,13 @@ class PillowTestCase(unittest.TestCase): self.assertTrue(found) return result - def tempfile(self, template, *extra): - import os - import os.path - import sys - import tempfile - files = [] - root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - try: - os.mkdir(root) - except OSError: - pass - for temp in (template,) + extra: - assert temp[:5] in ("temp.", "temp_") - name = os.path.basename(sys.argv[0]) - name = temp[:4] + os.path.splitext(name)[0][4:] - name = name + "_%d" % len(self._tempfiles) + temp[4:] - name = os.path.join(root, name) - files.append(name) - self._tempfiles.extend(files) - return files[0] - + def tempfile(self, template): + assert template[:5] in ("temp.", "temp_") + (fd, path) = tempfile.mkstemp(template[4:], template[:4]) + os.close(fd) + + self.addCleanup(self.delete_tempfile, path) + return path # helpers diff --git a/Tests/images/imagedraw/line_horizontal_slope1px_w2px.png b/Tests/images/imagedraw/line_horizontal_slope1px_w2px.png new file mode 100644 index 000000000..aaed76786 Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_slope1px_w2px.png differ diff --git a/Tests/images/imagedraw/line_horizontal_w101px.png b/Tests/images/imagedraw/line_horizontal_w101px.png new file mode 100644 index 000000000..bbcc8133f Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_w101px.png differ diff --git a/Tests/images/imagedraw/line_horizontal_w2px_inverted.png b/Tests/images/imagedraw/line_horizontal_w2px_inverted.png new file mode 100644 index 000000000..a2bb3bbab Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_w2px_inverted.png differ diff --git a/Tests/images/imagedraw/line_horizontal_w2px_normal.png b/Tests/images/imagedraw/line_horizontal_w2px_normal.png new file mode 100644 index 000000000..a6d409ea4 Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_w2px_normal.png differ diff --git a/Tests/images/imagedraw/line_horizontal_w3px.png b/Tests/images/imagedraw/line_horizontal_w3px.png new file mode 100644 index 000000000..06334d666 Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_w3px.png differ diff --git a/Tests/images/imagedraw/line_oblique_45_w3px_a.png b/Tests/images/imagedraw/line_oblique_45_w3px_a.png new file mode 100644 index 000000000..55c535e29 Binary files /dev/null and b/Tests/images/imagedraw/line_oblique_45_w3px_a.png differ diff --git a/Tests/images/imagedraw/line_oblique_45_w3px_b.png b/Tests/images/imagedraw/line_oblique_45_w3px_b.png new file mode 100644 index 000000000..8c1036559 Binary files /dev/null and b/Tests/images/imagedraw/line_oblique_45_w3px_b.png differ diff --git a/Tests/images/imagedraw/line_vertical_slope1px_w2px.png b/Tests/images/imagedraw/line_vertical_slope1px_w2px.png new file mode 100644 index 000000000..1d48a57e8 Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_slope1px_w2px.png differ diff --git a/Tests/images/imagedraw/line_vertical_w101px.png b/Tests/images/imagedraw/line_vertical_w101px.png new file mode 100644 index 000000000..558450950 Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_w101px.png differ diff --git a/Tests/images/imagedraw/line_vertical_w2px_inverted.png b/Tests/images/imagedraw/line_vertical_w2px_inverted.png new file mode 100644 index 000000000..74d666b88 Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_w2px_inverted.png differ diff --git a/Tests/images/imagedraw/line_vertical_w2px_normal.png b/Tests/images/imagedraw/line_vertical_w2px_normal.png new file mode 100644 index 000000000..5b18a7c94 Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_w2px_normal.png differ diff --git a/Tests/images/imagedraw/line_vertical_w3px.png b/Tests/images/imagedraw/line_vertical_w3px.png new file mode 100644 index 000000000..4e5234f2a Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_w3px.png differ diff --git a/Tests/images/imagedraw/square.png b/Tests/images/imagedraw/square.png new file mode 100644 index 000000000..842ee4722 Binary files /dev/null and b/Tests/images/imagedraw/square.png differ diff --git a/Tests/images/imagedraw/triangle_right.png b/Tests/images/imagedraw/triangle_right.png new file mode 100644 index 000000000..e91fa5802 Binary files /dev/null and b/Tests/images/imagedraw/triangle_right.png differ diff --git a/Tests/images/imagedraw_ellipse.png b/Tests/images/imagedraw_ellipse.png index fb03fd148..b52b12802 100644 Binary files a/Tests/images/imagedraw_ellipse.png and b/Tests/images/imagedraw_ellipse.png differ diff --git a/Tests/images/imagedraw_line.png b/Tests/images/imagedraw_line.png index 6d0e6994d..f7303a832 100644 Binary files a/Tests/images/imagedraw_line.png and b/Tests/images/imagedraw_line.png differ diff --git a/Tests/images/imagedraw_pieslice.png b/Tests/images/imagedraw_pieslice.png index 1b2acff92..2f8c09191 100644 Binary files a/Tests/images/imagedraw_pieslice.png and b/Tests/images/imagedraw_pieslice.png differ diff --git a/Tests/images/imagedraw_polygon.png b/Tests/images/imagedraw_polygon.png index 5f160be76..7199a25dd 100644 Binary files a/Tests/images/imagedraw_polygon.png and b/Tests/images/imagedraw_polygon.png differ diff --git a/Tests/large_memory_numpy_test.py b/Tests/large_memory_numpy_test.py index deb2fc8c6..8a13d0aea 100644 --- a/Tests/large_memory_numpy_test.py +++ b/Tests/large_memory_numpy_test.py @@ -1,3 +1,5 @@ +import sys + from helper import * # This test is not run automatically. @@ -18,6 +20,7 @@ YDIM = 32769 XDIM = 48000 +@unittest.skipIf(sys.maxsize <= 2**32, "requires 64 bit system") class LargeMemoryNumpyTest(PillowTestCase): def _write_png(self, xdim, ydim): diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py index 8552ed4dd..a63a42cd5 100644 --- a/Tests/large_memory_test.py +++ b/Tests/large_memory_test.py @@ -1,3 +1,5 @@ +import sys + from helper import * # This test is not run automatically. @@ -14,6 +16,7 @@ YDIM = 32769 XDIM = 48000 +@unittest.skipIf(sys.maxsize <= 2**32, "requires 64 bit system") class LargeMemoryTest(PillowTestCase): def _write_png(self, xdim, ydim): diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 24abcf73d..8c4c04cd4 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -22,7 +22,8 @@ class TestFontPcf(PillowTestCase): self.assertIsInstance(font, FontFile.FontFile) self.assertEqual(len([_f for _f in font.glyph if _f]), 192) - tempname = self.tempfile("temp.pil", "temp.pbm") + tempname = self.tempfile("temp.pil") + self.addCleanup(self.delete_tempfile, tempname[:-4]+'.pbm') font.save(tempname) return tempname diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 996c45e79..4610e2b0b 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -3,6 +3,13 @@ from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image from PIL import ImageColor from PIL import ImageDraw +import os.path + +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +GRAY = (190, 190, 190) +DEFAULT_MODE = 'RGB' +IMAGES_PATH = os.path.join('Tests', 'images', 'imagedraw') import sys @@ -249,7 +256,129 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_floodfill2.png")) + def create_base_image_draw(self, size, + mode=DEFAULT_MODE, + background1=WHITE, + background2=GRAY): + img = Image.new(mode, size, background1) + for x in range(0, size[0]): + for y in range(0, size[1]): + if (x + y) % 2 == 0: + img.putpixel((x, y), background2) + return (img, ImageDraw.Draw(img)) + + + def test_square(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'square.png')) + expected.load() + img, draw = self.create_base_image_draw((10, 10)) + draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) + self.assert_image_equal(img, expected, 'square as normal polygon failed') + img, draw = self.create_base_image_draw((10, 10)) + draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) + self.assert_image_equal(img, expected, 'square as inverted polygon failed') + img, draw = self.create_base_image_draw((10, 10)) + draw.rectangle((2, 2, 7, 7), BLACK) + self.assert_image_equal(img, expected, 'square as normal rectangle failed') + img, draw = self.create_base_image_draw((10, 10)) + draw.rectangle((7, 7, 2, 2), BLACK) + self.assert_image_equal(img, expected, 'square as inverted rectangle failed') + + + def test_triangle_right(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'triangle_right.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) + self.assert_image_equal(img, expected, 'triangle right failed') + + + def test_line_horizontal(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w2px_normal.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 2) + self.assert_image_equal(img, expected, 'line straigth horizontal normal 2px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w2px_inverted.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 2) + self.assert_image_equal(img, expected, 'line straigth horizontal inverted 2px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w3px.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line straigth horizontal normal 3px wide failed') + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line straigth horizontal inverted 3px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w101px.png')) + expected.load() + img, draw = self.create_base_image_draw((200, 110)) + draw.line((5, 55, 195, 55), BLACK, 101) + self.assert_image_equal(img, expected, 'line straigth horizontal 101px wide failed') + + def test_line_h_s1_w2(self): + self.skipTest('failing') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_slope1px_w2px.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 6), BLACK, 2) + self.assert_image_equal(img, expected, 'line horizontal 1px slope 2px wide failed') + + + def test_line_vertical(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w2px_normal.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 2) + self.assert_image_equal(img, expected, 'line straigth vertical normal 2px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w2px_inverted.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 2) + self.assert_image_equal(img, expected, 'line straigth vertical inverted 2px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w3px.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 3) + self.assert_image_equal(img, expected, 'line straigth vertical normal 3px wide failed') + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line straigth vertical inverted 3px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w101px.png')) + expected.load() + img, draw = self.create_base_image_draw((110, 200)) + draw.line((55, 5, 55, 195), BLACK, 101) + self.assert_image_equal(img, expected, 'line straigth vertical 101px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_slope1px_w2px.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 6, 14), BLACK, 2) + self.assert_image_equal(img, expected, 'line vertical 1px slope 2px wide failed') + + + def test_line_oblique_45(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'line_oblique_45_w3px_a.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 14), BLACK, 3) + self.assert_image_equal(img, expected, 'line oblique 45 normal 3px wide A failed') + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 14, 5, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line oblique 45 inverted 3px wide A failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_oblique_45_w3px_b.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 14), BLACK, 3) + self.assert_image_equal(img, expected, 'line oblique 45 normal 3px wide B failed') + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 14, 14, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line oblique 45 inverted 3px wide B failed') + + if __name__ == '__main__': unittest.main() # End of file + diff --git a/_imaging.c b/_imaging.c index 39b21f23e..92258032f 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.4.0" +#define PILLOW_VERSION "2.5.0" #include "Python.h" diff --git a/docs/developer.rst b/docs/developer.rst new file mode 100644 index 000000000..ea3b0f05e --- /dev/null +++ b/docs/developer.rst @@ -0,0 +1,17 @@ +Developer +========= + +.. Note:: When committing only trivial changes, please include [ci skip] in the commit message to avoid running tests on Travis-CI. Thank you! + + +Release +------- + +Details about making a Pillow release. + +Version number +~~~~~~~~~~~~~~ + +The version number is currently stored in 3 places:: + + PIL/__init__.py _imaging.c setup.py diff --git a/docs/guides.rst b/docs/guides.rst index 926d3cfcc..87ce75bd4 100644 --- a/docs/guides.rst +++ b/docs/guides.rst @@ -8,3 +8,4 @@ Guides handbook/tutorial handbook/concepts porting-pil-to-pillow + developer diff --git a/libImaging/Draw.c b/libImaging/Draw.c index f13ba4df0..2ff03e049 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -49,6 +49,13 @@ #define BLEND(mask, in1, in2, tmp1, tmp2)\ (MULDIV255(in1, 255 - mask, tmp1) + MULDIV255(in2, mask, tmp2)) +/* + * Rounds around zero (up=away from zero, down=torwards zero) + * This guarantees that ROUND_UP|DOWN(f) == -ROUND_UP|DOWN(-f) + */ +#define ROUND_UP(f) ((int) ((f) >= 0.0 ? floor((f) + 0.5F) : -floor(fabs(f) + 0.5F))) +#define ROUND_DOWN(f) ((int) ((f) >= 0.0 ? ceil((f) - 0.5F) : -ceil(fabs(f) - 0.5F))) + /* -------------------------------------------------------------------- */ /* Primitives */ /* -------------------------------------------------------------------- */ @@ -61,6 +68,9 @@ typedef struct { float dx; } Edge; +/* Type used in "polygon*" functions */ +typedef void (*hline_handler)(Imaging, int, int, int, int); + static inline void point8(Imaging im, int x, int y, int ink) { @@ -403,177 +413,100 @@ x_cmp(const void *x0, const void *x1) return 0; } + +/* + * Filled polygon draw function using scan line algorithm. + */ static inline int -polygon8(Imaging im, int n, Edge *e, int ink, int eofill) +polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, + hline_handler hline) { - int i, j; - float *xx; - int ymin, ymax; - float y; - if (n <= 0) + Edge** edge_table; + float* xx; + int edge_count = 0; + int ymin = im->ysize - 1; + int ymax = 0; + int i; + + if (n <= 0) { return 0; - - /* Find upper and lower polygon boundary (within image) */ - - ymin = e[0].ymin; - ymax = e[0].ymax; - for (i = 1; i < n; i++) { - if (e[i].ymin < ymin) ymin = e[i].ymin; - if (e[i].ymax > ymax) ymax = e[i].ymax; } - if (ymin < 0) - ymin = 0; - if (ymax >= im->ysize) - ymax = im->ysize-1; - - /* Process polygon edges */ - - xx = malloc(n * sizeof(float)); - if (!xx) + /* Initialize the edge table and find polygon boundaries */ + edge_table = malloc(sizeof(Edge*) * n); + if (!edge_table) { return -1; + } - for (;ymin <= ymax; ymin++) { - y = ymin+0.5F; - for (i = j = 0; i < n; i++) - if (y >= e[i].ymin && y <= e[i].ymax) { - if (e[i].d == 0) - hline8(im, e[i].xmin, ymin, e[i].xmax, ink); - else - xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; + for (i = 0; i < n; i++) { + /* This causes that the pixels of horizontal edges are drawn twice :( + * but without it there are inconsistencies in ellipses */ + if (e[i].ymin == e[i].ymax) { + (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink); + continue; + } + if (ymin > e[i].ymin) { + ymin = e[i].ymin; + } + if (ymax < e[i].ymax) { + ymax = e[i].ymax; + } + edge_table[edge_count++] = (e + i); + } + if (ymin < 0) { + ymin = 0; + } + if (ymax >= im->ysize) { + ymax = im->ysize - 1; + } + + /* Process the edge table with a scan line searching for intersections */ + xx = malloc(sizeof(float) * edge_count * 2); + if (!xx) { + free(edge_table); + return -1; + } + for (; ymin <= ymax; ymin++) { + int j = 0; + for (i = 0; i < edge_count; i++) { + Edge* current = edge_table[i]; + if (ymin >= current->ymin && ymin <= current->ymax) { + xx[j++] = (ymin - current->y0) * current->dx + current->x0; } - if (j == 2) { - if (xx[0] < xx[1]) - hline8(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); - else - hline8(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); - } else { - qsort(xx, j, sizeof(float), x_cmp); - for (i = 0; i < j-1 ; i += 2) - hline8(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); + /* Needed to draw consistent polygons */ + if (ymin == current->ymax && ymin < ymax) { + xx[j] = xx[j - 1]; + j++; + } + } + qsort(xx, j, sizeof(float), x_cmp); + for (i = 1; i < j; i += 2) { + (*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink); } } free(xx); - + free(edge_table); return 0; } +static inline int +polygon8(Imaging im, int n, Edge *e, int ink, int eofill) +{ + return polygon_generic(im, n, e, ink, eofill, hline8); +} + static inline int polygon32(Imaging im, int n, Edge *e, int ink, int eofill) { - int i, j; - float *xx; - int ymin, ymax; - float y; - - if (n <= 0) - return 0; - - /* Find upper and lower polygon boundary (within image) */ - - ymin = e[0].ymin; - ymax = e[0].ymax; - for (i = 1; i < n; i++) { - if (e[i].ymin < ymin) ymin = e[i].ymin; - if (e[i].ymax > ymax) ymax = e[i].ymax; - } - - if (ymin < 0) - ymin = 0; - if (ymax >= im->ysize) - ymax = im->ysize-1; - - /* Process polygon edges */ - - xx = malloc(n * sizeof(float)); - if (!xx) - return -1; - - for (;ymin <= ymax; ymin++) { - y = ymin+0.5F; - for (i = j = 0; i < n; i++) { - if (y >= e[i].ymin && y <= e[i].ymax) { - if (e[i].d == 0) - hline32(im, e[i].xmin, ymin, e[i].xmax, ink); - else - xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; - } - } - if (j == 2) { - if (xx[0] < xx[1]) - hline32(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); - else - hline32(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); - } else { - qsort(xx, j, sizeof(float), x_cmp); - for (i = 0; i < j-1 ; i += 2) - hline32(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); - } - } - - free(xx); - - return 0; + return polygon_generic(im, n, e, ink, eofill, hline32); } static inline int polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) { - int i, j; - float *xx; - int ymin, ymax; - float y; - - if (n <= 0) - return 0; - - /* Find upper and lower polygon boundary (within image) */ - - ymin = e[0].ymin; - ymax = e[0].ymax; - for (i = 1; i < n; i++) { - if (e[i].ymin < ymin) ymin = e[i].ymin; - if (e[i].ymax > ymax) ymax = e[i].ymax; - } - - if (ymin < 0) - ymin = 0; - if (ymax >= im->ysize) - ymax = im->ysize-1; - - /* Process polygon edges */ - - xx = malloc(n * sizeof(float)); - if (!xx) - return -1; - - for (;ymin <= ymax; ymin++) { - y = ymin+0.5F; - for (i = j = 0; i < n; i++) { - if (y >= e[i].ymin && y <= e[i].ymax) { - if (e[i].d == 0) - hline32rgba(im, e[i].xmin, ymin, e[i].xmax, ink); - else - xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; - } - } - if (j == 2) { - if (xx[0] < xx[1]) - hline32rgba(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); - else - hline32rgba(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); - } else { - qsort(xx, j, sizeof(float), x_cmp); - for (i = 0; i < j-1 ; i += 2) - hline32rgba(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); - } - } - - free(xx); - - return 0; + return polygon_generic(im, n, e, ink, eofill, hline32rgba); } static inline void @@ -663,11 +596,11 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, { DRAW* draw; INT32 ink; - - Edge e[4]; - int dx, dy; - double d; + double big_hypotenuse, small_hypotenuse, ratio_max, ratio_min; + int dxmin, dxmax, dymin, dymax; + Edge e[4]; + int vertices[4][2]; DRAWINIT(); @@ -678,24 +611,35 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, dx = x1-x0; dy = y1-y0; - if (dx == 0 && dy == 0) { draw->point(im, x0, y0, ink); return 0; } - d = width / sqrt((float) (dx*dx + dy*dy)) / 2.0; + big_hypotenuse = sqrt((double) (dx*dx + dy*dy)); + small_hypotenuse = (width - 1) / 2.0; + ratio_max = ROUND_UP(small_hypotenuse) / big_hypotenuse; + ratio_min = ROUND_DOWN(small_hypotenuse) / big_hypotenuse; - dx = (int) floor(d * (y1-y0) + 0.5); - dy = (int) floor(d * (x1-x0) + 0.5); + dxmin = ROUND_DOWN(ratio_min * dy); + dxmax = ROUND_DOWN(ratio_max * dy); + dymin = ROUND_DOWN(ratio_min * dx); + dymax = ROUND_DOWN(ratio_max * dx); + { + int vertices[4][2] = { + {x0 - dxmin, y0 + dymax}, + {x1 - dxmin, y1 + dymax}, + {x1 + dxmax, y1 - dymin}, + {x0 + dxmax, y0 - dymin} + }; - add_edge(e+0, x0 - dx, y0 + dy, x1 - dx, y1 + dy); - add_edge(e+1, x1 - dx, y1 + dy, x1 + dx, y1 - dy); - add_edge(e+2, x1 + dx, y1 - dy, x0 + dx, y0 - dy); - add_edge(e+3, x0 + dx, y0 - dy, x0 - dx, y0 + dy); - - draw->polygon(im, 4, e, ink, 0); + add_edge(e+0, vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1]); + add_edge(e+1, vertices[1][0], vertices[1][1], vertices[2][0], vertices[2][1]); + add_edge(e+2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]); + add_edge(e+3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]); + draw->polygon(im, 4, e, ink, 0); + } return 0; } diff --git a/libImaging/ImPlatform.h b/libImaging/ImPlatform.h index 8e85e627c..be1f20f3f 100644 --- a/libImaging/ImPlatform.h +++ b/libImaging/ImPlatform.h @@ -17,7 +17,7 @@ #error Sorry, this library requires ANSI header files. #endif -#if !defined(PIL_USE_INLINE) +#if defined(PIL_NO_INLINE) #define inline #else #if defined(_MSC_VER) && !defined(__GNUC__) diff --git a/setup.py b/setup.py index 9341b93bb..e94e34d28 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -VERSION = '2.4.0' +PILLOW_VERSION = '2.5.0' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None @@ -622,7 +622,7 @@ class pil_build_ext(build_ext): print("-" * 68) print("PIL SETUP SUMMARY") print("-" * 68) - print("version Pillow %s" % VERSION) + print("version Pillow %s" % PILLOW_VERSION) v = sys.version.split("[") print("platform %s %s" % (sys.platform, v[0].strip())) for v in v[1:]: @@ -718,7 +718,7 @@ class pil_build_ext(build_ext): setup( name=NAME, - version=VERSION, + version=PILLOW_VERSION, description='Python Imaging Library (Fork)', long_description=( _read('README.rst') + b'\n' + diff --git a/test-installed.py b/test-installed.py index 0248590a5..0fed377b8 100755 --- a/test-installed.py +++ b/test-installed.py @@ -20,5 +20,13 @@ if len(sys.argv) == 1: if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv): sys.argv.insert(1, '--no-path-adjustment') +if 'NOSE_PROCESSES' not in os.environ: + for arg in sys.argv: + if '--processes' in arg: + break + else: # for + sys.argv.insert(1, '--processes=-1') # -1 == number of cores + sys.argv.insert(1, '--process-timeout=30') + if __name__ == '__main__': nose.main()