mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-13 05:06:49 +03:00
Merge branch 'master' into reduce
This commit is contained in:
commit
dda5558129
|
@ -6,8 +6,6 @@ codecov:
|
||||||
# https://docs.codecov.io/docs/comparing-commits
|
# https://docs.codecov.io/docs/comparing-commits
|
||||||
allow_coverage_offsets: true
|
allow_coverage_offsets: true
|
||||||
|
|
||||||
token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae
|
|
||||||
|
|
||||||
comment: off
|
comment: off
|
||||||
|
|
||||||
# Matches 'omit:' in .coveragerc
|
# Matches 'omit:' in .coveragerc
|
||||||
|
|
18
.github/codecov-upstream.yml
vendored
Normal file
18
.github/codecov-upstream.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Documentation: https://docs.codecov.io/docs/codecov-yaml
|
||||||
|
|
||||||
|
codecov:
|
||||||
|
# Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]"
|
||||||
|
# https://github.com/codecov/support/issues/363
|
||||||
|
# https://docs.codecov.io/docs/comparing-commits
|
||||||
|
allow_coverage_offsets: true
|
||||||
|
|
||||||
|
token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae
|
||||||
|
|
||||||
|
comment: off
|
||||||
|
|
||||||
|
# Matches 'omit:' in .coveragerc
|
||||||
|
ignore:
|
||||||
|
- "Tests/32bit_segfault_check.py"
|
||||||
|
- "Tests/bench_cffi_access.py"
|
||||||
|
- "Tests/check_*.py"
|
||||||
|
- "Tests/createfontdatachunk.py"
|
8
.github/workflows/test-windows.yml
vendored
8
.github/workflows/test-windows.yml
vendored
|
@ -19,8 +19,8 @@ jobs:
|
||||||
platform-vcvars: "x86_amd64"
|
platform-vcvars: "x86_amd64"
|
||||||
platform-msbuild: "x64"
|
platform-msbuild: "x64"
|
||||||
- python-version: "pypy3.6"
|
- python-version: "pypy3.6"
|
||||||
pypy-version: "pypy3.6-v7.2.0-win32"
|
pypy-version: "pypy3.6-v7.3.0-win32"
|
||||||
pypy-url: "https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.2.0-win32.zip"
|
pypy-url: "https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-win32.zip"
|
||||||
exclude:
|
exclude:
|
||||||
- python-version: "pypy3.6"
|
- python-version: "pypy3.6"
|
||||||
architecture: "x64"
|
architecture: "x64"
|
||||||
|
@ -364,6 +364,10 @@ jobs:
|
||||||
name: errors
|
name: errors
|
||||||
path: Tests/errors
|
path: Tests/errors
|
||||||
|
|
||||||
|
- name: Prepare coverage token
|
||||||
|
if: success() && github.repository == 'python-pillow/Pillow'
|
||||||
|
run: cp .github/codecov-upstream.yml .codecov.yml
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
if: success()
|
if: success()
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
|
|
11
.github/workflows/test.yml
vendored
11
.github/workflows/test.yml
vendored
|
@ -83,21 +83,18 @@ jobs:
|
||||||
name: errors
|
name: errors
|
||||||
path: Tests/errors
|
path: Tests/errors
|
||||||
|
|
||||||
- name: Docs
|
|
||||||
if: matrix.python-version == 3.8
|
|
||||||
run: |
|
|
||||||
pip install sphinx-rtd-theme
|
|
||||||
make doccheck
|
|
||||||
|
|
||||||
- name: After success
|
- name: After success
|
||||||
if: success()
|
if: success()
|
||||||
run: |
|
run: |
|
||||||
.travis/after_success.sh
|
.travis/after_success.sh
|
||||||
env:
|
env:
|
||||||
MATRIX_OS: ${{ matrix.os }}
|
MATRIX_OS: ${{ matrix.os }}
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||||
|
|
||||||
|
- name: Prepare coverage token
|
||||||
|
if: success() && github.repository == 'python-pillow/Pillow'
|
||||||
|
run: cp .github/codecov-upstream.yml .codecov.yml
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
if: success()
|
if: success()
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk\
|
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
||||||
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
||||||
cmake imagemagick libharfbuzz-dev libfribidi-dev
|
cmake imagemagick libharfbuzz-dev libfribidi-dev
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
|
||||||
pip install pyqt5
|
pip install pyqt5
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# docs only on Python 3.7
|
# docs only on Python 3.8
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then pip install -r requirements.txt ; fi
|
if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then pip install -r requirements.txt ; fi
|
||||||
|
|
||||||
# webp
|
# webp
|
||||||
pushd depends && ./install_webp.sh && popd
|
pushd depends && ./install_webp.sh && popd
|
||||||
|
|
|
@ -5,4 +5,4 @@ set -e
|
||||||
python -m pytest -v -x --cov PIL --cov Tests --cov-report term Tests
|
python -m pytest -v -x --cov PIL --cov Tests --cov-report term Tests
|
||||||
|
|
||||||
# Docs
|
# Docs
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then make doccheck; fi
|
if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then make doccheck; fi
|
||||||
|
|
|
@ -5,9 +5,15 @@ Changelog (Pillow)
|
||||||
7.0.0 (unreleased)
|
7.0.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Use default DPI when exif provides invalid x_resolution #4147
|
||||||
|
[beipang2, radarhere]
|
||||||
|
|
||||||
- Change default resize resampling filter from NEAREST to BICUBIC #4255
|
- Change default resize resampling filter from NEAREST to BICUBIC #4255
|
||||||
[homm]
|
[homm]
|
||||||
|
|
||||||
|
- Fixed black lines on upscaled images with the BOX filter #4278
|
||||||
|
[homm]
|
||||||
|
|
||||||
- Better thumbnail aspect ratio preservation #4256
|
- Better thumbnail aspect ratio preservation #4256
|
||||||
[homm]
|
[homm]
|
||||||
|
|
||||||
|
|
BIN
Tests/images/invalid-exif-without-x-resolution.jpg
Normal file
BIN
Tests/images/invalid-exif-without-x-resolution.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
|
@ -41,6 +41,13 @@ class TestFileBmp(PillowTestCase):
|
||||||
self.assertEqual(im.size, reloaded.size)
|
self.assertEqual(im.size, reloaded.size)
|
||||||
self.assertEqual(reloaded.format, "BMP")
|
self.assertEqual(reloaded.format, "BMP")
|
||||||
|
|
||||||
|
def test_save_too_large(self):
|
||||||
|
outfile = self.tempfile("temp.bmp")
|
||||||
|
with Image.new("RGB", (1, 1)) as im:
|
||||||
|
im._size = (37838, 37838)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
im.save(outfile)
|
||||||
|
|
||||||
def test_dpi(self):
|
def test_dpi(self):
|
||||||
dpi = (72, 72)
|
dpi = (72, 72)
|
||||||
|
|
||||||
|
|
|
@ -638,6 +638,14 @@ class TestFileJpeg(PillowTestCase):
|
||||||
# OSError for unidentified image.
|
# OSError for unidentified image.
|
||||||
self.assertEqual(im.info.get("dpi"), (72, 72))
|
self.assertEqual(im.info.get("dpi"), (72, 72))
|
||||||
|
|
||||||
|
def test_invalid_exif_x_resolution(self):
|
||||||
|
# When no x or y resolution is defined in EXIF
|
||||||
|
im = Image.open("Tests/images/invalid-exif-without-x-resolution.jpg")
|
||||||
|
|
||||||
|
# This should return the default, and not a ValueError or
|
||||||
|
# OSError for an unidentified image.
|
||||||
|
self.assertEqual(im.info.get("dpi"), (72, 72))
|
||||||
|
|
||||||
def test_ifd_offset_exif(self):
|
def test_ifd_offset_exif(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
# This image has been manually hexedited to have an IFD offset of 10,
|
# This image has been manually hexedited to have an IFD offset of 10,
|
||||||
|
|
|
@ -49,7 +49,7 @@ class TestFileMsp(PillowTestCase):
|
||||||
with Image.open(target_path) as target:
|
with Image.open(target_path) as target:
|
||||||
self.assert_image_equal(im, target)
|
self.assert_image_equal(im, target)
|
||||||
|
|
||||||
@unittest.skipIf(not os.path.exists(EXTRA_DIR), "Extra image files not installed")
|
@unittest.skipUnless(os.path.exists(EXTRA_DIR), "Extra image files not installed")
|
||||||
def test_open_windows_v2(self):
|
def test_open_windows_v2(self):
|
||||||
|
|
||||||
files = (
|
files = (
|
||||||
|
|
|
@ -30,7 +30,7 @@ class TestFileSun(PillowTestCase):
|
||||||
with Image.open("Tests/images/sunraster.im1.png") as target:
|
with Image.open("Tests/images/sunraster.im1.png") as target:
|
||||||
self.assert_image_equal(im, target)
|
self.assert_image_equal(im, target)
|
||||||
|
|
||||||
@unittest.skipIf(not os.path.exists(EXTRA_DIR), "Extra image files not installed")
|
@unittest.skipUnless(os.path.exists(EXTRA_DIR), "Extra image files not installed")
|
||||||
def test_others(self):
|
def test_others(self):
|
||||||
files = (
|
files = (
|
||||||
os.path.join(EXTRA_DIR, f)
|
os.path.join(EXTRA_DIR, f)
|
||||||
|
|
|
@ -26,7 +26,7 @@ class TestUnsupportedWebp(PillowTestCase):
|
||||||
WebPImagePlugin.SUPPORTED = True
|
WebPImagePlugin.SUPPORTED = True
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(not HAVE_WEBP, "WebP support not installed")
|
@unittest.skipUnless(HAVE_WEBP, "WebP support not installed")
|
||||||
class TestFileWebp(PillowTestCase):
|
class TestFileWebp(PillowTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.rgb_mode = "RGB"
|
self.rgb_mode = "RGB"
|
||||||
|
|
|
@ -2,10 +2,9 @@ import unittest
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, features
|
from PIL import Image, ImageDraw, ImageFont, features
|
||||||
|
|
||||||
from .helper import PillowLeakTestCase, is_win32
|
from .helper import PillowLeakTestCase
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(is_win32(), "requires Unix or macOS")
|
|
||||||
class TestTTypeFontLeak(PillowLeakTestCase):
|
class TestTTypeFontLeak(PillowLeakTestCase):
|
||||||
# fails at iteration 3 in master
|
# fails at iteration 3 in master
|
||||||
iterations = 10
|
iterations = 10
|
||||||
|
@ -20,7 +19,7 @@ class TestTTypeFontLeak(PillowLeakTestCase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@unittest.skipIf(not features.check("freetype2"), "Test requires freetype2")
|
@unittest.skipUnless(features.check("freetype2"), "Test requires freetype2")
|
||||||
def test_leak(self):
|
def test_leak(self):
|
||||||
ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
|
ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
|
||||||
self._test_font(ttype)
|
self._test_font(ttype)
|
||||||
|
|
|
@ -13,7 +13,11 @@ class TestImageDraft(PillowTestCase):
|
||||||
im = Image.new(in_mode, in_size)
|
im = Image.new(in_mode, in_size)
|
||||||
data = tostring(im, "JPEG")
|
data = tostring(im, "JPEG")
|
||||||
im = fromstring(data)
|
im = fromstring(data)
|
||||||
im.draft(req_mode, req_size)
|
mode, box = im.draft(req_mode, req_size)
|
||||||
|
scale, _ = im.decoderconfig
|
||||||
|
self.assertEqual(box[:2], (0, 0))
|
||||||
|
self.assertTrue((im.width - scale) < box[2] <= im.width)
|
||||||
|
self.assertTrue((im.height - scale) < box[3] <= im.height)
|
||||||
return im
|
return im
|
||||||
|
|
||||||
def test_size(self):
|
def test_size(self):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase, hopper
|
from .helper import PillowTestCase, fromstring, hopper, tostring
|
||||||
|
|
||||||
|
|
||||||
class TestImageThumbnail(PillowTestCase):
|
class TestImageThumbnail(PillowTestCase):
|
||||||
|
@ -58,3 +58,15 @@ class TestImageThumbnail(PillowTestCase):
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
im.thumbnail((64, 64))
|
im.thumbnail((64, 64))
|
||||||
self.assertEqual(im.size, (64, 64))
|
self.assertEqual(im.size, (64, 64))
|
||||||
|
|
||||||
|
def test_DCT_scaling_edges(self):
|
||||||
|
# Make an image with red borders and size (N * 8) + 1 to cross DCT grid
|
||||||
|
im = Image.new("RGB", (257, 257), "red")
|
||||||
|
im.paste(Image.new("RGB", (235, 235)), (11, 11))
|
||||||
|
|
||||||
|
thumb = fromstring(tostring(im, "JPEG", quality=99, subsampling=0))
|
||||||
|
thumb.thumbnail((32, 32), Image.BICUBIC)
|
||||||
|
|
||||||
|
ref = im.resize((32, 32), Image.BICUBIC)
|
||||||
|
# This is still JPEG, some error is present. Without the fix it is 11.5
|
||||||
|
self.assert_image_similar(thumb, ref, 1.5)
|
||||||
|
|
|
@ -11,7 +11,7 @@ except ImportError:
|
||||||
image_font_installed = False
|
image_font_installed = False
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(not image_font_installed, "image font not installed")
|
@unittest.skipUnless(image_font_installed, "image font not installed")
|
||||||
class TestImageFontBitmap(PillowTestCase):
|
class TestImageFontBitmap(PillowTestCase):
|
||||||
def test_similar(self):
|
def test_similar(self):
|
||||||
text = "EmbeddedBitmap"
|
text = "EmbeddedBitmap"
|
||||||
|
|
|
@ -18,7 +18,7 @@ except (OSError, ImportError):
|
||||||
TK_MODES = ("1", "L", "P", "RGB", "RGBA")
|
TK_MODES = ("1", "L", "P", "RGB", "RGBA")
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(not HAS_TK, "Tk not installed")
|
@unittest.skipUnless(HAS_TK, "Tk not installed")
|
||||||
class TestImageTk(PillowTestCase):
|
class TestImageTk(PillowTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -16,7 +16,7 @@ class TestUtil(PillowTestCase):
|
||||||
# Assert
|
# Assert
|
||||||
self.assertTrue(it_is)
|
self.assertTrue(it_is)
|
||||||
|
|
||||||
@unittest.skipIf(not _util.py36, "os.path support for Paths added in 3.6")
|
@unittest.skipUnless(_util.py36, "os.path support for Paths added in 3.6")
|
||||||
def test_path_obj_is_path(self):
|
def test_path_obj_is_path(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
|
@ -42,7 +42,7 @@ clean:
|
||||||
-rm -rf $(BUILDDIR)/*
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
html:
|
html:
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
$(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,8 @@ Basic Installation
|
||||||
|
|
||||||
Install Pillow with :command:`pip`::
|
Install Pillow with :command:`pip`::
|
||||||
|
|
||||||
$ pip install Pillow
|
python -m pip install pip
|
||||||
|
python -m pip install Pillow
|
||||||
|
|
||||||
|
|
||||||
Windows Installation
|
Windows Installation
|
||||||
|
@ -58,7 +59,8 @@ supported Pythons in both 32 and 64-bit versions in wheel, egg, and
|
||||||
executable installers. These binaries have all of the optional
|
executable installers. These binaries have all of the optional
|
||||||
libraries included except for raqm and libimagequant::
|
libraries included except for raqm and libimagequant::
|
||||||
|
|
||||||
> pip install Pillow
|
python -m pip install pip
|
||||||
|
python -m pip install Pillow
|
||||||
|
|
||||||
|
|
||||||
macOS Installation
|
macOS Installation
|
||||||
|
@ -69,7 +71,8 @@ versions in the wheel format. These include support for all optional
|
||||||
libraries except libimagequant. Raqm support requires libraqm,
|
libraries except libimagequant. Raqm support requires libraqm,
|
||||||
fribidi, and harfbuzz to be installed separately::
|
fribidi, and harfbuzz to be installed separately::
|
||||||
|
|
||||||
$ pip install Pillow
|
python -m pip install pip
|
||||||
|
python -m pip install Pillow
|
||||||
|
|
||||||
Linux Installation
|
Linux Installation
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -79,7 +82,8 @@ versions in the manylinux wheel format. These include support for all
|
||||||
optional libraries except libimagequant. Raqm support requires
|
optional libraries except libimagequant. Raqm support requires
|
||||||
libraqm, fribidi, and harfbuzz to be installed separately::
|
libraqm, fribidi, and harfbuzz to be installed separately::
|
||||||
|
|
||||||
$ pip install Pillow
|
python -m pip install pip
|
||||||
|
python -m pip install Pillow
|
||||||
|
|
||||||
Most major Linux distributions, including Fedora, Debian/Ubuntu and
|
Most major Linux distributions, including Fedora, Debian/Ubuntu and
|
||||||
ArchLinux also include Pillow in packages that previously contained
|
ArchLinux also include Pillow in packages that previously contained
|
||||||
|
@ -92,11 +96,11 @@ Pillow can be installed on FreeBSD via the official Ports or Packages systems:
|
||||||
|
|
||||||
**Ports**::
|
**Ports**::
|
||||||
|
|
||||||
$ cd /usr/ports/graphics/py-pillow && make install clean
|
cd /usr/ports/graphics/py-pillow && make install clean
|
||||||
|
|
||||||
**Packages**::
|
**Packages**::
|
||||||
|
|
||||||
$ pkg install py36-pillow
|
pkg install py36-pillow
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -191,7 +195,8 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
Once you have installed the prerequisites, run::
|
Once you have installed the prerequisites, run::
|
||||||
|
|
||||||
$ pip install Pillow
|
python -m pip install pip
|
||||||
|
python -m pip install Pillow
|
||||||
|
|
||||||
If the prerequisites are installed in the standard library locations
|
If the prerequisites are installed in the standard library locations
|
||||||
for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no
|
for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no
|
||||||
|
@ -201,7 +206,7 @@ those locations by editing :file:`setup.py` or
|
||||||
:file:`setup.cfg`, or by adding environment variables on the command
|
:file:`setup.cfg`, or by adding environment variables on the command
|
||||||
line::
|
line::
|
||||||
|
|
||||||
$ CFLAGS="-I/usr/pkg/include" pip install pillow
|
CFLAGS="-I/usr/pkg/include" python -m pip install pillow
|
||||||
|
|
||||||
If Pillow has been previously built without the required
|
If Pillow has been previously built without the required
|
||||||
prerequisites, it may be necessary to manually clear the pip cache or
|
prerequisites, it may be necessary to manually clear the pip cache or
|
||||||
|
@ -245,11 +250,11 @@ Build Options
|
||||||
|
|
||||||
Sample usage::
|
Sample usage::
|
||||||
|
|
||||||
$ MAX_CONCURRENCY=1 python setup.py build_ext --enable-[feature] install
|
MAX_CONCURRENCY=1 python setup.py build_ext --enable-[feature] install
|
||||||
|
|
||||||
or using pip::
|
or using pip::
|
||||||
|
|
||||||
$ pip install pillow --global-option="build_ext" --global-option="--enable-[feature]"
|
python -m pip install pillow --global-option="build_ext" --global-option="--enable-[feature]"
|
||||||
|
|
||||||
|
|
||||||
Building on macOS
|
Building on macOS
|
||||||
|
@ -265,21 +270,22 @@ tools.
|
||||||
The easiest way to install external libraries is via `Homebrew
|
The easiest way to install external libraries is via `Homebrew
|
||||||
<https://brew.sh/>`_. After you install Homebrew, run::
|
<https://brew.sh/>`_. After you install Homebrew, run::
|
||||||
|
|
||||||
$ brew install libtiff libjpeg webp little-cms2
|
brew install libtiff libjpeg webp little-cms2
|
||||||
|
|
||||||
To install libraqm on macOS use Homebrew to install its dependencies::
|
To install libraqm on macOS use Homebrew to install its dependencies::
|
||||||
|
|
||||||
$ brew install freetype harfbuzz fribidi
|
brew install freetype harfbuzz fribidi
|
||||||
|
|
||||||
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
|
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
|
||||||
|
|
||||||
Now install Pillow with::
|
Now install Pillow with::
|
||||||
|
|
||||||
$ pip install Pillow
|
python -m pip install pip
|
||||||
|
python -m pip install Pillow
|
||||||
|
|
||||||
or from within the uncompressed source directory::
|
or from within the uncompressed source directory::
|
||||||
|
|
||||||
$ python setup.py install
|
python setup.py install
|
||||||
|
|
||||||
Building on Windows
|
Building on Windows
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -293,17 +299,17 @@ Building on FreeBSD
|
||||||
|
|
||||||
.. Note:: Only FreeBSD 10 and 11 tested
|
.. Note:: Only FreeBSD 10 and 11 tested
|
||||||
|
|
||||||
Make sure you have Python's development libraries installed.::
|
Make sure you have Python's development libraries installed::
|
||||||
|
|
||||||
$ sudo pkg install python2
|
sudo pkg install python2
|
||||||
|
|
||||||
Or for Python 3::
|
Or for Python 3::
|
||||||
|
|
||||||
$ sudo pkg install python3
|
sudo pkg install python3
|
||||||
|
|
||||||
Prerequisites are installed on **FreeBSD 10 or 11** with::
|
Prerequisites are installed on **FreeBSD 10 or 11** with::
|
||||||
|
|
||||||
$ sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi
|
sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi
|
||||||
|
|
||||||
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
|
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
|
||||||
|
|
||||||
|
@ -316,33 +322,33 @@ development libraries installed.
|
||||||
|
|
||||||
In Debian or Ubuntu::
|
In Debian or Ubuntu::
|
||||||
|
|
||||||
$ sudo apt-get install python-dev python-setuptools
|
sudo apt-get install python-dev python-setuptools
|
||||||
|
|
||||||
Or for Python 3::
|
Or for Python 3::
|
||||||
|
|
||||||
$ sudo apt-get install python3-dev python3-setuptools
|
sudo apt-get install python3-dev python3-setuptools
|
||||||
|
|
||||||
In Fedora, the command is::
|
In Fedora, the command is::
|
||||||
|
|
||||||
$ sudo dnf install python-devel redhat-rpm-config
|
sudo dnf install python-devel redhat-rpm-config
|
||||||
|
|
||||||
Or for Python 3::
|
Or for Python 3::
|
||||||
|
|
||||||
$ sudo dnf install python3-devel redhat-rpm-config
|
sudo dnf install python3-devel redhat-rpm-config
|
||||||
|
|
||||||
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
|
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
|
||||||
|
|
||||||
Prerequisites are installed on **Ubuntu 16.04 LTS** with::
|
Prerequisites are installed on **Ubuntu 16.04 LTS** with::
|
||||||
|
|
||||||
$ sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
|
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
|
||||||
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk \
|
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
|
||||||
libharfbuzz-dev libfribidi-dev
|
libharfbuzz-dev libfribidi-dev
|
||||||
|
|
||||||
Then see ``depends/install_raqm.sh`` to install libraqm.
|
Then see ``depends/install_raqm.sh`` to install libraqm.
|
||||||
|
|
||||||
Prerequisites are installed on recent **RedHat** **Centos** or **Fedora** with::
|
Prerequisites are installed on recent **RedHat** **Centos** or **Fedora** with::
|
||||||
|
|
||||||
$ sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \
|
sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \
|
||||||
freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \
|
freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \
|
||||||
harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel
|
harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel
|
||||||
|
|
||||||
|
@ -359,7 +365,7 @@ Building on Android
|
||||||
Basic Android support has been added for compilation within the Termux
|
Basic Android support has been added for compilation within the Termux
|
||||||
environment. The dependencies can be installed by::
|
environment. The dependencies can be installed by::
|
||||||
|
|
||||||
$ pkg install -y python ndk-sysroot clang make \
|
pkg install -y python ndk-sysroot clang make \
|
||||||
libjpeg-turbo
|
libjpeg-turbo
|
||||||
|
|
||||||
This has been tested within the Termux app on ChromeOS, on x86.
|
This has been tested within the Termux app on ChromeOS, on x86.
|
||||||
|
|
|
@ -52,12 +52,26 @@ Default resampling filter
|
||||||
|
|
||||||
The default resampling filter has been changed to the high-quality convolution
|
The default resampling filter has been changed to the high-quality convolution
|
||||||
``Image.BICUBIC`` instead of ``Image.NEAREST``, for the :py:meth:`~PIL.Image.Image.resize`
|
``Image.BICUBIC`` instead of ``Image.NEAREST``, for the :py:meth:`~PIL.Image.Image.resize`
|
||||||
method and the :py:meth:`~PIL.ImageOps.pad``, :py:meth:`~PIL.ImageOps.scale``
|
method and the :py:meth:`~PIL.ImageOps.pad`, :py:meth:`~PIL.ImageOps.scale`
|
||||||
and :py:meth:`~PIL.ImageOps.fit`` functions.
|
and :py:meth:`~PIL.ImageOps.fit` functions.
|
||||||
``Image.NEAREST`` is still always used for images in "P" and "1" modes.
|
``Image.NEAREST`` is still always used for images in "P" and "1" modes.
|
||||||
See :ref:`concept-filters` to learn the difference. In short,
|
See :ref:`concept-filters` to learn the difference. In short,
|
||||||
``Image.NEAREST`` is a very fast filter, but simple and low-quality.
|
``Image.NEAREST`` is a very fast filter, but simple and low-quality.
|
||||||
|
|
||||||
|
Image.draft() return value
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If the :py:meth:`~PIL.Image.Image.draft` method has no effect, it returns ``None``.
|
||||||
|
If it does have an effect, then it previously returned the image itself.
|
||||||
|
However, unlike other `chain methods`_, :py:meth:`~PIL.Image.Image.draft` does not
|
||||||
|
return a modified version of the image, but modifies it in-place. So instead, if
|
||||||
|
:py:meth:`~PIL.Image.Image.draft` has an effect, Pillow will now return a tuple
|
||||||
|
of the image mode and a co-ordinate box. The box is the original coordinates in the
|
||||||
|
bounds of resulting image. This may be useful in a subsequent
|
||||||
|
:py:meth:`~PIL.Image.Image.resize` call.
|
||||||
|
|
||||||
|
.. _chain methods: https://en.wikipedia.org/wiki/Method_chaining
|
||||||
|
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
@ -87,8 +101,8 @@ Image.__del__
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
Implicitly closing the image's underlying file in ``Image.__del__`` has been removed.
|
Implicitly closing the image's underlying file in ``Image.__del__`` has been removed.
|
||||||
Use a context manager or call ``Image.close()`` instead to close the file in a
|
Use a context manager or call :py:meth:`~PIL.Image.Image.close` instead to close
|
||||||
deterministic way.
|
the file in a deterministic way.
|
||||||
|
|
||||||
Previous method:
|
Previous method:
|
||||||
|
|
||||||
|
@ -104,8 +118,12 @@ Use instead:
|
||||||
with Image.open("hopper.png") as im:
|
with Image.open("hopper.png") as im:
|
||||||
im.save("out.jpg")
|
im.save("out.jpg")
|
||||||
|
|
||||||
Better thumbnail aspect ratio preservation
|
Better thumbnail geometry
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
When calculating the new dimensions in ``Image.thumbnail``, round to the
|
When calculating the new dimensions in :py:meth:`~PIL.Image.Image.thumbnail`,
|
||||||
nearest integer, instead of always rounding down.
|
round to the nearest integer, instead of always rounding down.
|
||||||
|
This better preserves the original aspect ratio.
|
||||||
|
|
||||||
|
When the image width or height is not divisible by 8 the last row and column
|
||||||
|
in the image get the correct weight after JPEG DCT scaling.
|
||||||
|
|
|
@ -321,12 +321,15 @@ def _save(im, fp, filename, bitmap_header=True):
|
||||||
# bitmap header
|
# bitmap header
|
||||||
if bitmap_header:
|
if bitmap_header:
|
||||||
offset = 14 + header + colors * 4
|
offset = 14 + header + colors * 4
|
||||||
|
file_size = offset + image
|
||||||
|
if file_size > 2 ** 32 - 1:
|
||||||
|
raise ValueError("File size is too large for the BMP format")
|
||||||
fp.write(
|
fp.write(
|
||||||
b"BM"
|
b"BM" # file type (magic)
|
||||||
+ o32(offset + image) # file type (magic)
|
+ o32(file_size) # file size
|
||||||
+ o32(0) # file size
|
+ o32(0) # reserved
|
||||||
+ o32(offset) # reserved
|
+ o32(offset) # image data offset
|
||||||
) # image data offset
|
)
|
||||||
|
|
||||||
# bitmap info header
|
# bitmap info header
|
||||||
fp.write(
|
fp.write(
|
||||||
|
|
|
@ -616,6 +616,7 @@ def _save_netpbm(im, fp, filename):
|
||||||
# below for information on how to enable this.
|
# below for information on how to enable this.
|
||||||
tempfile = im._dump()
|
tempfile = im._dump()
|
||||||
|
|
||||||
|
try:
|
||||||
with open(filename, "wb") as f:
|
with open(filename, "wb") as f:
|
||||||
if im.mode != "RGB":
|
if im.mode != "RGB":
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
|
@ -630,7 +631,10 @@ def _save_netpbm(im, fp, filename):
|
||||||
quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
|
quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
|
||||||
)
|
)
|
||||||
togif_proc = subprocess.Popen(
|
togif_proc = subprocess.Popen(
|
||||||
togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=subprocess.DEVNULL
|
togif_cmd,
|
||||||
|
stdin=quant_proc.stdout,
|
||||||
|
stdout=f,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
||||||
|
@ -643,7 +647,7 @@ def _save_netpbm(im, fp, filename):
|
||||||
retcode = togif_proc.wait()
|
retcode = togif_proc.wait()
|
||||||
if retcode:
|
if retcode:
|
||||||
raise subprocess.CalledProcessError(retcode, togif_cmd)
|
raise subprocess.CalledProcessError(retcode, togif_cmd)
|
||||||
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(tempfile)
|
os.unlink(tempfile)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|
|
@ -1127,15 +1127,17 @@ class Image:
|
||||||
Configures the image file loader so it returns a version of the
|
Configures the image file loader so it returns a version of the
|
||||||
image that as closely as possible matches the given mode and
|
image that as closely as possible matches the given mode and
|
||||||
size. For example, you can use this method to convert a color
|
size. For example, you can use this method to convert a color
|
||||||
JPEG to greyscale while loading it, or to extract a 128x192
|
JPEG to greyscale while loading it.
|
||||||
version from a PCD file.
|
|
||||||
|
If any changes are made, returns a tuple with the chosen ``mode`` and
|
||||||
|
``box`` with coordinates of the original image within the altered one.
|
||||||
|
|
||||||
Note that this method modifies the :py:class:`~PIL.Image.Image` object
|
Note that this method modifies the :py:class:`~PIL.Image.Image` object
|
||||||
in place. If the image has already been loaded, this method has no
|
in place. If the image has already been loaded, this method has no
|
||||||
effect.
|
effect.
|
||||||
|
|
||||||
Note: This method is not implemented for most images. It is
|
Note: This method is not implemented for most images. It is
|
||||||
currently implemented only for JPEG and PCD images.
|
currently implemented only for JPEG and MPO images.
|
||||||
|
|
||||||
:param mode: The requested mode.
|
:param mode: The requested mode.
|
||||||
:param size: The requested size.
|
:param size: The requested size.
|
||||||
|
@ -2177,14 +2179,17 @@ class Image:
|
||||||
x = max(round(x * size[1] / y), 1)
|
x = max(round(x * size[1] / y), 1)
|
||||||
y = round(size[1])
|
y = round(size[1])
|
||||||
size = x, y
|
size = x, y
|
||||||
|
box = None
|
||||||
|
|
||||||
if size == self.size:
|
if size == self.size:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.draft(None, size)
|
res = self.draft(None, size)
|
||||||
|
if res is not None:
|
||||||
|
box = res[1]
|
||||||
|
|
||||||
if self.size != size:
|
if self.size != size:
|
||||||
im = self.resize(size, resample)
|
im = self.resize(size, resample, box=box)
|
||||||
|
|
||||||
self.im = im.im
|
self.im = im.im
|
||||||
self._size = size
|
self._size = size
|
||||||
|
@ -2214,12 +2219,14 @@ class Image:
|
||||||
|
|
||||||
It may also be an :py:class:`~PIL.Image.ImageTransformHandler`
|
It may also be an :py:class:`~PIL.Image.ImageTransformHandler`
|
||||||
object::
|
object::
|
||||||
|
|
||||||
class Example(Image.ImageTransformHandler):
|
class Example(Image.ImageTransformHandler):
|
||||||
def transform(size, method, data, resample, fill=1):
|
def transform(size, method, data, resample, fill=1):
|
||||||
# Return result
|
# Return result
|
||||||
|
|
||||||
It may also be an object with a :py:meth:`~method.getdata` method
|
It may also be an object with a :py:meth:`~method.getdata` method
|
||||||
that returns a tuple supplying new **method** and **data** values::
|
that returns a tuple supplying new **method** and **data** values::
|
||||||
|
|
||||||
class Example(object):
|
class Example(object):
|
||||||
def getdata(self):
|
def getdata(self):
|
||||||
method = Image.EXTENT
|
method = Image.EXTENT
|
||||||
|
|
|
@ -122,11 +122,6 @@ class ImageFile(Image.Image):
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def draft(self, mode, size):
|
|
||||||
"""Set draft mode"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_format_mimetype(self):
|
def get_format_mimetype(self):
|
||||||
if self.custom_mimetype:
|
if self.custom_mimetype:
|
||||||
return self.custom_mimetype
|
return self.custom_mimetype
|
||||||
|
|
|
@ -234,6 +234,7 @@ def pad(image, size, method=Image.BICUBIC, color=None, centering=(0.5, 0.5)):
|
||||||
:param color: The background color of the padded image.
|
:param color: The background color of the padded image.
|
||||||
:param centering: Control the position of the original image within the
|
:param centering: Control the position of the original image within the
|
||||||
padded version.
|
padded version.
|
||||||
|
|
||||||
(0.5, 0.5) will keep the image centered
|
(0.5, 0.5) will keep the image centered
|
||||||
(0, 0) will keep the image aligned to the top left
|
(0, 0) will keep the image aligned to the top left
|
||||||
(1, 1) will keep the image aligned to the bottom
|
(1, 1) will keep the image aligned to the bottom
|
||||||
|
|
|
@ -166,10 +166,11 @@ def APP(self, marker):
|
||||||
# 1 dpcm = 2.54 dpi
|
# 1 dpcm = 2.54 dpi
|
||||||
dpi *= 2.54
|
dpi *= 2.54
|
||||||
self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5)
|
self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5)
|
||||||
except (KeyError, SyntaxError, ZeroDivisionError):
|
except (KeyError, SyntaxError, ValueError, ZeroDivisionError):
|
||||||
# SyntaxError for invalid/unreadable EXIF
|
# SyntaxError for invalid/unreadable EXIF
|
||||||
# KeyError for dpi not included
|
# KeyError for dpi not included
|
||||||
# ZeroDivisionError for invalid dpi rational value
|
# ZeroDivisionError for invalid dpi rational value
|
||||||
|
# ValueError for x_resolution[0] being an invalid float
|
||||||
self.info["dpi"] = 72, 72
|
self.info["dpi"] = 72, 72
|
||||||
|
|
||||||
|
|
||||||
|
@ -412,7 +413,8 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
return
|
return
|
||||||
|
|
||||||
d, e, o, a = self.tile[0]
|
d, e, o, a = self.tile[0]
|
||||||
scale = 0
|
scale = 1
|
||||||
|
original_size = self.size
|
||||||
|
|
||||||
if a[0] == "RGB" and mode in ["L", "YCbCr"]:
|
if a[0] == "RGB" and mode in ["L", "YCbCr"]:
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -435,7 +437,8 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
self.tile = [(d, e, o, a)]
|
self.tile = [(d, e, o, a)]
|
||||||
self.decoderconfig = (scale, 0)
|
self.decoderconfig = (scale, 0)
|
||||||
|
|
||||||
return self
|
box = (0, 0, original_size[0] / float(scale), original_size[1] / float(scale))
|
||||||
|
return (self.mode, box)
|
||||||
|
|
||||||
def load_djpeg(self):
|
def load_djpeg(self):
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
curl -fsSL -o pypy3.zip https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.2.0-win32.zip
|
curl -fsSL -o pypy3.zip https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-win32.zip
|
||||||
7z x pypy3.zip -oc:\
|
7z x pypy3.zip -oc:\
|
||||||
c:\Python37\Scripts\virtualenv.exe -p c:\pypy3.6-v7.2.0-win32\pypy3.exe c:\vp\pypy3
|
c:\Python37\Scripts\virtualenv.exe -p c:\pypy3.6-v7.3.0-win32\pypy3.exe c:\vp\pypy3
|
||||||
|
|
Loading…
Reference in New Issue
Block a user