Merge branch 'master' into gitignore-review
6
.github/CONTRIBUTING.md
vendored
|
@ -4,7 +4,7 @@ Bug fixes, feature additions, tests, documentation and more can be contributed v
|
||||||
|
|
||||||
## Bug fixes, feature additions, etc.
|
## Bug fixes, feature additions, etc.
|
||||||
|
|
||||||
Please send a pull request to the master branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new) or irc://irc.freenode.net#pil
|
Please send a pull request to the master branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new), [Gitter](https://gitter.im/python-pillow/Pillow) or irc://irc.freenode.net#pil
|
||||||
|
|
||||||
- Fork the Pillow repository.
|
- Fork the Pillow repository.
|
||||||
- Create a branch from master.
|
- Create a branch from master.
|
||||||
|
@ -21,7 +21,9 @@ Please send a pull request to the master branch. Please include [documentation](
|
||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
When reporting issues, please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. The best reproductions are self-contained scripts with minimal dependencies.
|
When reporting issues, please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
|
||||||
|
|
||||||
|
The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, Django, or buildout, try to replicate the issue just using Pillow.
|
||||||
|
|
||||||
### Provide details
|
### Provide details
|
||||||
|
|
||||||
|
|
49
.travis.yml
|
@ -1,3 +1,5 @@
|
||||||
|
dist: xenial
|
||||||
|
sudo: required
|
||||||
language: python
|
language: python
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
||||||
|
@ -12,13 +14,24 @@ matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- python: "pypy"
|
- python: "pypy"
|
||||||
|
dist: trusty
|
||||||
- python: "pypy3"
|
- python: "pypy3"
|
||||||
- python: '3.6'
|
dist: trusty
|
||||||
|
- python: '3.7'
|
||||||
- python: '2.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
|
||||||
|
- 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'
|
||||||
|
- python: '3.5'
|
||||||
|
dist: trusty
|
||||||
- python: '3.4'
|
- python: '3.4'
|
||||||
- python: '3.7-dev'
|
dist: trusty
|
||||||
- env: DOCKER="alpine" DOCKER_TAG="pytest"
|
- env: DOCKER="alpine" DOCKER_TAG="pytest"
|
||||||
- env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5
|
- env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5
|
||||||
- env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest"
|
- env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest"
|
||||||
|
@ -31,10 +44,6 @@ matrix:
|
||||||
- env: DOCKER="fedora-26-amd64" DOCKER_TAG="pytest"
|
- env: DOCKER="fedora-26-amd64" DOCKER_TAG="pytest"
|
||||||
- env: DOCKER="fedora-27-amd64" DOCKER_TAG="pytest"
|
- env: DOCKER="fedora-27-amd64" DOCKER_TAG="pytest"
|
||||||
|
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
sudo: required
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
|
|
||||||
|
@ -42,7 +51,7 @@ install:
|
||||||
- if [ "$DOCKER" == "" ]; then .travis/install.sh; fi
|
- if [ "$DOCKER" == "" ]; then .travis/install.sh; fi
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- if [ "$DOCKER" ]; then docker pull pythonpillow/$DOCKER:$DOCKER_TAG; fi
|
- if [ "$DOCKER" ]; then travis_retry docker pull pythonpillow/$DOCKER:$DOCKER_TAG; fi
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
# Qt needs a display for some of the tests, and it's only run on the system site packages install
|
# Qt needs a display for some of the tests, and it's only run on the system site packages install
|
||||||
|
@ -61,29 +70,3 @@ script:
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- .travis/after_success.sh
|
- .travis/after_success.sh
|
||||||
|
|
||||||
after_failure:
|
|
||||||
- |
|
|
||||||
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
|
||||||
curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
|
|
||||||
python travis_after_all.py
|
|
||||||
export $(cat .to_export_back)
|
|
||||||
if [ "$BUILD_LEADER" = "YES" ]; then
|
|
||||||
if [ "$BUILD_AGGREGATE_STATUS" = "others_failed" ]; then
|
|
||||||
echo "All jobs failed"
|
|
||||||
else
|
|
||||||
echo "Some jobs failed"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
after_script:
|
|
||||||
- |
|
|
||||||
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
|
||||||
echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS
|
|
||||||
fi
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
# travis encrypt AUTH_TOKEN=
|
|
||||||
secure: "Vzm7aG1Qv0SDQcqiPzZMedNLn5ZmpL7IzF0DYnqcD+/l+zmKU22SnJBcX0uVXumo+r7eZfpsShpqfcdsZvMlvmQnwz+Y6AGKQru9tCKZbTMnuRjWKKXekC+tr8Xt9CKvRVtte5PyXW31paxUI3/e+fQGBwoFjEEC+6EpEOjeRfE="
|
|
||||||
|
|
|
@ -30,20 +30,3 @@ if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then
|
||||||
depends/diffcover-install.sh
|
depends/diffcover-install.sh
|
||||||
depends/diffcover-run.sh
|
depends/diffcover-run.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# after_all
|
|
||||||
|
|
||||||
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
|
||||||
curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
|
|
||||||
python travis_after_all.py
|
|
||||||
export $(cat .to_export_back)
|
|
||||||
if [ "$BUILD_LEADER" = "YES" ]; then
|
|
||||||
if [ "$BUILD_AGGREGATE_STATUS" = "others_succeeded" ]; then
|
|
||||||
echo "All jobs succeeded! Triggering macOS build..."
|
|
||||||
# Trigger a macOS build at the pillow-wheels repo
|
|
||||||
./build_children.sh
|
|
||||||
else
|
|
||||||
echo "Some jobs failed"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ pip install -U pytest
|
||||||
pip install -U pytest-cov
|
pip install -U pytest-cov
|
||||||
pip install pyroma
|
pip install pyroma
|
||||||
pip install test-image-results
|
pip install test-image-results
|
||||||
|
pip install numpy
|
||||||
|
|
||||||
# docs only on Python 2.7
|
# docs only on Python 2.7
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi
|
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi
|
||||||
|
|
188
CHANGES.rst
|
@ -2,9 +2,159 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
5.1.0 (unreleased)
|
5.3.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Depends: Update libimagequant to 2.12.1 #3281
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add three-color support to ImageOps.colorize #3242
|
||||||
|
[tsennott]
|
||||||
|
|
||||||
|
- Tests: Add LA to TGA test modes #3222
|
||||||
|
[danpla]
|
||||||
|
|
||||||
|
- Skip outline if the draw operation fills with the same colour #2922
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Flake8 fixes #3173
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Avoid deprecated 'U' mode when opening files #2187
|
||||||
|
[jdufresne]
|
||||||
|
|
||||||
|
5.2.0 (2018-07-01)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Fixed saving a multiframe image as a single frame PDF #3137
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- If a Qt version is already imported, attempt to use it first #3143
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix transform fill color for alpha images #3147
|
||||||
|
[fozcode]
|
||||||
|
|
||||||
|
- TGA: Add support for writing RLE data #3186
|
||||||
|
[danpla]
|
||||||
|
|
||||||
|
- TGA: Read and write LA data #3178
|
||||||
|
[danpla]
|
||||||
|
|
||||||
|
- QuantOctree.c: Remove erroneous attempt to average over an empty range #3196
|
||||||
|
[tkoeppe]
|
||||||
|
|
||||||
|
- Changed ICNS format tests to pass on OS X 10.11 #3202
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed bug in ImageDraw.multiline_textsize() #3114
|
||||||
|
[tianyu139]
|
||||||
|
|
||||||
|
- Added getsize_multiline support for PIL.ImageFont #3113
|
||||||
|
[tianyu139]
|
||||||
|
|
||||||
|
- Added ImageFile get_format_mimetype method #3190
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed mmap file pointer to use context manager #3216
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed ellipse point calculations to be more evenly distributed #3142
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Only extract first Exif segment #2946
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Tests: Test ImageDraw2, WalImageFile #3135, #2989
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Remove unnecessary '#if 0' code #3075
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Tests: Added GD tests #1817
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix collections ABCs DeprecationWarning in Python 3.7 #3123
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- unpack_from is faster than unpack of slice #3201
|
||||||
|
[landfillbaby]
|
||||||
|
|
||||||
|
- Docs: Add coordinate system links and file handling links in documentation #3204, #3214
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Tests: TestFilePng: Fix test_save_l_transparency() #3182
|
||||||
|
[danpla]
|
||||||
|
|
||||||
|
- Docs: Correct argument name #3171
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Docs: Update CMake download URL #3166
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Docs: Improve Image.transform documentation #3164
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix transform fillcolor argument when image mode is RGBA or LA #3163
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Tests: More specific Exception testing #3158
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add getrgb HSB/HSV color strings #3148
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow float values in getrgb HSL color string #3146
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- AppVeyor: Upgrade to Python 2.7.15 and 3.4.4 #3140
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- AppVeyor: Upgrade to PyPy 6.0.0 #3133
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Deprecate PILLOW_VERSION and VERSION #3090
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Support Python 3.7 #3076
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Depends: Update freetype to 2.9.1, libjpeg to 9c, libwebp to 1.0.0 #3121, #3136, #3108
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Build macOS wheels with Xcode 6.4, supporting older macOS versions #3068
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix _i2f compilation on some GCC versions #3067
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Changed encoderinfo to have priority over info when saving GIF images #3086
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Rename PIL.version to PIL._version and remove it from module #3083
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Enable background colour parameter on rotate #3057
|
||||||
|
[storesource]
|
||||||
|
|
||||||
|
- Remove unnecessary `#if 1` directive #3072
|
||||||
|
[jdufresne]
|
||||||
|
|
||||||
|
- Remove unused Python class, Path #3070
|
||||||
|
[jdufresne]
|
||||||
|
|
||||||
|
- Fix dereferencing type-punned pointer will break strict-aliasing #3069
|
||||||
|
[jdufresne]
|
||||||
|
|
||||||
|
5.1.0 (2018-04-02)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Close fp before return in ImagingSavePPM #3061
|
||||||
|
[kathryndavies]
|
||||||
|
|
||||||
|
- Added documentation for ICNS append_images #3051
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Docs: Move intro text below its header #3021
|
- Docs: Move intro text below its header #3021
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
|
@ -13,12 +163,12 @@ Changelog (Pillow)
|
||||||
|
|
||||||
- Fix TypeError for JPEG2000 parser feed #3042
|
- Fix TypeError for JPEG2000 parser feed #3042
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- Certain corrupted jpegs can result in no data read #3023
|
- Certain corrupted jpegs can result in no data read #3023
|
||||||
[kkopachev]
|
[kkopachev]
|
||||||
|
|
||||||
- Add support for BLP file format #3007
|
- Add support for BLP file format #3007
|
||||||
[jleclanche]
|
[jleclanche]
|
||||||
|
|
||||||
- Simplify version checks #2998
|
- Simplify version checks #2998
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
@ -83,10 +233,6 @@ Changelog (Pillow)
|
||||||
- Docs: Changed documentation references to 2.x to 2.7 #2921
|
- Docs: Changed documentation references to 2.x to 2.7 #2921
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
|
||||||
5.0.1 (unreleased)
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fix memory leak when opening webp files #2974
|
- Fix memory leak when opening webp files #2974
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
|
@ -177,7 +323,7 @@ Changelog (Pillow)
|
||||||
- Add eog support for Ubuntu Image Viewer #2864
|
- Add eog support for Ubuntu Image Viewer #2864
|
||||||
[NafisFaysal]
|
[NafisFaysal]
|
||||||
|
|
||||||
- Test: Test on 3.7-dev on Travis.ci #2870
|
- Test: Test on 3.7-dev on Travis CI #2870
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- Dependencies: Update libtiff to 4.0.9 #2871
|
- Dependencies: Update libtiff to 4.0.9 #2871
|
||||||
|
@ -306,7 +452,7 @@ Changelog (Pillow)
|
||||||
- Fixed doc syntax in ImageDraw #2752
|
- Fixed doc syntax in ImageDraw #2752
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Fixed support for building on Windows/msys2. Added Appveyor CI coverage for python3 on msys2 #2476
|
- Fixed support for building on Windows/msys2. Added Appveyor CI coverage for python3 on msys2 #2746
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Fix ValueError in Exif/Tiff IFD #2719
|
- Fix ValueError in Exif/Tiff IFD #2719
|
||||||
|
@ -378,7 +524,7 @@ Changelog (Pillow)
|
||||||
- Use RGBX rawmode for RGB JPEG images where possible #1989
|
- Use RGBX rawmode for RGB JPEG images where possible #1989
|
||||||
[homm]
|
[homm]
|
||||||
|
|
||||||
- Remove palettes from non-palette modes in _new #2702
|
- Remove palettes from non-palette modes in _new #2704
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Delete transparency info when convert'ing RGB/L to RGBA #2633
|
- Delete transparency info when convert'ing RGB/L to RGBA #2633
|
||||||
|
@ -498,7 +644,7 @@ Changelog (Pillow)
|
||||||
- Doc: Clarified Image.save:append_images documentation #2604
|
- Doc: Clarified Image.save:append_images documentation #2604
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- CI: Amazon Linux and Centos6 docker images added to TravisCI #2585
|
- CI: Amazon Linux and Centos6 docker images added to Travis CI #2585
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Image.alpha_composite added #2595
|
- Image.alpha_composite added #2595
|
||||||
|
@ -567,7 +713,7 @@ Changelog (Pillow)
|
||||||
- Update Feature Detection #2520
|
- Update Feature Detection #2520
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- CI: Update pypy on TravisCI #2573
|
- CI: Update pypy on Travis CI #2573
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- ImageMorph: Fix wrong expected size of MRLs read from disk #2561
|
- ImageMorph: Fix wrong expected size of MRLs read from disk #2561
|
||||||
|
@ -801,10 +947,10 @@ Changelog (Pillow)
|
||||||
- Add center and translate option to Image.rotate. #2328
|
- Add center and translate option to Image.rotate. #2328
|
||||||
[lambdafu]
|
[lambdafu]
|
||||||
|
|
||||||
- Test: Relax WMF test condition, fixes #2323
|
- Test: Relax WMF test condition, fixes #2323. #2327
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Allow 0 size images, Fixes #2259, Reverts to pre-3.4 behavior.
|
- Allow 0 size images, Fixes #2259, Reverts to pre-3.4 behavior. #2262
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- SGI: Save uncompressed SGI/BW/RGB/RGBA files #2325
|
- SGI: Save uncompressed SGI/BW/RGB/RGBA files #2325
|
||||||
|
@ -1135,10 +1281,10 @@ Changelog (Pillow)
|
||||||
3.3.2 (2016-10-03)
|
3.3.2 (2016-10-03)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- Fix negative image sizes in Storage.c #2105
|
- Fix negative image sizes in Storage.c #2146
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Fix integer overflow in map.c #2105
|
- Fix integer overflow in map.c #2146
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
3.3.1 (2016-08-18)
|
3.3.1 (2016-08-18)
|
||||||
|
@ -1276,7 +1422,7 @@ Changelog (Pillow)
|
||||||
- Skip tests that require libtiff if it is not installed #1893 (fixes #1866)
|
- Skip tests that require libtiff if it is not installed #1893 (fixes #1866)
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Skip test when icc profile is not available, fixes #1887
|
- Skip test when icc profile is not available, fixes #1887. #1892
|
||||||
[doko42]
|
[doko42]
|
||||||
|
|
||||||
- Make deprecated functions raise NotImplementedError instead of Exception. #1862, #1890
|
- Make deprecated functions raise NotImplementedError instead of Exception. #1862, #1890
|
||||||
|
@ -1881,7 +2027,7 @@ Changelog (Pillow)
|
||||||
2.8.1 (2015-04-02)
|
2.8.1 (2015-04-02)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- Bug fix: Catch struct.error on invalid JPEG, fixes #1163
|
- Bug fix: Catch struct.error on invalid JPEG, fixes #1163. #1165
|
||||||
[wiredfool, hugovk]
|
[wiredfool, hugovk]
|
||||||
|
|
||||||
2.8.0 (2015-04-01)
|
2.8.0 (2015-04-01)
|
||||||
|
@ -2016,7 +2162,7 @@ Changelog (Pillow)
|
||||||
- Updated manifest #957
|
- Updated manifest #957
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Fix PyPy 2.4 regression #952
|
- Fix PyPy 2.4 regression #958
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Webp Metadata Skip Test comments #954
|
- Webp Metadata Skip Test comments #954
|
||||||
|
@ -2058,7 +2204,7 @@ Changelog (Pillow)
|
||||||
- Use redistributable ICC profiles for testing, skip if not available #923
|
- Use redistributable ICC profiles for testing, skip if not available #923
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Additional documentation for JPEG info and save options #890
|
- Additional documentation for JPEG info and save options #922
|
||||||
[wiredfool]
|
[wiredfool]
|
||||||
|
|
||||||
- Fix JPEG Encoding memory leak when exif or qtables were specified #921
|
- Fix JPEG Encoding memory leak when exif or qtables were specified #921
|
||||||
|
|
|
@ -24,7 +24,6 @@ exclude .editorconfig
|
||||||
exclude .landscape.yaml
|
exclude .landscape.yaml
|
||||||
exclude .travis
|
exclude .travis
|
||||||
exclude .travis/*
|
exclude .travis/*
|
||||||
exclude build_children.sh
|
|
||||||
exclude tox.ini
|
exclude tox.ini
|
||||||
global-exclude .git*
|
global-exclude .git*
|
||||||
global-exclude *.pyc
|
global-exclude *.pyc
|
||||||
|
|
2
Makefile
|
@ -91,7 +91,7 @@ sdist:
|
||||||
test:
|
test:
|
||||||
pytest -qq
|
pytest -qq
|
||||||
|
|
||||||
# https://docs.python.org/2/distutils/packageindex.html#the-pypirc-file
|
# https://docs.python.org/3/distutils/packageindex.html#the-pypirc-file
|
||||||
upload-test:
|
upload-test:
|
||||||
# [test]
|
# [test]
|
||||||
# username:
|
# username:
|
||||||
|
|
|
@ -14,7 +14,7 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
|
||||||
* - docs
|
* - docs
|
||||||
- |docs|
|
- |docs|
|
||||||
* - tests
|
* - tests
|
||||||
- | |linux| |macos| |windows| |coverage|
|
- |linux| |macos| |windows| |coverage|
|
||||||
* - package
|
* - package
|
||||||
- |zenodo| |version|
|
- |zenodo| |version|
|
||||||
* - social
|
* - social
|
||||||
|
@ -44,7 +44,7 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
|
||||||
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
|
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
|
||||||
|
|
||||||
.. |version| image:: https://img.shields.io/pypi/v/pillow.svg
|
.. |version| image:: https://img.shields.io/pypi/v/pillow.svg
|
||||||
:target: https://pypi.python.org/pypi/Pillow/
|
:target: https://pypi.org/project/Pillow/
|
||||||
:alt: Latest PyPI version
|
:alt: Latest PyPI version
|
||||||
|
|
||||||
.. |gitter| image:: https://badges.gitter.im/python-pillow/Pillow.svg
|
.. |gitter| image:: https://badges.gitter.im/python-pillow/Pillow.svg
|
||||||
|
|
37
RELEASING.md
|
@ -4,17 +4,17 @@
|
||||||
|
|
||||||
Released quarterly on the first day of January, April, July, October.
|
Released quarterly on the first day of January, April, July, October.
|
||||||
|
|
||||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||||
* [ ] Develop and prepare release in ``master`` branch.
|
* [ ] Develop and prepare release in ``master`` branch.
|
||||||
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
|
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
|
||||||
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in TravisCI.
|
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI.
|
||||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `PIL/version.py`
|
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Update `CHANGES.rst`.
|
* [ ] Update `CHANGES.rst`.
|
||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||||
* [ ] Create branch and tag for release e.g.:
|
* [ ] Create branch and tag for release e.g.:
|
||||||
```
|
```
|
||||||
$ git branch 2.9.x
|
$ git branch 5.2.x
|
||||||
$ git tag 2.9.0
|
$ git tag 5.2.0
|
||||||
$ git push --all
|
$ git push --all
|
||||||
$ git push --tags
|
$ git push --tags
|
||||||
```
|
```
|
||||||
|
@ -23,8 +23,9 @@ Released quarterly on the first day of January, April, July, October.
|
||||||
$ make sdist
|
$ make sdist
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](#binary-distributions)
|
* [ ] Create [binary distributions](#binary-distributions)
|
||||||
* [ ] Upload all binaries and source distributions with ``twine upload dist/Pillow-4.1.0-*``
|
* [ ] Upload all binaries and source distributions e.g. ``twine upload dist/Pillow-5.2.0-*``
|
||||||
* [ ] Manually hide old versions on PyPI such that only the latest major release is visible when viewing https://pypi.python.org/pypi/Pillow (https://pypi.python.org/pypi?:action=pkg_edit&name=Pillow)
|
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
|
||||||
|
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), append `.dev0` to version identifier in `src/PIL/_version.py`
|
||||||
|
|
||||||
## Point Release
|
## Point Release
|
||||||
|
|
||||||
|
@ -32,17 +33,17 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
|
|
||||||
* [ ] Make necessary changes in ``master`` branch.
|
* [ ] Make necessary changes in ``master`` branch.
|
||||||
* [ ] Update `CHANGES.rst`.
|
* [ ] Update `CHANGES.rst`.
|
||||||
* [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``2.9.x``.
|
* [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``5.2.x``.
|
||||||
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``2.9.x``.
|
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``5.2.x``.
|
||||||
* [ ] Checkout release branch e.g.:
|
* [ ] Check out release branch e.g.:
|
||||||
```
|
```
|
||||||
git checkout -t remotes/origin/2.9.x
|
git checkout -t remotes/origin/5.2.x
|
||||||
```
|
```
|
||||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `PIL/version.py`
|
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Run pre-release check via `make release-test`.
|
* [ ] Run pre-release check via `make release-test`.
|
||||||
* [ ] Create tag for release e.g.:
|
* [ ] Create tag for release e.g.:
|
||||||
```
|
```
|
||||||
$ git tag 2.9.1
|
$ git tag 5.2.1
|
||||||
$ git push --tags
|
$ git push --tags
|
||||||
```
|
```
|
||||||
* [ ] Create source distributions e.g.:
|
* [ ] Create source distributions e.g.:
|
||||||
|
@ -50,6 +51,7 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
$ make sdist
|
$ make sdist
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](#binary-distributions)
|
* [ ] Create [binary distributions](#binary-distributions)
|
||||||
|
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
|
||||||
|
|
||||||
## Embargoed Release
|
## Embargoed Release
|
||||||
|
|
||||||
|
@ -73,6 +75,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
$ make sdist
|
$ make sdist
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](#binary-distributions)
|
* [ ] Create [binary distributions](#binary-distributions)
|
||||||
|
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
|
||||||
|
|
||||||
## Binary Distributions
|
## Binary Distributions
|
||||||
|
|
||||||
|
@ -83,7 +86,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
### Mac and Linux
|
### Mac and Linux
|
||||||
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
||||||
```
|
```
|
||||||
$ git checkout https://github.com/python-pillow/pillow-wheels
|
$ git clone https://github.com/python-pillow/pillow-wheels
|
||||||
$ cd pillow-wheels
|
$ cd pillow-wheels
|
||||||
$ git submodule init
|
$ git submodule init
|
||||||
$ git submodule update
|
$ git submodule update
|
||||||
|
@ -91,7 +94,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
$ git fetch --all
|
$ git fetch --all
|
||||||
$ git checkout [[release tag]]
|
$ git checkout [[release tag]]
|
||||||
$ cd ..
|
$ cd ..
|
||||||
$ git commit -m "Pillow -> 2.9.0" Pillow
|
$ git commit -m "Pillow -> 5.2.0" Pillow
|
||||||
$ git push
|
$ git push
|
||||||
```
|
```
|
||||||
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
|
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
|
||||||
|
@ -99,8 +102,8 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
|
|
||||||
## Publicize Release
|
## Publicize Release
|
||||||
|
|
||||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328.
|
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
* [ ] Make sure the default version for Read the Docs is the latest release version, e.g. ``3.1.x`` rather than ``latest``: https://readthedocs.org/projects/pillow/versions/
|
* [ ] Make sure the default version for Read the Docs is the latest release version, i.e. ``5.2.0`` rather than ``latest`` e.g. https://pillow.readthedocs.io/en/5.2.x/
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
# Run from anywhere that PIL is importable.
|
# Run from anywhere that PIL is importable.
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from PIL._util import py3
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
if bytes is str:
|
if py3:
|
||||||
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00')))
|
|
||||||
else:
|
|
||||||
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00',
|
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00',
|
||||||
'latin-1')))
|
'latin-1')))
|
||||||
|
else:
|
||||||
|
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00')))
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
# Run from anywhere that PIL is importable.
|
# Run from anywhere that PIL is importable.
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from PIL._util import py3
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
if bytes is str:
|
if py3:
|
||||||
Image.open(BytesIO(bytes(
|
|
||||||
'\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang')))
|
|
||||||
else:
|
|
||||||
Image.open(BytesIO(bytes(
|
Image.open(BytesIO(bytes(
|
||||||
'\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang',
|
'\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang',
|
||||||
'latin-1')))
|
'latin-1')))
|
||||||
|
|
||||||
|
else:
|
||||||
|
Image.open(BytesIO(bytes(
|
||||||
|
'\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang')))
|
||||||
|
|
|
@ -8,6 +8,7 @@ import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from PIL import Image, ImageMath
|
from PIL import Image, ImageMath
|
||||||
|
from PIL._util import py3
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -152,7 +153,8 @@ class PillowTestCase(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def assert_image_similar_tofile(self, a, filename, epsilon, msg=None, mode=None):
|
def assert_image_similar_tofile(self, a, filename, epsilon, msg=None,
|
||||||
|
mode=None):
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
if mode:
|
if mode:
|
||||||
img = img.convert(mode)
|
img = img.convert(mode)
|
||||||
|
@ -190,6 +192,16 @@ class PillowTestCase(unittest.TestCase):
|
||||||
def assert_not_all_same(self, items, msg=None):
|
def assert_not_all_same(self, items, msg=None):
|
||||||
self.assertFalse(items.count(items[0]) == len(items), msg)
|
self.assertFalse(items.count(items[0]) == len(items), msg)
|
||||||
|
|
||||||
|
def assert_tuple_approx_equal(self, actuals, targets, threshold, msg):
|
||||||
|
"""Tests if actuals has values within threshold from targets"""
|
||||||
|
|
||||||
|
value = True
|
||||||
|
for i, target in enumerate(targets):
|
||||||
|
value *= (target - threshold <= actuals[i] <= target + threshold)
|
||||||
|
|
||||||
|
self.assertTrue(value,
|
||||||
|
msg + ': ' + repr(actuals) + ' != ' + repr(targets))
|
||||||
|
|
||||||
def skipKnownBadTest(self, msg=None, platform=None,
|
def skipKnownBadTest(self, msg=None, platform=None,
|
||||||
travis=None, interpreter=None):
|
travis=None, interpreter=None):
|
||||||
# Skip if platform/travis matches, and
|
# Skip if platform/travis matches, and
|
||||||
|
@ -229,23 +241,24 @@ class PillowTestCase(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS")
|
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS")
|
||||||
class PillowLeakTestCase(PillowTestCase):
|
class PillowLeakTestCase(PillowTestCase):
|
||||||
# requires unix/osx
|
# requires unix/macOS
|
||||||
iterations = 100 # count
|
iterations = 100 # count
|
||||||
mem_limit = 512 # k
|
mem_limit = 512 # k
|
||||||
|
|
||||||
def _get_mem_usage(self):
|
def _get_mem_usage(self):
|
||||||
"""
|
"""
|
||||||
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
||||||
between OSX and Linux rss reporting
|
between macOS and Linux rss reporting
|
||||||
|
|
||||||
:returns; memory usage in kilobytes
|
:returns: memory usage in kilobytes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from resource import getrusage, RUSAGE_SELF
|
from resource import getrusage, RUSAGE_SELF
|
||||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
# man 2 getrusage:
|
# man 2 getrusage:
|
||||||
# ru_maxrss the maximum resident set size utilized (in bytes).
|
# ru_maxrss
|
||||||
|
# This is the maximum resident set size utilized (in bytes).
|
||||||
return mem / 1024 # Kb
|
return mem / 1024 # Kb
|
||||||
else:
|
else:
|
||||||
# linux
|
# linux
|
||||||
|
@ -265,7 +278,10 @@ class PillowLeakTestCase(PillowTestCase):
|
||||||
|
|
||||||
# helpers
|
# helpers
|
||||||
|
|
||||||
py3 = sys.version_info.major >= 3
|
if not py3:
|
||||||
|
# Remove DeprecationWarning in Python 3
|
||||||
|
PillowTestCase.assertRaisesRegex = PillowTestCase.assertRaisesRegexp
|
||||||
|
PillowTestCase.assertRegex = PillowTestCase.assertRegexpMatches
|
||||||
|
|
||||||
|
|
||||||
def fromstring(data):
|
def fromstring(data):
|
||||||
|
|
BIN
Tests/images/bw_gradient.png
Normal file
After Width: | Height: | Size: 102 B |
BIN
Tests/images/hopper.gd
Normal file
BIN
Tests/images/hopper.wal
Normal file
BIN
Tests/images/imagedraw2_text.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
Tests/images/imagedraw_outline_chord_L.png
Normal file
After Width: | Height: | Size: 212 B |
BIN
Tests/images/imagedraw_outline_chord_RGB.png
Normal file
After Width: | Height: | Size: 259 B |
BIN
Tests/images/imagedraw_outline_ellipse_L.png
Normal file
After Width: | Height: | Size: 290 B |
BIN
Tests/images/imagedraw_outline_ellipse_RGB.png
Normal file
After Width: | Height: | Size: 378 B |
BIN
Tests/images/imagedraw_outline_pieslice_L.png
Normal file
After Width: | Height: | Size: 266 B |
BIN
Tests/images/imagedraw_outline_pieslice_RGB.png
Normal file
After Width: | Height: | Size: 340 B |
BIN
Tests/images/imagedraw_outline_polygon_L.png
Normal file
After Width: | Height: | Size: 309 B |
BIN
Tests/images/imagedraw_outline_polygon_RGB.png
Normal file
After Width: | Height: | Size: 393 B |
BIN
Tests/images/imagedraw_outline_rectangle_L.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
Tests/images/imagedraw_outline_rectangle_RGB.png
Normal file
After Width: | Height: | Size: 212 B |
BIN
Tests/images/imagedraw_outline_shape_L.png
Normal file
After Width: | Height: | Size: 263 B |
BIN
Tests/images/imagedraw_outline_shape_RGB.png
Normal file
After Width: | Height: | Size: 316 B |
BIN
Tests/images/la.tga
Normal file
BIN
Tests/images/rotate_45_no_fill.png
Normal file
After Width: | Height: | Size: 639 B |
BIN
Tests/images/rotate_45_with_fill.png
Normal file
After Width: | Height: | Size: 625 B |
BIN
Tests/images/tga/common/1x1_l.png
Normal file
After Width: | Height: | Size: 67 B |
BIN
Tests/images/tga/common/1x1_l_bl_raw.tga
Normal file
BIN
Tests/images/tga/common/1x1_l_bl_rle.tga
Normal file
BIN
Tests/images/tga/common/1x1_l_tl_raw.tga
Normal file
BIN
Tests/images/tga/common/1x1_l_tl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_l.png
Normal file
After Width: | Height: | Size: 385 B |
BIN
Tests/images/tga/common/200x32_l_bl_raw.tga
Normal file
BIN
Tests/images/tga/common/200x32_l_bl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_l_tl_raw.tga
Normal file
BIN
Tests/images/tga/common/200x32_l_tl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_la.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
Tests/images/tga/common/200x32_la_bl_raw.tga
Normal file
BIN
Tests/images/tga/common/200x32_la_bl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_la_tl_raw.tga
Normal file
BIN
Tests/images/tga/common/200x32_la_tl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_p.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
Tests/images/tga/common/200x32_p_bl_raw.tga
Normal file
BIN
Tests/images/tga/common/200x32_p_bl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_p_tl_raw.tga
Normal file
BIN
Tests/images/tga/common/200x32_p_tl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_rgb.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
Tests/images/tga/common/200x32_rgb_bl_raw.tga
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
Tests/images/tga/common/200x32_rgb_bl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_rgb_tl_raw.tga
Normal file
BIN
Tests/images/tga/common/200x32_rgb_tl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_rgba.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
Tests/images/tga/common/200x32_rgba_bl_raw.tga
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
Tests/images/tga/common/200x32_rgba_bl_rle.tga
Normal file
BIN
Tests/images/tga/common/200x32_rgba_tl_raw.tga
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
Tests/images/tga/common/200x32_rgba_tl_rle.tga
Normal file
12
Tests/images/tga/common/readme.txt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Images in this directory were created with GIMP.
|
||||||
|
|
||||||
|
TGAs have names in the following format:
|
||||||
|
|
||||||
|
{width}x{height}_{mode}_{origin}_{compression}.tga
|
||||||
|
|
||||||
|
Where:
|
||||||
|
mode is PIL mode in lower case (L, P, RGB, etc.)
|
||||||
|
origin:
|
||||||
|
"bl" - bottom left
|
||||||
|
"tl" - top left
|
||||||
|
compression is either "raw" or "rle"
|
|
@ -1,6 +1,6 @@
|
||||||
from helper import unittest, PillowTestCase
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageFilter
|
||||||
|
|
||||||
|
|
||||||
sample = Image.new("L", (7, 5))
|
sample = Image.new("L", (7, 5))
|
||||||
|
@ -16,7 +16,7 @@ sample.putdata(sum([
|
||||||
class TestBoxBlurApi(PillowTestCase):
|
class TestBoxBlurApi(PillowTestCase):
|
||||||
|
|
||||||
def test_imageops_box_blur(self):
|
def test_imageops_box_blur(self):
|
||||||
i = ImageOps.box_blur(sample, 1)
|
i = sample.filter(ImageFilter.BoxBlur(1))
|
||||||
self.assertEqual(i.mode, sample.mode)
|
self.assertEqual(i.mode, sample.mode)
|
||||||
self.assertEqual(i.size, sample.size)
|
self.assertEqual(i.size, sample.size)
|
||||||
self.assertIsInstance(i, Image.Image)
|
self.assertIsInstance(i, Image.Image)
|
||||||
|
|
523
Tests/test_color_lut.py
Normal file
|
@ -0,0 +1,523 @@
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
|
from array import array
|
||||||
|
|
||||||
|
from PIL import Image, ImageFilter
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
|
try:
|
||||||
|
import numpy
|
||||||
|
except ImportError:
|
||||||
|
numpy = None
|
||||||
|
|
||||||
|
|
||||||
|
class TestColorLut3DCoreAPI(PillowTestCase):
|
||||||
|
def generate_identity_table(self, channels, size):
|
||||||
|
if isinstance(size, tuple):
|
||||||
|
size1D, size2D, size3D = size
|
||||||
|
else:
|
||||||
|
size1D, size2D, size3D = (size, size, size)
|
||||||
|
|
||||||
|
table = [
|
||||||
|
[
|
||||||
|
r / float(size1D-1) if size1D != 1 else 0,
|
||||||
|
g / float(size2D-1) if size2D != 1 else 0,
|
||||||
|
b / float(size3D-1) if size3D != 1 else 0,
|
||||||
|
r / float(size1D-1) if size1D != 1 else 0,
|
||||||
|
g / float(size2D-1) if size2D != 1 else 0,
|
||||||
|
][:channels]
|
||||||
|
for b in range(size3D)
|
||||||
|
for g in range(size2D)
|
||||||
|
for r in range(size1D)
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
channels, size1D, size2D, size3D,
|
||||||
|
[item for sublist in table for item in sublist])
|
||||||
|
|
||||||
|
def test_wrong_args(self):
|
||||||
|
im = Image.new('RGB', (10, 10), 0)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "filter"):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.CUBIC,
|
||||||
|
*self.generate_identity_table(3, 3))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "image mode"):
|
||||||
|
im.im.color_lut_3d('wrong',
|
||||||
|
Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, 3))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "table_channels"):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.LINEAR,
|
||||||
|
*self.generate_identity_table(5, 3))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "table_channels"):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.LINEAR,
|
||||||
|
*self.generate_identity_table(1, 3))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "table_channels"):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.LINEAR,
|
||||||
|
*self.generate_identity_table(2, 3))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "Table size"):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, (1, 3, 3)))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "Table size"):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, (66, 3, 3)))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.LINEAR,
|
||||||
|
3, 2, 2, 2, [0, 0, 0] * 7)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.LINEAR,
|
||||||
|
3, 2, 2, 2, [0, 0, 0] * 9)
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.LINEAR,
|
||||||
|
3, 2, 2, 2, [0, 0, "0"] * 8)
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
im.im.color_lut_3d('RGB',
|
||||||
|
Image.LINEAR,
|
||||||
|
3, 2, 2, 2, 16)
|
||||||
|
|
||||||
|
def test_correct_args(self):
|
||||||
|
im = Image.new('RGB', (10, 10), 0)
|
||||||
|
|
||||||
|
im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, 3))
|
||||||
|
|
||||||
|
im.im.color_lut_3d('CMYK', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(4, 3))
|
||||||
|
|
||||||
|
im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, (2, 3, 3)))
|
||||||
|
|
||||||
|
im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, (65, 3, 3)))
|
||||||
|
|
||||||
|
im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, (3, 65, 3)))
|
||||||
|
|
||||||
|
im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, (3, 3, 65)))
|
||||||
|
|
||||||
|
def test_wrong_mode(self):
|
||||||
|
with self.assertRaisesRegex(ValueError, "wrong mode"):
|
||||||
|
im = Image.new('L', (10, 10), 0)
|
||||||
|
im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, 3))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "wrong mode"):
|
||||||
|
im = Image.new('RGB', (10, 10), 0)
|
||||||
|
im.im.color_lut_3d('L', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, 3))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "wrong mode"):
|
||||||
|
im = Image.new('L', (10, 10), 0)
|
||||||
|
im.im.color_lut_3d('L', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, 3))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "wrong mode"):
|
||||||
|
im = Image.new('RGB', (10, 10), 0)
|
||||||
|
im.im.color_lut_3d('RGBA', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, 3))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "wrong mode"):
|
||||||
|
im = Image.new('RGB', (10, 10), 0)
|
||||||
|
im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(4, 3))
|
||||||
|
|
||||||
|
def test_correct_mode(self):
|
||||||
|
im = Image.new('RGBA', (10, 10), 0)
|
||||||
|
im.im.color_lut_3d('RGBA', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, 3))
|
||||||
|
|
||||||
|
im = Image.new('RGBA', (10, 10), 0)
|
||||||
|
im.im.color_lut_3d('RGBA', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(4, 3))
|
||||||
|
|
||||||
|
im = Image.new('RGB', (10, 10), 0)
|
||||||
|
im.im.color_lut_3d('HSV', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, 3))
|
||||||
|
|
||||||
|
im = Image.new('RGB', (10, 10), 0)
|
||||||
|
im.im.color_lut_3d('RGBA', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(4, 3))
|
||||||
|
|
||||||
|
def test_identities(self):
|
||||||
|
g = Image.linear_gradient('L')
|
||||||
|
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
|
||||||
|
g.transpose(Image.ROTATE_180)])
|
||||||
|
|
||||||
|
# Fast test with small cubes
|
||||||
|
for size in [2, 3, 5, 7, 11, 16, 17]:
|
||||||
|
self.assert_image_equal(im, im._new(
|
||||||
|
im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, size))))
|
||||||
|
|
||||||
|
# Not so fast
|
||||||
|
self.assert_image_equal(im, im._new(
|
||||||
|
im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, (2, 2, 65)))))
|
||||||
|
|
||||||
|
def test_identities_4_channels(self):
|
||||||
|
g = Image.linear_gradient('L')
|
||||||
|
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
|
||||||
|
g.transpose(Image.ROTATE_180)])
|
||||||
|
|
||||||
|
# Red channel copied to alpha
|
||||||
|
self.assert_image_equal(
|
||||||
|
Image.merge('RGBA', (im.split()*2)[:4]),
|
||||||
|
im._new(im.im.color_lut_3d('RGBA', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(4, 17))))
|
||||||
|
|
||||||
|
def test_copy_alpha_channel(self):
|
||||||
|
g = Image.linear_gradient('L')
|
||||||
|
im = Image.merge('RGBA', [g, g.transpose(Image.ROTATE_90),
|
||||||
|
g.transpose(Image.ROTATE_180),
|
||||||
|
g.transpose(Image.ROTATE_270)])
|
||||||
|
|
||||||
|
self.assert_image_equal(im, im._new(
|
||||||
|
im.im.color_lut_3d('RGBA', Image.LINEAR,
|
||||||
|
*self.generate_identity_table(3, 17))))
|
||||||
|
|
||||||
|
def test_channels_order(self):
|
||||||
|
g = Image.linear_gradient('L')
|
||||||
|
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
|
||||||
|
g.transpose(Image.ROTATE_180)])
|
||||||
|
|
||||||
|
# Reverse channels by splitting and using table
|
||||||
|
self.assert_image_equal(
|
||||||
|
Image.merge('RGB', im.split()[::-1]),
|
||||||
|
im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
3, 2, 2, 2, [
|
||||||
|
0, 0, 0, 0, 0, 1,
|
||||||
|
0, 1, 0, 0, 1, 1,
|
||||||
|
|
||||||
|
1, 0, 0, 1, 0, 1,
|
||||||
|
1, 1, 0, 1, 1, 1,
|
||||||
|
])))
|
||||||
|
|
||||||
|
def test_overflow(self):
|
||||||
|
g = Image.linear_gradient('L')
|
||||||
|
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
|
||||||
|
g.transpose(Image.ROTATE_180)])
|
||||||
|
|
||||||
|
transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
3, 2, 2, 2,
|
||||||
|
[
|
||||||
|
-1, -1, -1, 2, -1, -1,
|
||||||
|
-1, 2, -1, 2, 2, -1,
|
||||||
|
|
||||||
|
-1, -1, 2, 2, -1, 2,
|
||||||
|
-1, 2, 2, 2, 2, 2,
|
||||||
|
])).load()
|
||||||
|
self.assertEqual(transformed[0, 0], (0, 0, 255))
|
||||||
|
self.assertEqual(transformed[50, 50], (0, 0, 255))
|
||||||
|
self.assertEqual(transformed[255, 0], (0, 255, 255))
|
||||||
|
self.assertEqual(transformed[205, 50], (0, 255, 255))
|
||||||
|
self.assertEqual(transformed[0, 255], (255, 0, 0))
|
||||||
|
self.assertEqual(transformed[50, 205], (255, 0, 0))
|
||||||
|
self.assertEqual(transformed[255, 255], (255, 255, 0))
|
||||||
|
self.assertEqual(transformed[205, 205], (255, 255, 0))
|
||||||
|
|
||||||
|
transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
|
||||||
|
3, 2, 2, 2,
|
||||||
|
[
|
||||||
|
-3, -3, -3, 5, -3, -3,
|
||||||
|
-3, 5, -3, 5, 5, -3,
|
||||||
|
|
||||||
|
-3, -3, 5, 5, -3, 5,
|
||||||
|
-3, 5, 5, 5, 5, 5,
|
||||||
|
])).load()
|
||||||
|
self.assertEqual(transformed[0, 0], (0, 0, 255))
|
||||||
|
self.assertEqual(transformed[50, 50], (0, 0, 255))
|
||||||
|
self.assertEqual(transformed[255, 0], (0, 255, 255))
|
||||||
|
self.assertEqual(transformed[205, 50], (0, 255, 255))
|
||||||
|
self.assertEqual(transformed[0, 255], (255, 0, 0))
|
||||||
|
self.assertEqual(transformed[50, 205], (255, 0, 0))
|
||||||
|
self.assertEqual(transformed[255, 255], (255, 255, 0))
|
||||||
|
self.assertEqual(transformed[205, 205], (255, 255, 0))
|
||||||
|
|
||||||
|
|
||||||
|
class TestColorLut3DFilter(PillowTestCase):
|
||||||
|
def test_wrong_args(self):
|
||||||
|
with self.assertRaisesRegex(ValueError, "should be either an integer"):
|
||||||
|
ImageFilter.Color3DLUT("small", [1])
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "should be either an integer"):
|
||||||
|
ImageFilter.Color3DLUT((11, 11), [1])
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, r"in \[2, 65\] range"):
|
||||||
|
ImageFilter.Color3DLUT((11, 11, 1), [1])
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, r"in \[2, 65\] range"):
|
||||||
|
ImageFilter.Color3DLUT((11, 11, 66), [1])
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "table should have .+ items"):
|
||||||
|
ImageFilter.Color3DLUT((3, 3, 3), [1, 1, 1])
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "table should have .+ items"):
|
||||||
|
ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 2)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "should have a length of 4"):
|
||||||
|
ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 27, channels=4)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "should have a length of 3"):
|
||||||
|
ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "Only 3 or 4 output"):
|
||||||
|
ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8, channels=2)
|
||||||
|
|
||||||
|
def test_convert_table(self):
|
||||||
|
lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
|
||||||
|
self.assertEqual(tuple(lut.size), (2, 2, 2))
|
||||||
|
self.assertEqual(lut.name, "Color 3D LUT")
|
||||||
|
|
||||||
|
lut = ImageFilter.Color3DLUT((2, 2, 2), [
|
||||||
|
(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11),
|
||||||
|
(12, 13, 14), (15, 16, 17), (18, 19, 20), (21, 22, 23)])
|
||||||
|
self.assertEqual(tuple(lut.size), (2, 2, 2))
|
||||||
|
self.assertEqual(lut.table, list(range(24)))
|
||||||
|
|
||||||
|
lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8,
|
||||||
|
channels=4)
|
||||||
|
|
||||||
|
@unittest.skipIf(numpy is None, "Numpy is not installed")
|
||||||
|
def test_numpy_sources(self):
|
||||||
|
table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16)
|
||||||
|
with self.assertRaisesRegex(ValueError, "should have either channels"):
|
||||||
|
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||||
|
|
||||||
|
table = numpy.ones((7, 6, 5, 3), dtype=numpy.float16)
|
||||||
|
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||||
|
self.assertIsInstance(lut.table, numpy.ndarray)
|
||||||
|
self.assertEqual(lut.table.dtype, table.dtype)
|
||||||
|
self.assertEqual(lut.table.shape, (table.size,))
|
||||||
|
|
||||||
|
table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16)
|
||||||
|
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||||
|
self.assertEqual(lut.table.shape, (table.size,))
|
||||||
|
|
||||||
|
table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
|
||||||
|
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||||
|
self.assertEqual(lut.table.shape, (table.size,))
|
||||||
|
|
||||||
|
# Check application
|
||||||
|
Image.new('RGB', (10, 10), 0).filter(lut)
|
||||||
|
|
||||||
|
# Check copy
|
||||||
|
table[0] = 33
|
||||||
|
self.assertEqual(lut.table[0], 1)
|
||||||
|
|
||||||
|
# Check not copy
|
||||||
|
table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
|
||||||
|
lut = ImageFilter.Color3DLUT((5, 6, 7), table, _copy_table=False)
|
||||||
|
table[0] = 33
|
||||||
|
self.assertEqual(lut.table[0], 33)
|
||||||
|
|
||||||
|
@unittest.skipIf(numpy is None, "Numpy is not installed")
|
||||||
|
def test_numpy_formats(self):
|
||||||
|
g = Image.linear_gradient('L')
|
||||||
|
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
|
||||||
|
g.transpose(Image.ROTATE_180)])
|
||||||
|
|
||||||
|
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
|
||||||
|
lambda r, g, b: (r, g, b))
|
||||||
|
lut.table = numpy.array(lut.table, dtype=numpy.float32)[:-1]
|
||||||
|
with self.assertRaisesRegex(ValueError, "should have table_channels"):
|
||||||
|
im.filter(lut)
|
||||||
|
|
||||||
|
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
|
||||||
|
lambda r, g, b: (r, g, b))
|
||||||
|
lut.table = (numpy.array(lut.table, dtype=numpy.float32)
|
||||||
|
.reshape((7 * 9 * 11), 3))
|
||||||
|
with self.assertRaisesRegex(ValueError, "should have table_channels"):
|
||||||
|
im.filter(lut)
|
||||||
|
|
||||||
|
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
|
||||||
|
lambda r, g, b: (r, g, b))
|
||||||
|
lut.table = numpy.array(lut.table, dtype=numpy.float16)
|
||||||
|
self.assert_image_equal(im, im.filter(lut))
|
||||||
|
|
||||||
|
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
|
||||||
|
lambda r, g, b: (r, g, b))
|
||||||
|
lut.table = numpy.array(lut.table, dtype=numpy.float32)
|
||||||
|
self.assert_image_equal(im, im.filter(lut))
|
||||||
|
|
||||||
|
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
|
||||||
|
lambda r, g, b: (r, g, b))
|
||||||
|
lut.table = numpy.array(lut.table, dtype=numpy.float64)
|
||||||
|
self.assert_image_equal(im, im.filter(lut))
|
||||||
|
|
||||||
|
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
|
||||||
|
lambda r, g, b: (r, g, b))
|
||||||
|
lut.table = numpy.array(lut.table, dtype=numpy.int32)
|
||||||
|
im.filter(lut)
|
||||||
|
lut.table = numpy.array(lut.table, dtype=numpy.int8)
|
||||||
|
im.filter(lut)
|
||||||
|
|
||||||
|
def test_repr(self):
|
||||||
|
lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
|
||||||
|
self.assertEqual(repr(lut),
|
||||||
|
"<Color3DLUT from list size=2x2x2 channels=3>")
|
||||||
|
|
||||||
|
lut = ImageFilter.Color3DLUT(
|
||||||
|
(3, 4, 5), array('f', [0, 0, 0, 0] * (3 * 4 * 5)),
|
||||||
|
channels=4, target_mode='YCbCr', _copy_table=False)
|
||||||
|
self.assertEqual(
|
||||||
|
repr(lut),
|
||||||
|
"<Color3DLUT from array size=3x4x5 channels=4 target_mode=YCbCr>")
|
||||||
|
|
||||||
|
|
||||||
|
class TestGenerateColorLut3D(PillowTestCase):
|
||||||
|
def test_wrong_channels_count(self):
|
||||||
|
with self.assertRaisesRegex(ValueError, "3 or 4 output channels"):
|
||||||
|
ImageFilter.Color3DLUT.generate(
|
||||||
|
5, channels=2, callback=lambda r, g, b: (r, g, b))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "should have either channels"):
|
||||||
|
ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "should have either channels"):
|
||||||
|
ImageFilter.Color3DLUT.generate(
|
||||||
|
5, channels=4, callback=lambda r, g, b: (r, g, b))
|
||||||
|
|
||||||
|
def test_3_channels(self):
|
||||||
|
lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
|
||||||
|
self.assertEqual(tuple(lut.size), (5, 5, 5))
|
||||||
|
self.assertEqual(lut.name, "Color 3D LUT")
|
||||||
|
self.assertEqual(lut.table[:24], [
|
||||||
|
0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.75, 0.0, 0.0,
|
||||||
|
1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0])
|
||||||
|
|
||||||
|
def test_4_channels(self):
|
||||||
|
lut = ImageFilter.Color3DLUT.generate(
|
||||||
|
5, channels=4, callback=lambda r, g, b: (b, r, g, (r+g+b) / 2))
|
||||||
|
self.assertEqual(tuple(lut.size), (5, 5, 5))
|
||||||
|
self.assertEqual(lut.name, "Color 3D LUT")
|
||||||
|
self.assertEqual(lut.table[:24], [
|
||||||
|
0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25,
|
||||||
|
0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_apply(self):
|
||||||
|
lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
|
||||||
|
|
||||||
|
g = Image.linear_gradient('L')
|
||||||
|
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
|
||||||
|
g.transpose(Image.ROTATE_180)])
|
||||||
|
self.assertEqual(im, im.filter(lut))
|
||||||
|
|
||||||
|
|
||||||
|
class TestTransformColorLut3D(PillowTestCase):
|
||||||
|
def test_wrong_args(self):
|
||||||
|
source = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "Only 3 or 4 output"):
|
||||||
|
source.transform(lambda r, g, b: (r, g, b), channels=8)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "should have either channels"):
|
||||||
|
source.transform(lambda r, g, b: (r, g, b), channels=4)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "should have either channels"):
|
||||||
|
source.transform(lambda r, g, b: (r, g, b, 1))
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
source.transform(lambda r, g, b, a: (r, g, b))
|
||||||
|
|
||||||
|
def test_target_mode(self):
|
||||||
|
source = ImageFilter.Color3DLUT.generate(
|
||||||
|
2, lambda r, g, b: (r, g, b), target_mode='HSV')
|
||||||
|
|
||||||
|
lut = source.transform(lambda r, g, b: (r, g, b))
|
||||||
|
self.assertEqual(lut.mode, 'HSV')
|
||||||
|
|
||||||
|
lut = source.transform(lambda r, g, b: (r, g, b), target_mode='RGB')
|
||||||
|
self.assertEqual(lut.mode, 'RGB')
|
||||||
|
|
||||||
|
def test_3_to_3_channels(self):
|
||||||
|
source = ImageFilter.Color3DLUT.generate(
|
||||||
|
(3, 4, 5), lambda r, g, b: (r, g, b))
|
||||||
|
lut = source.transform(lambda r, g, b: (r*r, g*g, b*b))
|
||||||
|
self.assertEqual(tuple(lut.size), tuple(source.size))
|
||||||
|
self.assertEqual(len(lut.table), len(source.table))
|
||||||
|
self.assertNotEqual(lut.table, source.table)
|
||||||
|
self.assertEqual(lut.table[0:10], [
|
||||||
|
0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0])
|
||||||
|
|
||||||
|
def test_3_to_4_channels(self):
|
||||||
|
source = ImageFilter.Color3DLUT.generate(
|
||||||
|
(6, 5, 4), lambda r, g, b: (r, g, b))
|
||||||
|
lut = source.transform(lambda r, g, b: (r*r, g*g, b*b, 1), channels=4)
|
||||||
|
self.assertEqual(tuple(lut.size), tuple(source.size))
|
||||||
|
self.assertNotEqual(len(lut.table), len(source.table))
|
||||||
|
self.assertNotEqual(lut.table, source.table)
|
||||||
|
self.assertEqual(lut.table[0:16], [
|
||||||
|
0.0, 0.0, 0.0, 1, 0.2**2, 0.0, 0.0, 1,
|
||||||
|
0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1])
|
||||||
|
|
||||||
|
def test_4_to_3_channels(self):
|
||||||
|
source = ImageFilter.Color3DLUT.generate(
|
||||||
|
(3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4)
|
||||||
|
lut = source.transform(lambda r, g, b, a: (a - r*r, a - g*g, a - b*b),
|
||||||
|
channels=3)
|
||||||
|
self.assertEqual(tuple(lut.size), tuple(source.size))
|
||||||
|
self.assertNotEqual(len(lut.table), len(source.table))
|
||||||
|
self.assertNotEqual(lut.table, source.table)
|
||||||
|
self.assertEqual(lut.table[0:18], [
|
||||||
|
1.0, 1.0, 1.0, 0.75, 1.0, 1.0, 0.0, 1.0, 1.0,
|
||||||
|
1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0])
|
||||||
|
|
||||||
|
def test_4_to_4_channels(self):
|
||||||
|
source = ImageFilter.Color3DLUT.generate(
|
||||||
|
(6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4)
|
||||||
|
lut = source.transform(lambda r, g, b, a: (r*r, g*g, b*b, a - 0.5))
|
||||||
|
self.assertEqual(tuple(lut.size), tuple(source.size))
|
||||||
|
self.assertEqual(len(lut.table), len(source.table))
|
||||||
|
self.assertNotEqual(lut.table, source.table)
|
||||||
|
self.assertEqual(lut.table[0:16], [
|
||||||
|
0.0, 0.0, 0.0, 0.5, 0.2**2, 0.0, 0.0, 0.5,
|
||||||
|
0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5])
|
||||||
|
|
||||||
|
def test_with_normals_3_channels(self):
|
||||||
|
source = ImageFilter.Color3DLUT.generate(
|
||||||
|
(6, 5, 4), lambda r, g, b: (r*r, g*g, b*b))
|
||||||
|
lut = source.transform(
|
||||||
|
lambda nr, ng, nb, r, g, b: (nr - r, ng - g, nb - b),
|
||||||
|
with_normals=True)
|
||||||
|
self.assertEqual(tuple(lut.size), tuple(source.size))
|
||||||
|
self.assertEqual(len(lut.table), len(source.table))
|
||||||
|
self.assertNotEqual(lut.table, source.table)
|
||||||
|
self.assertEqual(lut.table[0:18], [
|
||||||
|
0.0, 0.0, 0.0, 0.16, 0.0, 0.0, 0.24, 0.0, 0.0,
|
||||||
|
0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0])
|
||||||
|
|
||||||
|
def test_with_normals_4_channels(self):
|
||||||
|
source = ImageFilter.Color3DLUT.generate(
|
||||||
|
(3, 6, 5), lambda r, g, b: (r*r, g*g, b*b, 1), channels=4)
|
||||||
|
lut = source.transform(
|
||||||
|
lambda nr, ng, nb, r, g, b, a: (nr - r, ng - g, nb - b, a-0.5),
|
||||||
|
with_normals=True)
|
||||||
|
self.assertEqual(tuple(lut.size), tuple(source.size))
|
||||||
|
self.assertEqual(len(lut.table), len(source.table))
|
||||||
|
self.assertNotEqual(lut.table, source.table)
|
||||||
|
self.assertEqual(lut.table[0:16], [
|
||||||
|
0.0, 0.0, 0.0, 0.5, 0.25, 0.0, 0.0, 0.5,
|
||||||
|
0.0, 0.0, 0.0, 0.5, 0.0, 0.16, 0.0, 0.5])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -76,7 +76,7 @@ class TestFileEps(PillowTestCase):
|
||||||
plot_image = Image.open("Tests/images/reqd_showpage.eps")
|
plot_image = Image.open("Tests/images/reqd_showpage.eps")
|
||||||
target = Image.open("Tests/images/reqd_showpage.png")
|
target = Image.open("Tests/images/reqd_showpage.png")
|
||||||
|
|
||||||
#should not crash/hang
|
# should not crash/hang
|
||||||
plot_image.load()
|
plot_image.load()
|
||||||
# fonts could be slightly different
|
# fonts could be slightly different
|
||||||
self.assert_image_similar(plot_image, target, 6)
|
self.assert_image_similar(plot_image, target, 6)
|
||||||
|
@ -195,41 +195,15 @@ class TestFileEps(PillowTestCase):
|
||||||
self.assertEqual(t.readline().strip('\r\n'), 'baz', ending)
|
self.assertEqual(t.readline().strip('\r\n'), 'baz', ending)
|
||||||
self.assertEqual(t.readline().strip('\r\n'), 'bif', ending)
|
self.assertEqual(t.readline().strip('\r\n'), 'bif', ending)
|
||||||
|
|
||||||
def _test_readline_stringio(self, test_string, ending):
|
def _test_readline_io_psfile(self, test_string, ending):
|
||||||
# check all the freaking line endings possible
|
f = io.BytesIO(test_string.encode('latin-1'))
|
||||||
try:
|
t = EpsImagePlugin.PSFile(f)
|
||||||
import StringIO
|
|
||||||
except ImportError:
|
|
||||||
# don't skip, it skips everything in the parent test
|
|
||||||
return
|
|
||||||
t = StringIO.StringIO(test_string)
|
|
||||||
self._test_readline(t, ending)
|
self._test_readline(t, ending)
|
||||||
|
|
||||||
def _test_readline_io(self, test_string, ending):
|
|
||||||
if str is bytes:
|
|
||||||
t = io.StringIO(unicode(test_string))
|
|
||||||
else:
|
|
||||||
t = io.StringIO(test_string)
|
|
||||||
self._test_readline(t, ending)
|
|
||||||
|
|
||||||
def _test_readline_file_universal(self, test_string, ending):
|
|
||||||
f = self.tempfile('temp.txt')
|
|
||||||
with open(f, 'wb') as w:
|
|
||||||
if str is bytes:
|
|
||||||
w.write(test_string)
|
|
||||||
else:
|
|
||||||
w.write(test_string.encode('UTF-8'))
|
|
||||||
|
|
||||||
with open(f, 'rU') as t:
|
|
||||||
self._test_readline(t, ending)
|
|
||||||
|
|
||||||
def _test_readline_file_psfile(self, test_string, ending):
|
def _test_readline_file_psfile(self, test_string, ending):
|
||||||
f = self.tempfile('temp.txt')
|
f = self.tempfile('temp.txt')
|
||||||
with open(f, 'wb') as w:
|
with open(f, 'wb') as w:
|
||||||
if str is bytes:
|
w.write(test_string.encode('latin-1'))
|
||||||
w.write(test_string)
|
|
||||||
else:
|
|
||||||
w.write(test_string.encode('UTF-8'))
|
|
||||||
|
|
||||||
with open(f, 'rb') as r:
|
with open(f, 'rb') as r:
|
||||||
t = EpsImagePlugin.PSFile(r)
|
t = EpsImagePlugin.PSFile(r)
|
||||||
|
@ -238,29 +212,12 @@ class TestFileEps(PillowTestCase):
|
||||||
def test_readline(self):
|
def test_readline(self):
|
||||||
# check all the freaking line endings possible from the spec
|
# check all the freaking line endings possible from the spec
|
||||||
# test_string = u'something\r\nelse\n\rbaz\rbif\n'
|
# test_string = u'something\r\nelse\n\rbaz\rbif\n'
|
||||||
line_endings = ['\r\n', '\n']
|
line_endings = ['\r\n', '\n', '\n\r', '\r']
|
||||||
not_working_endings = ['\n\r', '\r']
|
|
||||||
strings = ['something', 'else', 'baz', 'bif']
|
strings = ['something', 'else', 'baz', 'bif']
|
||||||
|
|
||||||
for ending in line_endings:
|
for ending in line_endings:
|
||||||
s = ending.join(strings)
|
s = ending.join(strings)
|
||||||
# Native Python versions will pass these endings.
|
self._test_readline_io_psfile(s, ending)
|
||||||
# self._test_readline_stringio(s, ending)
|
|
||||||
# self._test_readline_io(s, ending)
|
|
||||||
# self._test_readline_file_universal(s, ending)
|
|
||||||
|
|
||||||
self._test_readline_file_psfile(s, ending)
|
|
||||||
|
|
||||||
for ending in not_working_endings:
|
|
||||||
# these only work with the PSFile, while they're in spec,
|
|
||||||
# they're not likely to be used
|
|
||||||
s = ending.join(strings)
|
|
||||||
|
|
||||||
# Native Python versions may fail on these endings.
|
|
||||||
# self._test_readline_stringio(s, ending)
|
|
||||||
# self._test_readline_io(s, ending)
|
|
||||||
# self._test_readline_file_universal(s, ending)
|
|
||||||
|
|
||||||
self._test_readline_file_psfile(s, ending)
|
self._test_readline_file_psfile(s, ending)
|
||||||
|
|
||||||
def test_open_eps(self):
|
def test_open_eps(self):
|
||||||
|
|
28
Tests/test_file_gd.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
|
from PIL import GdImageFile
|
||||||
|
|
||||||
|
import io
|
||||||
|
|
||||||
|
TEST_GD_FILE = "Tests/images/hopper.gd"
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileGd(PillowTestCase):
|
||||||
|
|
||||||
|
def test_sanity(self):
|
||||||
|
im = GdImageFile.open(TEST_GD_FILE)
|
||||||
|
self.assertEqual(im.size, (128, 128))
|
||||||
|
self.assertEqual(im.format, "GD")
|
||||||
|
|
||||||
|
def test_bad_mode(self):
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
GdImageFile.open, TEST_GD_FILE, 'bad mode')
|
||||||
|
|
||||||
|
def test_invalid_file(self):
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
self.assertRaises(IOError, GdImageFile.open, invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -305,9 +305,12 @@ class TestFileGif(PillowTestCase):
|
||||||
|
|
||||||
out = self.tempfile('temp.gif')
|
out = self.tempfile('temp.gif')
|
||||||
im = Image.new('L', (100, 100), '#000')
|
im = Image.new('L', (100, 100), '#000')
|
||||||
im.save(out, duration=duration)
|
|
||||||
reread = Image.open(out)
|
|
||||||
|
|
||||||
|
# Check that the argument has priority over the info settings
|
||||||
|
im.info['duration'] = 100
|
||||||
|
im.save(out, duration=duration)
|
||||||
|
|
||||||
|
reread = Image.open(out)
|
||||||
self.assertEqual(reread.info['duration'], duration)
|
self.assertEqual(reread.info['duration'], duration)
|
||||||
|
|
||||||
def test_multiple_duration(self):
|
def test_multiple_duration(self):
|
||||||
|
@ -399,7 +402,8 @@ class TestFileGif(PillowTestCase):
|
||||||
|
|
||||||
def test_comment(self):
|
def test_comment(self):
|
||||||
im = Image.open(TEST_GIF)
|
im = Image.open(TEST_GIF)
|
||||||
self.assertEqual(im.info['comment'], b"File written by Adobe Photoshop\xa8 4.0")
|
self.assertEqual(im.info['comment'],
|
||||||
|
b"File written by Adobe Photoshop\xa8 4.0")
|
||||||
|
|
||||||
out = self.tempfile('temp.gif')
|
out = self.tempfile('temp.gif')
|
||||||
im = Image.new('L', (100, 100), '#000')
|
im = Image.new('L', (100, 100), '#000')
|
||||||
|
|
|
@ -40,11 +40,11 @@ class TestFileIcns(PillowTestCase):
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
|
|
||||||
temp_file = self.tempfile("temp.icns")
|
temp_file = self.tempfile("temp.icns")
|
||||||
provided_im = Image.new('RGBA', (32, 32), (255, 0, 0, 0))
|
provided_im = Image.new('RGBA', (32, 32), (255, 0, 0, 128))
|
||||||
im.save(temp_file, append_images=[provided_im])
|
im.save(temp_file, append_images=[provided_im])
|
||||||
|
|
||||||
reread = Image.open(temp_file)
|
reread = Image.open(temp_file)
|
||||||
self.assert_image_equal(reread, im)
|
self.assert_image_similar(reread, im, 1)
|
||||||
|
|
||||||
reread = Image.open(temp_file)
|
reread = Image.open(temp_file)
|
||||||
reread.size = (16, 16, 2)
|
reread.size = (16, 16, 2)
|
||||||
|
|
|
@ -41,13 +41,14 @@ class TestFileJpeg(PillowTestCase):
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
|
|
||||||
# internal version number
|
# internal version number
|
||||||
self.assertRegexpMatches(Image.core.jpeglib_version, r"\d+\.\d+$")
|
self.assertRegex(Image.core.jpeglib_version, r"\d+\.\d+$")
|
||||||
|
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertEqual(im.mode, "RGB")
|
self.assertEqual(im.mode, "RGB")
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
self.assertEqual(im.format, "JPEG")
|
self.assertEqual(im.format, "JPEG")
|
||||||
|
self.assertEqual(im.get_format_mimetype(), "image/jpeg")
|
||||||
|
|
||||||
def test_app(self):
|
def test_app(self):
|
||||||
# Test APP/COM reader (@PIL135)
|
# Test APP/COM reader (@PIL135)
|
||||||
|
@ -363,7 +364,6 @@ class TestFileJpeg(PillowTestCase):
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def _n_qtables_helper(self, n, test_file):
|
def _n_qtables_helper(self, n, test_file):
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
f = self.tempfile('temp.jpg')
|
f = self.tempfile('temp.jpg')
|
||||||
|
@ -439,17 +439,17 @@ class TestFileJpeg(PillowTestCase):
|
||||||
self._n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg")
|
self._n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg")
|
||||||
|
|
||||||
# not a sequence
|
# not a sequence
|
||||||
self.assertRaises(Exception, self.roundtrip, im, qtables='a')
|
self.assertRaises(ValueError, self.roundtrip, im, qtables='a')
|
||||||
# sequence wrong length
|
# sequence wrong length
|
||||||
self.assertRaises(Exception, self.roundtrip, im, qtables=[])
|
self.assertRaises(ValueError, self.roundtrip, im, qtables=[])
|
||||||
# sequence wrong length
|
# sequence wrong length
|
||||||
self.assertRaises(Exception,
|
self.assertRaises(ValueError,
|
||||||
self.roundtrip, im, qtables=[1, 2, 3, 4, 5])
|
self.roundtrip, im, qtables=[1, 2, 3, 4, 5])
|
||||||
|
|
||||||
# qtable entry not a sequence
|
# qtable entry not a sequence
|
||||||
self.assertRaises(Exception, self.roundtrip, im, qtables=[1])
|
self.assertRaises(ValueError, self.roundtrip, im, qtables=[1])
|
||||||
# qtable entry has wrong number of items
|
# qtable entry has wrong number of items
|
||||||
self.assertRaises(Exception,
|
self.assertRaises(ValueError,
|
||||||
self.roundtrip, im, qtables=[[1, 2, 3, 4]])
|
self.roundtrip, im, qtables=[[1, 2, 3, 4]])
|
||||||
|
|
||||||
@unittest.skipUnless(djpeg_available(), "djpeg not available")
|
@unittest.skipUnless(djpeg_available(), "djpeg not available")
|
||||||
|
@ -599,7 +599,7 @@ class TestFileCloseW32(PillowTestCase):
|
||||||
im = Image.open(tmpfile)
|
im = Image.open(tmpfile)
|
||||||
fp = im.fp
|
fp = im.fp
|
||||||
self.assertFalse(fp.closed)
|
self.assertFalse(fp.closed)
|
||||||
self.assertRaises(Exception, os.remove, tmpfile)
|
self.assertRaises(WindowsError, os.remove, tmpfile)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertTrue(fp.closed)
|
self.assertTrue(fp.closed)
|
||||||
# this should not fail, as load should have closed the file.
|
# this should not fail, as load should have closed the file.
|
||||||
|
|
|
@ -31,7 +31,7 @@ class TestFileJpeg2k(PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
# Internal version number
|
# Internal version number
|
||||||
self.assertRegexpMatches(Image.core.jp2klib_version, r'\d+\.\d+\.\d+$')
|
self.assertRegex(Image.core.jp2klib_version, r'\d+\.\d+\.\d+$')
|
||||||
|
|
||||||
im = Image.open('Tests/images/test-card-lossless.jp2')
|
im = Image.open('Tests/images/test-card-lossless.jp2')
|
||||||
px = im.load()
|
px = im.load()
|
||||||
|
@ -146,13 +146,13 @@ class TestFileJpeg2k(PillowTestCase):
|
||||||
self.assertEqual(j2k.mode, 'I;16')
|
self.assertEqual(j2k.mode, 'I;16')
|
||||||
self.assertEqual(jp2.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')
|
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
|
||||||
jp2 = Image.open('Tests/images/16bit.cropped.jp2')
|
jp2 = Image.open('Tests/images/16bit.cropped.jp2')
|
||||||
self.assert_image_similar(jp2, tiff_16bit, 1e-3)
|
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')
|
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
|
||||||
j2k = Image.open('Tests/images/16bit.cropped.j2k')
|
j2k = Image.open('Tests/images/16bit.cropped.j2k')
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from helper import unittest, PillowTestCase, hopper, py3
|
from helper import unittest, PillowTestCase, hopper
|
||||||
from PIL import features
|
from PIL import features
|
||||||
|
from PIL._util import py3
|
||||||
|
|
||||||
from ctypes import c_float
|
from ctypes import c_float
|
||||||
import io
|
import io
|
||||||
|
@ -30,7 +31,7 @@ class LibTiffTestCase(PillowTestCase):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.assertEqual(im._compression, 'group4')
|
self.assertEqual(im._compression, 'group4')
|
||||||
except:
|
except AttributeError:
|
||||||
print("No _compression")
|
print("No _compression")
|
||||||
print(dir(im))
|
print(dir(im))
|
||||||
|
|
||||||
|
@ -125,7 +126,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0))
|
im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0))
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
self.assert_image_equal_tofile(im, 'Tests/images/tiff_adobe_deflate.png')
|
self.assert_image_equal_tofile(im,
|
||||||
|
'Tests/images/tiff_adobe_deflate.png')
|
||||||
|
|
||||||
def test_write_metadata(self):
|
def test_write_metadata(self):
|
||||||
""" Test metadata writing through libtiff """
|
""" Test metadata writing through libtiff """
|
||||||
|
@ -193,7 +195,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
for tag in im.tag_v2:
|
for tag in im.tag_v2:
|
||||||
try:
|
try:
|
||||||
del(core_items[tag])
|
del(core_items[tag])
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Type codes:
|
# Type codes:
|
||||||
|
@ -216,7 +218,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
if info.length == 0:
|
if info.length == 0:
|
||||||
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
|
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
|
||||||
else:
|
else:
|
||||||
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
|
new_ifd[tag] = tuple(values[info.type]
|
||||||
|
for _ in range(info.length))
|
||||||
|
|
||||||
# Extra samples really doesn't make sense in this application.
|
# Extra samples really doesn't make sense in this application.
|
||||||
del(new_ifd[338])
|
del(new_ifd[338])
|
||||||
|
@ -524,7 +527,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
f.write(src.read())
|
f.write(src.read())
|
||||||
|
|
||||||
im = Image.open(tmpfile)
|
im = Image.open(tmpfile)
|
||||||
count = im.n_frames
|
im.n_frames
|
||||||
im.close()
|
im.close()
|
||||||
try:
|
try:
|
||||||
os.remove(tmpfile) # Windows PermissionError here!
|
os.remove(tmpfile) # Windows PermissionError here!
|
||||||
|
@ -577,10 +580,14 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
self.assertEqual(im.mode, "RGBA")
|
self.assertEqual(im.mode, "RGBA")
|
||||||
self.assertEqual(im.size, (100, 40))
|
self.assertEqual(im.size, (100, 40))
|
||||||
self.assertEqual(im.tile, [('tiff_lzw', (0, 0, 100, 40), 0, ('RGBa;16N', 'tiff_lzw', False))])
|
self.assertEqual(
|
||||||
|
im.tile,
|
||||||
|
[('tiff_lzw', (0, 0, 100, 40), 0, ('RGBa;16N', 'tiff_lzw', False))]
|
||||||
|
)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
self.assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
|
self.assert_image_equal_tofile(
|
||||||
|
im, "Tests/images/tiff_16bit_RGBa_target.png")
|
||||||
|
|
||||||
def test_gimp_tiff(self):
|
def test_gimp_tiff(self):
|
||||||
# Read TIFF JPEG images from GIMP [@PIL168]
|
# Read TIFF JPEG images from GIMP [@PIL168]
|
||||||
|
@ -606,7 +613,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im = Image.open("Tests/images/copyleft.tiff")
|
im = Image.open("Tests/images/copyleft.tiff")
|
||||||
self.assertEqual(im.mode, 'RGB')
|
self.assertEqual(im.mode, 'RGB')
|
||||||
|
|
||||||
self.assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode='RGB')
|
self.assert_image_equal_tofile(im, "Tests/images/copyleft.png",
|
||||||
|
mode='RGB')
|
||||||
|
|
||||||
def test_lzw(self):
|
def test_lzw(self):
|
||||||
im = Image.open("Tests/images/hopper_lzw.tif")
|
im = Image.open("Tests/images/hopper_lzw.tif")
|
||||||
|
|
|
@ -20,7 +20,8 @@ class TestFilePdf(PillowTestCase):
|
||||||
self.assertTrue(os.path.isfile(outfile))
|
self.assertTrue(os.path.isfile(outfile))
|
||||||
self.assertGreater(os.path.getsize(outfile), 0)
|
self.assertGreater(os.path.getsize(outfile), 0)
|
||||||
with PdfParser.PdfParser(outfile) as pdf:
|
with PdfParser.PdfParser(outfile) as pdf:
|
||||||
if kwargs.get("append_images", False) or kwargs.get("append", False):
|
if kwargs.get("append_images", False) or \
|
||||||
|
kwargs.get("append", False):
|
||||||
self.assertGreater(len(pdf.pages), 1)
|
self.assertGreater(len(pdf.pages), 1)
|
||||||
else:
|
else:
|
||||||
self.assertGreater(len(pdf.pages), 0)
|
self.assertGreater(len(pdf.pages), 0)
|
||||||
|
@ -104,9 +105,21 @@ class TestFilePdf(PillowTestCase):
|
||||||
self.assertTrue(os.path.isfile(outfile))
|
self.assertTrue(os.path.isfile(outfile))
|
||||||
self.assertGreater(os.path.getsize(outfile), 0)
|
self.assertGreater(os.path.getsize(outfile), 0)
|
||||||
|
|
||||||
|
def test_multiframe_normal_save(self):
|
||||||
|
# Test saving a multiframe image without save_all
|
||||||
|
im = Image.open("Tests/images/dispose_bgnd.gif")
|
||||||
|
|
||||||
|
outfile = self.tempfile('temp.pdf')
|
||||||
|
im.save(outfile)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.isfile(outfile))
|
||||||
|
self.assertGreater(os.path.getsize(outfile), 0)
|
||||||
|
|
||||||
def test_pdf_open(self):
|
def test_pdf_open(self):
|
||||||
# fail on a buffer full of null bytes
|
# fail on a buffer full of null bytes
|
||||||
self.assertRaises(PdfParser.PdfFormatError, PdfParser.PdfParser, buf=bytearray(65536))
|
self.assertRaises(
|
||||||
|
PdfParser.PdfFormatError,
|
||||||
|
PdfParser.PdfParser, buf=bytearray(65536))
|
||||||
|
|
||||||
# make an empty PDF object
|
# make an empty PDF object
|
||||||
with PdfParser.PdfParser() as empty_pdf:
|
with PdfParser.PdfParser() as empty_pdf:
|
||||||
|
@ -143,7 +156,10 @@ class TestFilePdf(PillowTestCase):
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
temp_dir = tempfile.mkdtemp()
|
temp_dir = tempfile.mkdtemp()
|
||||||
try:
|
try:
|
||||||
self.assertRaises(IOError, im.save, os.path.join(temp_dir, "nonexistent.pdf"), append=True)
|
self.assertRaises(IOError,
|
||||||
|
im.save,
|
||||||
|
os.path.join(temp_dir, "nonexistent.pdf"),
|
||||||
|
append=True)
|
||||||
finally:
|
finally:
|
||||||
os.rmdir(temp_dir)
|
os.rmdir(temp_dir)
|
||||||
|
|
||||||
|
@ -194,7 +210,8 @@ class TestFilePdf(PillowTestCase):
|
||||||
# append two images
|
# append two images
|
||||||
mode_CMYK = hopper("CMYK")
|
mode_CMYK = hopper("CMYK")
|
||||||
mode_P = hopper("P")
|
mode_P = hopper("P")
|
||||||
mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P])
|
mode_CMYK.save(pdf_filename,
|
||||||
|
append=True, save_all=True, append_images=[mode_P])
|
||||||
|
|
||||||
# open the PDF again, check pages and info again
|
# open the PDF again, check pages and info again
|
||||||
with PdfParser.PdfParser(pdf_filename) as pdf:
|
with PdfParser.PdfParser(pdf_filename) as pdf:
|
||||||
|
@ -209,7 +226,9 @@ class TestFilePdf(PillowTestCase):
|
||||||
|
|
||||||
def test_pdf_info(self):
|
def test_pdf_info(self):
|
||||||
# make a PDF file
|
# make a PDF file
|
||||||
pdf_filename = self.helper_save_as_pdf("RGB", title="title", author="author", subject="subject", keywords="keywords", creator="creator", producer="producer")
|
pdf_filename = self.helper_save_as_pdf(
|
||||||
|
"RGB", title="title", author="author", subject="subject",
|
||||||
|
keywords="keywords", creator="creator", producer="producer")
|
||||||
|
|
||||||
# open it, check pages and info
|
# open it, check pages and info
|
||||||
with PdfParser.PdfParser(pdf_filename) as pdf:
|
with PdfParser.PdfParser(pdf_filename) as pdf:
|
||||||
|
|
|
@ -13,6 +13,7 @@ class TestFilePixar(PillowTestCase):
|
||||||
self.assertEqual(im.mode, "RGB")
|
self.assertEqual(im.mode, "RGB")
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
self.assertEqual(im.format, "PIXAR")
|
self.assertEqual(im.format, "PIXAR")
|
||||||
|
self.assertIsNone(im.get_format_mimetype())
|
||||||
|
|
||||||
im2 = hopper()
|
im2 = hopper()
|
||||||
self.assert_image_similar(im, im2, 4.8)
|
self.assert_image_similar(im, im2, 4.8)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from helper import unittest, PillowTestCase, PillowLeakTestCase, hopper
|
from helper import unittest, PillowTestCase, PillowLeakTestCase, hopper
|
||||||
from PIL import Image, ImageFile, PngImagePlugin
|
from PIL import Image, ImageFile, PngImagePlugin
|
||||||
|
from PIL._util import py3
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import zlib
|
import zlib
|
||||||
|
@ -68,8 +69,7 @@ class TestFilePng(PillowTestCase):
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
|
|
||||||
# internal version number
|
# internal version number
|
||||||
self.assertRegexpMatches(
|
self.assertRegex(Image.core.zlib_version, r"\d+\.\d+\.\d+(\.\d+)?$")
|
||||||
Image.core.zlib_version, r"\d+\.\d+\.\d+(\.\d+)?$")
|
|
||||||
|
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = self.tempfile("temp.png")
|
||||||
|
|
||||||
|
@ -292,15 +292,29 @@ class TestFilePng(PillowTestCase):
|
||||||
self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))])
|
self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))])
|
||||||
|
|
||||||
def test_save_l_transparency(self):
|
def test_save_l_transparency(self):
|
||||||
|
# There are 559 transparent pixels in l_trns.png.
|
||||||
|
num_transparent = 559
|
||||||
|
|
||||||
in_file = "Tests/images/l_trns.png"
|
in_file = "Tests/images/l_trns.png"
|
||||||
im = Image.open(in_file)
|
im = Image.open(in_file)
|
||||||
|
self.assertEqual(im.mode, "L")
|
||||||
|
self.assertEqual(im.info["transparency"], 255)
|
||||||
|
|
||||||
|
im_rgba = im.convert('RGBA')
|
||||||
|
self.assertEqual(
|
||||||
|
im_rgba.getchannel("A").getcolors()[0][0], num_transparent)
|
||||||
|
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = self.tempfile("temp.png")
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
||||||
# There are 559 transparent pixels.
|
test_im = Image.open(test_file)
|
||||||
im = im.convert('RGBA')
|
self.assertEqual(test_im.mode, "L")
|
||||||
self.assertEqual(im.getchannel('A').getcolors()[0][0], 559)
|
self.assertEqual(test_im.info["transparency"], 255)
|
||||||
|
self.assert_image_equal(im, test_im)
|
||||||
|
|
||||||
|
test_im_rgba = test_im.convert('RGBA')
|
||||||
|
self.assertEqual(
|
||||||
|
test_im_rgba.getchannel('A').getcolors()[0][0], num_transparent)
|
||||||
|
|
||||||
def test_save_rgb_single_transparency(self):
|
def test_save_rgb_single_transparency(self):
|
||||||
in_file = "Tests/images/caption_6_33_22.png"
|
in_file = "Tests/images/caption_6_33_22.png"
|
||||||
|
@ -341,7 +355,8 @@ class TestFilePng(PillowTestCase):
|
||||||
broken_crc_chunk_data = chunk_data[:-1] + b'q' # break CRC
|
broken_crc_chunk_data = chunk_data[:-1] + b'q' # break CRC
|
||||||
|
|
||||||
image_data = HEAD + broken_crc_chunk_data + TAIL
|
image_data = HEAD + broken_crc_chunk_data + TAIL
|
||||||
self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data))
|
self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile,
|
||||||
|
BytesIO(image_data))
|
||||||
|
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
try:
|
try:
|
||||||
|
@ -357,7 +372,8 @@ class TestFilePng(PillowTestCase):
|
||||||
|
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
try:
|
try:
|
||||||
self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data))
|
self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile,
|
||||||
|
BytesIO(image_data))
|
||||||
finally:
|
finally:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||||
|
|
||||||
|
@ -420,7 +436,7 @@ class TestFilePng(PillowTestCase):
|
||||||
im = roundtrip(im, pnginfo=info)
|
im = roundtrip(im, pnginfo=info)
|
||||||
self.assertEqual(im.info, {"Text": value})
|
self.assertEqual(im.info, {"Text": value})
|
||||||
|
|
||||||
if str is not bytes:
|
if py3:
|
||||||
rt_text(" Aa" + chr(0xa0) + chr(0xc4) + chr(0xff)) # Latin1
|
rt_text(" Aa" + chr(0xa0) + chr(0xc4) + chr(0xff)) # Latin1
|
||||||
rt_text(chr(0x400) + chr(0x472) + chr(0x4ff)) # Cyrillic
|
rt_text(chr(0x400) + chr(0x472) + chr(0x4ff)) # Cyrillic
|
||||||
rt_text(chr(0x4e00) + chr(0x66f0) + # CJK
|
rt_text(chr(0x4e00) + chr(0x66f0) + # CJK
|
||||||
|
|
|
@ -12,7 +12,7 @@ class TestFileTar(PillowTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs:
|
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):
|
def test_sanity(self):
|
||||||
if "zip_decoder" in codecs:
|
if "zip_decoder" in codecs:
|
||||||
|
|
|
@ -1,10 +1,76 @@
|
||||||
|
import os
|
||||||
|
from glob import glob
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
from helper import unittest, PillowTestCase
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
_TGA_DIR = os.path.join("Tests", "images", "tga")
|
||||||
|
_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")
|
||||||
|
|
||||||
|
|
||||||
class TestFileTga(PillowTestCase):
|
class TestFileTga(PillowTestCase):
|
||||||
|
|
||||||
|
_MODES = ("L", "LA", "P", "RGB", "RGBA")
|
||||||
|
_ORIGINS = ("tl", "bl")
|
||||||
|
|
||||||
|
_ORIGIN_TO_ORIENTATION = {
|
||||||
|
"tl": 1,
|
||||||
|
"bl": -1
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_sanity(self):
|
||||||
|
for mode in self._MODES:
|
||||||
|
png_paths = glob(
|
||||||
|
os.path.join(
|
||||||
|
_TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower())))
|
||||||
|
|
||||||
|
for png_path in png_paths:
|
||||||
|
reference_im = Image.open(png_path)
|
||||||
|
self.assertEqual(reference_im.mode, mode)
|
||||||
|
|
||||||
|
path_no_ext = os.path.splitext(png_path)[0]
|
||||||
|
for origin, rle in product(self._ORIGINS, (True, False)):
|
||||||
|
tga_path = "{}_{}_{}.tga".format(
|
||||||
|
path_no_ext, origin, "rle" if rle else "raw")
|
||||||
|
|
||||||
|
original_im = Image.open(tga_path)
|
||||||
|
if rle:
|
||||||
|
self.assertEqual(
|
||||||
|
original_im.info["compression"], "tga_rle")
|
||||||
|
self.assertEqual(
|
||||||
|
original_im.info["orientation"],
|
||||||
|
self._ORIGIN_TO_ORIENTATION[origin])
|
||||||
|
if mode == "P":
|
||||||
|
self.assertEqual(
|
||||||
|
original_im.getpalette(),
|
||||||
|
reference_im.getpalette())
|
||||||
|
|
||||||
|
self.assert_image_equal(original_im, reference_im)
|
||||||
|
|
||||||
|
# Generate a new test name every time so the
|
||||||
|
# test will not fail with permission error
|
||||||
|
# on Windows.
|
||||||
|
test_file = self.tempfile("temp.tga")
|
||||||
|
|
||||||
|
original_im.save(test_file, rle=rle)
|
||||||
|
saved_im = Image.open(test_file)
|
||||||
|
if rle:
|
||||||
|
self.assertEqual(
|
||||||
|
saved_im.info["compression"],
|
||||||
|
original_im.info["compression"])
|
||||||
|
self.assertEqual(
|
||||||
|
saved_im.info["orientation"],
|
||||||
|
original_im.info["orientation"])
|
||||||
|
if mode == "P":
|
||||||
|
self.assertEqual(
|
||||||
|
saved_im.getpalette(),
|
||||||
|
original_im.getpalette())
|
||||||
|
|
||||||
|
self.assert_image_equal(saved_im, original_im)
|
||||||
|
|
||||||
def test_id_field(self):
|
def test_id_field(self):
|
||||||
# tga file with id field
|
# tga file with id field
|
||||||
test_file = "Tests/images/tga_id_field.tga"
|
test_file = "Tests/images/tga_id_field.tga"
|
||||||
|
@ -41,9 +107,6 @@ class TestFileTga(PillowTestCase):
|
||||||
test_im = Image.open(test_file)
|
test_im = Image.open(test_file)
|
||||||
self.assertEqual(test_im.size, (100, 100))
|
self.assertEqual(test_im.size, (100, 100))
|
||||||
|
|
||||||
# Unsupported mode save
|
|
||||||
self.assertRaises(IOError, lambda: im.convert("LA").save(test_file))
|
|
||||||
|
|
||||||
def test_save_rle(self):
|
def test_save_rle(self):
|
||||||
test_file = "Tests/images/rgb32rle.tga"
|
test_file = "Tests/images/rgb32rle.tga"
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
|
@ -60,8 +123,25 @@ class TestFileTga(PillowTestCase):
|
||||||
test_im = Image.open(test_file)
|
test_im = Image.open(test_file)
|
||||||
self.assertEqual(test_im.size, (199, 199))
|
self.assertEqual(test_im.size, (199, 199))
|
||||||
|
|
||||||
# Unsupported mode save
|
def test_save_l_transparency(self):
|
||||||
self.assertRaises(IOError, lambda: im.convert("LA").save(test_file))
|
# There are 559 transparent pixels in la.tga.
|
||||||
|
num_transparent = 559
|
||||||
|
|
||||||
|
in_file = "Tests/images/la.tga"
|
||||||
|
im = Image.open(in_file)
|
||||||
|
self.assertEqual(im.mode, "LA")
|
||||||
|
self.assertEqual(
|
||||||
|
im.getchannel("A").getcolors()[0][0], num_transparent)
|
||||||
|
|
||||||
|
test_file = self.tempfile("temp.tga")
|
||||||
|
im.save(test_file)
|
||||||
|
|
||||||
|
test_im = Image.open(test_file)
|
||||||
|
self.assertEqual(test_im.mode, "LA")
|
||||||
|
self.assertEqual(
|
||||||
|
test_im.getchannel("A").getcolors()[0][0], num_transparent)
|
||||||
|
|
||||||
|
self.assert_image_equal(im, test_im)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -3,9 +3,10 @@ from io import BytesIO
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from helper import unittest, PillowTestCase, hopper, py3
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
from PIL import Image, TiffImagePlugin
|
||||||
|
from PIL._util import py3
|
||||||
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION, RESOLUTION_UNIT
|
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION, RESOLUTION_UNIT
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -62,8 +63,11 @@ class TestFileTiff(PillowTestCase):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
def test_set_legacy_api(self):
|
def test_set_legacy_api(self):
|
||||||
with self.assertRaises(Exception):
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
ImageFileDirectory_v2.legacy_api = None
|
with self.assertRaises(Exception) as e:
|
||||||
|
ifd.legacy_api = None
|
||||||
|
self.assertEqual(str(e.exception),
|
||||||
|
"Not allowing setting of legacy api")
|
||||||
|
|
||||||
def test_xyres_tiff(self):
|
def test_xyres_tiff(self):
|
||||||
filename = "Tests/images/pil168.tif"
|
filename = "Tests/images/pil168.tif"
|
||||||
|
@ -440,7 +444,8 @@ class TestFileTiff(PillowTestCase):
|
||||||
for im in ims:
|
for im in ims:
|
||||||
yield im
|
yield im
|
||||||
mp = io.BytesIO()
|
mp = io.BytesIO()
|
||||||
im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims))
|
im.save(mp, format="TIFF", save_all=True,
|
||||||
|
append_images=imGenerator(ims))
|
||||||
|
|
||||||
mp.seek(0, os.SEEK_SET)
|
mp.seek(0, os.SEEK_SET)
|
||||||
reread = Image.open(mp)
|
reread = Image.open(mp)
|
||||||
|
@ -501,7 +506,7 @@ class TestFileTiffW32(PillowTestCase):
|
||||||
im = Image.open(tmpfile)
|
im = Image.open(tmpfile)
|
||||||
fp = im.fp
|
fp = im.fp
|
||||||
self.assertFalse(fp.closed)
|
self.assertFalse(fp.closed)
|
||||||
self.assertRaises(Exception, os.remove, tmpfile)
|
self.assertRaises(WindowsError, os.remove, tmpfile)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertTrue(fp.closed)
|
self.assertTrue(fp.closed)
|
||||||
|
|
||||||
|
|
23
Tests/test_file_wal.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
|
from PIL import WalImageFile
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileWal(PillowTestCase):
|
||||||
|
|
||||||
|
def test_open(self):
|
||||||
|
# Arrange
|
||||||
|
TEST_FILE = "Tests/images/hopper.wal"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im = WalImageFile.open(TEST_FILE)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(im.format, "WAL")
|
||||||
|
self.assertEqual(im.format_description, "Quake2 Texture")
|
||||||
|
self.assertEqual(im.mode, "P")
|
||||||
|
self.assertEqual(im.size, (128, 128))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -83,7 +83,8 @@ class TestFileWebpAnimation(PillowTestCase):
|
||||||
|
|
||||||
temp_file1 = self.tempfile("temp.webp")
|
temp_file1 = self.tempfile("temp.webp")
|
||||||
frame1.copy().save(temp_file1,
|
frame1.copy().save(temp_file1,
|
||||||
save_all=True, append_images=[frame2], lossless=True)
|
save_all=True, append_images=[frame2],
|
||||||
|
lossless=True)
|
||||||
check(temp_file1)
|
check(temp_file1)
|
||||||
|
|
||||||
# Tests appending using a generator
|
# Tests appending using a generator
|
||||||
|
@ -92,7 +93,8 @@ class TestFileWebpAnimation(PillowTestCase):
|
||||||
yield im
|
yield im
|
||||||
temp_file2 = self.tempfile("temp_generator.webp")
|
temp_file2 = self.tempfile("temp_generator.webp")
|
||||||
frame1.copy().save(temp_file2,
|
frame1.copy().save(temp_file2,
|
||||||
save_all=True, append_images=imGenerator([frame2]), lossless=True)
|
save_all=True, append_images=imGenerator([frame2]),
|
||||||
|
lossless=True)
|
||||||
check(temp_file2)
|
check(temp_file2)
|
||||||
|
|
||||||
def test_timestamp_and_duration(self):
|
def test_timestamp_and_duration(self):
|
||||||
|
|
|
@ -16,7 +16,8 @@ class TestTTypeFontLeak(PillowLeakTestCase):
|
||||||
self._test_leak(lambda: draw.text((0, 0), "some text "*1024, # ~10k
|
self._test_leak(lambda: draw.text((0, 0), "some text "*1024, # ~10k
|
||||||
font=font, fill="black"))
|
font=font, fill="black"))
|
||||||
|
|
||||||
@unittest.skipIf(not features.check('freetype2'), "Test requires freetype2")
|
@unittest.skipIf(not 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)
|
||||||
|
|
|
@ -2,6 +2,7 @@ from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
from PIL import Image, FontFile, PcfFontFile
|
from PIL import Image, FontFile, PcfFontFile
|
||||||
from PIL import ImageFont, ImageDraw
|
from PIL import ImageFont, ImageDraw
|
||||||
|
from PIL._util import py3
|
||||||
|
|
||||||
codecs = dir(Image.core)
|
codecs = dir(Image.core)
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ class TestFontPcf(PillowTestCase):
|
||||||
with open(fontname, "rb") as test_file:
|
with open(fontname, "rb") as test_file:
|
||||||
font = PcfFontFile.PcfFontFile(test_file)
|
font = PcfFontFile.PcfFontFile(test_file)
|
||||||
self.assertIsInstance(font, FontFile.FontFile)
|
self.assertIsInstance(font, FontFile.FontFile)
|
||||||
#check the number of characters in the font
|
# check the number of characters in the font
|
||||||
self.assertEqual(len([_f for _f in font.glyph if _f]), 223)
|
self.assertEqual(len([_f for _f in font.glyph if _f]), 223)
|
||||||
|
|
||||||
tempname = self.tempfile("temp.pil")
|
tempname = self.tempfile("temp.pil")
|
||||||
|
@ -66,7 +67,7 @@ class TestFontPcf(PillowTestCase):
|
||||||
def _test_high_characters(self, message):
|
def _test_high_characters(self, message):
|
||||||
tempname = self.save_font()
|
tempname = self.save_font()
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
im = Image.new("L", (750, 30) , "white")
|
im = Image.new("L", (750, 30), "white")
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.text((0, 0), message, "black", font=font)
|
draw.text((0, 0), message, "black", font=font)
|
||||||
with Image.open('Tests/images/high_ascii_chars.png') as target:
|
with Image.open('Tests/images/high_ascii_chars.png') as target:
|
||||||
|
@ -76,7 +77,7 @@ class TestFontPcf(PillowTestCase):
|
||||||
message = "".join(chr(i+1) for i in range(140, 232))
|
message = "".join(chr(i+1) for i in range(140, 232))
|
||||||
self._test_high_characters(message)
|
self._test_high_characters(message)
|
||||||
# accept bytes instances in Py3.
|
# accept bytes instances in Py3.
|
||||||
if bytes is not str:
|
if py3:
|
||||||
self._test_high_characters(message.encode('latin1'))
|
self._test_high_characters(message.encode('latin1'))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from helper import unittest, PillowTestCase, hopper
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from PIL._util import py3
|
||||||
|
|
||||||
import colorsys
|
import colorsys
|
||||||
import itertools
|
import itertools
|
||||||
|
@ -57,10 +58,10 @@ class TestFormatHSV(PillowTestCase):
|
||||||
|
|
||||||
(r, g, b) = im.split()
|
(r, g, b) = im.split()
|
||||||
|
|
||||||
if bytes is str:
|
if py3:
|
||||||
conv_func = self.str_to_float
|
|
||||||
else:
|
|
||||||
conv_func = self.int_to_float
|
conv_func = self.int_to_float
|
||||||
|
else:
|
||||||
|
conv_func = self.str_to_float
|
||||||
|
|
||||||
if hasattr(itertools, 'izip'):
|
if hasattr(itertools, 'izip'):
|
||||||
iter_helper = itertools.izip
|
iter_helper = itertools.izip
|
||||||
|
@ -72,11 +73,11 @@ class TestFormatHSV(PillowTestCase):
|
||||||
for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(),
|
for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(),
|
||||||
b.tobytes())]
|
b.tobytes())]
|
||||||
|
|
||||||
if str is bytes:
|
if py3:
|
||||||
new_bytes = b''.join(chr(h)+chr(s)+chr(v) for (
|
new_bytes = b''.join(bytes(chr(h)+chr(s)+chr(v), 'latin-1') for (
|
||||||
h, s, v) in converted)
|
h, s, v) in converted)
|
||||||
else:
|
else:
|
||||||
new_bytes = b''.join(bytes(chr(h)+chr(s)+chr(v), 'latin-1') for (
|
new_bytes = b''.join(chr(h)+chr(s)+chr(v) for (
|
||||||
h, s, v) in converted)
|
h, s, v) in converted)
|
||||||
|
|
||||||
hsv = Image.frombytes(mode, r.size, new_bytes)
|
hsv = Image.frombytes(mode, r.size, new_bytes)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from helper import unittest, PillowTestCase, hopper
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from PIL._util import py3
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,12 +61,12 @@ class TestImage(PillowTestCase):
|
||||||
self.assertEqual(im.height, 4)
|
self.assertEqual(im.height, 4)
|
||||||
|
|
||||||
def test_invalid_image(self):
|
def test_invalid_image(self):
|
||||||
if str is bytes:
|
if py3:
|
||||||
import StringIO
|
|
||||||
im = StringIO.StringIO('')
|
|
||||||
else:
|
|
||||||
import io
|
import io
|
||||||
im = io.BytesIO(b'')
|
im = io.BytesIO(b'')
|
||||||
|
else:
|
||||||
|
import StringIO
|
||||||
|
im = StringIO.StringIO('')
|
||||||
self.assertRaises(IOError, Image.open, im)
|
self.assertRaises(IOError, Image.open, im)
|
||||||
|
|
||||||
def test_bad_mode(self):
|
def test_bad_mode(self):
|
||||||
|
@ -112,7 +113,6 @@ class TestImage(PillowTestCase):
|
||||||
self.assertRaises(ValueError, im.save, temp_file)
|
self.assertRaises(ValueError, im.save, temp_file)
|
||||||
|
|
||||||
def test_internals(self):
|
def test_internals(self):
|
||||||
|
|
||||||
im = Image.new("L", (100, 100))
|
im = Image.new("L", (100, 100))
|
||||||
im.readonly = 1
|
im.readonly = 1
|
||||||
im._copy()
|
im._copy()
|
||||||
|
@ -122,8 +122,15 @@ class TestImage(PillowTestCase):
|
||||||
im.paste(0, (0, 0, 100, 100))
|
im.paste(0, (0, 0, 100, 100))
|
||||||
self.assertFalse(im.readonly)
|
self.assertFalse(im.readonly)
|
||||||
|
|
||||||
test_file = self.tempfile("temp.ppm")
|
def test_dump(self):
|
||||||
im._dump(test_file)
|
im = Image.new("L", (10, 10))
|
||||||
|
im._dump(self.tempfile("temp_L.ppm"))
|
||||||
|
|
||||||
|
im = Image.new("RGB", (10, 10))
|
||||||
|
im._dump(self.tempfile("temp_RGB.ppm"))
|
||||||
|
|
||||||
|
im = Image.new("HSV", (10, 10))
|
||||||
|
self.assertRaises(ValueError, im._dump, self.tempfile("temp_HSV.ppm"))
|
||||||
|
|
||||||
def test_comparison_with_other_type(self):
|
def test_comparison_with_other_type(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -294,8 +294,9 @@ int main(int argc, char* argv[])
|
||||||
compiler = ccompiler.new_compiler()
|
compiler = ccompiler.new_compiler()
|
||||||
compiler.add_include_dir(sysconfig.get_python_inc())
|
compiler.add_include_dir(sysconfig.get_python_inc())
|
||||||
|
|
||||||
libdir = sysconfig.get_config_var('LIBDIR') or sysconfig.get_python_inc().replace('include', 'libs')
|
libdir = (sysconfig.get_config_var('LIBDIR') or
|
||||||
print (libdir)
|
sysconfig.get_python_inc().replace('include', 'libs'))
|
||||||
|
print(libdir)
|
||||||
compiler.add_library_dir(libdir)
|
compiler.add_library_dir(libdir)
|
||||||
objects = compiler.compile(['embed_pil.c'])
|
objects = compiler.compile(['embed_pil.c'])
|
||||||
compiler.link_executable(objects, 'embed_pil')
|
compiler.link_executable(objects, 'embed_pil')
|
||||||
|
|
|
@ -15,9 +15,11 @@ class TestImageArray(PillowTestCase):
|
||||||
self.assertEqual(test("L"), (3, (100, 128), '|u1', 12800))
|
self.assertEqual(test("L"), (3, (100, 128), '|u1', 12800))
|
||||||
|
|
||||||
# FIXME: wrong?
|
# FIXME: wrong?
|
||||||
self.assertEqual(test("I"), (3, (100, 128), Image._ENDIAN + 'i4', 51200))
|
self.assertEqual(test("I"), (3, (100, 128),
|
||||||
|
Image._ENDIAN + 'i4', 51200))
|
||||||
# FIXME: wrong?
|
# FIXME: wrong?
|
||||||
self.assertEqual(test("F"), (3, (100, 128), Image._ENDIAN + 'f4', 51200))
|
self.assertEqual(test("F"), (3, (100, 128),
|
||||||
|
Image._ENDIAN + 'f4', 51200))
|
||||||
|
|
||||||
self.assertEqual(test("LA"), (3, (100, 128, 2), '|u1', 25600))
|
self.assertEqual(test("LA"), (3, (100, 128, 2), '|u1', 25600))
|
||||||
self.assertEqual(test("RGB"), (3, (100, 128, 3), '|u1', 38400))
|
self.assertEqual(test("RGB"), (3, (100, 128, 3), '|u1', 38400))
|
||||||
|
|
|
@ -67,13 +67,13 @@ class TestImageConvert(PillowTestCase):
|
||||||
|
|
||||||
f = self.tempfile('temp.png')
|
f = self.tempfile('temp.png')
|
||||||
|
|
||||||
l = im.convert('L')
|
im_l = im.convert('L')
|
||||||
self.assertEqual(l.info['transparency'], 0) # undone
|
self.assertEqual(im_l.info['transparency'], 0) # undone
|
||||||
l.save(f)
|
im_l.save(f)
|
||||||
|
|
||||||
rgb = im.convert('RGB')
|
im_rgb = im.convert('RGB')
|
||||||
self.assertEqual(rgb.info['transparency'], (0, 0, 0)) # undone
|
self.assertEqual(im_rgb.info['transparency'], (0, 0, 0)) # undone
|
||||||
rgb.save(f)
|
im_rgb.save(f)
|
||||||
|
|
||||||
# ref https://github.com/python-pillow/Pillow/issues/664
|
# ref https://github.com/python-pillow/Pillow/issues/664
|
||||||
|
|
||||||
|
@ -83,12 +83,12 @@ class TestImageConvert(PillowTestCase):
|
||||||
im.info['transparency'] = 128
|
im.info['transparency'] = 128
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
rgba = im.convert('RGBA')
|
im_rgba = im.convert('RGBA')
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assertNotIn('transparency', rgba.info)
|
self.assertNotIn('transparency', im_rgba.info)
|
||||||
# https://github.com/python-pillow/Pillow/issues/2702
|
# https://github.com/python-pillow/Pillow/issues/2702
|
||||||
self.assertEqual(rgba.palette, None)
|
self.assertEqual(im_rgba.palette, None)
|
||||||
|
|
||||||
def test_trns_l(self):
|
def test_trns_l(self):
|
||||||
im = hopper('L')
|
im = hopper('L')
|
||||||
|
@ -96,19 +96,20 @@ class TestImageConvert(PillowTestCase):
|
||||||
|
|
||||||
f = self.tempfile('temp.png')
|
f = self.tempfile('temp.png')
|
||||||
|
|
||||||
rgb = im.convert('RGB')
|
im_rgb = im.convert('RGB')
|
||||||
self.assertEqual(rgb.info['transparency'], (128, 128, 128)) # undone
|
self.assertEqual(im_rgb.info['transparency'],
|
||||||
rgb.save(f)
|
(128, 128, 128)) # undone
|
||||||
|
im_rgb.save(f)
|
||||||
|
|
||||||
p = im.convert('P')
|
im_p = im.convert('P')
|
||||||
self.assertIn('transparency', p.info)
|
self.assertIn('transparency', im_p.info)
|
||||||
p.save(f)
|
im_p.save(f)
|
||||||
|
|
||||||
p = self.assert_warning(
|
im_p = self.assert_warning(
|
||||||
UserWarning,
|
UserWarning,
|
||||||
im.convert, 'P', palette=Image.ADAPTIVE)
|
im.convert, 'P', palette=Image.ADAPTIVE)
|
||||||
self.assertNotIn('transparency', p.info)
|
self.assertNotIn('transparency', im_p.info)
|
||||||
p.save(f)
|
im_p.save(f)
|
||||||
|
|
||||||
def test_trns_RGB(self):
|
def test_trns_RGB(self):
|
||||||
im = hopper('RGB')
|
im = hopper('RGB')
|
||||||
|
@ -116,23 +117,35 @@ class TestImageConvert(PillowTestCase):
|
||||||
|
|
||||||
f = self.tempfile('temp.png')
|
f = self.tempfile('temp.png')
|
||||||
|
|
||||||
l = im.convert('L')
|
im_l = im.convert('L')
|
||||||
self.assertEqual(l.info['transparency'], l.getpixel((0, 0))) # undone
|
self.assertEqual(im_l.info['transparency'],
|
||||||
l.save(f)
|
im_l.getpixel((0, 0))) # undone
|
||||||
|
im_l.save(f)
|
||||||
|
|
||||||
p = im.convert('P')
|
im_p = im.convert('P')
|
||||||
self.assertIn('transparency', p.info)
|
self.assertIn('transparency', im_p.info)
|
||||||
p.save(f)
|
im_p.save(f)
|
||||||
|
|
||||||
p = im.convert('RGBA')
|
im_rgba = im.convert('RGBA')
|
||||||
self.assertNotIn('transparency', p.info)
|
self.assertNotIn('transparency', im_rgba.info)
|
||||||
p.save(f)
|
im_rgba.save(f)
|
||||||
|
|
||||||
p = self.assert_warning(
|
im_p = self.assert_warning(
|
||||||
UserWarning,
|
UserWarning,
|
||||||
im.convert, 'P', palette=Image.ADAPTIVE)
|
im.convert, 'P', palette=Image.ADAPTIVE)
|
||||||
self.assertNotIn('transparency', p.info)
|
self.assertNotIn('transparency', im_p.info)
|
||||||
p.save(f)
|
im_p.save(f)
|
||||||
|
|
||||||
|
def test_gif_with_rgba_palette_to_p(self):
|
||||||
|
# See https://github.com/python-pillow/Pillow/issues/2433
|
||||||
|
im = Image.open('Tests/images/hopper.gif')
|
||||||
|
im.info['transparency'] = 255
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.palette.mode, 'RGBA')
|
||||||
|
im_p = im.convert('P')
|
||||||
|
|
||||||
|
# Should not raise ValueError: unrecognized raw mode
|
||||||
|
im_p.load()
|
||||||
|
|
||||||
def test_p_la(self):
|
def test_p_la(self):
|
||||||
im = hopper('RGBA')
|
im = hopper('RGBA')
|
||||||
|
@ -191,7 +204,8 @@ class TestImageConvert(PillowTestCase):
|
||||||
if converted_im.mode == 'RGB':
|
if converted_im.mode == 'RGB':
|
||||||
self.assert_image_similar(converted_im, target, 3)
|
self.assert_image_similar(converted_im, target, 3)
|
||||||
else:
|
else:
|
||||||
self.assert_image_similar(converted_im, target.getchannel(0), 1)
|
self.assert_image_similar(converted_im,
|
||||||
|
target.getchannel(0), 1)
|
||||||
|
|
||||||
matrix_convert('RGB')
|
matrix_convert('RGB')
|
||||||
matrix_convert('L')
|
matrix_convert('L')
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from helper import unittest, PillowTestCase, hopper, py3
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
from PIL._util import py3
|
||||||
|
|
||||||
|
|
||||||
class TestImageGetIm(PillowTestCase):
|
class TestImageGetIm(PillowTestCase):
|
||||||
|
|
|
@ -39,7 +39,7 @@ class TestImageQuantize(PillowTestCase):
|
||||||
def test_rgba_quantize(self):
|
def test_rgba_quantize(self):
|
||||||
image = hopper('RGBA')
|
image = hopper('RGBA')
|
||||||
image.quantize()
|
image.quantize()
|
||||||
self.assertRaises(Exception, image.quantize, method=0)
|
self.assertRaises(ValueError, image.quantize, method=0)
|
||||||
|
|
||||||
def test_quantize(self):
|
def test_quantize(self):
|
||||||
image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB')
|
image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB')
|
||||||
|
|
|
@ -245,8 +245,8 @@ class CoreResampleAlphaCorrectTest(PillowTestCase):
|
||||||
for y in range(i.size[1]):
|
for y in range(i.size[1]):
|
||||||
used_colors = {px[x, y][0] for x in range(i.size[0])}
|
used_colors = {px[x, y][0] for x in range(i.size[0])}
|
||||||
self.assertEqual(256, len(used_colors),
|
self.assertEqual(256, len(used_colors),
|
||||||
'All colors should present in resized image. '
|
'All colors should present in resized image. '
|
||||||
'Only {} on {} line.'.format(len(used_colors), y))
|
'Only {} on {} line.'.format(len(used_colors), y))
|
||||||
|
|
||||||
@unittest.skip("current implementation isn't precise enough")
|
@unittest.skip("current implementation isn't precise enough")
|
||||||
def test_levels_rgba(self):
|
def test_levels_rgba(self):
|
||||||
|
@ -288,10 +288,14 @@ class CoreResampleAlphaCorrectTest(PillowTestCase):
|
||||||
def test_dirty_pixels_rgba(self):
|
def test_dirty_pixels_rgba(self):
|
||||||
case = self.make_dirty_case('RGBA', (255, 255, 0, 128), (0, 0, 255, 0))
|
case = self.make_dirty_case('RGBA', (255, 255, 0, 128), (0, 0, 255, 0))
|
||||||
self.run_dirty_case(case.resize((20, 20), Image.BOX), (255, 255, 0))
|
self.run_dirty_case(case.resize((20, 20), Image.BOX), (255, 255, 0))
|
||||||
self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255, 255, 0))
|
self.run_dirty_case(case.resize((20, 20), Image.BILINEAR),
|
||||||
self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255, 255, 0))
|
(255, 255, 0))
|
||||||
self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255, 255, 0))
|
self.run_dirty_case(case.resize((20, 20), Image.HAMMING),
|
||||||
self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255, 255, 0))
|
(255, 255, 0))
|
||||||
|
self.run_dirty_case(case.resize((20, 20), Image.BICUBIC),
|
||||||
|
(255, 255, 0))
|
||||||
|
self.run_dirty_case(case.resize((20, 20), Image.LANCZOS),
|
||||||
|
(255, 255, 0))
|
||||||
|
|
||||||
def test_dirty_pixels_la(self):
|
def test_dirty_pixels_la(self):
|
||||||
case = self.make_dirty_case('LA', (255, 128), (0, 0))
|
case = self.make_dirty_case('LA', (255, 128), (0, 0))
|
||||||
|
@ -367,40 +371,45 @@ class CoreResampleCoefficientsTest(PillowTestCase):
|
||||||
im = Image.new('RGBA', (1280, 1280), (0x20, 0x40, 0x60, 0xff))
|
im = Image.new('RGBA', (1280, 1280), (0x20, 0x40, 0x60, 0xff))
|
||||||
histogram = im.resize((256, 256), Image.BICUBIC).histogram()
|
histogram = im.resize((256, 256), Image.BICUBIC).histogram()
|
||||||
|
|
||||||
self.assertEqual(histogram[0x100 * 0 + 0x20], 0x10000) # first channel
|
# first channel
|
||||||
self.assertEqual(histogram[0x100 * 1 + 0x40], 0x10000) # second channel
|
self.assertEqual(histogram[0x100 * 0 + 0x20], 0x10000)
|
||||||
self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000) # third channel
|
# second channel
|
||||||
self.assertEqual(histogram[0x100 * 3 + 0xff], 0x10000) # fourth channel
|
self.assertEqual(histogram[0x100 * 1 + 0x40], 0x10000)
|
||||||
|
# third channel
|
||||||
|
self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000)
|
||||||
|
# fourth channel
|
||||||
|
self.assertEqual(histogram[0x100 * 3 + 0xff], 0x10000)
|
||||||
|
|
||||||
|
|
||||||
class CoreResampleBoxTest(PillowTestCase):
|
class CoreResampleBoxTest(PillowTestCase):
|
||||||
def test_wrong_arguments(self):
|
def test_wrong_arguments(self):
|
||||||
im = hopper()
|
im = hopper()
|
||||||
for resample in (Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING,
|
for resample in (Image.NEAREST, Image.BOX, Image.BILINEAR,
|
||||||
Image.BICUBIC, Image.LANCZOS):
|
Image.HAMMING, Image.BICUBIC, Image.LANCZOS):
|
||||||
im.resize((32, 32), resample, (0, 0, im.width, im.height))
|
im.resize((32, 32), resample, (0, 0, im.width, im.height))
|
||||||
im.resize((32, 32), resample, (20, 20, im.width, im.height))
|
im.resize((32, 32), resample, (20, 20, im.width, im.height))
|
||||||
im.resize((32, 32), resample, (20, 20, 20, 100))
|
im.resize((32, 32), resample, (20, 20, 20, 100))
|
||||||
im.resize((32, 32), resample, (20, 20, 100, 20))
|
im.resize((32, 32), resample, (20, 20, 100, 20))
|
||||||
|
|
||||||
with self.assertRaisesRegexp(TypeError, "must be sequence of length 4"):
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
"must be sequence of length 4"):
|
||||||
im.resize((32, 32), resample, (im.width, im.height))
|
im.resize((32, 32), resample, (im.width, im.height))
|
||||||
|
|
||||||
with self.assertRaisesRegexp(ValueError, "can't be negative"):
|
with self.assertRaisesRegex(ValueError, "can't be negative"):
|
||||||
im.resize((32, 32), resample, (-20, 20, 100, 100))
|
im.resize((32, 32), resample, (-20, 20, 100, 100))
|
||||||
with self.assertRaisesRegexp(ValueError, "can't be negative"):
|
with self.assertRaisesRegex(ValueError, "can't be negative"):
|
||||||
im.resize((32, 32), resample, (20, -20, 100, 100))
|
im.resize((32, 32), resample, (20, -20, 100, 100))
|
||||||
|
|
||||||
with self.assertRaisesRegexp(ValueError, "can't be empty"):
|
with self.assertRaisesRegex(ValueError, "can't be empty"):
|
||||||
im.resize((32, 32), resample, (20.1, 20, 20, 100))
|
im.resize((32, 32), resample, (20.1, 20, 20, 100))
|
||||||
with self.assertRaisesRegexp(ValueError, "can't be empty"):
|
with self.assertRaisesRegex(ValueError, "can't be empty"):
|
||||||
im.resize((32, 32), resample, (20, 20.1, 100, 20))
|
im.resize((32, 32), resample, (20, 20.1, 100, 20))
|
||||||
with self.assertRaisesRegexp(ValueError, "can't be empty"):
|
with self.assertRaisesRegex(ValueError, "can't be empty"):
|
||||||
im.resize((32, 32), resample, (20.1, 20.1, 20, 20))
|
im.resize((32, 32), resample, (20.1, 20.1, 20, 20))
|
||||||
|
|
||||||
with self.assertRaisesRegexp(ValueError, "can't exceed"):
|
with self.assertRaisesRegex(ValueError, "can't exceed"):
|
||||||
im.resize((32, 32), resample, (0, 0, im.width + 1, im.height))
|
im.resize((32, 32), resample, (0, 0, im.width + 1, im.height))
|
||||||
with self.assertRaisesRegexp(ValueError, "can't exceed"):
|
with self.assertRaisesRegex(ValueError, "can't exceed"):
|
||||||
im.resize((32, 32), resample, (0, 0, im.width, im.height + 1))
|
im.resize((32, 32), resample, (0, 0, im.width, im.height + 1))
|
||||||
|
|
||||||
def resize_tiled(self, im, dst_size, xtiles, ytiles):
|
def resize_tiled(self, im, dst_size, xtiles, ytiles):
|
||||||
|
@ -447,7 +456,7 @@ class CoreResampleBoxTest(PillowTestCase):
|
||||||
|
|
||||||
# error with box should be much smaller than without
|
# error with box should be much smaller than without
|
||||||
self.assert_image_similar(reference, with_box, 6)
|
self.assert_image_similar(reference, with_box, 6)
|
||||||
with self.assertRaisesRegexp(AssertionError, "difference 29\."):
|
with self.assertRaisesRegex(AssertionError, "difference 29\."):
|
||||||
self.assert_image_similar(reference, without_box, 5)
|
self.assert_image_similar(reference, without_box, 5)
|
||||||
|
|
||||||
def test_formats(self):
|
def test_formats(self):
|
||||||
|
@ -490,7 +499,7 @@ class CoreResampleBoxTest(PillowTestCase):
|
||||||
try:
|
try:
|
||||||
res = im.resize(size, Image.LANCZOS, box)
|
res = im.resize(size, Image.LANCZOS, box)
|
||||||
self.assertEqual(res.size, size)
|
self.assertEqual(res.size, size)
|
||||||
with self.assertRaisesRegexp(AssertionError, "difference \d"):
|
with self.assertRaisesRegex(AssertionError, "difference \d"):
|
||||||
# check that the difference at least that much
|
# check that the difference at least that much
|
||||||
self.assert_image_similar(res, im.crop(box), 20)
|
self.assert_image_similar(res, im.crop(box), 20)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
|
|
|
@ -39,15 +39,15 @@ class TestImagingCoreResize(PillowTestCase):
|
||||||
self.assertEqual(r.im.bands, im.im.bands)
|
self.assertEqual(r.im.bands, im.im.bands)
|
||||||
|
|
||||||
def test_reduce_filters(self):
|
def test_reduce_filters(self):
|
||||||
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING,
|
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR,
|
||||||
Image.BICUBIC, Image.LANCZOS]:
|
Image.HAMMING, Image.BICUBIC, Image.LANCZOS]:
|
||||||
r = self.resize(hopper("RGB"), (15, 12), f)
|
r = self.resize(hopper("RGB"), (15, 12), f)
|
||||||
self.assertEqual(r.mode, "RGB")
|
self.assertEqual(r.mode, "RGB")
|
||||||
self.assertEqual(r.size, (15, 12))
|
self.assertEqual(r.size, (15, 12))
|
||||||
|
|
||||||
def test_enlarge_filters(self):
|
def test_enlarge_filters(self):
|
||||||
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING,
|
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR,
|
||||||
Image.BICUBIC, Image.LANCZOS]:
|
Image.HAMMING, Image.BICUBIC, Image.LANCZOS]:
|
||||||
r = self.resize(hopper("RGB"), (212, 195), f)
|
r = self.resize(hopper("RGB"), (212, 195), f)
|
||||||
self.assertEqual(r.mode, "RGB")
|
self.assertEqual(r.mode, "RGB")
|
||||||
self.assertEqual(r.size, (212, 195))
|
self.assertEqual(r.size, (212, 195))
|
||||||
|
@ -66,8 +66,8 @@ class TestImagingCoreResize(PillowTestCase):
|
||||||
}
|
}
|
||||||
samples['dirty'].putpixel((1, 1), 128)
|
samples['dirty'].putpixel((1, 1), 128)
|
||||||
|
|
||||||
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING,
|
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR,
|
||||||
Image.BICUBIC, Image.LANCZOS]:
|
Image.HAMMING, Image.BICUBIC, Image.LANCZOS]:
|
||||||
# samples resized with current filter
|
# samples resized with current filter
|
||||||
references = {
|
references = {
|
||||||
name: self.resize(ch, (4, 4), f)
|
name: self.resize(ch, (4, 4), f)
|
||||||
|
@ -90,8 +90,8 @@ class TestImagingCoreResize(PillowTestCase):
|
||||||
self.assert_image_equal(ch, references[channels[i]])
|
self.assert_image_equal(ch, references[channels[i]])
|
||||||
|
|
||||||
def test_enlarge_zero(self):
|
def test_enlarge_zero(self):
|
||||||
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING,
|
for f in [Image.NEAREST, Image.BOX, Image.BILINEAR,
|
||||||
Image.BICUBIC, Image.LANCZOS]:
|
Image.HAMMING, Image.BICUBIC, Image.LANCZOS]:
|
||||||
r = self.resize(Image.new('RGB', (0, 0), "white"), (212, 195), f)
|
r = self.resize(Image.new('RGB', (0, 0), "white"), (212, 195), f)
|
||||||
self.assertEqual(r.mode, "RGB")
|
self.assertEqual(r.mode, "RGB")
|
||||||
self.assertEqual(r.size, (212, 195))
|
self.assertEqual(r.size, (212, 195))
|
||||||
|
|
|
@ -95,8 +95,34 @@ class TestImageRotate(PillowTestCase):
|
||||||
im = hopper()
|
im = hopper()
|
||||||
self.rotate(im, im.mode, 45, center=(0, 0))
|
self.rotate(im, im.mode, 45, center=(0, 0))
|
||||||
self.rotate(im, im.mode, 45, translate=(im.size[0]/2, 0))
|
self.rotate(im, im.mode, 45, translate=(im.size[0]/2, 0))
|
||||||
self.rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0]/2, 0))
|
self.rotate(im, im.mode, 45, center=(0, 0),
|
||||||
|
translate=(im.size[0]/2, 0))
|
||||||
|
|
||||||
|
def test_rotate_no_fill(self):
|
||||||
|
im = Image.new('RGB', (100, 100), 'green')
|
||||||
|
target = Image.open('Tests/images/rotate_45_no_fill.png')
|
||||||
|
im = im.rotate(45)
|
||||||
|
self.assert_image_equal(im, target)
|
||||||
|
|
||||||
|
def test_rotate_with_fill(self):
|
||||||
|
im = Image.new('RGB', (100, 100), 'green')
|
||||||
|
target = Image.open('Tests/images/rotate_45_with_fill.png')
|
||||||
|
im = im.rotate(45, fillcolor='white')
|
||||||
|
self.assert_image_equal(im, target)
|
||||||
|
|
||||||
|
def test_alpha_rotate_no_fill(self):
|
||||||
|
# Alpha images are handled differently internally
|
||||||
|
im = Image.new('RGBA', (10, 10), 'green')
|
||||||
|
im = im.rotate(45, expand=1)
|
||||||
|
corner = im.getpixel((0,0))
|
||||||
|
self.assertEqual(corner, (0, 0, 0, 0))
|
||||||
|
|
||||||
|
def test_alpha_rotate_with_fill(self):
|
||||||
|
# Alpha images are handled differently internally
|
||||||
|
im = Image.new('RGBA', (10, 10), 'green')
|
||||||
|
im = im.rotate(45, expand=1, fillcolor=(255, 0, 0, 255))
|
||||||
|
corner = im.getpixel((0,0))
|
||||||
|
self.assertEqual(corner, (255, 0, 0, 255))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -53,15 +53,20 @@ class TestImageTransform(PillowTestCase):
|
||||||
self.assert_image_equal(transformed, scaled)
|
self.assert_image_equal(transformed, scaled)
|
||||||
|
|
||||||
def test_fill(self):
|
def test_fill(self):
|
||||||
im = hopper('RGB')
|
for mode, pixel in [
|
||||||
(w, h) = im.size
|
['RGB', (255, 0, 0)],
|
||||||
transformed = im.transform(im.size, Image.EXTENT,
|
['RGBA', (255, 0, 0, 255)],
|
||||||
(0, 0,
|
['LA', (76, 0)]
|
||||||
w*2, h*2),
|
]:
|
||||||
Image.BILINEAR,
|
im = hopper(mode)
|
||||||
fillcolor = 'red')
|
(w, h) = im.size
|
||||||
|
transformed = im.transform(im.size, Image.EXTENT,
|
||||||
|
(0, 0,
|
||||||
|
w*2, h*2),
|
||||||
|
Image.BILINEAR,
|
||||||
|
fillcolor='red')
|
||||||
|
|
||||||
self.assertEqual(transformed.getpixel((w-1, h-1)), (255, 0, 0))
|
self.assertEqual(transformed.getpixel((w-1, h-1)), pixel)
|
||||||
|
|
||||||
def test_mesh(self):
|
def test_mesh(self):
|
||||||
# this should be a checkerboard of halfsized hoppers in ul, lr
|
# this should be a checkerboard of halfsized hoppers in ul, lr
|
||||||
|
|
|
@ -43,7 +43,7 @@ class TestImageCms(PillowTestCase):
|
||||||
self.assertEqual(list(map(type, v)), [str, str, str, str])
|
self.assertEqual(list(map(type, v)), [str, str, str, str])
|
||||||
|
|
||||||
# internal version number
|
# internal version number
|
||||||
self.assertRegexpMatches(ImageCms.core.littlecms_version, r"\d+\.\d+$")
|
self.assertRegex(ImageCms.core.littlecms_version, r"\d+\.\d+$")
|
||||||
|
|
||||||
self.skip_missing()
|
self.skip_missing()
|
||||||
i = ImageCms.profileToProfile(hopper(), SRGB, SRGB)
|
i = ImageCms.profileToProfile(hopper(), SRGB, SRGB)
|
||||||
|
@ -196,13 +196,13 @@ class TestImageCms(PillowTestCase):
|
||||||
# not a linear luminance map. so L != 128:
|
# not a linear luminance map. so L != 128:
|
||||||
self.assertEqual(k, (137, 128, 128))
|
self.assertEqual(k, (137, 128, 128))
|
||||||
|
|
||||||
l = i_lab.getdata(0)
|
l_data = i_lab.getdata(0)
|
||||||
a = i_lab.getdata(1)
|
a_data = i_lab.getdata(1)
|
||||||
b = i_lab.getdata(2)
|
b_data = i_lab.getdata(2)
|
||||||
|
|
||||||
self.assertEqual(list(l), [137] * 100)
|
self.assertEqual(list(l_data), [137] * 100)
|
||||||
self.assertEqual(list(a), [128] * 100)
|
self.assertEqual(list(a_data), [128] * 100)
|
||||||
self.assertEqual(list(b), [128] * 100)
|
self.assertEqual(list(b_data), [128] * 100)
|
||||||
|
|
||||||
def test_lab_color(self):
|
def test_lab_color(self):
|
||||||
psRGB = ImageCms.createProfile("sRGB")
|
psRGB = ImageCms.createProfile("sRGB")
|
||||||
|
@ -286,53 +286,104 @@ class TestImageCms(PillowTestCase):
|
||||||
self.assertEqual(truncate_tuple(tup1), truncate_tuple(tup2))
|
self.assertEqual(truncate_tuple(tup1), truncate_tuple(tup2))
|
||||||
|
|
||||||
self.assertEqual(p.attributes, 4294967296)
|
self.assertEqual(p.attributes, 4294967296)
|
||||||
assert_truncated_tuple_equal(p.blue_colorant, ((0.14306640625, 0.06060791015625, 0.7140960693359375), (0.1558847490315394, 0.06603820639433387, 0.06060791015625)))
|
assert_truncated_tuple_equal(
|
||||||
assert_truncated_tuple_equal(p.blue_primary, ((0.14306641366715667, 0.06060790921083026, 0.7140960805782015), (0.15588475410450106, 0.06603820408959558, 0.06060790921083026)))
|
p.blue_colorant,
|
||||||
|
((0.14306640625, 0.06060791015625, 0.7140960693359375),
|
||||||
|
(0.1558847490315394, 0.06603820639433387, 0.06060791015625)))
|
||||||
|
assert_truncated_tuple_equal(
|
||||||
|
p.blue_primary,
|
||||||
|
((0.14306641366715667, 0.06060790921083026, 0.7140960805782015),
|
||||||
|
(0.15588475410450106, 0.06603820408959558, 0.06060790921083026)))
|
||||||
assert_truncated_tuple_equal(p.chromatic_adaptation, (((1.04791259765625, 0.0229339599609375, -0.050201416015625), (0.02960205078125, 0.9904632568359375, -0.0170745849609375), (-0.009246826171875, 0.0150604248046875, 0.7517852783203125)), ((1.0267159024652783, 0.022470062342089134, 0.0229339599609375), (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875))))
|
assert_truncated_tuple_equal(p.chromatic_adaptation, (((1.04791259765625, 0.0229339599609375, -0.050201416015625), (0.02960205078125, 0.9904632568359375, -0.0170745849609375), (-0.009246826171875, 0.0150604248046875, 0.7517852783203125)), ((1.0267159024652783, 0.022470062342089134, 0.0229339599609375), (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875))))
|
||||||
self.assertIsNone(p.chromaticity)
|
self.assertIsNone(p.chromaticity)
|
||||||
self.assertEqual(p.clut, {0: (False, False, True), 1: (False, False, True), 2: (False, False, True), 3: (False, False, True)})
|
self.assertEqual(p.clut, {
|
||||||
|
0: (False, False, True),
|
||||||
|
1: (False, False, True),
|
||||||
|
2: (False, False, True),
|
||||||
|
3: (False, False, True)
|
||||||
|
})
|
||||||
self.assertEqual(p.color_space, 'RGB')
|
self.assertEqual(p.color_space, 'RGB')
|
||||||
self.assertIsNone(p.colorant_table)
|
self.assertIsNone(p.colorant_table)
|
||||||
self.assertIsNone(p.colorant_table_out)
|
self.assertIsNone(p.colorant_table_out)
|
||||||
self.assertIsNone(p.colorimetric_intent)
|
self.assertIsNone(p.colorimetric_intent)
|
||||||
self.assertEqual(p.connection_space, 'XYZ ')
|
self.assertEqual(p.connection_space, 'XYZ ')
|
||||||
self.assertEqual(p.copyright, 'Copyright International Color Consortium, 2009')
|
self.assertEqual(p.copyright,
|
||||||
self.assertEqual(p.creation_date, datetime.datetime(2009, 2, 27, 21, 36, 31))
|
'Copyright International Color Consortium, 2009')
|
||||||
|
self.assertEqual(p.creation_date,
|
||||||
|
datetime.datetime(2009, 2, 27, 21, 36, 31))
|
||||||
self.assertEqual(p.device_class, 'mntr')
|
self.assertEqual(p.device_class, 'mntr')
|
||||||
assert_truncated_tuple_equal(p.green_colorant, ((0.3851470947265625, 0.7168731689453125, 0.097076416015625), (0.32119769927720654, 0.5978443449048152, 0.7168731689453125)))
|
assert_truncated_tuple_equal(
|
||||||
assert_truncated_tuple_equal(p.green_primary, ((0.3851470888162112, 0.7168731974161346, 0.09707641738998518), (0.32119768793686687, 0.5978443567149709, 0.7168731974161346)))
|
p.green_colorant,
|
||||||
|
((0.3851470947265625, 0.7168731689453125, 0.097076416015625),
|
||||||
|
(0.32119769927720654, 0.5978443449048152, 0.7168731689453125)))
|
||||||
|
assert_truncated_tuple_equal(
|
||||||
|
p.green_primary,
|
||||||
|
((0.3851470888162112, 0.7168731974161346, 0.09707641738998518),
|
||||||
|
(0.32119768793686687, 0.5978443567149709, 0.7168731974161346)))
|
||||||
self.assertEqual(p.header_flags, 0)
|
self.assertEqual(p.header_flags, 0)
|
||||||
self.assertEqual(p.header_manufacturer, '\x00\x00\x00\x00')
|
self.assertEqual(p.header_manufacturer, '\x00\x00\x00\x00')
|
||||||
self.assertEqual(p.header_model, '\x00\x00\x00\x00')
|
self.assertEqual(p.header_model, '\x00\x00\x00\x00')
|
||||||
self.assertEqual(p.icc_measurement_condition, {'backing': (0.0, 0.0, 0.0), 'flare': 0.0, 'geo': 'unknown', 'observer': 1, 'illuminant_type': 'D65'})
|
self.assertEqual(p.icc_measurement_condition, {
|
||||||
|
'backing': (0.0, 0.0, 0.0),
|
||||||
|
'flare': 0.0,
|
||||||
|
'geo': 'unknown',
|
||||||
|
'observer': 1,
|
||||||
|
'illuminant_type': 'D65'
|
||||||
|
})
|
||||||
self.assertEqual(p.icc_version, 33554432)
|
self.assertEqual(p.icc_version, 33554432)
|
||||||
self.assertIsNone(p.icc_viewing_condition)
|
self.assertIsNone(p.icc_viewing_condition)
|
||||||
self.assertEqual(p.intent_supported, {0: (True, True, True), 1: (True, True, True), 2: (True, True, True), 3: (True, True, True)})
|
self.assertEqual(p.intent_supported, {
|
||||||
|
0: (True, True, True),
|
||||||
|
1: (True, True, True),
|
||||||
|
2: (True, True, True),
|
||||||
|
3: (True, True, True)
|
||||||
|
})
|
||||||
self.assertTrue(p.is_matrix_shaper)
|
self.assertTrue(p.is_matrix_shaper)
|
||||||
self.assertEqual(p.luminance, ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0)))
|
self.assertEqual(p.luminance, ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0)))
|
||||||
self.assertIsNone(p.manufacturer)
|
self.assertIsNone(p.manufacturer)
|
||||||
assert_truncated_tuple_equal(p.media_black_point, ((0.012054443359375, 0.0124969482421875, 0.01031494140625), (0.34573304157549234, 0.35842450765864337, 0.0124969482421875)))
|
assert_truncated_tuple_equal(
|
||||||
assert_truncated_tuple_equal(p.media_white_point, ((0.964202880859375, 1.0, 0.8249053955078125), (0.3457029219802284, 0.3585375327567059, 1.0)))
|
p.media_black_point,
|
||||||
assert_truncated_tuple_equal((p.media_white_point_temperature,), (5000.722328847392,))
|
((0.012054443359375, 0.0124969482421875, 0.01031494140625),
|
||||||
self.assertEqual(p.model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
|
(0.34573304157549234, 0.35842450765864337, 0.0124969482421875)))
|
||||||
|
assert_truncated_tuple_equal(
|
||||||
|
p.media_white_point,
|
||||||
|
((0.964202880859375, 1.0, 0.8249053955078125),
|
||||||
|
(0.3457029219802284, 0.3585375327567059, 1.0)))
|
||||||
|
assert_truncated_tuple_equal(
|
||||||
|
(p.media_white_point_temperature,),
|
||||||
|
(5000.722328847392,))
|
||||||
|
self.assertEqual(p.model,
|
||||||
|
'IEC 61966-2-1 Default RGB Colour Space - sRGB')
|
||||||
self.assertEqual(p.pcs, 'XYZ')
|
self.assertEqual(p.pcs, 'XYZ')
|
||||||
self.assertIsNone(p.perceptual_rendering_intent_gamut)
|
self.assertIsNone(p.perceptual_rendering_intent_gamut)
|
||||||
self.assertEqual(p.product_copyright, 'Copyright International Color Consortium, 2009')
|
self.assertEqual(p.product_copyright,
|
||||||
|
'Copyright International Color Consortium, 2009')
|
||||||
self.assertEqual(p.product_desc, 'sRGB IEC61966-2-1 black scaled')
|
self.assertEqual(p.product_desc, 'sRGB IEC61966-2-1 black scaled')
|
||||||
self.assertEqual(p.product_description, 'sRGB IEC61966-2-1 black scaled')
|
self.assertEqual(p.product_description,
|
||||||
|
'sRGB IEC61966-2-1 black scaled')
|
||||||
self.assertEqual(p.product_manufacturer, '')
|
self.assertEqual(p.product_manufacturer, '')
|
||||||
self.assertEqual(p.product_model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
|
self.assertEqual(
|
||||||
self.assertEqual(p.profile_description, 'sRGB IEC61966-2-1 black scaled')
|
p.product_model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
|
||||||
self.assertEqual(p.profile_id, b')\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r')
|
self.assertEqual(
|
||||||
assert_truncated_tuple_equal(p.red_colorant, ((0.436065673828125, 0.2224884033203125, 0.013916015625), (0.6484536316398539, 0.3308524880306778, 0.2224884033203125)))
|
p.profile_description, 'sRGB IEC61966-2-1 black scaled')
|
||||||
assert_truncated_tuple_equal(p.red_primary, ((0.43606566581047446, 0.22248840582960838, 0.013916015621759925), (0.6484536250319214, 0.3308524944738204, 0.22248840582960838)))
|
self.assertEqual(
|
||||||
|
p.profile_id, b')\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r')
|
||||||
|
assert_truncated_tuple_equal(
|
||||||
|
p.red_colorant,
|
||||||
|
((0.436065673828125, 0.2224884033203125, 0.013916015625),
|
||||||
|
(0.6484536316398539, 0.3308524880306778, 0.2224884033203125)))
|
||||||
|
assert_truncated_tuple_equal(
|
||||||
|
p.red_primary,
|
||||||
|
((0.43606566581047446, 0.22248840582960838, 0.013916015621759925),
|
||||||
|
(0.6484536250319214, 0.3308524944738204, 0.22248840582960838)))
|
||||||
self.assertEqual(p.rendering_intent, 0)
|
self.assertEqual(p.rendering_intent, 0)
|
||||||
self.assertIsNone(p.saturation_rendering_intent_gamut)
|
self.assertIsNone(p.saturation_rendering_intent_gamut)
|
||||||
self.assertIsNone(p.screening_description)
|
self.assertIsNone(p.screening_description)
|
||||||
self.assertIsNone(p.target)
|
self.assertIsNone(p.target)
|
||||||
self.assertEqual(p.technology, 'CRT ')
|
self.assertEqual(p.technology, 'CRT ')
|
||||||
self.assertEqual(p.version, 2.0)
|
self.assertEqual(p.version, 2.0)
|
||||||
self.assertEqual(p.viewing_condition, 'Reference Viewing Condition in IEC 61966-2-1')
|
self.assertEqual(p.viewing_condition,
|
||||||
|
'Reference Viewing Condition in IEC 61966-2-1')
|
||||||
self.assertEqual(p.xcolor_space, 'RGB ')
|
self.assertEqual(p.xcolor_space, 'RGB ')
|
||||||
|
|
||||||
def test_profile_typesafety(self):
|
def test_profile_typesafety(self):
|
||||||
|
@ -346,7 +397,8 @@ class TestImageCms(PillowTestCase):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
ImageCms.ImageCmsProfile(1).tobytes()
|
ImageCms.ImageCmsProfile(1).tobytes()
|
||||||
|
|
||||||
def assert_aux_channel_preserved(self, mode, transform_in_place, preserved_channel):
|
def assert_aux_channel_preserved(self, mode,
|
||||||
|
transform_in_place, preserved_channel):
|
||||||
def create_test_image():
|
def create_test_image():
|
||||||
# set up test image with something interesting in the tested aux
|
# set up test image with something interesting in the tested aux
|
||||||
# channel.
|
# channel.
|
||||||
|
@ -379,29 +431,35 @@ class TestImageCms(PillowTestCase):
|
||||||
# create some transform, it doesn't matter which one
|
# create some transform, it doesn't matter which one
|
||||||
source_profile = ImageCms.createProfile("sRGB")
|
source_profile = ImageCms.createProfile("sRGB")
|
||||||
destination_profile = ImageCms.createProfile("sRGB")
|
destination_profile = ImageCms.createProfile("sRGB")
|
||||||
t = ImageCms.buildTransform(source_profile, destination_profile, inMode=mode, outMode=mode)
|
t = ImageCms.buildTransform(
|
||||||
|
source_profile, destination_profile, inMode=mode, outMode=mode)
|
||||||
|
|
||||||
# apply transform
|
# apply transform
|
||||||
if transform_in_place:
|
if transform_in_place:
|
||||||
ImageCms.applyTransform(source_image, t, inPlace=True)
|
ImageCms.applyTransform(source_image, t, inPlace=True)
|
||||||
result_image = source_image
|
result_image = source_image
|
||||||
else:
|
else:
|
||||||
result_image = ImageCms.applyTransform(source_image, t, inPlace=False)
|
result_image = ImageCms.applyTransform(
|
||||||
|
source_image, t, inPlace=False)
|
||||||
result_image_aux = result_image.getchannel(preserved_channel)
|
result_image_aux = result_image.getchannel(preserved_channel)
|
||||||
|
|
||||||
self.assert_image_equal(source_image_aux, result_image_aux)
|
self.assert_image_equal(source_image_aux, result_image_aux)
|
||||||
|
|
||||||
def test_preserve_auxiliary_channels_rgba(self):
|
def test_preserve_auxiliary_channels_rgba(self):
|
||||||
self.assert_aux_channel_preserved(mode='RGBA', transform_in_place=False, preserved_channel='A')
|
self.assert_aux_channel_preserved(mode='RGBA',
|
||||||
|
transform_in_place=False, preserved_channel='A')
|
||||||
|
|
||||||
def test_preserve_auxiliary_channels_rgba_in_place(self):
|
def test_preserve_auxiliary_channels_rgba_in_place(self):
|
||||||
self.assert_aux_channel_preserved(mode='RGBA', transform_in_place=True, preserved_channel='A')
|
self.assert_aux_channel_preserved(mode='RGBA',
|
||||||
|
transform_in_place=True, preserved_channel='A')
|
||||||
|
|
||||||
def test_preserve_auxiliary_channels_rgbx(self):
|
def test_preserve_auxiliary_channels_rgbx(self):
|
||||||
self.assert_aux_channel_preserved(mode='RGBX', transform_in_place=False, preserved_channel='X')
|
self.assert_aux_channel_preserved(mode='RGBX',
|
||||||
|
transform_in_place=False, preserved_channel='X')
|
||||||
|
|
||||||
def test_preserve_auxiliary_channels_rgbx_in_place(self):
|
def test_preserve_auxiliary_channels_rgbx_in_place(self):
|
||||||
self.assert_aux_channel_preserved(mode='RGBX', transform_in_place=True, preserved_channel='X')
|
self.assert_aux_channel_preserved(mode='RGBX',
|
||||||
|
transform_in_place=True, preserved_channel='X')
|
||||||
|
|
||||||
def test_auxiliary_channels_isolated(self):
|
def test_auxiliary_channels_isolated(self):
|
||||||
# test data in aux channels does not affect non-aux channels
|
# test data in aux channels does not affect non-aux channels
|
||||||
|
@ -422,20 +480,29 @@ class TestImageCms(PillowTestCase):
|
||||||
source_profile = ImageCms.createProfile(src_format[1])
|
source_profile = ImageCms.createProfile(src_format[1])
|
||||||
destination_profile = ImageCms.createProfile(dst_format[1])
|
destination_profile = ImageCms.createProfile(dst_format[1])
|
||||||
source_image = src_format[3]
|
source_image = src_format[3]
|
||||||
test_transform = ImageCms.buildTransform(source_profile, destination_profile, inMode=src_format[0], outMode=dst_format[0])
|
test_transform = ImageCms.buildTransform(
|
||||||
|
source_profile, destination_profile,
|
||||||
|
inMode=src_format[0], outMode=dst_format[0])
|
||||||
|
|
||||||
# test conversion from aux-ful source
|
# test conversion from aux-ful source
|
||||||
if transform_in_place:
|
if transform_in_place:
|
||||||
test_image = source_image.copy()
|
test_image = source_image.copy()
|
||||||
ImageCms.applyTransform(test_image, test_transform, inPlace=True)
|
ImageCms.applyTransform(
|
||||||
|
test_image, test_transform, inPlace=True)
|
||||||
else:
|
else:
|
||||||
test_image = ImageCms.applyTransform(source_image, test_transform, inPlace=False)
|
test_image = ImageCms.applyTransform(
|
||||||
|
source_image, test_transform, inPlace=False)
|
||||||
|
|
||||||
# reference conversion from aux-less source
|
# reference conversion from aux-less source
|
||||||
reference_transform = ImageCms.buildTransform(source_profile, destination_profile, inMode=src_format[2], outMode=dst_format[2])
|
reference_transform = ImageCms.buildTransform(
|
||||||
reference_image = ImageCms.applyTransform(source_image.convert(src_format[2]), reference_transform)
|
source_profile, destination_profile,
|
||||||
|
inMode=src_format[2], outMode=dst_format[2])
|
||||||
|
reference_image = ImageCms.applyTransform(
|
||||||
|
source_image.convert(src_format[2]),
|
||||||
|
reference_transform)
|
||||||
|
|
||||||
self.assert_image_equal(test_image.convert(dst_format[2]), reference_image)
|
self.assert_image_equal(test_image.convert(dst_format[2]),
|
||||||
|
reference_image)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -31,7 +31,8 @@ class TestImageColor(PillowTestCase):
|
||||||
|
|
||||||
# case insensitivity
|
# case insensitivity
|
||||||
self.assertEqual(ImageColor.getrgb("#DEF"), ImageColor.getrgb("#def"))
|
self.assertEqual(ImageColor.getrgb("#DEF"), ImageColor.getrgb("#def"))
|
||||||
self.assertEqual(ImageColor.getrgb("#CDEF"), ImageColor.getrgb("#cdef"))
|
self.assertEqual(ImageColor.getrgb("#CDEF"),
|
||||||
|
ImageColor.getrgb("#cdef"))
|
||||||
self.assertEqual(ImageColor.getrgb("#DEFDEF"),
|
self.assertEqual(ImageColor.getrgb("#DEFDEF"),
|
||||||
ImageColor.getrgb("#defdef"))
|
ImageColor.getrgb("#defdef"))
|
||||||
self.assertEqual(ImageColor.getrgb("#CDEFCDEF"),
|
self.assertEqual(ImageColor.getrgb("#CDEFCDEF"),
|
||||||
|
@ -78,6 +79,26 @@ class TestImageColor(PillowTestCase):
|
||||||
self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360,100%,50%)"))
|
self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360,100%,50%)"))
|
||||||
self.assertEqual((0, 255, 255), ImageColor.getrgb("hsl(180,100%,50%)"))
|
self.assertEqual((0, 255, 255), ImageColor.getrgb("hsl(180,100%,50%)"))
|
||||||
|
|
||||||
|
self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(0,100%,100%)"))
|
||||||
|
self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360,100%,100%)"))
|
||||||
|
self.assertEqual((0, 255, 255),
|
||||||
|
ImageColor.getrgb("hsv(180,100%,100%)"))
|
||||||
|
|
||||||
|
# alternate format
|
||||||
|
self.assertEqual(ImageColor.getrgb("hsb(0,100%,50%)"),
|
||||||
|
ImageColor.getrgb("hsv(0,100%,50%)"))
|
||||||
|
|
||||||
|
# floats
|
||||||
|
self.assertEqual((254, 3, 3),
|
||||||
|
ImageColor.getrgb("hsl(0.1,99.2%,50.3%)"))
|
||||||
|
self.assertEqual((255, 0, 0),
|
||||||
|
ImageColor.getrgb("hsl(360.,100.0%,50%)"))
|
||||||
|
|
||||||
|
self.assertEqual((253, 2, 2),
|
||||||
|
ImageColor.getrgb("hsv(0.1,99.2%,99.3%)"))
|
||||||
|
self.assertEqual((255, 0, 0),
|
||||||
|
ImageColor.getrgb("hsv(360.,100.0%,100%)"))
|
||||||
|
|
||||||
# case insensitivity
|
# case insensitivity
|
||||||
self.assertEqual(ImageColor.getrgb("RGB(255,0,0)"),
|
self.assertEqual(ImageColor.getrgb("RGB(255,0,0)"),
|
||||||
ImageColor.getrgb("rgb(255,0,0)"))
|
ImageColor.getrgb("rgb(255,0,0)"))
|
||||||
|
@ -87,6 +108,10 @@ class TestImageColor(PillowTestCase):
|
||||||
ImageColor.getrgb("rgba(255,0,0,0)"))
|
ImageColor.getrgb("rgba(255,0,0,0)"))
|
||||||
self.assertEqual(ImageColor.getrgb("HSL(0,100%,50%)"),
|
self.assertEqual(ImageColor.getrgb("HSL(0,100%,50%)"),
|
||||||
ImageColor.getrgb("hsl(0,100%,50%)"))
|
ImageColor.getrgb("hsl(0,100%,50%)"))
|
||||||
|
self.assertEqual(ImageColor.getrgb("HSV(0,100%,50%)"),
|
||||||
|
ImageColor.getrgb("hsv(0,100%,50%)"))
|
||||||
|
self.assertEqual(ImageColor.getrgb("HSB(0,100%,50%)"),
|
||||||
|
ImageColor.getrgb("hsb(0,100%,50%)"))
|
||||||
|
|
||||||
# space agnosticism
|
# space agnosticism
|
||||||
self.assertEqual((255, 0, 0),
|
self.assertEqual((255, 0, 0),
|
||||||
|
@ -97,6 +122,8 @@ class TestImageColor(PillowTestCase):
|
||||||
ImageColor.getrgb("rgba( 255 , 0 , 0 , 0 )"))
|
ImageColor.getrgb("rgba( 255 , 0 , 0 , 0 )"))
|
||||||
self.assertEqual((255, 0, 0),
|
self.assertEqual((255, 0, 0),
|
||||||
ImageColor.getrgb("hsl( 0 , 100% , 50% )"))
|
ImageColor.getrgb("hsl( 0 , 100% , 50% )"))
|
||||||
|
self.assertEqual((255, 0, 0),
|
||||||
|
ImageColor.getrgb("hsv( 0 , 100% , 100% )"))
|
||||||
|
|
||||||
# wrong number of components
|
# wrong number of components
|
||||||
self.assertRaises(ValueError, ImageColor.getrgb, "rgb(255,0)")
|
self.assertRaises(ValueError, ImageColor.getrgb, "rgb(255,0)")
|
||||||
|
@ -116,6 +143,12 @@ class TestImageColor(PillowTestCase):
|
||||||
self.assertRaises(ValueError, ImageColor.getrgb, "hsl(0,100,50%)")
|
self.assertRaises(ValueError, ImageColor.getrgb, "hsl(0,100,50%)")
|
||||||
self.assertRaises(ValueError, ImageColor.getrgb, "hsl(0,100%,50)")
|
self.assertRaises(ValueError, ImageColor.getrgb, "hsl(0,100%,50)")
|
||||||
|
|
||||||
|
self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0,100%)")
|
||||||
|
self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0,100%,0%,0%)")
|
||||||
|
self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0%,100%,50%)")
|
||||||
|
self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0,100,50%)")
|
||||||
|
self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0,100%,50)")
|
||||||
|
|
||||||
# look for rounding errors (based on code by Tim Hatch)
|
# look for rounding errors (based on code by Tim Hatch)
|
||||||
def test_rounding_errors(self):
|
def test_rounding_errors(self):
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
from helper import unittest, PillowTestCase, hopper
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
from PIL import ImageColor
|
|
||||||
from PIL import ImageDraw
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import sys
|
from helper import PillowTestCase, hopper, unittest
|
||||||
|
from PIL import Image, ImageColor, ImageDraw
|
||||||
|
|
||||||
BLACK = (0, 0, 0)
|
BLACK = (0, 0, 0)
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
|
@ -173,6 +169,16 @@ class TestImageDraw(PillowTestCase):
|
||||||
self.assert_image_similar(
|
self.assert_image_similar(
|
||||||
im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1)
|
im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1)
|
||||||
|
|
||||||
|
def test_ellipse_symmetric(self):
|
||||||
|
for bbox in [
|
||||||
|
(25, 25, 76, 76),
|
||||||
|
(25, 25, 75, 75)
|
||||||
|
]:
|
||||||
|
im = Image.new("RGB", (101, 101))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
draw.ellipse(bbox, fill="green", outline="blue")
|
||||||
|
self.assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT))
|
||||||
|
|
||||||
def helper_line(self, points):
|
def helper_line(self, points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -360,8 +366,6 @@ class TestImageDraw(PillowTestCase):
|
||||||
ImageDraw.floodfill(im, (W, H), red)
|
ImageDraw.floodfill(im, (W, H), red)
|
||||||
self.assert_image_equal(im, im_floodfill)
|
self.assert_image_equal(im, im_floodfill)
|
||||||
|
|
||||||
@unittest.skipIf(hasattr(sys, 'pypy_version_info'),
|
|
||||||
"Causes fatal RPython error on PyPy")
|
|
||||||
def test_floodfill_border(self):
|
def test_floodfill_border(self):
|
||||||
# floodfill() is experimental
|
# floodfill() is experimental
|
||||||
|
|
||||||
|
@ -572,6 +576,47 @@ class TestImageDraw(PillowTestCase):
|
||||||
draw.textsize("\n")
|
draw.textsize("\n")
|
||||||
draw.textsize("test\n")
|
draw.textsize("test\n")
|
||||||
|
|
||||||
|
def test_same_color_outline(self):
|
||||||
|
# Prepare shape
|
||||||
|
x0, y0 = 5, 5
|
||||||
|
x1, y1 = 5, 50
|
||||||
|
x2, y2 = 95, 50
|
||||||
|
x3, y3 = 95, 5
|
||||||
|
|
||||||
|
s = ImageDraw.Outline()
|
||||||
|
s.move(x0, y0)
|
||||||
|
s.curve(x1, y1, x2, y2, x3, y3)
|
||||||
|
s.line(x0, y0)
|
||||||
|
|
||||||
|
# Begin
|
||||||
|
for mode in ["RGB", "L"]:
|
||||||
|
for fill, outline in [
|
||||||
|
["red", None],
|
||||||
|
["red", "red"],
|
||||||
|
["red", "#f00"]
|
||||||
|
]:
|
||||||
|
for operation, args in {
|
||||||
|
'chord':[BBOX1, 0, 180],
|
||||||
|
'ellipse':[BBOX1],
|
||||||
|
'shape':[s],
|
||||||
|
'pieslice':[BBOX1, -90, 45],
|
||||||
|
'polygon':[[(18, 30), (85, 30), (60, 72)]],
|
||||||
|
'rectangle':[BBOX1]
|
||||||
|
}.items():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new(mode, (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw_method = getattr(draw, operation)
|
||||||
|
args += [fill, outline]
|
||||||
|
draw_method(*args)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
expected = ("Tests/images/imagedraw_outline"
|
||||||
|
"_{}_{}.png".format(operation, mode))
|
||||||
|
self.assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
229
Tests/test_imagedraw2.py
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from helper import PillowTestCase, hopper, unittest
|
||||||
|
from PIL import Image, ImageDraw2, features
|
||||||
|
|
||||||
|
BLACK = (0, 0, 0)
|
||||||
|
WHITE = (255, 255, 255)
|
||||||
|
GRAY = (190, 190, 190)
|
||||||
|
DEFAULT_MODE = "RGB"
|
||||||
|
IMAGES_PATH = os.path.join("Tests", "images", "imagedraw")
|
||||||
|
|
||||||
|
# Image size
|
||||||
|
W, H = 100, 100
|
||||||
|
|
||||||
|
# Bounding box points
|
||||||
|
X0 = int(W / 4)
|
||||||
|
X1 = int(X0 * 3)
|
||||||
|
Y0 = int(H / 4)
|
||||||
|
Y1 = int(X0 * 3)
|
||||||
|
|
||||||
|
# Two kinds of bounding box
|
||||||
|
BBOX1 = [(X0, Y0), (X1, Y1)]
|
||||||
|
BBOX2 = [X0, Y0, X1, Y1]
|
||||||
|
|
||||||
|
# Two kinds of coordinate sequences
|
||||||
|
POINTS1 = [(10, 10), (20, 40), (30, 30)]
|
||||||
|
POINTS2 = [10, 10, 20, 40, 30, 30]
|
||||||
|
|
||||||
|
KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)]
|
||||||
|
|
||||||
|
HAS_FREETYPE = features.check("freetype2")
|
||||||
|
FONT_PATH = "Tests/fonts/FreeMono.ttf"
|
||||||
|
|
||||||
|
|
||||||
|
class TestImageDraw(PillowTestCase):
|
||||||
|
|
||||||
|
def test_sanity(self):
|
||||||
|
im = hopper("RGB").copy()
|
||||||
|
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
pen = ImageDraw2.Pen("blue", width=7)
|
||||||
|
draw.line(list(range(10)), pen)
|
||||||
|
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
draw, handler = ImageDraw.getdraw(im)
|
||||||
|
pen = ImageDraw2.Pen("blue", width=7)
|
||||||
|
draw.line(list(range(10)), pen)
|
||||||
|
|
||||||
|
def helper_ellipse(self, mode, bbox):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
pen = ImageDraw2.Pen("blue", width=2)
|
||||||
|
brush = ImageDraw2.Brush("green")
|
||||||
|
expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.ellipse(bbox, pen, brush)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
|
def test_ellipse1(self):
|
||||||
|
self.helper_ellipse("RGB", BBOX1)
|
||||||
|
|
||||||
|
def test_ellipse2(self):
|
||||||
|
self.helper_ellipse("RGB", BBOX2)
|
||||||
|
|
||||||
|
def test_ellipse_edge(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
brush = ImageDraw2.Brush("white")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.ellipse(((0, 0), (W-1, H)), brush)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_similar(
|
||||||
|
im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1)
|
||||||
|
|
||||||
|
def helper_line(self, points):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
pen = ImageDraw2.Pen("yellow", width=2)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.line(points, pen)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(
|
||||||
|
im, Image.open("Tests/images/imagedraw_line.png"))
|
||||||
|
|
||||||
|
def test_line1_pen(self):
|
||||||
|
self.helper_line(POINTS1)
|
||||||
|
|
||||||
|
def test_line2_pen(self):
|
||||||
|
self.helper_line(POINTS2)
|
||||||
|
|
||||||
|
def test_line_pen_as_brush(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
pen = None
|
||||||
|
brush = ImageDraw2.Pen("yellow", width=2)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
# Pass in the pen as the brush parameter
|
||||||
|
draw.line(POINTS1, pen, brush)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(
|
||||||
|
im, Image.open("Tests/images/imagedraw_line.png"))
|
||||||
|
|
||||||
|
def helper_polygon(self, points):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
pen = ImageDraw2.Pen("blue", width=2)
|
||||||
|
brush = ImageDraw2.Brush("red")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.polygon(points, pen, brush)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(
|
||||||
|
im, Image.open("Tests/images/imagedraw_polygon.png"))
|
||||||
|
|
||||||
|
def test_polygon1(self):
|
||||||
|
self.helper_polygon(POINTS1)
|
||||||
|
|
||||||
|
def test_polygon2(self):
|
||||||
|
self.helper_polygon(POINTS2)
|
||||||
|
|
||||||
|
def helper_rectangle(self, bbox):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
pen = ImageDraw2.Pen("green", width=2)
|
||||||
|
brush = ImageDraw2.Brush("black")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.rectangle(bbox, pen, brush)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(
|
||||||
|
im, Image.open("Tests/images/imagedraw_rectangle.png"))
|
||||||
|
|
||||||
|
def test_rectangle1(self):
|
||||||
|
self.helper_rectangle(BBOX1)
|
||||||
|
|
||||||
|
def test_rectangle2(self):
|
||||||
|
self.helper_rectangle(BBOX2)
|
||||||
|
|
||||||
|
def test_big_rectangle(self):
|
||||||
|
# Test drawing a rectangle bigger than the image
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
bbox = [(-1, -1), (W + 1, H + 1)]
|
||||||
|
brush = ImageDraw2.Brush("orange")
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
expected = "Tests/images/imagedraw_big_rectangle.png"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.rectangle(bbox, brush)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
|
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
|
||||||
|
def test_text(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
font = ImageDraw2.Font("white", FONT_PATH)
|
||||||
|
expected = "Tests/images/imagedraw2_text.png"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.text((5, 5), "ImageDraw2", font)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_similar(im, Image.open(expected), 13)
|
||||||
|
|
||||||
|
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
|
||||||
|
def test_textsize(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
font = ImageDraw2.Font("white", FONT_PATH)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
size = draw.textsize("ImageDraw2", font)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(size[1], 12)
|
||||||
|
|
||||||
|
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
|
||||||
|
def test_textsize_empty_string(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
font = ImageDraw2.Font("white", FONT_PATH)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
# Should not cause 'SystemError: <built-in method getsize of
|
||||||
|
# ImagingFont object at 0x...> returned NULL without setting an error'
|
||||||
|
draw.textsize("", font)
|
||||||
|
draw.textsize("\n", font)
|
||||||
|
draw.textsize("test\n", font)
|
||||||
|
|
||||||
|
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
|
||||||
|
def test_flush(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw2.Draw(im)
|
||||||
|
font = ImageDraw2.Font("white", FONT_PATH)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.text((5, 5), "ImageDraw2", font)
|
||||||
|
im2 = draw.flush()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(im, im2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
|
@ -45,8 +45,9 @@ class TestImageEnhance(PillowTestCase):
|
||||||
|
|
||||||
for op in ['Color', 'Brightness', 'Contrast', 'Sharpness']:
|
for op in ['Color', 'Brightness', 'Contrast', 'Sharpness']:
|
||||||
for amount in [0, 0.5, 1.0]:
|
for amount in [0, 0.5, 1.0]:
|
||||||
self._check_alpha(getattr(ImageEnhance, op)(original).enhance(amount),
|
self._check_alpha(
|
||||||
original, op, amount)
|
getattr(ImageEnhance, op)(original).enhance(amount),
|
||||||
|
original, op, amount)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -215,14 +215,25 @@ class TestPyDecoder(PillowTestCase):
|
||||||
buf = BytesIO(b'\x00'*255)
|
buf = BytesIO(b'\x00'*255)
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
im.tile = [("MOCK", (xoff, yoff, xoff+xsize + 100, yoff+ysize), 32, None)]
|
im.tile = [
|
||||||
|
("MOCK", (xoff, yoff, xoff+xsize + 100, yoff+ysize), 32, None)
|
||||||
|
]
|
||||||
d = self.get_decoder()
|
d = self.get_decoder()
|
||||||
|
|
||||||
self.assertRaises(ValueError, im.load)
|
self.assertRaises(ValueError, im.load)
|
||||||
|
|
||||||
im.tile = [("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize + 100), 32, None)]
|
im.tile = [
|
||||||
|
("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize + 100), 32, None)
|
||||||
|
]
|
||||||
self.assertRaises(ValueError, im.load)
|
self.assertRaises(ValueError, im.load)
|
||||||
|
|
||||||
|
def test_no_format(self):
|
||||||
|
buf = BytesIO(b'\x00'*255)
|
||||||
|
|
||||||
|
im = MockImageFile(buf)
|
||||||
|
self.assertIsNone(im.format)
|
||||||
|
self.assertIsNone(im.get_format_mimetype())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -42,7 +42,7 @@ class SimplePatcher(object):
|
||||||
delattr(self._parent_obj, self._attr_name)
|
delattr(self._parent_obj, self._attr_name)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not Available")
|
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
|
||||||
class TestImageFont(PillowTestCase):
|
class TestImageFont(PillowTestCase):
|
||||||
LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC
|
LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC
|
||||||
|
|
||||||
|
@ -67,16 +67,18 @@ class TestImageFont(PillowTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
freetype_version = tuple(ImageFont.core.freetype2_version.split('.'))[:2]
|
freetype_version = tuple(
|
||||||
self.metrics = self.METRICS.get(freetype_version, self.METRICS['Default'])
|
ImageFont.core.freetype2_version.split('.')
|
||||||
|
)[:2]
|
||||||
|
self.metrics = self.METRICS.get(freetype_version,
|
||||||
|
self.METRICS['Default'])
|
||||||
|
|
||||||
def get_font(self):
|
def get_font(self):
|
||||||
return ImageFont.truetype(FONT_PATH, FONT_SIZE,
|
return ImageFont.truetype(FONT_PATH, FONT_SIZE,
|
||||||
layout_engine=self.LAYOUT_ENGINE)
|
layout_engine=self.LAYOUT_ENGINE)
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
self.assertRegexpMatches(
|
self.assertRegex(ImageFont.core.freetype2_version, r"\d+\.\d+\.\d+$")
|
||||||
ImageFont.core.freetype2_version, r"\d+\.\d+\.\d+$")
|
|
||||||
|
|
||||||
def test_font_properties(self):
|
def test_font_properties(self):
|
||||||
ttf = self.get_font()
|
ttf = self.get_font()
|
||||||
|
@ -203,7 +205,8 @@ class TestImageFont(PillowTestCase):
|
||||||
target_img = Image.open(target)
|
target_img = Image.open(target)
|
||||||
|
|
||||||
# Epsilon ~.5 fails with FreeType 2.7
|
# Epsilon ~.5 fails with FreeType 2.7
|
||||||
self.assert_image_similar(im, target_img, self.metrics['multiline'])
|
self.assert_image_similar(im, target_img,
|
||||||
|
self.metrics['multiline'])
|
||||||
|
|
||||||
def test_unknown_align(self):
|
def test_unknown_align(self):
|
||||||
im = Image.new(mode='RGB', size=(300, 100))
|
im = Image.new(mode='RGB', size=(300, 100))
|
||||||
|
@ -211,10 +214,9 @@ class TestImageFont(PillowTestCase):
|
||||||
ttf = self.get_font()
|
ttf = self.get_font()
|
||||||
|
|
||||||
# Act/Assert
|
# Act/Assert
|
||||||
self.assertRaises(AssertionError,
|
self.assertRaises(
|
||||||
draw.multiline_text, (0, 0), TEST_TEXT,
|
AssertionError,
|
||||||
font=ttf,
|
draw.multiline_text, (0, 0), TEST_TEXT, font=ttf, align="unknown")
|
||||||
align="unknown")
|
|
||||||
|
|
||||||
def test_draw_align(self):
|
def test_draw_align(self):
|
||||||
im = Image.new('RGB', (300, 100), 'white')
|
im = Image.new('RGB', (300, 100), 'white')
|
||||||
|
@ -232,6 +234,11 @@ class TestImageFont(PillowTestCase):
|
||||||
self.assertEqual(draw.textsize(TEST_TEXT, font=ttf),
|
self.assertEqual(draw.textsize(TEST_TEXT, font=ttf),
|
||||||
draw.multiline_textsize(TEST_TEXT, font=ttf))
|
draw.multiline_textsize(TEST_TEXT, font=ttf))
|
||||||
|
|
||||||
|
# Test that multiline_textsize corresponds to ImageFont.textsize()
|
||||||
|
# for single line text
|
||||||
|
self.assertEqual(ttf.getsize('A'),
|
||||||
|
draw.multiline_textsize('A', font=ttf))
|
||||||
|
|
||||||
# Test that textsize() can pass on additional arguments
|
# Test that textsize() can pass on additional arguments
|
||||||
# to multiline_textsize()
|
# to multiline_textsize()
|
||||||
draw.textsize(TEST_TEXT, font=ttf, spacing=4)
|
draw.textsize(TEST_TEXT, font=ttf, spacing=4)
|
||||||
|
@ -409,7 +416,7 @@ class TestImageFont(PillowTestCase):
|
||||||
im = Image.new(mode='RGB', size=(300, 100))
|
im = Image.new(mode='RGB', size=(300, 100))
|
||||||
target = im.copy()
|
target = im.copy()
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
#should not crash here.
|
# should not crash here.
|
||||||
draw.text((10, 10), '', font=font)
|
draw.text((10, 10), '', font=font)
|
||||||
self.assert_image_equal(im, target)
|
self.assert_image_equal(im, target)
|
||||||
|
|
||||||
|
@ -424,7 +431,8 @@ class TestImageFont(PillowTestCase):
|
||||||
# Make a copy of FreeTypeFont so we can patch the original
|
# Make a copy of FreeTypeFont so we can patch the original
|
||||||
free_type_font = copy.deepcopy(ImageFont.FreeTypeFont)
|
free_type_font = copy.deepcopy(ImageFont.FreeTypeFont)
|
||||||
with SimplePatcher(ImageFont, '_FreeTypeFont', free_type_font):
|
with SimplePatcher(ImageFont, '_FreeTypeFont', free_type_font):
|
||||||
def loadable_font(filepath, size, index, encoding, *args, **kwargs):
|
def loadable_font(filepath, size, index, encoding,
|
||||||
|
*args, **kwargs):
|
||||||
if filepath == path_to_fake:
|
if filepath == path_to_fake:
|
||||||
return ImageFont._FreeTypeFont(FONT_PATH, size, index,
|
return ImageFont._FreeTypeFont(FONT_PATH, size, index,
|
||||||
encoding, *args, **kwargs)
|
encoding, *args, **kwargs)
|
||||||
|
@ -510,6 +518,12 @@ class TestImageFont(PillowTestCase):
|
||||||
self.assertEqual(t.getsize('M'), self.metrics['getters'])
|
self.assertEqual(t.getsize('M'), self.metrics['getters'])
|
||||||
self.assertEqual(t.getsize('y'), (12, 20))
|
self.assertEqual(t.getsize('y'), (12, 20))
|
||||||
self.assertEqual(t.getsize('a'), (12, 16))
|
self.assertEqual(t.getsize('a'), (12, 16))
|
||||||
|
self.assertEqual(t.getsize_multiline('A'), (12, 16))
|
||||||
|
self.assertEqual(t.getsize_multiline('AB'), (24, 16))
|
||||||
|
self.assertEqual(t.getsize_multiline('a'), (12, 16))
|
||||||
|
self.assertEqual(t.getsize_multiline('ABC\n'), (36, 36))
|
||||||
|
self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36))
|
||||||
|
self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36))
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(HAS_RAQM, "Raqm not Available")
|
@unittest.skipUnless(HAS_RAQM, "Raqm not Available")
|
||||||
|
|
|
@ -19,7 +19,8 @@ class TestImageFontBitmap(PillowTestCase):
|
||||||
font='Tests/fonts/DejaVuSans-bitmap.ttf', size=24)
|
font='Tests/fonts/DejaVuSans-bitmap.ttf', size=24)
|
||||||
size_outline = font_outline.getsize(text)
|
size_outline = font_outline.getsize(text)
|
||||||
size_bitmap = font_bitmap.getsize(text)
|
size_bitmap = font_bitmap.getsize(text)
|
||||||
size_final = max(size_outline[0], size_bitmap[0]), max(size_outline[1], size_bitmap[1])
|
size_final = (max(size_outline[0], size_bitmap[0]),
|
||||||
|
max(size_outline[1], size_bitmap[1]))
|
||||||
im_bitmap = Image.new('RGB', size_final, (255, 255, 255))
|
im_bitmap = Image.new('RGB', size_final, (255, 255, 255))
|
||||||
im_outline = im_bitmap.copy()
|
im_outline = im_bitmap.copy()
|
||||||
draw_bitmap = ImageDraw.Draw(im_bitmap)
|
draw_bitmap = ImageDraw.Draw(im_bitmap)
|
||||||
|
|