mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 17:24:31 +03:00
Merge branch 'master' into perfperf-improv-ImageDraw-floodfill
This commit is contained in:
commit
5e2d6c951a
|
@ -71,7 +71,7 @@ build_script:
|
|||
|
||||
test_script:
|
||||
- cd c:\pillow
|
||||
- '%PYTHON%\%PIP_DIR%\pip.exe install "pytest<3.7" pytest-cov'
|
||||
- '%PYTHON%\%PIP_DIR%\pip.exe install pytest pytest-cov'
|
||||
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov-report term --cov-report xml Tests'
|
||||
#- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest?
|
||||
|
||||
|
|
19
.travis.yml
19
.travis.yml
|
@ -1,3 +1,5 @@
|
|||
dist: xenial
|
||||
sudo: required
|
||||
language: python
|
||||
cache: pip
|
||||
|
||||
|
@ -12,13 +14,24 @@ matrix:
|
|||
fast_finish: true
|
||||
include:
|
||||
- python: "pypy"
|
||||
dist: trusty
|
||||
- python: "pypy3"
|
||||
- python: '3.7-dev'
|
||||
dist: trusty
|
||||
- python: '3.7'
|
||||
- python: '2.7'
|
||||
- python: '2.7'
|
||||
dist: trusty
|
||||
- python: "2.7_with_system_site_packages" # For PyQt4
|
||||
- python: "2.7_with_system_site_packages" # For PyQt4
|
||||
dist: trusty
|
||||
- python: '3.6'
|
||||
- python: '3.6'
|
||||
dist: trusty
|
||||
- python: '3.5'
|
||||
- python: '3.5'
|
||||
dist: trusty
|
||||
- python: '3.4'
|
||||
dist: trusty
|
||||
- env: DOCKER="alpine" DOCKER_TAG="pytest"
|
||||
- env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5
|
||||
- env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest"
|
||||
|
@ -31,10 +44,6 @@ matrix:
|
|||
- env: DOCKER="fedora-26-amd64" DOCKER_TAG="pytest"
|
||||
- env: DOCKER="fedora-27-amd64" DOCKER_TAG="pytest"
|
||||
|
||||
dist: trusty
|
||||
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
|
|
21
CHANGES.rst
21
CHANGES.rst
|
@ -5,6 +5,27 @@ Changelog (Pillow)
|
|||
5.3.0 (unreleased)
|
||||
------------------
|
||||
|
||||
- Changed ImageFilter.Kernel to subclass ImageFilter.BuiltinFilter, instead of the other way around #3273
|
||||
[radarhere]
|
||||
|
||||
- Remove unused draw.draw_line, draw.draw_point and font.getabc methods #3232
|
||||
[hugovk]
|
||||
|
||||
- Tests: Added ImageFilter tests #3295
|
||||
[radarhere]
|
||||
|
||||
- Tests: Added ImageChops tests #3230
|
||||
[hugovk, radarhere]
|
||||
|
||||
- AppVeyor: Download lib if not present in pillow-depends #3316
|
||||
[radarhere]
|
||||
|
||||
- Travis CI: Add Python 3.7 and Xenial #3234
|
||||
[hugovk]
|
||||
|
||||
- Docs: Added documentation for NumPy conversion #3301
|
||||
[radarhere]
|
||||
|
||||
- Depends: Update libimagequant to 2.12.1 #3281
|
||||
[radarhere]
|
||||
|
||||
|
|
|
@ -89,12 +89,12 @@ Released as needed privately to individual vendors for critical security-related
|
|||
$ git clone https://github.com/python-pillow/pillow-wheels
|
||||
$ cd pillow-wheels
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
$ git submodule update Pillow
|
||||
$ cd Pillow
|
||||
$ git fetch --all
|
||||
$ git checkout [[release tag]]
|
||||
$ cd ..
|
||||
$ git commit -m "Pillow -> 2.9.0" Pillow
|
||||
$ git commit -m "Pillow -> 5.2.0" Pillow
|
||||
$ git push
|
||||
```
|
||||
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
|
||||
|
|
|
@ -146,13 +146,13 @@ class TestFileJpeg2k(PillowTestCase):
|
|||
self.assertEqual(j2k.mode, 'I;16')
|
||||
self.assertEqual(jp2.mode, 'I;16')
|
||||
|
||||
def test_16bit_monchrome_jp2_like_tiff(self):
|
||||
def test_16bit_monochrome_jp2_like_tiff(self):
|
||||
|
||||
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
|
||||
jp2 = Image.open('Tests/images/16bit.cropped.jp2')
|
||||
self.assert_image_similar(jp2, tiff_16bit, 1e-3)
|
||||
|
||||
def test_16bit_monchrome_j2k_like_tiff(self):
|
||||
def test_16bit_monochrome_j2k_like_tiff(self):
|
||||
|
||||
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
|
||||
j2k = Image.open('Tests/images/16bit.cropped.j2k')
|
||||
|
|
|
@ -12,7 +12,7 @@ class TestFileTar(PillowTestCase):
|
|||
|
||||
def setUp(self):
|
||||
if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs:
|
||||
self.skipTest("neither jpeg nor zip support not available")
|
||||
self.skipTest("neither jpeg nor zip support available")
|
||||
|
||||
def test_sanity(self):
|
||||
if "zip_decoder" in codecs:
|
||||
|
|
|
@ -94,6 +94,15 @@ class TestImageFilter(PillowTestCase):
|
|||
self.assertEqual(rankfilter.size, 1)
|
||||
self.assertEqual(rankfilter.rank, 2)
|
||||
|
||||
def test_builtinfilter_p(self):
|
||||
builtinFilter = ImageFilter.BuiltinFilter()
|
||||
|
||||
self.assertRaises(ValueError, builtinFilter.filter, hopper("P"))
|
||||
|
||||
def test_kernel_not_enough_coefficients(self):
|
||||
self.assertRaises(ValueError,
|
||||
lambda: ImageFilter.Kernel((3, 3), (0, 0)))
|
||||
|
||||
def test_consistency_3x3(self):
|
||||
source = Image.open("Tests/images/hopper.bmp")
|
||||
reference = Image.open("Tests/images/hopper_emboss.bmp")
|
||||
|
|
|
@ -3,6 +3,16 @@ from helper import unittest, PillowTestCase, hopper
|
|||
from PIL import Image
|
||||
from PIL import ImageChops
|
||||
|
||||
BLACK = (0, 0, 0)
|
||||
BROWN = (127, 64, 0)
|
||||
CYAN = (0, 255, 255)
|
||||
DARK_GREEN = (0, 128, 0)
|
||||
GREEN = (0, 255, 0)
|
||||
ORANGE = (255, 128, 0)
|
||||
WHITE = (255, 255, 255)
|
||||
|
||||
GREY = 128
|
||||
|
||||
|
||||
class TestImageChops(PillowTestCase):
|
||||
|
||||
|
@ -35,6 +45,303 @@ class TestImageChops(PillowTestCase):
|
|||
ImageChops.offset(im, 10)
|
||||
ImageChops.offset(im, 10, 20)
|
||||
|
||||
def test_add(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.add(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||
self.assertEqual(new.getpixel((50, 50)), ORANGE)
|
||||
|
||||
def test_add_scale_offset(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.add(im1, im2, scale=2.5, offset=100)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (0, 0, 100, 100))
|
||||
self.assertEqual(new.getpixel((50, 50)), (202, 151, 100))
|
||||
|
||||
def test_add_clip(self):
|
||||
# Arrange
|
||||
im = hopper()
|
||||
|
||||
# Act
|
||||
new = ImageChops.add(im, im)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getpixel((50, 50)), (255, 255, 254))
|
||||
|
||||
def test_add_modulo(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.add_modulo(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||
self.assertEqual(new.getpixel((50, 50)), ORANGE)
|
||||
|
||||
def test_add_modulo_no_clip(self):
|
||||
# Arrange
|
||||
im = hopper()
|
||||
|
||||
# Act
|
||||
new = ImageChops.add_modulo(im, im)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getpixel((50, 50)), (224, 76, 254))
|
||||
|
||||
def test_blend(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.blend(im1, im2, 0.5)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||
self.assertEqual(new.getpixel((50, 50)), BROWN)
|
||||
|
||||
def test_constant(self):
|
||||
# Arrange
|
||||
im = Image.new("RGB", (20, 10))
|
||||
|
||||
# Act
|
||||
new = ImageChops.constant(im, GREY)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.size, im.size)
|
||||
self.assertEqual(new.getpixel((0, 0)), GREY)
|
||||
self.assertEqual(new.getpixel((19, 9)), GREY)
|
||||
|
||||
def test_darker_image(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.darker(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assert_image_equal(new, im2)
|
||||
|
||||
def test_darker_pixel(self):
|
||||
# Arrange
|
||||
im1 = hopper()
|
||||
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.darker(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getpixel((50, 50)), (240, 166, 0))
|
||||
|
||||
def test_difference(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_arc_end_le_start.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_arc_no_loops.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.difference(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||
|
||||
def test_difference_pixel(self):
|
||||
# Arrange
|
||||
im1 = hopper()
|
||||
im2 = Image.open("Tests/images/imagedraw_polygon_kite_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.difference(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getpixel((50, 50)), (240, 166, 128))
|
||||
|
||||
def test_duplicate(self):
|
||||
# Arrange
|
||||
im = hopper()
|
||||
|
||||
# Act
|
||||
new = ImageChops.duplicate(im)
|
||||
|
||||
# Assert
|
||||
self.assert_image_equal(new, im)
|
||||
|
||||
def test_invert(self):
|
||||
# Arrange
|
||||
im = Image.open("Tests/images/imagedraw_floodfill.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.invert(im)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (0, 0, 100, 100))
|
||||
self.assertEqual(new.getpixel((0, 0)), WHITE)
|
||||
self.assertEqual(new.getpixel((50, 50)), CYAN)
|
||||
|
||||
def test_lighter_image(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.lighter(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assert_image_equal(new, im1)
|
||||
|
||||
def test_lighter_pixel(self):
|
||||
# Arrange
|
||||
im1 = hopper()
|
||||
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.lighter(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getpixel((50, 50)), (255, 255, 127))
|
||||
|
||||
def test_multiply_black(self):
|
||||
"""If you multiply an image with a solid black image,
|
||||
the result is black."""
|
||||
# Arrange
|
||||
im1 = hopper()
|
||||
black = Image.new("RGB", im1.size, "black")
|
||||
|
||||
# Act
|
||||
new = ImageChops.multiply(im1, black)
|
||||
|
||||
# Assert
|
||||
self.assert_image_equal(new, black)
|
||||
|
||||
def test_multiply_green(self):
|
||||
# Arrange
|
||||
im = Image.open("Tests/images/imagedraw_floodfill.png")
|
||||
green = Image.new("RGB", im.size, "green")
|
||||
|
||||
# Act
|
||||
new = ImageChops.multiply(im, green)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||
self.assertEqual(new.getpixel((25, 25)), DARK_GREEN)
|
||||
self.assertEqual(new.getpixel((50, 50)), BLACK)
|
||||
|
||||
def test_multiply_white(self):
|
||||
"""If you multiply with a solid white image,
|
||||
the image is unaffected."""
|
||||
# Arrange
|
||||
im1 = hopper()
|
||||
white = Image.new("RGB", im1.size, "white")
|
||||
|
||||
# Act
|
||||
new = ImageChops.multiply(im1, white)
|
||||
|
||||
# Assert
|
||||
self.assert_image_equal(new, im1)
|
||||
|
||||
def test_offset(self):
|
||||
# Arrange
|
||||
im = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||
xoffset = 45
|
||||
yoffset = 20
|
||||
|
||||
# Act
|
||||
new = ImageChops.offset(im, xoffset, yoffset)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (0, 45, 100, 96))
|
||||
self.assertEqual(new.getpixel((50, 50)), BLACK)
|
||||
self.assertEqual(new.getpixel((50+xoffset, 50+yoffset)), DARK_GREEN)
|
||||
|
||||
# Test no yoffset
|
||||
self.assertEqual(ImageChops.offset(im, xoffset),
|
||||
ImageChops.offset(im, xoffset, xoffset))
|
||||
|
||||
def test_screen(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_floodfill.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.screen(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||
self.assertEqual(new.getpixel((50, 50)), ORANGE)
|
||||
|
||||
def test_subtract(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.subtract(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (25, 50, 76, 76))
|
||||
self.assertEqual(new.getpixel((50, 50)), GREEN)
|
||||
self.assertEqual(new.getpixel((50, 51)), BLACK)
|
||||
|
||||
def test_subtract_scale_offset(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.subtract(im1, im2, scale=2.5, offset=100)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (0, 0, 100, 100))
|
||||
self.assertEqual(new.getpixel((50, 50)), (100, 202, 100))
|
||||
|
||||
def test_subtract_clip(self):
|
||||
# Arrange
|
||||
im1 = hopper()
|
||||
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.subtract(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getpixel((50, 50)), (0, 0, 127))
|
||||
|
||||
def test_subtract_modulo(self):
|
||||
# Arrange
|
||||
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.subtract_modulo(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getbbox(), (25, 50, 76, 76))
|
||||
self.assertEqual(new.getpixel((50, 50)), GREEN)
|
||||
self.assertEqual(new.getpixel((50, 51)), BLACK)
|
||||
|
||||
def test_subtract_modulo_no_clip(self):
|
||||
# Arrange
|
||||
im1 = hopper()
|
||||
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||
|
||||
# Act
|
||||
new = ImageChops.subtract_modulo(im1, im2)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(new.getpixel((50, 50)), (241, 167, 127))
|
||||
|
||||
def test_logical(self):
|
||||
|
||||
def table(op, a, b):
|
||||
|
|
|
@ -366,6 +366,11 @@ class TestImageDraw(PillowTestCase):
|
|||
ImageDraw.floodfill(im, (W, H), red)
|
||||
self.assert_image_equal(im, im_floodfill)
|
||||
|
||||
# Test filling at the edge of an image
|
||||
im = Image.new("RGB", (1, 1))
|
||||
ImageDraw.floodfill(im, (0, 0), red)
|
||||
self.assert_image_equal(im, Image.new("RGB", (1, 1), red))
|
||||
|
||||
def test_floodfill_border(self):
|
||||
# floodfill() is experimental
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from helper import unittest, PillowTestCase, hopper
|
||||
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase
|
||||
from test_imageqt import PillowQPixmapTestCase
|
||||
|
||||
from PIL import ImageQt
|
||||
|
||||
|
@ -7,7 +7,6 @@ from PIL import ImageQt
|
|||
class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase):
|
||||
|
||||
def roundtrip(self, expected):
|
||||
PillowQtTestCase.setUp(self)
|
||||
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
|
||||
# Qt saves all pixmaps as rgb
|
||||
self.assert_image_equal(result, expected.convert('RGB'))
|
||||
|
|
|
@ -25,7 +25,6 @@ if ImageQt.qt_is_installed:
|
|||
class TestToQImage(PillowQtTestCase, PillowTestCase):
|
||||
|
||||
def test_sanity(self):
|
||||
PillowQtTestCase.setUp(self)
|
||||
for mode in ('RGB', 'RGBA', 'L', 'P', '1'):
|
||||
src = hopper(mode)
|
||||
data = ImageQt.toqimage(src)
|
||||
|
@ -61,8 +60,6 @@ class TestToQImage(PillowQtTestCase, PillowTestCase):
|
|||
self.assert_image_equal(reloaded, src)
|
||||
|
||||
def test_segfault(self):
|
||||
PillowQtTestCase.setUp(self)
|
||||
|
||||
app = QApplication([])
|
||||
ex = Example()
|
||||
assert(app) # Silence warning
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from helper import unittest, PillowTestCase, hopper
|
||||
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase
|
||||
from test_imageqt import PillowQPixmapTestCase
|
||||
|
||||
from PIL import ImageQt
|
||||
|
||||
|
@ -10,8 +10,6 @@ if ImageQt.qt_is_installed:
|
|||
class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase):
|
||||
|
||||
def test_sanity(self):
|
||||
PillowQtTestCase.setUp(self)
|
||||
|
||||
for mode in ('1', 'RGB', 'RGBA', 'L', 'P'):
|
||||
data = ImageQt.toqpixmap(hopper(mode))
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
# install extra test images
|
||||
|
||||
rm -r test_images
|
||||
rm -rf test_images
|
||||
|
||||
# Use SVN to just fetch a single git subdirectory
|
||||
svn checkout https://github.com/python-pillow/pillow-depends/trunk/test_images
|
||||
|
|
2
docs/_templates/sidebarhelp.html
vendored
2
docs/_templates/sidebarhelp.html
vendored
|
@ -1,4 +1,4 @@
|
|||
<h3>Need help?</h3>
|
||||
<p>
|
||||
You can get help via IRC at <a href="irc://irc.freenode.net#pil">irc://irc.freenode.net#pil</a>, <a href="https://gitter.im/python-pillow/Pillow">Gitter</a> or Stack Overflow <a href="https://stackoverflow.com/questions/tagged/pillow">here</a> and <a href="https://stackoverflow.com/questions/tagged/python-imaging-library">here</a>. Please <a href="https://github.com/python-pillow/Pillow/issues/new">report issues on GitHub</a>.
|
||||
You can get help via IRC at <a href="irc://irc.freenode.net#pil">irc://irc.freenode.net#pil</a>, <a href="https://gitter.im/python-pillow/Pillow">Gitter</a> or <a href="https://stackoverflow.com/questions/tagged/python-imaging-library">Stack Overflow</a>. Please <a href="https://github.com/python-pillow/Pillow/issues/new">report issues on GitHub</a>.
|
||||
</p>
|
||||
|
|
|
@ -529,8 +529,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
|||
.. note::
|
||||
|
||||
To enable PNG support, you need to build and install the ZLIB compression
|
||||
library before building the Python Imaging Library. See the installation
|
||||
documentation for details.
|
||||
library before building the Python Imaging Library. See the `installation
|
||||
documentation <../installation.html>`_ for details.
|
||||
|
||||
PPM
|
||||
^^^
|
||||
|
|
|
@ -178,7 +178,7 @@ Many of Pillow's features require external libraries:
|
|||
shaping (using HarfBuzz), and proper script itemization. As a
|
||||
result, Raqm can support most writing systems covered by Unicode.
|
||||
* libraqm depends on the following libraries: FreeType, HarfBuzz,
|
||||
FriBiDi, make sure that you install them before install libraqm
|
||||
FriBiDi, make sure that you install them before installing libraqm
|
||||
if not available as package in your system.
|
||||
* setting text direction or font features is not supported without
|
||||
libraqm.
|
||||
|
|
|
@ -6,9 +6,10 @@ Added Complex Text Rendering
|
|||
|
||||
Pillow now supports complex text rendering for scripts requiring glyph
|
||||
composition and bidirectional flow. This optional feature adds three
|
||||
dependencies: harfbuzz, fribidi, and raqm. See the install
|
||||
documentation for further details. This feature is tested and works on
|
||||
Unix and Mac, but has not yet been built on Windows platforms.
|
||||
dependencies: harfbuzz, fribidi, and raqm. See the `install
|
||||
documentation <../installation.html>`_ for further details. This feature is
|
||||
tested and works on Unix and Mac, but has not yet been built on Windows
|
||||
platforms.
|
||||
|
||||
New Optional Parameters
|
||||
=======================
|
||||
|
|
|
@ -2440,9 +2440,20 @@ def fromarray(obj, mode=None):
|
|||
Creates an image memory from an object exporting the array interface
|
||||
(using the buffer protocol).
|
||||
|
||||
If obj is not contiguous, then the tobytes method is called
|
||||
If **obj** is not contiguous, then the tobytes method is called
|
||||
and :py:func:`~PIL.Image.frombuffer` is used.
|
||||
|
||||
If you have an image in NumPy::
|
||||
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
im = Image.open('hopper.jpg')
|
||||
a = numpy.asarray(im)
|
||||
|
||||
Then this can be used to convert it to a Pillow image::
|
||||
|
||||
im = Image.fromarray(a)
|
||||
|
||||
:param obj: Object with array interface
|
||||
:param mode: Mode to use (will be determined from type if None)
|
||||
See: :ref:`concept-modes`.
|
||||
|
|
|
@ -353,42 +353,27 @@ def floodfill(image, xy, value, border=None, thresh=0):
|
|||
return # seed point outside image
|
||||
edge = {(x, y)}
|
||||
full_edge = set() # use a set to record each unique pixel processed
|
||||
if border is None:
|
||||
while edge:
|
||||
new_edge = set()
|
||||
for (x, y) in edge: # 4 adjacent method
|
||||
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
|
||||
if (s, t) in full_edge:
|
||||
continue # if already processed, skip
|
||||
try:
|
||||
p = pixel[s, t]
|
||||
except IndexError:
|
||||
pass
|
||||
while edge:
|
||||
new_edge = set()
|
||||
for (x, y) in edge: # 4 adjacent method
|
||||
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
|
||||
if (s, t) in full_edge:
|
||||
continue # if already processed, skip
|
||||
try:
|
||||
p = pixel[s, t]
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
else:
|
||||
if border is None:
|
||||
fill = _color_diff(p, background) <= thresh
|
||||
else:
|
||||
if _color_diff(p, background) <= thresh:
|
||||
pixel[s, t] = value
|
||||
new_edge.add((s, t))
|
||||
full_edge.add((s, t))
|
||||
full_edge = edge # do not record useless pixels to reduce memory consumption
|
||||
edge = new_edge
|
||||
else:
|
||||
while edge:
|
||||
new_edge = set()
|
||||
for (x, y) in edge:
|
||||
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
|
||||
if (s, t) in full_edge:
|
||||
continue
|
||||
try:
|
||||
p = pixel[s, t]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if p != value and p != border:
|
||||
pixel[s, t] = value
|
||||
new_edge.add((s, t))
|
||||
full_edge.add((s, t))
|
||||
full_edge = edge
|
||||
edge = new_edge
|
||||
fill = p != value and p != border
|
||||
if fill:
|
||||
pixel[s, t] = value
|
||||
new_edge.add((s, t))
|
||||
full_edge.add((s, t))
|
||||
full_edge = edge # do not record useless pixels to reduce memory consumption
|
||||
edge = new_edge
|
||||
|
||||
|
||||
def _color_diff(rgb1, rgb2):
|
||||
|
|
|
@ -33,7 +33,14 @@ class MultibandFilter(Filter):
|
|||
pass
|
||||
|
||||
|
||||
class Kernel(MultibandFilter):
|
||||
class BuiltinFilter(MultibandFilter):
|
||||
def filter(self, image):
|
||||
if image.mode == "P":
|
||||
raise ValueError("cannot filter palette images")
|
||||
return image.filter(*self.filterargs)
|
||||
|
||||
|
||||
class Kernel(BuiltinFilter):
|
||||
"""
|
||||
Create a convolution kernel. The current version only
|
||||
supports 3x3 and 5x5 integer and floating point kernels.
|
||||
|
@ -60,16 +67,6 @@ class Kernel(MultibandFilter):
|
|||
raise ValueError("not enough coefficients in kernel")
|
||||
self.filterargs = size, scale, offset, kernel
|
||||
|
||||
def filter(self, image):
|
||||
if image.mode == "P":
|
||||
raise ValueError("cannot filter palette images")
|
||||
return image.filter(*self.filterargs)
|
||||
|
||||
|
||||
class BuiltinFilter(Kernel):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
class RankFilter(Filter):
|
||||
"""
|
||||
|
|
|
@ -59,7 +59,7 @@ class ImagePalette(object):
|
|||
|
||||
def getdata(self):
|
||||
"""
|
||||
Get palette contents in format suitable # for the low-level
|
||||
Get palette contents in format suitable for the low-level
|
||||
``im.putpalette`` primitive.
|
||||
|
||||
.. warning:: This method is experimental.
|
||||
|
|
|
@ -793,7 +793,7 @@ def jpeg_factory(fp=None, filename=None):
|
|||
return im
|
||||
|
||||
|
||||
# -------------------------------------------------------------------q-
|
||||
# ---------------------------------------------------------------------
|
||||
# Registry stuff
|
||||
|
||||
Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
|
||||
|
|
|
@ -85,7 +85,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
return self.__frame
|
||||
|
||||
|
||||
# -------------------------------------------------------------------q-
|
||||
# ---------------------------------------------------------------------
|
||||
# Registry stuff
|
||||
|
||||
# Note that since MPO shares a factory with JPEG, we do not need to do a
|
||||
|
|
|
@ -2697,22 +2697,6 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args)
|
|||
return Py_None;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
_draw_line(ImagingDrawObject* self, PyObject* args)
|
||||
{
|
||||
int x0, y0, x1, y1;
|
||||
int ink;
|
||||
if (!PyArg_ParseTuple(args, "(ii)(ii)i", &x0, &y0, &x1, &y1, &ink))
|
||||
return NULL;
|
||||
|
||||
if (ImagingDrawLine(self->image->image, x0, y0, x1, y1,
|
||||
&ink, self->blend) < 0)
|
||||
return NULL;
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
_draw_lines(ImagingDrawObject* self, PyObject* args)
|
||||
{
|
||||
|
@ -2766,21 +2750,6 @@ _draw_lines(ImagingDrawObject* self, PyObject* args)
|
|||
return Py_None;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
_draw_point(ImagingDrawObject* self, PyObject* args)
|
||||
{
|
||||
int x, y;
|
||||
int ink;
|
||||
if (!PyArg_ParseTuple(args, "(ii)i", &x, &y, &ink))
|
||||
return NULL;
|
||||
|
||||
if (ImagingDrawPoint(self->image->image, x, y, &ink, self->blend) < 0)
|
||||
return NULL;
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
_draw_points(ImagingDrawObject* self, PyObject* args)
|
||||
{
|
||||
|
@ -2961,14 +2930,12 @@ _draw_rectangle(ImagingDrawObject* self, PyObject* args)
|
|||
static struct PyMethodDef _draw_methods[] = {
|
||||
#ifdef WITH_IMAGEDRAW
|
||||
/* Graphics (ImageDraw) */
|
||||
{"draw_line", (PyCFunction)_draw_line, 1},
|
||||
{"draw_lines", (PyCFunction)_draw_lines, 1},
|
||||
#ifdef WITH_ARROW
|
||||
{"draw_outline", (PyCFunction)_draw_outline, 1},
|
||||
#endif
|
||||
{"draw_polygon", (PyCFunction)_draw_polygon, 1},
|
||||
{"draw_rectangle", (PyCFunction)_draw_rectangle, 1},
|
||||
{"draw_point", (PyCFunction)_draw_point, 1},
|
||||
{"draw_points", (PyCFunction)_draw_points, 1},
|
||||
{"draw_arc", (PyCFunction)_draw_arc, 1},
|
||||
{"draw_bitmap", (PyCFunction)_draw_bitmap, 1},
|
||||
|
|
|
@ -674,47 +674,6 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
font_getabc(FontObject* self, PyObject* args)
|
||||
{
|
||||
FT_ULong ch;
|
||||
FT_Face face;
|
||||
double a, b, c;
|
||||
|
||||
/* calculate ABC values for a given string */
|
||||
|
||||
PyObject* string;
|
||||
if (!PyArg_ParseTuple(args, "O:getabc", &string))
|
||||
return NULL;
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03000000
|
||||
if (!PyUnicode_Check(string)) {
|
||||
#else
|
||||
if (!PyUnicode_Check(string) && !PyString_Check(string)) {
|
||||
#endif
|
||||
PyErr_SetString(PyExc_TypeError, "expected string");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (font_getchar(string, 0, &ch)) {
|
||||
int index, error;
|
||||
face = self->face;
|
||||
index = FT_Get_Char_Index(face, ch);
|
||||
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
|
||||
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
|
||||
if (error)
|
||||
return geterror(error);
|
||||
a = face->glyph->metrics.horiBearingX / 64.0;
|
||||
b = face->glyph->metrics.width / 64.0;
|
||||
c = (face->glyph->metrics.horiAdvance -
|
||||
face->glyph->metrics.horiBearingX -
|
||||
face->glyph->metrics.width) / 64.0;
|
||||
} else
|
||||
a = b = c = 0.0;
|
||||
|
||||
return Py_BuildValue("ddd", a, b, c);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
font_render(FontObject* self, PyObject* args)
|
||||
{
|
||||
|
@ -854,7 +813,6 @@ font_dealloc(FontObject* self)
|
|||
static PyMethodDef font_methods[] = {
|
||||
{"render", (PyCFunction) font_render, METH_VARARGS},
|
||||
{"getsize", (PyCFunction) font_getsize, METH_VARARGS},
|
||||
{"getabc", (PyCFunction) font_getabc, METH_VARARGS},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ from unzip import unzip
|
|||
from untar import untar
|
||||
import os
|
||||
|
||||
from fetch import fetch
|
||||
from config import compilers, compiler_from_env, libs
|
||||
|
||||
|
||||
|
@ -44,6 +45,8 @@ def extract(src, dest):
|
|||
def extract_libs():
|
||||
for name, lib in libs.items():
|
||||
filename = lib['filename']
|
||||
if not os.path.exists(filename):
|
||||
filename = fetch(lib['url'])
|
||||
if name == 'openjpeg':
|
||||
for compiler in compilers.values():
|
||||
if not os.path.exists(os.path.join(
|
||||
|
|
Loading…
Reference in New Issue
Block a user