mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 18:06:18 +03:00
Merge
This commit is contained in:
commit
5c40cb1ba6
52
.travis.yml
52
.travis.yml
|
@ -3,8 +3,6 @@ language: python
|
|||
notifications:
|
||||
irc: "chat.freenode.net#pil"
|
||||
|
||||
env: MAX_CONCURRENCY=4
|
||||
|
||||
# Run slow PyPy* first, to give them a headstart and reduce waiting time.
|
||||
# Run latest 3.x and 2.x next, to get quick compatibility results.
|
||||
# Then run the remainder.
|
||||
|
@ -22,9 +20,7 @@ install:
|
|||
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
|
||||
- "travis_retry pip install cffi"
|
||||
- "travis_retry pip install coverage nose"
|
||||
|
||||
# Pyroma installation is slow on Py3, so just do it for Py2.
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then travis_retry pip install pyroma; fi
|
||||
- "travis_retry pip install pyroma"
|
||||
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
|
||||
|
||||
|
@ -56,7 +52,6 @@ after_success:
|
|||
- travis_retry pip install coveralls-merge
|
||||
- coveralls-merge coverage.c.json
|
||||
|
||||
|
||||
- travis_retry pip install pep8 pyflakes
|
||||
- pep8 --statistics --count PIL/*.py
|
||||
- pep8 --statistics --count Tests/*.py
|
||||
|
@ -64,10 +59,53 @@ after_success:
|
|||
- pyflakes PIL/*.py | tee >(wc -l)
|
||||
- pyflakes Tests/*.py | tee >(wc -l)
|
||||
|
||||
|
||||
# Coverage and quality reports on just the latest diff.
|
||||
# (Installation is very slow on Py3, so just do it for Py2.)
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; 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 succeded! Triggering OS X build..."
|
||||
# Trigger an OS X build at the pillow-wheels repo
|
||||
./build_children.sh
|
||||
else
|
||||
echo "Some jobs failed"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
env:
|
||||
global:
|
||||
# travis encrypt AUTH_TOKEN=
|
||||
secure: "Vzm7aG1Qv0SDQcqiPzZMedNLn5ZmpL7IzF0DYnqcD+/l+zmKU22SnJBcX0uVXumo+r7eZfpsShpqfcdsZvMlvmQnwz+Y6AGKQru9tCKZbTMnuRjWKKXekC+tr8Xt9CKvRVtte5PyXW31paxUI3/e+fQGBwoFjEEC+6EpEOjeRfE="
|
||||
|
|
234
CHANGES.rst
234
CHANGES.rst
|
@ -1,14 +1,194 @@
|
|||
Changelog (Pillow)
|
||||
==================
|
||||
|
||||
2.7.0 (unreleased)
|
||||
2.9.0 (Unreleased)
|
||||
------------------
|
||||
|
||||
- Provide n_frames attribute to multi-frame formats #1261
|
||||
[anntzer, radarhere]
|
||||
|
||||
- Add duration and loop set to GifImagePlugin #1172
|
||||
[radarhere]
|
||||
|
||||
- Ico files are little endian #1232
|
||||
[wiredfool]
|
||||
|
||||
- Upgrade olefile from 0.30 to 0.42b #1226
|
||||
[radarhere, decalage2]
|
||||
|
||||
- Setting transparency value to 0 when the tRNS contains only null byte(s) #1239
|
||||
[juztin]
|
||||
|
||||
- Separated out feature checking from selftest #1233
|
||||
[radarhere]
|
||||
|
||||
- Style/health fixes
|
||||
[radarhere]
|
||||
|
||||
- Update WebP from 0.4.1 to 0.4.3 #1235
|
||||
[radarhere]
|
||||
|
||||
- Release GIL during image load (decode) #1224
|
||||
[lkesteloot]
|
||||
|
||||
- Added icns save #1185
|
||||
[radarhere]
|
||||
|
||||
- Fix putdata memory leak #1196
|
||||
[benoit-pierre]
|
||||
|
||||
- Keep user-specified ordering of icon sizes #1193
|
||||
[karimbahgat]
|
||||
|
||||
- Tiff: allow writing floating point tag values #1113
|
||||
[bpedersen2]
|
||||
|
||||
2.8.2 (2015-06-06)
|
||||
------------------
|
||||
|
||||
- Bug fix: Fixed Tiff handling of bad EXIF data
|
||||
[radarhere]
|
||||
|
||||
2.8.1 (2015-04-02)
|
||||
------------------
|
||||
|
||||
- Bug fix: Catch struct.error on invalid JPEG, fixes #1163
|
||||
[wiredfool, hugovk]
|
||||
|
||||
2.8.0 (2015-04-01)
|
||||
------------------
|
||||
|
||||
- Fix 32-bit BMP loading (RGBA or RGBX)
|
||||
[artscoop]
|
||||
|
||||
- Fix UnboundLocalError in ImageFile #1131
|
||||
[davarisg]
|
||||
|
||||
- Re-enable test image caching
|
||||
[hugovk, homm]
|
||||
|
||||
- Fix: Cannot identify EPS images, fixes #1104
|
||||
[hugovk]
|
||||
|
||||
- Configure setuptools to run nosetests, fixes #729
|
||||
[aclark4life]
|
||||
|
||||
- Style/health fixes
|
||||
[radarhere, hugovk]
|
||||
|
||||
- Add support for HTTP response objects to Image.open()
|
||||
[mfitzp]
|
||||
|
||||
- Improve reference docs for PIL.ImageDraw.Draw.pieslice() #1145
|
||||
[audreyr]
|
||||
|
||||
- Added copy method font_variant() and accessible properties to truetype() #1123
|
||||
[radarhere]
|
||||
|
||||
- Fix ImagingEffectNoise #1128
|
||||
[hugovk]
|
||||
|
||||
- Remove unreachable code
|
||||
[hugovk]
|
||||
|
||||
- Let Python do the endian stuff + tests #1121
|
||||
[amoibos, radarhere]
|
||||
|
||||
- Fix webp decode memory leak #1114
|
||||
[benoit-pierre]
|
||||
|
||||
- Fast path for opaque pixels in RGBa unpacker #1088
|
||||
[bgilbert]
|
||||
|
||||
- Enable basic support for 'RGBa' raw encoding/decoding #1096
|
||||
[immerrr]
|
||||
|
||||
- Fix pickling L mode images with no palette, #1095
|
||||
[hugovk]
|
||||
|
||||
- iPython display hook #1091
|
||||
[wiredfool]
|
||||
|
||||
- Adjust buffer size when quality=keep, fixes #148 (again)
|
||||
[wiredfool]
|
||||
|
||||
- Fix for corrupted bitmaps embedded in truetype fonts. #1072
|
||||
[jackyyf, wiredfool]
|
||||
|
||||
2.7.0 (2015-01-01)
|
||||
------------------
|
||||
|
||||
- Split Sane into a separate repo: https://github.com/python-pillow/Sane
|
||||
[hugovk]
|
||||
|
||||
- Look for OSX and Linux fonts in common places. #1054
|
||||
[charleslaw]
|
||||
|
||||
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
|
||||
[wiredfool]
|
||||
|
||||
- Use underscores, not spaces, in TIFF tag kwargs. #1044, #1058
|
||||
[anntzer, hugovk]
|
||||
|
||||
- Update PSDraw for Python3, add tests. #1055
|
||||
[hugovk]
|
||||
|
||||
- Use Bicubic filtering by default for thumbnails. Don't use Jpeg Draft mode for thumbnails. #1029
|
||||
[homm]
|
||||
|
||||
- Fix MSVC compiler error: Use Py_ssize_t instead of ssize_t #1051
|
||||
[cgohlke]
|
||||
|
||||
- Fix compiler error: MSVC needs variables defined at the start of the block #1048
|
||||
[cgohlke]
|
||||
|
||||
- The GIF Palette optimization algorithm is only applicable to mode='P' or 'L' #993
|
||||
[moriyoshi]
|
||||
|
||||
- Use PySide as an alternative to PyQt4/5.
|
||||
[holg]
|
||||
|
||||
- Replace affine-based im.resize implementation with convolution-based im.stretch #997
|
||||
[homm]
|
||||
|
||||
- Replace Gaussian Blur implementation with iterated fast box blur. #961 Note: Radius parameter is interpreted differently than before.
|
||||
[homm]
|
||||
|
||||
- Better docs explaining import _imaging failure #1016, build #1017, mode #1018, PyAccess, PixelAccess objects #1019 Image.quantize #1020 and Image.save #1021
|
||||
[wiredfool]
|
||||
|
||||
- Fix for saving TIFF image into an io.BytesIO buffer #1011
|
||||
[mfergie]
|
||||
|
||||
- Fix antialias compilation on debug versions of Python #1010
|
||||
[wiredfool]
|
||||
|
||||
- Fix for Image.putdata segfault #1009
|
||||
[wiredfool]
|
||||
|
||||
- Ico save, additional tests #1007
|
||||
[exherb]
|
||||
|
||||
- Use PyQt4 if it has already been imported, otherwise prefer PyQt5. #1003
|
||||
[AurelienBallier]
|
||||
|
||||
- Speedup resample implementation up to 2.5 times. #977
|
||||
[homm]
|
||||
|
||||
- Speed up rotation by using cache aware loops, added transpose to rotations. #994
|
||||
[homm]
|
||||
|
||||
- Fix Bicubic interpolation #970
|
||||
[homm]
|
||||
|
||||
- Support for 4-bit greyscale TIFF images #980
|
||||
[hugovk]
|
||||
|
||||
- Updated manifest #957
|
||||
[wiredfool]
|
||||
|
||||
- Fix PyPy 2.4 regression #952
|
||||
[wiredfool]
|
||||
[wiredfool]
|
||||
|
||||
- Webp Metadata Skip Test comments #954
|
||||
[wiredfool]
|
||||
|
@ -16,6 +196,15 @@ Changelog (Pillow)
|
|||
- Fixes for things rpmlint complains about #942
|
||||
[manisandro]
|
||||
|
||||
2.6.2 (2015-01-01)
|
||||
------------------
|
||||
|
||||
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
|
||||
[wiredfool]
|
||||
|
||||
- Fix Regression in PyPy 2.4 in streamio #958
|
||||
[wiredfool]
|
||||
|
||||
2.6.1 (2014-10-11)
|
||||
------------------
|
||||
|
||||
|
@ -23,7 +212,7 @@ Changelog (Pillow)
|
|||
[wiredfool]
|
||||
|
||||
- Fix manifest to include all test files.
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
2.6.0 (2014-10-01)
|
||||
------------------
|
||||
|
@ -193,7 +382,7 @@ Changelog (Pillow)
|
|||
[wirefool]
|
||||
|
||||
- Top level flake8 fixes #741
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
- Remove obsolete Animated Raster Graphics (ARG) support
|
||||
[hugovk]
|
||||
|
@ -322,7 +511,7 @@ Changelog (Pillow)
|
|||
[larsmans]
|
||||
|
||||
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
- Merge from Philippe Lagadec’s OleFileIO_PL fork
|
||||
[vadmium]
|
||||
|
@ -737,13 +926,13 @@ Changelog (Pillow)
|
|||
[blueyed]
|
||||
|
||||
- Package cleanup and additional documentation
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.7.4 (2011-07-21)
|
||||
------------------
|
||||
|
||||
- Fix brown bag release
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.7.3 (2011-07-20)
|
||||
------------------
|
||||
|
@ -755,19 +944,19 @@ Changelog (Pillow)
|
|||
------------------
|
||||
|
||||
- Bug fix: Python 2.4 compat
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.7.1 (2011-05-31)
|
||||
------------------
|
||||
|
||||
- More multi-arch support
|
||||
[SteveM, regebro, barry, aclark]
|
||||
[SteveM, regebro, barry, aclark4life]
|
||||
|
||||
1.7.0 (2011-05-27)
|
||||
------------------
|
||||
|
||||
- Add support for multi-arch library directory /usr/lib/x86_64-linux-gnu
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.6 (12/01/2010)
|
||||
----------------
|
||||
|
@ -776,28 +965,28 @@ Changelog (Pillow)
|
|||
[elro]
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.5 (11/28/2010)
|
||||
----------------
|
||||
|
||||
- Module and package fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.4 (11/28/2010)
|
||||
----------------
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.3 (11/28/2010)
|
||||
----------------
|
||||
|
||||
- Add support for /lib64 and /usr/lib64 library directories on Linux
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.2 (08/02/2010)
|
||||
----------------
|
||||
|
@ -806,26 +995,29 @@ Changelog (Pillow)
|
|||
[jezdez]
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.1 (07/31/2010)
|
||||
----------------
|
||||
|
||||
- Removed setuptools_hg requirement
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.0 (07/30/2010)
|
||||
----------------
|
||||
|
||||
- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required.
|
||||
- Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
.. Note:: What follows is the original PIL 1.1.7 CHANGES
|
||||
|
||||
0.2b5 - 1.1.7 (1995-2010)
|
||||
-------------------------
|
||||
|
||||
::
|
||||
|
||||
-*- coding: utf-8 -*-
|
||||
|
@ -1685,7 +1877,7 @@ Changelog (Pillow)
|
|||
(1.1.2c1 and 1.1.2 final released)
|
||||
|
||||
+ Adapted to Python 2.1. Among other things, all uses of the
|
||||
"regex" module has been repleased with "re".
|
||||
"regex" module have been replaced with "re".
|
||||
|
||||
+ Fixed attribute error when reading large PNG files (this bug
|
||||
was introduced in maintenance code released after the 1.1.1
|
||||
|
@ -2309,7 +2501,7 @@ Changelog (Pillow)
|
|||
the default value is 75.
|
||||
|
||||
JPEG smooth smooth dithered images. value
|
||||
is strengh (1-100). default is
|
||||
is strength (1-100). default is
|
||||
off (0).
|
||||
|
||||
PNG optimize minimize output file at the
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
# Contributing
|
||||
# Contributing to Pillow
|
||||
|
||||
## Fixes, Features and Changes
|
||||
Bug fixes, feature additions, tests, documentation and more can be contributed via [issues](https://github.com/python-pillow/Pillow/issues) and/or [pull requests](https://github.com/python-pillow/Pillow/issues). All contributions are welcome.
|
||||
|
||||
Send a pull request. We'll generally want documentation and [tests](Tests/README.rst) for new features. Tests or documentation on their own are also welcomed. Feel free to ask questions as an [issue](https://github.com/python-pillow/Pillow/issues/new) or on IRC (irc.freenode.net, #pil)
|
||||
## Bug fixes, feature additions, etc.
|
||||
|
||||
- Fork the repo
|
||||
- Make a branch
|
||||
- Add your changes + Tests
|
||||
- Run the test suite. Try to run on both Python 2.x and 3.x, or you'll get tripped up. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
|
||||
- Push to your fork, and make a pull request.
|
||||
Please send a pull request to the master branch. Please include [documentation](http://pillow.readthedocs.org) 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
|
||||
|
||||
A few guidelines:
|
||||
- Try to keep any code commits clean and separate from reformatting commits.
|
||||
- All new code is going to need tests.
|
||||
- Try to follow PEP8.
|
||||
- Fork the Pillow repository.
|
||||
- Create a branch from master.
|
||||
- Develop bug fixes, features, tests, etc.
|
||||
- Run the test suite on both Python 2.x and 3.x. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
|
||||
- Create a pull request to pull the changes from your branch to the Pillow master.
|
||||
|
||||
## Bugs
|
||||
### Guidelines
|
||||
|
||||
When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. The best reproductions are self-contained scripts that pull in as few dependencies as possible. An entire Django stack is harder to handle.
|
||||
- Separate code commits from reformatting commits.
|
||||
- Provide tests for any newly added code.
|
||||
- Follow PEP8.
|
||||
|
||||
## 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.
|
||||
|
||||
### Provide details
|
||||
|
||||
Let us know:
|
||||
- What did you do?
|
||||
- What did you expect to happen?
|
||||
- What actually happened?
|
||||
|
|
|
@ -2,21 +2,17 @@ include *.c
|
|||
include *.h
|
||||
include *.md
|
||||
include *.py
|
||||
include *.sh
|
||||
include *.rst
|
||||
include *.txt
|
||||
include *.yaml
|
||||
include .coveragerc
|
||||
include .gitattributes
|
||||
include .travis.yml
|
||||
include LICENSE
|
||||
include Makefile
|
||||
include tox.ini
|
||||
recursive-include PIL *.md
|
||||
recursive-include Sane *.c
|
||||
recursive-include Sane *.py
|
||||
recursive-include Sane *.rst
|
||||
recursive-include Sane *.txt
|
||||
recursive-include Sane CHANGES
|
||||
recursive-include Sane README.rst
|
||||
recursive-include Scripts *.py
|
||||
recursive-include Scripts *.rst
|
||||
recursive-include Scripts *.sh
|
||||
|
@ -64,6 +60,7 @@ recursive-include Tests *.ttf
|
|||
recursive-include Tests *.txt
|
||||
recursive-include Tests *.webp
|
||||
recursive-include Tests *.xpm
|
||||
recursive-include Tests *.msp
|
||||
recursive-include Tk *.c
|
||||
recursive-include Tk *.rst
|
||||
recursive-include depends *.rst
|
||||
|
|
103
Makefile
103
Makefile
|
@ -1,28 +1,5 @@
|
|||
.PHONY: pre clean install test inplace coverage test-dep help docs livedocs
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " clean remove build products"
|
||||
@echo " install make and install"
|
||||
@echo " test run tests on installed pillow"
|
||||
@echo " inplace make inplace extension"
|
||||
@echo " coverage run coverage test (in progress)"
|
||||
@echo " docs make html docs"
|
||||
@echo " docserver run an http server on the docs directory"
|
||||
@echo " test-dep install coveraget and test dependencies"
|
||||
|
||||
pre:
|
||||
virtualenv .
|
||||
bin/pip install -r requirements.txt
|
||||
bin/python setup.py develop
|
||||
bin/python selftest.py
|
||||
bin/nosetests Tests/test_*.py
|
||||
bin/python setup.py install
|
||||
bin/python test-installed.py
|
||||
check-manifest
|
||||
pyroma .
|
||||
viewdoc
|
||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||
.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test
|
||||
|
||||
clean:
|
||||
python setup.py clean
|
||||
|
@ -30,32 +7,70 @@ clean:
|
|||
rm -r build || true
|
||||
find . -name __pycache__ | xargs rm -r || true
|
||||
|
||||
install:
|
||||
python setup.py install
|
||||
python selftest.py --installed
|
||||
|
||||
test:
|
||||
python test-installed.py
|
||||
|
||||
inplace: clean
|
||||
python setup.py build_ext --inplace
|
||||
|
||||
coverage:
|
||||
# requires nose-cov
|
||||
coverage erase
|
||||
coverage run --parallel-mode --include=PIL/* selftest.py
|
||||
nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py
|
||||
# doesn't combine properly before report,
|
||||
# writing report instead of displaying invalid report
|
||||
# Doesn't combine properly before report, writing report instead of displaying invalid report.
|
||||
rm -r htmlcov || true
|
||||
coverage combine
|
||||
coverage report
|
||||
|
||||
test-dep:
|
||||
pip install coveralls nose nose-cov pep8 pyflakes
|
||||
|
||||
docs:
|
||||
doc:
|
||||
$(MAKE) -C docs html
|
||||
|
||||
docserver:
|
||||
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
|
||||
docserve:
|
||||
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
|
||||
|
||||
help:
|
||||
@echo "Welcome to Pillow development. Please use \`make <target>' where <target> is one of"
|
||||
@echo " clean remove build products"
|
||||
@echo " coverage run coverage test (in progress)"
|
||||
@echo " doc make html docs"
|
||||
@echo " docserve run an http server on the docs directory"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " inplace make inplace extension"
|
||||
@echo " install make and install"
|
||||
@echo " install-req install documentation and test dependencies"
|
||||
@echo " release-test run code and package tests before release"
|
||||
@echo " test run tests on installed pillow"
|
||||
@echo " upload build and upload sdists to PyPI"
|
||||
@echo " upload-test build and upload sdists to test.pythonpackages.com"
|
||||
|
||||
inplace: clean
|
||||
python setup.py build_ext --inplace
|
||||
|
||||
install:
|
||||
python setup.py install
|
||||
python selftest.py --installed
|
||||
|
||||
install-req:
|
||||
pip install -r requirements.txt
|
||||
|
||||
release-test:
|
||||
$(MAKE) install-req
|
||||
python setup.py develop
|
||||
python selftest.py
|
||||
nosetests Tests/test_*.py
|
||||
python setup.py install
|
||||
python test-installed.py
|
||||
check-manifest
|
||||
pyroma .
|
||||
viewdoc
|
||||
|
||||
sdist:
|
||||
python setup.py sdist --format=gztar,zip
|
||||
|
||||
test:
|
||||
python test-installed.py
|
||||
|
||||
# https://docs.python.org/2/distutils/packageindex.html#the-pypirc-file
|
||||
upload-test:
|
||||
# [test]
|
||||
# username:
|
||||
# password:
|
||||
# repository = http://test.pythonpackages.com
|
||||
python setup.py sdist --format=gztar,zip upload -r test
|
||||
|
||||
upload:
|
||||
python setup.py sdist --format=gztar,zip upload
|
||||
|
|
|
@ -69,8 +69,8 @@ def bdf_char(f):
|
|||
bitmap.append(s[:-1])
|
||||
bitmap = b"".join(bitmap)
|
||||
|
||||
[x, y, l, d] = [int(s) for s in props["BBX"].split()]
|
||||
[dx, dy] = [int(s) for s in props["DWIDTH"].split()]
|
||||
[x, y, l, d] = [int(p) for p in props["BBX"].split()]
|
||||
[dx, dy] = [int(p) for p in props["DWIDTH"].split()]
|
||||
|
||||
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ __version__ = "0.7"
|
|||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
import math
|
||||
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
@ -48,7 +49,7 @@ BIT2MODE = {
|
|||
8: ("P", "P"),
|
||||
16: ("RGB", "BGR;15"),
|
||||
24: ("RGB", "BGR"),
|
||||
32: ("RGB", "BGRX")
|
||||
32: ("RGB", "BGRX"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -56,131 +57,146 @@ def _accept(prefix):
|
|||
return prefix[:2] == b"BM"
|
||||
|
||||
|
||||
##
|
||||
# ==============================================================================
|
||||
# Image plugin for the Windows BMP format.
|
||||
|
||||
# ==============================================================================
|
||||
class BmpImageFile(ImageFile.ImageFile):
|
||||
""" Image plugin for the Windows Bitmap format (BMP) """
|
||||
|
||||
format = "BMP"
|
||||
# -------------------------------------------------------------- Description
|
||||
format_description = "Windows Bitmap"
|
||||
format = "BMP"
|
||||
# --------------------------------------------------- BMP Compression values
|
||||
COMPRESSIONS = {'RAW': 0, 'RLE8': 1, 'RLE4': 2, 'BITFIELDS': 3, 'JPEG': 4, 'PNG': 5}
|
||||
RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5
|
||||
|
||||
def _bitmap(self, header=0, offset=0):
|
||||
""" Read relevant info about the BMP """
|
||||
read, seek = self.fp.read, self.fp.seek
|
||||
if header:
|
||||
self.fp.seek(header)
|
||||
|
||||
read = self.fp.read
|
||||
|
||||
# CORE/INFO
|
||||
s = read(4)
|
||||
s = s + ImageFile._safe_read(self.fp, i32(s)-4)
|
||||
|
||||
if len(s) == 12:
|
||||
|
||||
# OS/2 1.0 CORE
|
||||
bits = i16(s[10:])
|
||||
self.size = i16(s[4:]), i16(s[6:])
|
||||
compression = 0
|
||||
lutsize = 3
|
||||
colors = 0
|
||||
direction = -1
|
||||
|
||||
elif len(s) in [40, 64, 108, 124]:
|
||||
|
||||
# WIN 3.1 or OS/2 2.0 INFO
|
||||
bits = i16(s[14:])
|
||||
self.size = i32(s[4:]), i32(s[8:])
|
||||
compression = i32(s[16:])
|
||||
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
|
||||
lutsize = 4
|
||||
colors = i32(s[32:])
|
||||
direction = -1
|
||||
if i8(s[11]) == 0xff:
|
||||
# upside-down storage
|
||||
self.size = self.size[0], 2**32 - self.size[1]
|
||||
direction = 0
|
||||
|
||||
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701),
|
||||
pxperm))
|
||||
|
||||
seek(header)
|
||||
file_info = dict()
|
||||
file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size)
|
||||
file_info['direction'] = -1
|
||||
# --------------------- If requested, read header at a specific position
|
||||
header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size
|
||||
# --------------------------------------------------- IBM OS/2 Bitmap v1
|
||||
# ------ This format has different offsets because of width/height types
|
||||
if file_info['header_size'] == 12:
|
||||
file_info['width'] = i16(header_data[0:2])
|
||||
file_info['height'] = i16(header_data[2:4])
|
||||
file_info['planes'] = i16(header_data[4:6])
|
||||
file_info['bits'] = i16(header_data[6:8])
|
||||
file_info['compression'] = self.RAW
|
||||
file_info['palette_padding'] = 3
|
||||
# ---------------------------------------------- Windows Bitmap v2 to v5
|
||||
elif file_info['header_size'] in (40, 64, 108, 124): # v3, OS/2 v2, v4, v5
|
||||
if file_info['header_size'] >= 40: # v3 and OS/2
|
||||
file_info['y_flip'] = i8(header_data[7]) == 0xff
|
||||
file_info['direction'] = 1 if file_info['y_flip'] else -1
|
||||
file_info['width'] = i32(header_data[0:4])
|
||||
file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8])
|
||||
file_info['planes'] = i16(header_data[8:10])
|
||||
file_info['bits'] = i16(header_data[10:12])
|
||||
file_info['compression'] = i32(header_data[12:16])
|
||||
file_info['data_size'] = i32(header_data[16:20]) # byte size of pixel data
|
||||
file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))
|
||||
file_info['colors'] = i32(header_data[28:32])
|
||||
file_info['palette_padding'] = 4
|
||||
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), file_info['pixels_per_meter']))
|
||||
if file_info['compression'] == self.BITFIELDS:
|
||||
if len(header_data) >= 52:
|
||||
for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
|
||||
file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
|
||||
else:
|
||||
for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']:
|
||||
file_info[mask] = i32(read(4))
|
||||
file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'])
|
||||
file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask'])
|
||||
else:
|
||||
raise IOError("Unsupported BMP header type (%d)" % len(s))
|
||||
|
||||
if (self.size[0]*self.size[1]) > 2**31:
|
||||
# Prevent DOS for > 2gb images
|
||||
raise IOError("Unsupported BMP header type (%d)" % file_info['header_size'])
|
||||
# ------------------ Special case : header is reported 40, which
|
||||
# ---------------------- is shorter than real size for bpp >= 16
|
||||
self.size = file_info['width'], file_info['height']
|
||||
# -------- If color count was not found in the header, compute from bits
|
||||
file_info['colors'] = file_info['colors'] if file_info.get('colors', 0) else (1 << file_info['bits'])
|
||||
# -------------------------------- Check abnormal values for DOS attacks
|
||||
if file_info['width'] * file_info['height'] > 2**31:
|
||||
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
|
||||
|
||||
if not colors:
|
||||
colors = 1 << bits
|
||||
|
||||
# MODE
|
||||
try:
|
||||
self.mode, rawmode = BIT2MODE[bits]
|
||||
except KeyError:
|
||||
raise IOError("Unsupported BMP pixel depth (%d)" % bits)
|
||||
|
||||
if compression == 3:
|
||||
# BI_BITFIELDS compression
|
||||
mask = i32(read(4)), i32(read(4)), i32(read(4))
|
||||
if bits == 32 and mask == (0xff0000, 0x00ff00, 0x0000ff):
|
||||
rawmode = "BGRX"
|
||||
elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f):
|
||||
rawmode = "BGR;16"
|
||||
elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f):
|
||||
rawmode = "BGR;15"
|
||||
else:
|
||||
# print bits, map(hex, mask)
|
||||
raise IOError("Unsupported BMP bitfields layout")
|
||||
elif compression != 0:
|
||||
raise IOError("Unsupported BMP compression (%d)" % compression)
|
||||
|
||||
# LUT
|
||||
if self.mode == "P":
|
||||
palette = []
|
||||
greyscale = 1
|
||||
if colors == 2:
|
||||
indices = (0, 255)
|
||||
elif colors > 2**16 or colors <= 0: # We're reading a i32.
|
||||
raise IOError("Unsupported BMP Palette size (%d)" % colors)
|
||||
else:
|
||||
indices = list(range(colors))
|
||||
for i in indices:
|
||||
rgb = read(lutsize)[:3]
|
||||
if rgb != o8(i)*3:
|
||||
greyscale = 0
|
||||
palette.append(rgb)
|
||||
if greyscale:
|
||||
if colors == 2:
|
||||
self.mode = rawmode = "1"
|
||||
# ----------------------- Check bit depth for unusual unsupported values
|
||||
self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None))
|
||||
if self.mode is None:
|
||||
raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits'])
|
||||
# ----------------- Process BMP with Bitfields compression (not palette)
|
||||
if file_info['compression'] == self.BITFIELDS:
|
||||
SUPPORTED = {
|
||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
|
||||
24: [(0xff0000, 0xff00, 0xff)],
|
||||
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]}
|
||||
MASK_MODES = {
|
||||
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
||||
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
||||
(16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"}
|
||||
if file_info['bits'] in SUPPORTED:
|
||||
if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
|
||||
raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])]
|
||||
self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
|
||||
elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]:
|
||||
raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])]
|
||||
else:
|
||||
self.mode = rawmode = "L"
|
||||
raise IOError("Unsupported BMP bitfields layout")
|
||||
else:
|
||||
self.mode = "P"
|
||||
self.palette = ImagePalette.raw(
|
||||
"BGR", b"".join(palette)
|
||||
)
|
||||
raise IOError("Unsupported BMP bitfields layout")
|
||||
elif file_info['compression'] == self.RAW:
|
||||
if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset
|
||||
raw_mode, self.mode = "BGRA", "RGBA"
|
||||
else:
|
||||
raise IOError("Unsupported BMP compression (%d)" % file_info['compression'])
|
||||
# ---------------- Once the header is processed, process the palette/LUT
|
||||
if self.mode == "P": # Paletted for 1, 4 and 8 bit images
|
||||
# ----------------------------------------------------- 1-bit images
|
||||
if not (0 < file_info['colors'] <= 65536):
|
||||
raise IOError("Unsupported BMP Palette size (%d)" % file_info['colors'])
|
||||
else:
|
||||
padding = file_info['palette_padding']
|
||||
palette = read(padding * file_info['colors'])
|
||||
greyscale = True
|
||||
indices = (0, 255) if file_info['colors'] == 2 else list(range(file_info['colors']))
|
||||
# ------------------ Check if greyscale and ignore palette if so
|
||||
for ind, val in enumerate(indices):
|
||||
rgb = palette[ind*padding:ind*padding + 3]
|
||||
if rgb != o8(val) * 3:
|
||||
greyscale = False
|
||||
# -------- If all colors are grey, white or black, ditch palette
|
||||
if greyscale:
|
||||
self.mode = "1" if file_info['colors'] == 2 else "L"
|
||||
raw_mode = self.mode
|
||||
else:
|
||||
self.mode = "P"
|
||||
self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", palette)
|
||||
|
||||
if not offset:
|
||||
offset = self.fp.tell()
|
||||
|
||||
self.tile = [("raw",
|
||||
(0, 0) + self.size,
|
||||
offset,
|
||||
(rawmode, ((self.size[0]*bits+31) >> 3) & (~3),
|
||||
direction))]
|
||||
|
||||
self.info["compression"] = compression
|
||||
# ----------------------------- Finally set the tile data for the plugin
|
||||
self.info['compression'] = file_info['compression']
|
||||
self.tile = [('raw', (0, 0, file_info['width'], file_info['height']), offset or self.fp.tell(),
|
||||
(raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction'])
|
||||
)]
|
||||
|
||||
def _open(self):
|
||||
|
||||
# HEAD
|
||||
s = self.fp.read(14)
|
||||
if s[:2] != b"BM":
|
||||
""" Open file, check magic number and read header """
|
||||
# read 14 bytes: magic number, filesize, reserved, header final offset
|
||||
head_data = self.fp.read(14)
|
||||
# choke if the file does not have the required magic bytes
|
||||
if head_data[0:2] != b"BM":
|
||||
raise SyntaxError("Not a BMP file")
|
||||
offset = i32(s[10:])
|
||||
|
||||
# read the start position of the BMP image data (u32)
|
||||
offset = i32(head_data[10:14])
|
||||
# load bitmap information (offset=raster info)
|
||||
self._bitmap(offset=offset)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Image plugin for the DIB format (BMP alias)
|
||||
# ==============================================================================
|
||||
class DibImageFile(BmpImageFile):
|
||||
|
||||
format = "DIB"
|
||||
|
@ -198,6 +214,7 @@ SAVE = {
|
|||
"L": ("L", 8, 256),
|
||||
"P": ("P", 8, 256),
|
||||
"RGB": ("BGR", 24, 0),
|
||||
"RGBA": ("BGRA", 32, 0),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
# file (for example a TAR file).
|
||||
|
||||
|
||||
class ContainerIO:
|
||||
class ContainerIO(object):
|
||||
|
||||
##
|
||||
# Create file object.
|
||||
|
|
|
@ -62,6 +62,10 @@ class DcxImageFile(PcxImageFile):
|
|||
self.__fp = self.fp
|
||||
self.seek(0)
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return len(self._offset)
|
||||
|
||||
def seek(self, frame):
|
||||
if frame >= len(self._offset):
|
||||
raise EOFError("attempt to seek outside DCX directory")
|
||||
|
|
|
@ -157,7 +157,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
return im
|
||||
|
||||
|
||||
class PSFile:
|
||||
class PSFile(object):
|
||||
"""
|
||||
Wrapper for bytesio object that treats either CR or LF as end of line.
|
||||
"""
|
||||
|
@ -248,7 +248,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
# Note: The DSC spec says that BoundingBox
|
||||
# fields should be integers, but some drivers
|
||||
# put floating point values there anyway.
|
||||
box = [int(float(s)) for s in v.split()]
|
||||
box = [int(float(i)) for i in v.split()]
|
||||
self.size = box[2] - box[0], box[3] - box[1]
|
||||
self.tile = [("eps", (0, 0) + self.size, offset,
|
||||
(length, box))]
|
||||
|
@ -275,20 +275,20 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
|
||||
s = fp.readline().strip('\r\n')
|
||||
|
||||
if s[0] != "%":
|
||||
if s[:1] != "%":
|
||||
break
|
||||
|
||||
#
|
||||
# Scan for an "ImageData" descriptor
|
||||
|
||||
while s[0] == "%":
|
||||
while s[:1] == "%":
|
||||
|
||||
if len(s) > 255:
|
||||
raise SyntaxError("not an EPS file")
|
||||
|
||||
if s[:11] == "%ImageData:":
|
||||
# Encoded bitmapped image.
|
||||
[x, y, bi, mo, z3, z4, en, id] = s[11:].split(None, 7)
|
||||
x, y, bi, mo = s[11:].split(None, 7)[:4]
|
||||
|
||||
if int(bi) != 8:
|
||||
break
|
||||
|
@ -365,7 +365,7 @@ def _save(im, fp, filename, eps=1):
|
|||
else:
|
||||
raise ValueError("image mode is not supported")
|
||||
|
||||
class NoCloseStream:
|
||||
class NoCloseStream(object):
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ _handler = None
|
|||
#
|
||||
# @param handler Handler object.
|
||||
|
||||
|
||||
def register_handler(handler):
|
||||
global _handler
|
||||
_handler = handler
|
||||
|
@ -25,9 +26,11 @@ def register_handler(handler):
|
|||
# --------------------------------------------------------------------
|
||||
# Image adapter
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:6] == b"SIMPLE"
|
||||
|
||||
|
||||
class FITSStubImageFile(ImageFile.StubImageFile):
|
||||
|
||||
format = "FITS"
|
||||
|
|
|
@ -86,9 +86,10 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
||||
|
||||
# set things up to decode first frame
|
||||
self.frame = -1
|
||||
self.__frame = -1
|
||||
self.__fp = self.fp
|
||||
|
||||
self.__rewind = self.fp.tell()
|
||||
self._n_frames = None
|
||||
self.seek(0)
|
||||
|
||||
def _palette(self, palette, shift):
|
||||
|
@ -109,11 +110,35 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
palette[i] = (r, g, b)
|
||||
i += 1
|
||||
|
||||
def seek(self, frame):
|
||||
@property
|
||||
def n_frames(self):
|
||||
if self._n_frames is None:
|
||||
current = self.tell()
|
||||
try:
|
||||
while True:
|
||||
self.seek(self.tell() + 1)
|
||||
except EOFError:
|
||||
self._n_frames = self.tell() + 1
|
||||
self.seek(current)
|
||||
return self._n_frames
|
||||
|
||||
if frame != self.frame + 1:
|
||||
def seek(self, frame):
|
||||
if frame == self.__frame:
|
||||
return
|
||||
if frame < self.__frame:
|
||||
self._seek(0)
|
||||
for f in range(self.__frame + 1, frame + 1):
|
||||
self._seek(f)
|
||||
|
||||
def _seek(self, frame):
|
||||
if frame == 0:
|
||||
self.__frame = -1
|
||||
self.__fp.seek(self.__rewind)
|
||||
self.__offset = 128
|
||||
|
||||
if frame != self.__frame + 1:
|
||||
raise ValueError("cannot seek to frame %d" % frame)
|
||||
self.frame = frame
|
||||
self.__frame = frame
|
||||
|
||||
# move to next frame
|
||||
self.fp = self.__fp
|
||||
|
@ -128,11 +153,10 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
self.decodermaxblock = framesize
|
||||
self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
|
||||
|
||||
self.__offset = self.__offset + framesize
|
||||
self.__offset += framesize
|
||||
|
||||
def tell(self):
|
||||
|
||||
return self.frame
|
||||
return self.__frame
|
||||
|
||||
#
|
||||
# registry
|
||||
|
|
|
@ -17,11 +17,6 @@
|
|||
import os
|
||||
from PIL import Image, _binary
|
||||
|
||||
try:
|
||||
import zlib
|
||||
except ImportError:
|
||||
zlib = None
|
||||
|
||||
WIDTH = 800
|
||||
|
||||
|
||||
|
@ -36,7 +31,7 @@ def puti16(fp, values):
|
|||
##
|
||||
# Base class for raster font file handlers.
|
||||
|
||||
class FontFile:
|
||||
class FontFile(object):
|
||||
|
||||
bitmap = None
|
||||
|
||||
|
@ -83,7 +78,8 @@ class FontFile:
|
|||
glyph = self[i]
|
||||
if glyph:
|
||||
d, dst, src, im = glyph
|
||||
xx, yy = src[2] - src[0], src[3] - src[1]
|
||||
xx = src[2] - src[0]
|
||||
# yy = src[3] - src[1]
|
||||
x0, y0 = x, y
|
||||
x = x + xx
|
||||
if x > WIDTH:
|
||||
|
|
|
@ -20,7 +20,7 @@ __version__ = "0.1"
|
|||
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL.OleFileIO import *
|
||||
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
|
||||
|
||||
|
||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||
|
@ -130,15 +130,15 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
fp = self.ole.openstream(stream)
|
||||
|
||||
# skip prefix
|
||||
p = fp.read(28)
|
||||
fp.read(28)
|
||||
|
||||
# header stream
|
||||
s = fp.read(36)
|
||||
|
||||
size = i32(s, 4), i32(s, 8)
|
||||
tilecount = i32(s, 12)
|
||||
# tilecount = i32(s, 12)
|
||||
tilesize = i32(s, 16), i32(s, 20)
|
||||
channels = i32(s, 24)
|
||||
# channels = i32(s, 24)
|
||||
offset = i32(s, 28)
|
||||
length = i32(s, 32)
|
||||
|
||||
|
|
|
@ -39,8 +39,8 @@ class GbrImageFile(ImageFile.ImageFile):
|
|||
|
||||
width = i32(self.fp.read(4))
|
||||
height = i32(self.fp.read(4))
|
||||
bytes = i32(self.fp.read(4))
|
||||
if width <= 0 or height <= 0 or bytes != 1:
|
||||
color_depth = i32(self.fp.read(4))
|
||||
if width <= 0 or height <= 0 or color_depth != 1:
|
||||
raise SyntaxError("not a GIMP brush")
|
||||
|
||||
comment = self.fp.read(header_size - 20)[:-1]
|
||||
|
|
|
@ -24,13 +24,11 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
__version__ = "0.9"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
|
@ -89,9 +87,30 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
|
||||
self.__fp = self.fp # FIXME: hack
|
||||
self.__rewind = self.fp.tell()
|
||||
self.seek(0) # get ready to read first frame
|
||||
self._n_frames = None
|
||||
self._seek(0) # get ready to read first frame
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
if self._n_frames is None:
|
||||
current = self.tell()
|
||||
try:
|
||||
while True:
|
||||
self.seek(self.tell() + 1)
|
||||
except EOFError:
|
||||
self._n_frames = self.tell() + 1
|
||||
self.seek(current)
|
||||
return self._n_frames
|
||||
|
||||
def seek(self, frame):
|
||||
if frame == self.__frame:
|
||||
return
|
||||
if frame < self.__frame:
|
||||
self._seek(0)
|
||||
for f in range(self.__frame + 1, frame + 1):
|
||||
self._seek(f)
|
||||
|
||||
def _seek(self, frame):
|
||||
|
||||
if frame == 0:
|
||||
# rewind
|
||||
|
@ -271,7 +290,7 @@ def _save(im, fp, filename):
|
|||
pass # write uncompressed file
|
||||
|
||||
if im.mode in RAWMODE:
|
||||
imOut = im
|
||||
im_out = im
|
||||
else:
|
||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
||||
# should automatically convert images on save...)
|
||||
|
@ -279,9 +298,9 @@ def _save(im, fp, filename):
|
|||
palette_size = 256
|
||||
if im.palette:
|
||||
palette_size = len(im.palette.getdata()[1]) // 3
|
||||
imOut = im.convert("P", palette=1, colors=palette_size)
|
||||
im_out = im.convert("P", palette=1, colors=palette_size)
|
||||
else:
|
||||
imOut = im.convert("L")
|
||||
im_out = im.convert("L")
|
||||
|
||||
# header
|
||||
try:
|
||||
|
@ -290,63 +309,16 @@ def _save(im, fp, filename):
|
|||
palette = None
|
||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||
|
||||
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
|
||||
header, used_palette_colors = getheader(im_out, palette, im.encoderinfo)
|
||||
for s in header:
|
||||
fp.write(s)
|
||||
|
||||
flags = 0
|
||||
|
||||
try:
|
||||
interlace = im.encoderinfo["interlace"]
|
||||
except KeyError:
|
||||
interlace = 1
|
||||
|
||||
# workaround for @PIL153
|
||||
if min(im.size) < 16:
|
||||
interlace = 0
|
||||
|
||||
if interlace:
|
||||
flags = flags | 64
|
||||
|
||||
try:
|
||||
transparency = im.encoderinfo["transparency"]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
transparency = int(transparency)
|
||||
# optimize the block away if transparent color is not used
|
||||
transparentColorExists = True
|
||||
# adjust the transparency index after optimize
|
||||
if usedPaletteColors is not None and len(usedPaletteColors) < 256:
|
||||
for i in range(len(usedPaletteColors)):
|
||||
if usedPaletteColors[i] == transparency:
|
||||
transparency = i
|
||||
transparentColorExists = True
|
||||
break
|
||||
else:
|
||||
transparentColorExists = False
|
||||
|
||||
# transparency extension block
|
||||
if transparentColorExists:
|
||||
fp.write(b"!" +
|
||||
o8(249) + # extension intro
|
||||
o8(4) + # length
|
||||
o8(1) + # transparency info present
|
||||
o16(0) + # duration
|
||||
o8(transparency) # transparency index
|
||||
+ o8(0))
|
||||
|
||||
# local image header
|
||||
fp.write(b"," +
|
||||
o16(0) + o16(0) + # bounding box
|
||||
o16(im.size[0]) + # size
|
||||
o16(im.size[1]) +
|
||||
o8(flags) + # flags
|
||||
o8(8)) # bits
|
||||
get_local_header(fp, im)
|
||||
|
||||
imOut.encoderconfig = (8, interlace)
|
||||
ImageFile._save(imOut, fp, [("gif", (0, 0)+im.size, 0,
|
||||
RAWMODE[imOut.mode])])
|
||||
im_out.encoderconfig = (8, get_interlace(im))
|
||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||
RAWMODE[im_out.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
|
||||
|
@ -358,6 +330,85 @@ def _save(im, fp, filename):
|
|||
pass
|
||||
|
||||
|
||||
def get_interlace(im):
|
||||
try:
|
||||
interlace = im.encoderinfo["interlace"]
|
||||
except KeyError:
|
||||
interlace = 1
|
||||
|
||||
# workaround for @PIL153
|
||||
if min(im.size) < 16:
|
||||
interlace = 0
|
||||
|
||||
return interlace
|
||||
|
||||
|
||||
def get_local_header(fp, im, offset=(0, 0)):
|
||||
transparent_color_exists = False
|
||||
try:
|
||||
transparency = im.encoderinfo["transparency"]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
transparency = int(transparency)
|
||||
# optimize the block away if transparent color is not used
|
||||
transparent_color_exists = True
|
||||
|
||||
if _get_optimize(im, im.encoderinfo):
|
||||
used_palette_colors = _get_used_palette_colors(im)
|
||||
|
||||
# adjust the transparency index after optimize
|
||||
if len(used_palette_colors) < 256:
|
||||
for i in range(len(used_palette_colors)):
|
||||
if used_palette_colors[i] == transparency:
|
||||
transparency = i
|
||||
transparent_color_exists = True
|
||||
break
|
||||
else:
|
||||
transparent_color_exists = False
|
||||
|
||||
if "duration" in im.encoderinfo:
|
||||
duration = int(im.encoderinfo["duration"] / 10)
|
||||
else:
|
||||
duration = 0
|
||||
if transparent_color_exists or duration != 0:
|
||||
transparency_flag = 1 if transparent_color_exists else 0
|
||||
if not transparent_color_exists:
|
||||
transparency = 0
|
||||
|
||||
fp.write(b"!" +
|
||||
o8(249) + # extension intro
|
||||
o8(4) + # length
|
||||
o8(transparency_flag) + # transparency info present
|
||||
o16(duration) + # duration
|
||||
o8(transparency) + # transparency index
|
||||
o8(0))
|
||||
|
||||
if "loop" in im.encoderinfo:
|
||||
number_of_loops = im.encoderinfo["loop"]
|
||||
fp.write(b"!" +
|
||||
o8(255) + # extension intro
|
||||
o8(11) +
|
||||
b"NETSCAPE2.0" +
|
||||
o8(3) +
|
||||
o8(1) +
|
||||
o16(number_of_loops) + # number of loops
|
||||
o8(0))
|
||||
|
||||
flags = 0
|
||||
|
||||
if get_interlace(im):
|
||||
flags = flags | 64
|
||||
|
||||
fp.write(b"," +
|
||||
o16(offset[0]) + # offset
|
||||
o16(offset[1]) +
|
||||
o16(im.size[0]) + # size
|
||||
o16(im.size[1]) +
|
||||
o8(flags) + # flags
|
||||
o8(8)) # bits
|
||||
|
||||
|
||||
def _save_netpbm(im, fp, filename):
|
||||
|
||||
#
|
||||
|
@ -407,11 +458,26 @@ def _save_netpbm(im, fp, filename):
|
|||
# --------------------------------------------------------------------
|
||||
# GIF utilities
|
||||
|
||||
def _get_optimize(im, info):
|
||||
return im.mode in ("P", "L") and info and info.get("optimize", 0)
|
||||
|
||||
|
||||
def _get_used_palette_colors(im):
|
||||
used_palette_colors = []
|
||||
|
||||
# check which colors are used
|
||||
i = 0
|
||||
for count in im.histogram():
|
||||
if count:
|
||||
used_palette_colors.append(i)
|
||||
i += 1
|
||||
|
||||
return used_palette_colors
|
||||
|
||||
|
||||
def getheader(im, palette=None, info=None):
|
||||
"""Return a list of strings representing a GIF header"""
|
||||
|
||||
optimize = info and info.get("optimize", 0)
|
||||
|
||||
# Header Block
|
||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||
header = [
|
||||
|
@ -422,74 +488,68 @@ def getheader(im, palette=None, info=None):
|
|||
|
||||
if im.mode == "P":
|
||||
if palette and isinstance(palette, bytes):
|
||||
sourcePalette = palette[:768]
|
||||
source_palette = palette[:768]
|
||||
else:
|
||||
sourcePalette = im.im.getpalette("RGB")[:768]
|
||||
source_palette = im.im.getpalette("RGB")[:768]
|
||||
else: # L-mode
|
||||
if palette and isinstance(palette, bytes):
|
||||
sourcePalette = palette[:768]
|
||||
source_palette = palette[:768]
|
||||
else:
|
||||
sourcePalette = bytearray([i//3 for i in range(768)])
|
||||
source_palette = bytearray([i//3 for i in range(768)])
|
||||
|
||||
usedPaletteColors = paletteBytes = None
|
||||
used_palette_colors = palette_bytes = None
|
||||
|
||||
if optimize:
|
||||
usedPaletteColors = []
|
||||
|
||||
# check which colors are used
|
||||
i = 0
|
||||
for count in im.histogram():
|
||||
if count:
|
||||
usedPaletteColors.append(i)
|
||||
i += 1
|
||||
if _get_optimize(im, info):
|
||||
used_palette_colors = _get_used_palette_colors(im)
|
||||
|
||||
# create the new palette if not every color is used
|
||||
if len(usedPaletteColors) < 256:
|
||||
paletteBytes = b""
|
||||
newPositions = {}
|
||||
if len(used_palette_colors) < 256:
|
||||
palette_bytes = b""
|
||||
new_positions = {}
|
||||
|
||||
i = 0
|
||||
# pick only the used colors from the palette
|
||||
for oldPosition in usedPaletteColors:
|
||||
paletteBytes += sourcePalette[oldPosition*3:oldPosition*3+3]
|
||||
newPositions[oldPosition] = i
|
||||
for oldPosition in used_palette_colors:
|
||||
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
|
||||
new_positions[oldPosition] = i
|
||||
i += 1
|
||||
|
||||
# replace the palette color id of all pixel with the new id
|
||||
imageBytes = bytearray(im.tobytes())
|
||||
for i in range(len(imageBytes)):
|
||||
imageBytes[i] = newPositions[imageBytes[i]]
|
||||
im.frombytes(bytes(imageBytes))
|
||||
newPaletteBytes = (paletteBytes +
|
||||
(768 - len(paletteBytes)) * b'\x00')
|
||||
im.putpalette(newPaletteBytes)
|
||||
im.palette = ImagePalette.ImagePalette("RGB", palette=paletteBytes,
|
||||
size=len(paletteBytes))
|
||||
image_bytes = bytearray(im.tobytes())
|
||||
for i in range(len(image_bytes)):
|
||||
image_bytes[i] = new_positions[image_bytes[i]]
|
||||
im.frombytes(bytes(image_bytes))
|
||||
new_palette_bytes = (palette_bytes +
|
||||
(768 - len(palette_bytes)) * b'\x00')
|
||||
im.putpalette(new_palette_bytes)
|
||||
im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=palette_bytes,
|
||||
size=len(palette_bytes))
|
||||
|
||||
if not paletteBytes:
|
||||
paletteBytes = sourcePalette
|
||||
if not palette_bytes:
|
||||
palette_bytes = source_palette
|
||||
|
||||
# Logical Screen Descriptor
|
||||
# calculate the palette size for the header
|
||||
import math
|
||||
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1
|
||||
if colorTableSize < 0:
|
||||
colorTableSize = 0
|
||||
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
|
||||
if color_table_size < 0:
|
||||
color_table_size = 0
|
||||
# size of global color table + global color table flag
|
||||
header.append(o8(colorTableSize + 128))
|
||||
header.append(o8(color_table_size + 128))
|
||||
# background + reserved/aspect
|
||||
header.append(o8(0) + o8(0))
|
||||
# end of Logical Screen Descriptor
|
||||
|
||||
# add the missing amount of bytes
|
||||
# the palette has to be 2<<n in size
|
||||
actualTargetSizeDiff = (2 << colorTableSize) - len(paletteBytes)//3
|
||||
if actualTargetSizeDiff > 0:
|
||||
paletteBytes += o8(0) * 3 * actualTargetSizeDiff
|
||||
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
|
||||
if actual_target_size_diff > 0:
|
||||
palette_bytes += o8(0) * 3 * actual_target_size_diff
|
||||
|
||||
# Header + Logical Screen Descriptor + Global Color Table
|
||||
header.append(paletteBytes)
|
||||
return header, usedPaletteColors
|
||||
header.append(palette_bytes)
|
||||
return header, used_palette_colors
|
||||
|
||||
|
||||
def getdata(im, offset=(0, 0), **params):
|
||||
|
@ -497,7 +557,7 @@ def getdata(im, offset=(0, 0), **params):
|
|||
The first string is a local image header, the rest contains
|
||||
encoded image data."""
|
||||
|
||||
class collector:
|
||||
class Collector(object):
|
||||
data = []
|
||||
|
||||
def write(self, data):
|
||||
|
@ -505,19 +565,13 @@ def getdata(im, offset=(0, 0), **params):
|
|||
|
||||
im.load() # make sure raster data is available
|
||||
|
||||
fp = collector()
|
||||
fp = Collector()
|
||||
|
||||
try:
|
||||
im.encoderinfo = params
|
||||
|
||||
# local image header
|
||||
fp.write(b"," +
|
||||
o16(offset[0]) + # offset
|
||||
o16(offset[1]) +
|
||||
o16(im.size[0]) + # size
|
||||
o16(im.size[1]) +
|
||||
o8(0) + # flags
|
||||
o8(8)) # bits
|
||||
get_local_header(fp, im, offset)
|
||||
|
||||
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ def sphere_decreasing(middle, pos):
|
|||
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
|
||||
|
||||
|
||||
class GradientFile:
|
||||
class GradientFile(object):
|
||||
|
||||
gradient = None
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ from PIL._binary import o8
|
|||
##
|
||||
# File handler for GIMP's palette format.
|
||||
|
||||
class GimpPaletteFile:
|
||||
class GimpPaletteFile(object):
|
||||
|
||||
rawmode = "RGB"
|
||||
|
||||
|
|
|
@ -17,7 +17,11 @@
|
|||
|
||||
from PIL import Image, ImageFile, PngImagePlugin, _binary
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
|
||||
if enable_jpeg2k:
|
||||
|
@ -90,7 +94,7 @@ def read_32(fobj, start_length, size):
|
|||
|
||||
def read_mk(fobj, start_length, size):
|
||||
# Alpha masks seem to be uncompressed
|
||||
(start, length) = start_length
|
||||
start = start_length[0]
|
||||
fobj.seek(start)
|
||||
pixel_size = (size[0] * size[2], size[1] * size[2])
|
||||
sizesq = pixel_size[0] * pixel_size[1]
|
||||
|
@ -126,7 +130,7 @@ def read_png_or_jpeg2000(fobj, start_length, size):
|
|||
raise ValueError('Unsupported icon subimage format')
|
||||
|
||||
|
||||
class IcnsFile:
|
||||
class IcnsFile(object):
|
||||
|
||||
SIZES = {
|
||||
(512, 512, 2): [
|
||||
|
@ -247,7 +251,7 @@ class IcnsFile:
|
|||
|
||||
class IcnsImageFile(ImageFile.ImageFile):
|
||||
"""
|
||||
PIL read-only image support for Mac OS .icns files.
|
||||
PIL image support for Mac OS .icns files.
|
||||
Chooses the best resolution, but will possibly load
|
||||
a different size image if you mutate the size attribute
|
||||
before calling 'load'.
|
||||
|
@ -293,12 +297,64 @@ class IcnsImageFile(ImageFile.ImageFile):
|
|||
self.tile = ()
|
||||
self.load_end()
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
"""
|
||||
Saves the image as a series of PNG files,
|
||||
that are then converted to a .icns file
|
||||
using the OS X command line utility 'iconutil'.
|
||||
|
||||
OS X only.
|
||||
"""
|
||||
try:
|
||||
fp.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
# create the temporary set of pngs
|
||||
iconset = tempfile.mkdtemp('.iconset')
|
||||
last_w = None
|
||||
last_im = None
|
||||
for w in [16, 32, 128, 256, 512]:
|
||||
prefix = 'icon_{}x{}'.format(w, w)
|
||||
|
||||
if last_w == w:
|
||||
im_scaled = last_im
|
||||
else:
|
||||
im_scaled = im.resize((w, w), Image.LANCZOS)
|
||||
im_scaled.save(os.path.join(iconset, prefix+'.png'))
|
||||
|
||||
im_scaled = im.resize((w*2, w*2), Image.LANCZOS)
|
||||
im_scaled.save(os.path.join(iconset, prefix+'@2x.png'))
|
||||
last_im = im_scaled
|
||||
|
||||
# iconutil -c icns -o {} {}
|
||||
from subprocess import Popen, PIPE, CalledProcessError
|
||||
|
||||
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
|
||||
stderr = tempfile.TemporaryFile()
|
||||
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr)
|
||||
|
||||
convert_proc.stdout.close()
|
||||
|
||||
retcode = convert_proc.wait()
|
||||
|
||||
# remove the temporary files
|
||||
shutil.rmtree(iconset)
|
||||
|
||||
if retcode:
|
||||
raise CalledProcessError(retcode, convert_cmd)
|
||||
|
||||
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
|
||||
Image.register_extension("ICNS", '.icns')
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
Image.register_save("ICNS", _save)
|
||||
|
||||
Image.register_mime("ICNS", "image/icns")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
import sys
|
||||
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
|
||||
for size in imf.info['sizes']:
|
||||
imf.size = size
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
|
||||
__version__ = "0.1"
|
||||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
||||
from math import log, ceil
|
||||
|
||||
|
@ -37,11 +40,46 @@ i32 = _binary.i32le
|
|||
_MAGIC = b"\0\0\1\0"
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
fp.write(_MAGIC) # (2+2)
|
||||
sizes = im.encoderinfo.get("sizes",
|
||||
[(16, 16), (24, 24), (32, 32), (48, 48),
|
||||
(64, 64), (128, 128), (255, 255)])
|
||||
width, height = im.size
|
||||
filter(lambda x: False if (x[0] > width or x[1] > height or
|
||||
x[0] > 255 or x[1] > 255) else True, sizes)
|
||||
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
|
||||
offset = fp.tell() + len(sizes)*16
|
||||
for size in sizes:
|
||||
width, height = size
|
||||
fp.write(struct.pack("B", width)) # bWidth(1)
|
||||
fp.write(struct.pack("B", height)) # bHeight(1)
|
||||
fp.write(b"\0") # bColorCount(1)
|
||||
fp.write(b"\0") # bReserved(1)
|
||||
fp.write(b"\0\0") # wPlanes(2)
|
||||
fp.write(struct.pack("<H", 32)) # wBitCount(2)
|
||||
|
||||
image_io = BytesIO()
|
||||
tmp = im.copy()
|
||||
tmp.thumbnail(size, Image.LANCZOS)
|
||||
tmp.save(image_io, "png")
|
||||
image_io.seek(0)
|
||||
image_bytes = image_io.read()
|
||||
bytes_len = len(image_bytes)
|
||||
fp.write(struct.pack("<I", bytes_len)) # dwBytesInRes(4)
|
||||
fp.write(struct.pack("<I", offset)) # dwImageOffset(4)
|
||||
current = fp.tell()
|
||||
fp.seek(offset)
|
||||
fp.write(image_bytes)
|
||||
offset = offset + bytes_len
|
||||
fp.seek(current)
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == _MAGIC
|
||||
|
||||
|
||||
class IcoFile:
|
||||
class IcoFile(object):
|
||||
def __init__(self, buf):
|
||||
"""
|
||||
Parse image from file-like object containing ico file data
|
||||
|
@ -234,11 +272,12 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
self.size = im.size
|
||||
|
||||
def load_seek(self):
|
||||
# Flage the ImageFile.Parser so that it
|
||||
# Flag the ImageFile.Parser so that it
|
||||
# just does all the decode at the end.
|
||||
pass
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("ICO", IcoImageFile, _accept)
|
||||
Image.register_save("ICO", _save)
|
||||
Image.register_extension("ICO", ".ico")
|
||||
|
|
|
@ -260,6 +260,10 @@ class ImImageFile(ImageFile.ImageFile):
|
|||
self.tile = [("raw", (0, 0)+self.size, offs,
|
||||
(self.rawmode, 0, -1))]
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return self.info[FRAMES]
|
||||
|
||||
def seek(self, frame):
|
||||
|
||||
if frame < 0 or frame >= self.info[FRAMES]:
|
||||
|
@ -313,7 +317,7 @@ SAVE = {
|
|||
def _save(im, fp, filename, check=0):
|
||||
|
||||
try:
|
||||
type, rawmode = SAVE[im.mode]
|
||||
image_type, rawmode = SAVE[im.mode]
|
||||
except KeyError:
|
||||
raise ValueError("Cannot save %s images as IM" % im.mode)
|
||||
|
||||
|
@ -325,7 +329,7 @@ def _save(im, fp, filename, check=0):
|
|||
if check:
|
||||
return check
|
||||
|
||||
fp.write(("Image type: %s image\r\n" % type).encode('ascii'))
|
||||
fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
|
||||
if filename:
|
||||
fp.write(("Name: %s\r\n" % filename).encode('ascii'))
|
||||
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
|
||||
|
|
169
PIL/Image.py
169
PIL/Image.py
|
@ -35,7 +35,7 @@ class DecompressionBombWarning(RuntimeWarning):
|
|||
pass
|
||||
|
||||
|
||||
class _imaging_not_installed:
|
||||
class _imaging_not_installed(object):
|
||||
# module placeholder
|
||||
def __getattr__(self, id):
|
||||
raise ImportError("The _imaging C module is not installed")
|
||||
|
@ -55,10 +55,11 @@ except ImportError:
|
|||
pass
|
||||
|
||||
try:
|
||||
# If the _imaging C module is not present, you can still use
|
||||
# the "open" function to identify files, but you cannot load
|
||||
# them. Note that other modules should not refer to _imaging
|
||||
# directly; import Image and use the Image.core variable instead.
|
||||
# If the _imaging C module is not present, Pillow will not load.
|
||||
# Note that other modules should not refer to _imaging directly;
|
||||
# import Image and use the Image.core variable instead.
|
||||
# Also note that Image.core is not a publicly documented interface,
|
||||
# and should be considered private and subject to change.
|
||||
from PIL import _imaging as core
|
||||
if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
|
||||
raise ImportError("The _imaging extension was built for another "
|
||||
|
@ -91,6 +92,7 @@ except ImportError as v:
|
|||
RuntimeWarning
|
||||
)
|
||||
# Fail here anyway. Don't let people run with a mostly broken Pillow.
|
||||
# see docs/porting-pil-to-pillow.rst
|
||||
raise
|
||||
|
||||
try:
|
||||
|
@ -107,6 +109,8 @@ from PIL._util import deferred_error
|
|||
|
||||
import os
|
||||
import sys
|
||||
import io
|
||||
import struct
|
||||
|
||||
# type stuff
|
||||
import collections
|
||||
|
@ -150,6 +154,7 @@ FLIP_TOP_BOTTOM = 1
|
|||
ROTATE_90 = 2
|
||||
ROTATE_180 = 3
|
||||
ROTATE_270 = 4
|
||||
TRANSPOSE = 5
|
||||
|
||||
# transforms
|
||||
AFFINE = 0
|
||||
|
@ -159,11 +164,10 @@ QUAD = 3
|
|||
MESH = 4
|
||||
|
||||
# resampling filters
|
||||
NONE = 0
|
||||
NEAREST = 0
|
||||
ANTIALIAS = 1 # 3-lobed lanczos
|
||||
LINEAR = BILINEAR = 2
|
||||
CUBIC = BICUBIC = 3
|
||||
NEAREST = NONE = 0
|
||||
LANCZOS = ANTIALIAS = 1
|
||||
BILINEAR = LINEAR = 2
|
||||
BICUBIC = CUBIC = 3
|
||||
|
||||
# dithers
|
||||
NONE = 0
|
||||
|
@ -383,7 +387,7 @@ def init():
|
|||
for plugin in _plugins:
|
||||
try:
|
||||
if DEBUG:
|
||||
print ("Importing %s" % plugin)
|
||||
print("Importing %s" % plugin)
|
||||
__import__("PIL.%s" % plugin, globals(), locals(), [])
|
||||
except ImportError:
|
||||
if DEBUG:
|
||||
|
@ -439,7 +443,7 @@ def coerce_e(value):
|
|||
return value if isinstance(value, _E) else _E(value)
|
||||
|
||||
|
||||
class _E:
|
||||
class _E(object):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
|
@ -474,7 +478,7 @@ def _getscaleoffset(expr):
|
|||
# --------------------------------------------------------------------
|
||||
# Implementation wrapper
|
||||
|
||||
class Image:
|
||||
class Image(object):
|
||||
"""
|
||||
This class represents an image object. To create
|
||||
:py:class:`~PIL.Image.Image` objects, use the appropriate factory
|
||||
|
@ -542,7 +546,7 @@ class Image:
|
|||
self.fp.close()
|
||||
except Exception as msg:
|
||||
if DEBUG:
|
||||
print ("Error closing: %s" % msg)
|
||||
print("Error closing: %s" % msg)
|
||||
|
||||
# Instead of simply setting to None, we're setting up a
|
||||
# deferred error that will better explain that the core image
|
||||
|
@ -596,6 +600,16 @@ class Image:
|
|||
id(self)
|
||||
)
|
||||
|
||||
def _repr_png_(self):
|
||||
""" iPython display hook support
|
||||
|
||||
:returns: png version of the image as bytes
|
||||
"""
|
||||
from io import BytesIO
|
||||
b = BytesIO()
|
||||
self.save(b, 'PNG')
|
||||
return b.getvalue()
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == "__array_interface__":
|
||||
# numpy array interface support
|
||||
|
@ -623,7 +637,7 @@ class Image:
|
|||
self.mode = mode
|
||||
self.size = size
|
||||
self.im = core.new(mode, size)
|
||||
if mode in ("L", "P"):
|
||||
if mode in ("L", "P") and palette:
|
||||
self.putpalette(palette)
|
||||
self.frombytes(data)
|
||||
|
||||
|
@ -742,6 +756,7 @@ class Image:
|
|||
associated with the image.
|
||||
|
||||
:returns: An image access object.
|
||||
:rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
|
||||
"""
|
||||
if self.im and self.palette and self.palette.dirty:
|
||||
# realize palette
|
||||
|
@ -801,7 +816,7 @@ class Image:
|
|||
use other thresholds, use the :py:meth:`~PIL.Image.Image.point`
|
||||
method.
|
||||
|
||||
:param mode: The requested mode.
|
||||
:param mode: The requested mode. See: :ref:`concept-modes`.
|
||||
:param matrix: An optional conversion matrix. If given, this
|
||||
should be 4- or 16-tuple containing floating point values.
|
||||
:param dither: Dithering method, used when converting from
|
||||
|
@ -875,16 +890,15 @@ class Image:
|
|||
elif self.mode == 'P' and mode == 'RGBA':
|
||||
t = self.info['transparency']
|
||||
delete_trns = True
|
||||
|
||||
|
||||
if isinstance(t, bytes):
|
||||
self.im.putpalettealphas(t)
|
||||
elif isinstance(t, int):
|
||||
self.im.putpalettealpha(t,0)
|
||||
self.im.putpalettealpha(t, 0)
|
||||
else:
|
||||
raise ValueError("Transparency for P mode should" +
|
||||
" be bytes or int")
|
||||
|
||||
|
||||
if mode == "P" and palette == ADAPTIVE:
|
||||
im = self.im.quantize(colors)
|
||||
new = self._new(im)
|
||||
|
@ -936,14 +950,19 @@ class Image:
|
|||
return new_im
|
||||
|
||||
def quantize(self, colors=256, method=None, kmeans=0, palette=None):
|
||||
"""
|
||||
Convert the image to 'P' mode with the specified number
|
||||
of colors.
|
||||
|
||||
# methods:
|
||||
# 0 = median cut
|
||||
# 1 = maximum coverage
|
||||
# 2 = fast octree
|
||||
:param colors: The desired number of colors, <= 256
|
||||
:param method: 0 = median cut
|
||||
1 = maximum coverage
|
||||
2 = fast octree
|
||||
:param kmeans: Integer
|
||||
:param palette: Quantize to the :py:class:`PIL.ImagingPalette` palette.
|
||||
:returns: A new image
|
||||
|
||||
# NOTE: this functionality will be moved to the extended
|
||||
# quantizer interface in a later version of PIL.
|
||||
"""
|
||||
|
||||
self.load()
|
||||
|
||||
|
@ -1269,11 +1288,11 @@ class Image:
|
|||
images (in the latter case, the alpha band is used as mask).
|
||||
Where the mask is 255, the given image is copied as is. Where
|
||||
the mask is 0, the current value is preserved. Intermediate
|
||||
values can be used for transparency effects.
|
||||
values will mix the two images together, including their alpha
|
||||
channels if they have them.
|
||||
|
||||
Note that if you paste an "RGBA" image, the alpha band is
|
||||
ignored. You can work around this by using the same image as
|
||||
both source image and mask.
|
||||
See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
|
||||
combine images with respect to their alpha channels.
|
||||
|
||||
:param im: Source image or pixel value (integer or tuple).
|
||||
:param box: An optional 4-tuple giving the region to paste into.
|
||||
|
@ -1514,21 +1533,20 @@ class Image:
|
|||
(width, height).
|
||||
:param resample: An optional resampling filter. This can be
|
||||
one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
|
||||
:py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
|
||||
environment), :py:attr:`PIL.Image.BICUBIC` (cubic spline
|
||||
interpolation in a 4x4 environment), or
|
||||
:py:attr:`PIL.Image.ANTIALIAS` (a high-quality downsampling filter).
|
||||
:py:attr:`PIL.Image.BILINEAR` (linear interpolation),
|
||||
:py:attr:`PIL.Image.BICUBIC` (cubic spline interpolation), or
|
||||
:py:attr:`PIL.Image.LANCZOS` (a high-quality downsampling filter).
|
||||
If omitted, or if the image has mode "1" or "P", it is
|
||||
set :py:attr:`PIL.Image.NEAREST`.
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
"""
|
||||
|
||||
if resample not in (NEAREST, BILINEAR, BICUBIC, ANTIALIAS):
|
||||
if resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS):
|
||||
raise ValueError("unknown resampling filter")
|
||||
|
||||
self.load()
|
||||
|
||||
size=tuple(size)
|
||||
size = tuple(size)
|
||||
if self.size == size:
|
||||
return self._new(self.im)
|
||||
|
||||
|
@ -1538,16 +1556,7 @@ class Image:
|
|||
if self.mode == 'RGBA':
|
||||
return self.convert('RGBa').resize(size, resample).convert('RGBA')
|
||||
|
||||
if resample == ANTIALIAS:
|
||||
# requires stretch support (imToolkit & PIL 1.1.3)
|
||||
try:
|
||||
im = self.im.stretch(size, resample)
|
||||
except AttributeError:
|
||||
raise ValueError("unsupported resampling filter")
|
||||
else:
|
||||
im = self.im.resize(size, resample)
|
||||
|
||||
return self._new(im)
|
||||
return self._new(self.im.resize(size, resample))
|
||||
|
||||
def rotate(self, angle, resample=NEAREST, expand=0):
|
||||
"""
|
||||
|
@ -1618,15 +1627,16 @@ class Image:
|
|||
|
||||
Keyword options can be used to provide additional instructions
|
||||
to the writer. If a writer doesn't recognise an option, it is
|
||||
silently ignored. The available options are described later in
|
||||
this handbook.
|
||||
silently ignored. The available options are described in the
|
||||
:doc:`image format documentation
|
||||
<../handbook/image-file-formats>` for each writer.
|
||||
|
||||
You can use a file object instead of a filename. In this case,
|
||||
you must always specify the format. The file object must
|
||||
implement the **seek**, **tell**, and **write**
|
||||
implement the ``seek``, ``tell``, and ``write``
|
||||
methods, and be opened in binary mode.
|
||||
|
||||
:param file: File name or file object.
|
||||
:param fp: File name or file object.
|
||||
:param format: Optional format override. If omitted, the
|
||||
format to use is determined from the filename extension.
|
||||
If a file object was used instead of a filename, this
|
||||
|
@ -1753,7 +1763,7 @@ class Image:
|
|||
"""
|
||||
return 0
|
||||
|
||||
def thumbnail(self, size, resample=ANTIALIAS):
|
||||
def thumbnail(self, size, resample=BICUBIC):
|
||||
"""
|
||||
Make this image into a thumbnail. This method modifies the
|
||||
image to contain a thumbnail version of itself, no larger than
|
||||
|
@ -1762,12 +1772,7 @@ class Image:
|
|||
:py:meth:`~PIL.Image.Image.draft` method to configure the file reader
|
||||
(where applicable), and finally resizes the image.
|
||||
|
||||
Note that the bilinear and bicubic filters in the current
|
||||
version of PIL are not well-suited for thumbnail generation.
|
||||
You should use :py:attr:`PIL.Image.ANTIALIAS` unless speed is much more
|
||||
important than quality.
|
||||
|
||||
Also note that this function modifies the :py:class:`~PIL.Image.Image`
|
||||
Note that this function modifies the :py:class:`~PIL.Image.Image`
|
||||
object in place. If you need to use the full resolution image as well,
|
||||
apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
|
||||
image.
|
||||
|
@ -1775,10 +1780,9 @@ class Image:
|
|||
:param size: Requested size.
|
||||
:param resample: Optional resampling filter. This can be one
|
||||
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
|
||||
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS`
|
||||
(best quality). If omitted, it defaults to
|
||||
:py:attr:`PIL.Image.ANTIALIAS`. (was :py:attr:`PIL.Image.NEAREST`
|
||||
prior to version 2.5.0)
|
||||
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.LANCZOS`.
|
||||
If omitted, it defaults to :py:attr:`PIL.Image.BICUBIC`.
|
||||
(was :py:attr:`PIL.Image.NEAREST` prior to version 2.5.0)
|
||||
:returns: None
|
||||
"""
|
||||
|
||||
|
@ -1797,14 +1801,7 @@ class Image:
|
|||
|
||||
self.draft(None, size)
|
||||
|
||||
self.load()
|
||||
|
||||
try:
|
||||
im = self.resize(size, resample)
|
||||
except ValueError:
|
||||
if resample != ANTIALIAS:
|
||||
raise
|
||||
im = self.resize(size, NEAREST) # fallback
|
||||
im = self.resize(size, resample)
|
||||
|
||||
self.im = im.im
|
||||
self.mode = im.mode
|
||||
|
@ -1813,7 +1810,7 @@ class Image:
|
|||
self.readonly = 0
|
||||
self.pyaccess = None
|
||||
|
||||
# FIXME: the different tranform methods need further explanation
|
||||
# FIXME: the different transform methods need further explanation
|
||||
# instead of bloating the method docs, add a separate chapter.
|
||||
def transform(self, size, method, data=None, resample=NEAREST, fill=1):
|
||||
"""
|
||||
|
@ -1920,13 +1917,13 @@ class Image:
|
|||
|
||||
:param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`,
|
||||
:py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`,
|
||||
:py:attr:`PIL.Image.ROTATE_180`, or :py:attr:`PIL.Image.ROTATE_270`.
|
||||
:py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270` or
|
||||
:py:attr:`PIL.Image.TRANSPOSE`.
|
||||
:returns: Returns a flipped or rotated copy of this image.
|
||||
"""
|
||||
|
||||
self.load()
|
||||
im = self.im.transpose(method)
|
||||
return self._new(im)
|
||||
return self._new(self.im.transpose(method))
|
||||
|
||||
def effect_spread(self, distance):
|
||||
"""
|
||||
|
@ -1978,12 +1975,12 @@ class _ImageCrop(Image):
|
|||
# --------------------------------------------------------------------
|
||||
# Abstract handlers.
|
||||
|
||||
class ImagePointHandler:
|
||||
class ImagePointHandler(object):
|
||||
# used as a mixin by point transforms (for use with im.point)
|
||||
pass
|
||||
|
||||
|
||||
class ImageTransformHandler:
|
||||
class ImageTransformHandler(object):
|
||||
# used as a mixin by geometry transforms (for use with im.transform)
|
||||
pass
|
||||
|
||||
|
@ -2004,7 +2001,8 @@ def new(mode, size, color=0):
|
|||
"""
|
||||
Creates a new image with the given mode and size.
|
||||
|
||||
:param mode: The mode to use for the new image.
|
||||
:param mode: The mode to use for the new image. See:
|
||||
:ref:`concept-modes`.
|
||||
:param size: A 2-tuple, containing (width, height) in pixels.
|
||||
:param color: What color to use for the image. Default is black.
|
||||
If given, this should be a single integer or floating point value
|
||||
|
@ -2037,14 +2035,14 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
|
|||
|
||||
You can also use any pixel decoder supported by PIL. For more
|
||||
information on available decoders, see the section
|
||||
**Writing Your Own File Decoder**.
|
||||
:ref:`Writing Your Own File Decoder <file-decoders>`.
|
||||
|
||||
Note that this function decodes pixel data only, not entire images.
|
||||
If you have an entire image in a string, wrap it in a
|
||||
:py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load
|
||||
it.
|
||||
|
||||
:param mode: The image mode.
|
||||
:param mode: The image mode. See: :ref:`concept-modes`.
|
||||
:param size: The image size.
|
||||
:param data: A byte buffer containing raw data for the given mode.
|
||||
:param decoder_name: What decoder to use.
|
||||
|
@ -2096,7 +2094,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
|
|||
issues a warning if you do this; to disable the warning, you should provide
|
||||
the full set of parameters. See below for details.
|
||||
|
||||
:param mode: The image mode.
|
||||
:param mode: The image mode. See: :ref:`concept-modes`.
|
||||
:param size: The image size.
|
||||
:param data: A bytes or other buffer object containing raw
|
||||
data for the given mode.
|
||||
|
@ -2147,7 +2145,8 @@ def fromarray(obj, mode=None):
|
|||
|
||||
:param obj: Object with array interface
|
||||
:param mode: Mode to use (will be determined from type if None)
|
||||
:returns: An image memory.
|
||||
See: :ref:`concept-modes`.
|
||||
:returns: An image object.
|
||||
|
||||
.. versionadded:: 1.1.6
|
||||
"""
|
||||
|
@ -2250,6 +2249,11 @@ def open(fp, mode="r"):
|
|||
else:
|
||||
filename = ""
|
||||
|
||||
try:
|
||||
fp.seek(0)
|
||||
except (AttributeError, io.UnsupportedOperation):
|
||||
fp = io.BytesIO(fp.read())
|
||||
|
||||
prefix = fp.read(16)
|
||||
|
||||
preinit()
|
||||
|
@ -2262,7 +2266,7 @@ def open(fp, mode="r"):
|
|||
im = factory(fp, filename)
|
||||
_decompression_bomb_check(im.size)
|
||||
return im
|
||||
except (SyntaxError, IndexError, TypeError):
|
||||
except (SyntaxError, IndexError, TypeError, struct.error):
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
|
@ -2277,7 +2281,7 @@ def open(fp, mode="r"):
|
|||
im = factory(fp, filename)
|
||||
_decompression_bomb_check(im.size)
|
||||
return im
|
||||
except (SyntaxError, IndexError, TypeError):
|
||||
except (SyntaxError, IndexError, TypeError, struct.error):
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
|
@ -2364,7 +2368,8 @@ def merge(mode, bands):
|
|||
"""
|
||||
Merge a set of single band images into a new multiband image.
|
||||
|
||||
:param mode: The mode to use for the output image.
|
||||
:param mode: The mode to use for the output image. See:
|
||||
:ref:`concept-modes`.
|
||||
:param bands: A sequence containing one single-band image for
|
||||
each band in the output image. All bands must have the
|
||||
same size.
|
||||
|
|
|
@ -64,7 +64,7 @@ pyCMS
|
|||
|
||||
0.0.2 alpha Jan 6, 2002
|
||||
|
||||
Added try/except statements arount type() checks of
|
||||
Added try/except statements around type() checks of
|
||||
potential CObjects... Python won't let you use type()
|
||||
on them, and raises a TypeError (stupid, if you ask
|
||||
me!)
|
||||
|
@ -123,8 +123,8 @@ FLAGS = {
|
|||
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
||||
"NOTPRECALC": 256,
|
||||
"NULLTRANSFORM": 512, # Don't transform anyway
|
||||
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
|
||||
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces
|
||||
"HIGHRESPRECALC": 1024, # Use more memory to give better accuracy
|
||||
"LOWRESPRECALC": 2048, # Use less memory to minimize resources
|
||||
"WHITEBLACKCOMPENSATION": 8192,
|
||||
"BLACKPOINTCOMPENSATION": 8192,
|
||||
"GAMUTCHECK": 4096, # Out of Gamut alarm
|
||||
|
@ -147,7 +147,7 @@ for flag in FLAGS.values():
|
|||
##
|
||||
# Profile.
|
||||
|
||||
class ImageCmsProfile:
|
||||
class ImageCmsProfile(object):
|
||||
|
||||
def __init__(self, profile):
|
||||
"""
|
||||
|
@ -573,7 +573,7 @@ def applyTransform(im, transform, inPlace=0):
|
|||
This function applies a pre-calculated transform (from
|
||||
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
|
||||
to an image. The transform can be used for multiple images, saving
|
||||
considerable calcuation time if doing the same conversion multiple times.
|
||||
considerable calculation time if doing the same conversion multiple times.
|
||||
|
||||
If you want to modify im in-place instead of receiving a new image as
|
||||
the return value, set inPlace to TRUE. This can only be done if
|
||||
|
@ -858,7 +858,7 @@ def getDefaultIntent(profile):
|
|||
If an error occurs while trying to obtain the default intent, a
|
||||
PyCMSError is raised.
|
||||
|
||||
Use this function to determine the default (and usually best optomized)
|
||||
Use this function to determine the default (and usually best optimized)
|
||||
rendering intent for this profile. Most profiles support multiple
|
||||
rendering intents, but are intended mostly for one type of conversion.
|
||||
If you wish to use a different intent than returned, use
|
||||
|
@ -914,7 +914,7 @@ def isIntentSupported(profile, intent, direction):
|
|||
|
||||
see the pyCMS documentation for details on rendering intents and what
|
||||
they do.
|
||||
:param direction: Integer specifing if the profile is to be used for input,
|
||||
:param direction: Integer specifying if the profile is to be used for input,
|
||||
output, or proof
|
||||
|
||||
INPUT = 0 (or use ImageCms.DIRECTION_INPUT)
|
||||
|
|
|
@ -47,7 +47,7 @@ except ImportError:
|
|||
# Application code should use the <b>Draw</b> factory, instead of
|
||||
# directly.
|
||||
|
||||
class ImageDraw:
|
||||
class ImageDraw(object):
|
||||
|
||||
##
|
||||
# Create a drawing instance.
|
||||
|
|
|
@ -19,25 +19,25 @@
|
|||
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||
|
||||
|
||||
class Pen:
|
||||
class Pen(object):
|
||||
def __init__(self, color, width=1, opacity=255):
|
||||
self.color = ImageColor.getrgb(color)
|
||||
self.width = width
|
||||
|
||||
|
||||
class Brush:
|
||||
class Brush(object):
|
||||
def __init__(self, color, opacity=255):
|
||||
self.color = ImageColor.getrgb(color)
|
||||
|
||||
|
||||
class Font:
|
||||
class Font(object):
|
||||
def __init__(self, color, file, size=12):
|
||||
# FIXME: add support for bitmap fonts
|
||||
self.color = ImageColor.getrgb(color)
|
||||
self.font = ImageFont.truetype(file, size)
|
||||
|
||||
|
||||
class Draw:
|
||||
class Draw(object):
|
||||
|
||||
def __init__(self, image, size=None, color=None):
|
||||
if not hasattr(image, "im"):
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
from PIL import Image, ImageFilter, ImageStat
|
||||
|
||||
|
||||
class _Enhance:
|
||||
class _Enhance(object):
|
||||
|
||||
def enhance(self, factor):
|
||||
"""
|
||||
|
@ -53,6 +53,7 @@ class Color(_Enhance):
|
|||
|
||||
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
|
||||
|
||||
|
||||
class Contrast(_Enhance):
|
||||
"""Adjust image contrast.
|
||||
|
||||
|
@ -72,7 +73,7 @@ class Contrast(_Enhance):
|
|||
class Brightness(_Enhance):
|
||||
"""Adjust image brightness.
|
||||
|
||||
This class can be used to control the brighntess of an image. An
|
||||
This class can be used to control the brightness of an image. An
|
||||
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
|
||||
original image.
|
||||
"""
|
||||
|
|
|
@ -196,6 +196,9 @@ class ImageFile(Image.Image):
|
|||
except AttributeError:
|
||||
prefix = b""
|
||||
|
||||
# Buffer length read; assign a default value
|
||||
t = 0
|
||||
|
||||
for d, e, o, a in self.tile:
|
||||
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
|
||||
seek(o)
|
||||
|
@ -308,7 +311,7 @@ class StubImageFile(ImageFile):
|
|||
)
|
||||
|
||||
|
||||
class Parser:
|
||||
class Parser(object):
|
||||
"""
|
||||
Incremental image parser. This class implements the standard
|
||||
feed/close consumer interface.
|
||||
|
@ -319,6 +322,7 @@ class Parser:
|
|||
image = None
|
||||
data = None
|
||||
decoder = None
|
||||
offset = 0
|
||||
finished = 0
|
||||
|
||||
def reset(self):
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from functools import reduce
|
||||
import functools
|
||||
|
||||
|
||||
class Filter(object):
|
||||
|
@ -43,7 +43,7 @@ class Kernel(Filter):
|
|||
def __init__(self, size, kernel, scale=None, offset=0):
|
||||
if scale is None:
|
||||
# default scale is sum of kernel
|
||||
scale = reduce(lambda a, b: a+b, kernel)
|
||||
scale = functools.reduce(lambda a, b: a+b, kernel)
|
||||
if size[0] * size[1] != len(kernel):
|
||||
raise ValueError("not enough coefficients in kernel")
|
||||
self.filterargs = size, scale, offset, kernel
|
||||
|
@ -162,8 +162,13 @@ class UnsharpMask(Filter):
|
|||
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
|
||||
the parameters.
|
||||
|
||||
.. _digital unsharp masking:
|
||||
https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
|
||||
:param radius: Blur Radius
|
||||
:param percent: Unsharp strength, in percent
|
||||
:param threshold: Threshold controls the minimum brightness change that
|
||||
will be sharpened
|
||||
|
||||
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
|
||||
|
||||
"""
|
||||
name = "UnsharpMask"
|
||||
|
||||
|
|
|
@ -25,8 +25,6 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isDirectory, isPath
|
||||
import os
|
||||
|
@ -38,7 +36,7 @@ except ImportError:
|
|||
warnings = None
|
||||
|
||||
|
||||
class _imagingft_not_installed:
|
||||
class _imagingft_not_installed(object):
|
||||
# module placeholder
|
||||
def __getattr__(self, id):
|
||||
raise ImportError("The _imagingft C module is not installed")
|
||||
|
@ -64,7 +62,7 @@ except ImportError:
|
|||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
class ImageFont:
|
||||
class ImageFont(object):
|
||||
"PIL font wrapper"
|
||||
|
||||
def _load_pilfont(self, filename):
|
||||
|
@ -120,7 +118,7 @@ class ImageFont:
|
|||
# Wrapper for FreeType fonts. Application code should use the
|
||||
# <b>truetype</b> factory function to create font objects.
|
||||
|
||||
class FreeTypeFont:
|
||||
class FreeTypeFont(object):
|
||||
"FreeType font wrapper (requires _imagingft service)"
|
||||
|
||||
def __init__(self, font=None, size=10, index=0, encoding="", file=None):
|
||||
|
@ -133,6 +131,11 @@ class FreeTypeFont:
|
|||
DeprecationWarning)
|
||||
font = file
|
||||
|
||||
self.path = font
|
||||
self.size = size
|
||||
self.index = index
|
||||
self.encoding = encoding
|
||||
|
||||
if isPath(font):
|
||||
self.font = core.getfont(font, size, index, encoding)
|
||||
else:
|
||||
|
@ -162,6 +165,22 @@ class FreeTypeFont:
|
|||
self.font.render(text, im.id, mode == "1")
|
||||
return im, offset
|
||||
|
||||
def font_variant(self, font=None, size=None, index=None, encoding=None):
|
||||
"""
|
||||
Create a copy of this FreeTypeFont object,
|
||||
using any specified arguments to override the settings.
|
||||
|
||||
Parameters are identical to the parameters used to initialize this
|
||||
object, minus the deprecated 'file' argument.
|
||||
|
||||
:return: A FreeTypeFont object.
|
||||
"""
|
||||
return FreeTypeFont(font=self.path if font is None else font,
|
||||
size=self.size if size is None else size,
|
||||
index=self.index if index is None else index,
|
||||
encoding=self.encoding if encoding is None else
|
||||
encoding)
|
||||
|
||||
##
|
||||
# Wrapper that creates a transposed font from any existing font
|
||||
# object.
|
||||
|
@ -172,7 +191,7 @@ class FreeTypeFont:
|
|||
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
||||
|
||||
|
||||
class TransposedFont:
|
||||
class TransposedFont(object):
|
||||
"Wrapper for writing rotated or mirrored text"
|
||||
|
||||
def __init__(self, font, orientation=None):
|
||||
|
@ -214,7 +233,7 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
|||
|
||||
This function requires the _imagingft service.
|
||||
|
||||
:param filename: A truetype font file. Under Windows, if the file
|
||||
:param font: A truetype font file. Under Windows, if the file
|
||||
is not found in this filename, the loader also looks in
|
||||
Windows :file:`fonts/` directory.
|
||||
:param size: The requested size, in points.
|
||||
|
@ -224,6 +243,7 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
|||
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
||||
and "armn" (Apple Roman). See the FreeType documentation
|
||||
for more information.
|
||||
:param filename: Deprecated. Please use font instead.
|
||||
:return: A font object.
|
||||
:exception IOError: If the file could not be read.
|
||||
"""
|
||||
|
@ -239,14 +259,44 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
|||
try:
|
||||
return FreeTypeFont(font, size, index, encoding)
|
||||
except IOError:
|
||||
ttf_filename = os.path.basename(font)
|
||||
|
||||
dirs = []
|
||||
if sys.platform == "win32":
|
||||
# check the windows font repository
|
||||
# NOTE: must use uppercase WINDIR, to work around bugs in
|
||||
# 1.5.2's os.environ.get()
|
||||
windir = os.environ.get("WINDIR")
|
||||
if windir:
|
||||
filename = os.path.join(windir, "fonts", font)
|
||||
return FreeTypeFont(filename, size, index, encoding)
|
||||
dirs.append(os.path.join(windir, "fonts"))
|
||||
elif sys.platform in ('linux', 'linux2'):
|
||||
lindirs = os.environ.get("XDG_DATA_DIRS", "")
|
||||
if not lindirs:
|
||||
# According to the freedesktop spec, XDG_DATA_DIRS should
|
||||
# default to /usr/share
|
||||
lindirs = '/usr/share'
|
||||
dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
|
||||
elif sys.platform == 'darwin':
|
||||
dirs += ['/Library/Fonts', '/System/Library/Fonts',
|
||||
os.path.expanduser('~/Library/Fonts')]
|
||||
|
||||
ext = os.path.splitext(ttf_filename)[1]
|
||||
first_font_with_a_different_extension = None
|
||||
for directory in dirs:
|
||||
for walkroot, walkdir, walkfilenames in os.walk(directory):
|
||||
for walkfilename in walkfilenames:
|
||||
if ext and walkfilename == ttf_filename:
|
||||
fontpath = os.path.join(walkroot, walkfilename)
|
||||
return FreeTypeFont(fontpath, size, index, encoding)
|
||||
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
|
||||
fontpath = os.path.join(walkroot, walkfilename)
|
||||
if os.path.splitext(fontpath)[1] == '.ttf':
|
||||
return FreeTypeFont(fontpath, size, index, encoding)
|
||||
if not ext and first_font_with_a_different_extension is None:
|
||||
first_font_with_a_different_extension = fontpath
|
||||
if first_font_with_a_different_extension:
|
||||
return FreeTypeFont(first_font_with_a_different_extension, size,
|
||||
index, encoding)
|
||||
raise
|
||||
|
||||
|
||||
|
@ -259,15 +309,15 @@ def load_path(filename):
|
|||
:return: A font object.
|
||||
:exception IOError: If the file could not be read.
|
||||
"""
|
||||
for dir in sys.path:
|
||||
if isDirectory(dir):
|
||||
for directory in sys.path:
|
||||
if isDirectory(directory):
|
||||
if not isinstance(filename, str):
|
||||
if bytes is str:
|
||||
filename = filename.encode("utf-8")
|
||||
else:
|
||||
filename = filename.decode("utf-8")
|
||||
try:
|
||||
return load(os.path.join(dir, filename))
|
||||
return load(os.path.join(directory, filename))
|
||||
except IOError:
|
||||
pass
|
||||
raise IOError("cannot find font file")
|
||||
|
|
|
@ -31,7 +31,7 @@ def _isconstant(v):
|
|||
return isinstance(v, int) or isinstance(v, float)
|
||||
|
||||
|
||||
class _Operand:
|
||||
class _Operand(object):
|
||||
# wraps an image operand, providing standard operators
|
||||
|
||||
def __init__(self, im):
|
||||
|
|
|
@ -20,7 +20,7 @@ _modes = {}
|
|||
##
|
||||
# Wrapper for mode strings.
|
||||
|
||||
class ModeDescriptor:
|
||||
class ModeDescriptor(object):
|
||||
|
||||
def __init__(self, mode, bands, basemode, basetype):
|
||||
self.mode = mode
|
||||
|
|
|
@ -12,7 +12,7 @@ import re
|
|||
LUT_SIZE = 1 << 9
|
||||
|
||||
|
||||
class LutBuilder:
|
||||
class LutBuilder(object):
|
||||
"""A class for building a MorphLut from a descriptive language
|
||||
|
||||
The input patterns is a list of a strings sequences like these::
|
||||
|
@ -176,7 +176,7 @@ class LutBuilder:
|
|||
return self.lut
|
||||
|
||||
|
||||
class MorphOp:
|
||||
class MorphOp(object):
|
||||
"""A class for binary morphological operators"""
|
||||
|
||||
def __init__(self,
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
from PIL import Image
|
||||
from PIL._util import isStringType
|
||||
import operator
|
||||
from functools import reduce
|
||||
import functools
|
||||
|
||||
|
||||
#
|
||||
|
@ -213,7 +213,7 @@ def equalize(image, mask=None):
|
|||
if len(histo) <= 1:
|
||||
lut.extend(list(range(256)))
|
||||
else:
|
||||
step = (reduce(operator.add, histo) - histo[-1]) // 255
|
||||
step = (functools.reduce(operator.add, histo) - histo[-1]) // 255
|
||||
if not step:
|
||||
lut.extend(list(range(256)))
|
||||
else:
|
||||
|
@ -233,7 +233,6 @@ def expand(image, border=0, fill=0):
|
|||
:param fill: Pixel fill value (a color value). Default is 0 (black).
|
||||
:return: An image.
|
||||
"""
|
||||
"Add border to image"
|
||||
left, top, right, bottom = _border(border)
|
||||
width = left + image.size[0] + right
|
||||
height = top + image.size[1] + bottom
|
||||
|
@ -441,3 +440,22 @@ def unsharp_mask(im, radius=None, percent=None, threshold=None):
|
|||
return im.im.unsharp_mask(radius, percent, threshold)
|
||||
|
||||
usm = unsharp_mask
|
||||
|
||||
|
||||
def box_blur(image, radius):
|
||||
"""
|
||||
Blur the image by setting each pixel to the average value of the pixels
|
||||
in a square box extending radius pixels in each direction.
|
||||
Supports float radius of arbitrary size. Uses an optimized implementation
|
||||
which runs in linear time relative to the size of the image
|
||||
for any radius value.
|
||||
|
||||
:param image: The image to blur.
|
||||
:param radius: Size of the box in one direction. Radius 0 does not blur,
|
||||
returns an identical image. Radius 1 takes 1 pixel
|
||||
in each direction, i.e. 9 pixels in total.
|
||||
:return: An image.
|
||||
"""
|
||||
image.load()
|
||||
|
||||
return image._new(image.im.box_blur(radius))
|
||||
|
|
|
@ -21,7 +21,7 @@ import warnings
|
|||
from PIL import ImageColor
|
||||
|
||||
|
||||
class ImagePalette:
|
||||
class ImagePalette(object):
|
||||
"Color palette for palette mapped images"
|
||||
|
||||
def __init__(self, mode="RGB", palette=None, size=0):
|
||||
|
@ -225,8 +225,8 @@ def load(filename):
|
|||
p = PaletteFile.PaletteFile(fp)
|
||||
lut = p.getpalette()
|
||||
except (SyntaxError, ValueError):
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
pass
|
||||
|
||||
if not lut:
|
||||
|
|
|
@ -20,7 +20,7 @@ from PIL import Image
|
|||
# the Python class below is overridden by the C implementation.
|
||||
|
||||
|
||||
class Path:
|
||||
class Path(object):
|
||||
|
||||
def __init__(self, xy):
|
||||
pass
|
||||
|
|
|
@ -18,16 +18,24 @@
|
|||
|
||||
from PIL import Image
|
||||
from PIL._util import isPath
|
||||
import sys
|
||||
|
||||
try:
|
||||
from PyQt5.QtGui import QImage, qRgba
|
||||
except:
|
||||
if 'PyQt4.QtGui' not in sys.modules:
|
||||
try:
|
||||
from PyQt5.QtGui import QImage, qRgba
|
||||
except:
|
||||
try:
|
||||
from PyQt4.QtGui import QImage, qRgba
|
||||
except:
|
||||
from PySide.QtGui import QImage, qRgba
|
||||
|
||||
else: #PyQt4 is used
|
||||
from PyQt4.QtGui import QImage, qRgba
|
||||
|
||||
|
||||
##
|
||||
# (Internal) Turns an RGB color into a Qt compatible color integer.
|
||||
|
||||
|
||||
def rgb(r, g, b, a=255):
|
||||
# use qRgb to pack the colors, and then turn the resulting long
|
||||
# into a negative integer with the same bitpattern.
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
##
|
||||
|
||||
|
||||
class Iterator:
|
||||
class Iterator(object):
|
||||
"""
|
||||
This class implements an iterator object that can be used to loop
|
||||
over an image sequence.
|
||||
|
|
|
@ -56,7 +56,7 @@ def show(image, title=None, **options):
|
|||
##
|
||||
# Base class for viewers.
|
||||
|
||||
class Viewer:
|
||||
class Viewer(object):
|
||||
|
||||
# main api
|
||||
|
||||
|
|
|
@ -23,10 +23,10 @@
|
|||
|
||||
import math
|
||||
import operator
|
||||
from functools import reduce
|
||||
import functools
|
||||
|
||||
|
||||
class Stat:
|
||||
class Stat(object):
|
||||
|
||||
def __init__(self, image_or_list, mask=None):
|
||||
try:
|
||||
|
@ -71,7 +71,7 @@ class Stat:
|
|||
|
||||
v = []
|
||||
for i in range(0, len(self.h), 256):
|
||||
v.append(reduce(operator.add, self.h[i:i+256]))
|
||||
v.append(functools.reduce(operator.add, self.h[i:i+256]))
|
||||
return v
|
||||
|
||||
def _getsum(self):
|
||||
|
@ -79,10 +79,10 @@ class Stat:
|
|||
|
||||
v = []
|
||||
for i in range(0, len(self.h), 256):
|
||||
sum = 0.0
|
||||
layerSum = 0.0
|
||||
for j in range(256):
|
||||
sum += j * self.h[i + j]
|
||||
v.append(sum)
|
||||
layerSum += j * self.h[i + j]
|
||||
v.append(layerSum)
|
||||
return v
|
||||
|
||||
def _getsum2(self):
|
||||
|
|
|
@ -56,7 +56,7 @@ def _pilbitmap_check():
|
|||
# --------------------------------------------------------------------
|
||||
# PhotoImage
|
||||
|
||||
class PhotoImage:
|
||||
class PhotoImage(object):
|
||||
"""
|
||||
A Tkinter-compatible photo image. This can be used
|
||||
everywhere Tkinter expects an image object. If the image is an RGBA
|
||||
|
@ -190,7 +190,7 @@ class PhotoImage:
|
|||
# BitmapImage
|
||||
|
||||
|
||||
class BitmapImage:
|
||||
class BitmapImage(object):
|
||||
"""
|
||||
|
||||
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
|
||||
|
|
|
@ -21,7 +21,7 @@ import warnings
|
|||
from PIL import Image
|
||||
|
||||
|
||||
class HDC:
|
||||
class HDC(object):
|
||||
"""
|
||||
Wraps an HDC integer. The resulting object can be passed to the
|
||||
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
|
||||
|
@ -34,7 +34,7 @@ class HDC:
|
|||
return self.dc
|
||||
|
||||
|
||||
class HWND:
|
||||
class HWND(object):
|
||||
"""
|
||||
Wraps an HWND integer. The resulting object can be passed to the
|
||||
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
|
||||
|
@ -47,7 +47,7 @@ class HWND:
|
|||
return self.wnd
|
||||
|
||||
|
||||
class Dib:
|
||||
class Dib(object):
|
||||
"""
|
||||
A Windows bitmap with the given mode and size. The mode can be one of "1",
|
||||
"L", "P", or "RGB".
|
||||
|
@ -206,7 +206,7 @@ class Dib:
|
|||
##
|
||||
# Create a Window with the given title size.
|
||||
|
||||
class Window:
|
||||
class Window(object):
|
||||
|
||||
def __init__(self, title="PIL", width=None, height=None):
|
||||
self.hwnd = Image.core.createwindow(
|
||||
|
|
|
@ -222,7 +222,7 @@ def getiptcinfo(im):
|
|||
offset += 2
|
||||
# resource name (usually empty)
|
||||
name_len = i8(app[offset])
|
||||
name = app[offset+1:offset+1+name_len]
|
||||
# name = app[offset+1:offset+1+name_len]
|
||||
offset = 1 + offset + name_len
|
||||
if offset & 1:
|
||||
offset += 1
|
||||
|
@ -251,7 +251,7 @@ def getiptcinfo(im):
|
|||
return None # no properties
|
||||
|
||||
# create an IptcImagePlugin object without initializing it
|
||||
class FakeImage:
|
||||
class FakeImage(object):
|
||||
pass
|
||||
im = FakeImage()
|
||||
im.__class__ = IptcImageFile
|
||||
|
|
|
@ -12,14 +12,13 @@
|
|||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
import struct
|
||||
import os
|
||||
import io
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
def _parse_codestream(fp):
|
||||
"""Parse the JPEG 2000 codestream to extract the size and component
|
||||
|
@ -208,8 +207,8 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
|||
|
||||
|
||||
def _accept(prefix):
|
||||
return (prefix[:4] == b'\xff\x4f\xff\x51'
|
||||
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
||||
return (prefix[:4] == b'\xff\x4f\xff\x51' or
|
||||
prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# JPEG (JFIF) file handling
|
||||
#
|
||||
# See "Digital Compression and Coding of Continous-Tone Still Images,
|
||||
# See "Digital Compression and Coding of Continuous-Tone Still Images,
|
||||
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
|
||||
#
|
||||
# History:
|
||||
|
@ -355,7 +355,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
scale = s
|
||||
|
||||
self.tile = [(d, e, o, a)]
|
||||
self.decoderconfig = (scale, 1)
|
||||
self.decoderconfig = (scale, 0)
|
||||
|
||||
return self
|
||||
|
||||
|
@ -454,13 +454,13 @@ def _getmp(self):
|
|||
data = self.info["mp"]
|
||||
except KeyError:
|
||||
return None
|
||||
file = io.BytesIO(data)
|
||||
head = file.read(8)
|
||||
file_contents = io.BytesIO(data)
|
||||
head = file_contents.read(8)
|
||||
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
||||
mp = {}
|
||||
# process dictionary
|
||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||
info.load(file)
|
||||
info.load(file_contents)
|
||||
for key, value in info.items():
|
||||
mp[key] = _fixup(value)
|
||||
# it's an error not to have a number of images
|
||||
|
@ -684,7 +684,8 @@ def _save(im, fp, filename):
|
|||
# https://github.com/jdriscoll/django-imagekit/issues/50
|
||||
bufsize = 0
|
||||
if "optimize" in info or "progressive" in info or "progression" in info:
|
||||
if quality >= 95:
|
||||
# keep sets quality to 0, but the actual value may be high.
|
||||
if quality >= 95 or quality == 0:
|
||||
bufsize = 2 * im.size[0] * im.size[1]
|
||||
else:
|
||||
bufsize = im.size[0] * im.size[1]
|
||||
|
@ -703,7 +704,7 @@ def _save_cjpeg(im, fp, filename):
|
|||
tempfile = im._dump()
|
||||
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
||||
try:
|
||||
os.unlink(file)
|
||||
os.unlink(tempfile)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ Libjpeg ref.: http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
|||
"""
|
||||
|
||||
presets = {
|
||||
'web_low': {'subsampling': 2, # "4:1:1"
|
||||
'web_low': {'subsampling': 2, # "4:1:1"
|
||||
'quantization': [
|
||||
[20, 16, 25, 39, 50, 46, 62, 68,
|
||||
16, 18, 23, 38, 38, 53, 65, 68,
|
||||
|
@ -86,7 +86,7 @@ presets = {
|
|||
68, 68, 68, 68, 68, 68, 68, 68,
|
||||
68, 68, 68, 68, 68, 68, 68, 68]
|
||||
]},
|
||||
'web_medium': {'subsampling': 2, # "4:1:1"
|
||||
'web_medium': {'subsampling': 2, # "4:1:1"
|
||||
'quantization': [
|
||||
[16, 11, 11, 16, 23, 27, 31, 30,
|
||||
11, 12, 12, 15, 20, 23, 23, 30,
|
||||
|
@ -105,7 +105,7 @@ presets = {
|
|||
38, 35, 46, 53, 64, 64, 64, 64,
|
||||
48, 43, 53, 64, 64, 64, 64, 64]
|
||||
]},
|
||||
'web_high': {'subsampling': 0, # "4:4:4"
|
||||
'web_high': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 6, 4, 4, 6, 9, 11, 12, 16,
|
||||
4, 5, 5, 6, 8, 10, 12, 12,
|
||||
|
@ -124,7 +124,7 @@ presets = {
|
|||
31, 31, 31, 31, 31, 31, 31, 31,
|
||||
31, 31, 31, 31, 31, 31, 31, 31]
|
||||
]},
|
||||
'web_very_high': {'subsampling': 0, # "4:4:4"
|
||||
'web_very_high': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 2, 2, 2, 2, 3, 4, 5, 6,
|
||||
2, 2, 2, 2, 3, 4, 5, 6,
|
||||
|
@ -143,7 +143,7 @@ presets = {
|
|||
15, 12, 12, 12, 12, 12, 12, 12,
|
||||
15, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
'web_maximum': {'subsampling': 0, # "4:4:4"
|
||||
'web_maximum': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
|
@ -162,7 +162,7 @@ presets = {
|
|||
3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3]
|
||||
]},
|
||||
'low': {'subsampling': 2, # "4:1:1"
|
||||
'low': {'subsampling': 2, # "4:1:1"
|
||||
'quantization': [
|
||||
[18, 14, 14, 21, 30, 35, 34, 17,
|
||||
14, 16, 16, 19, 26, 23, 12, 12,
|
||||
|
@ -181,7 +181,7 @@ presets = {
|
|||
17, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
'medium': {'subsampling': 2, # "4:1:1"
|
||||
'medium': {'subsampling': 2, # "4:1:1"
|
||||
'quantization': [
|
||||
[12, 8, 8, 12, 17, 21, 24, 17,
|
||||
8, 9, 9, 11, 15, 19, 12, 12,
|
||||
|
@ -200,7 +200,7 @@ presets = {
|
|||
17, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
'high': {'subsampling': 0, # "4:4:4"
|
||||
'high': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 6, 4, 4, 6, 9, 11, 12, 16,
|
||||
4, 5, 5, 6, 8, 10, 12, 12,
|
||||
|
@ -219,7 +219,7 @@ presets = {
|
|||
17, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
'maximum': {'subsampling': 0, # "4:4:4"
|
||||
'maximum': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 2, 2, 2, 2, 3, 4, 5, 6,
|
||||
2, 2, 2, 2, 3, 4, 5, 6,
|
||||
|
|
|
@ -21,7 +21,7 @@ __version__ = "0.1"
|
|||
|
||||
|
||||
from PIL import Image, TiffImagePlugin
|
||||
from PIL.OleFileIO import *
|
||||
from PIL.OleFileIO import MAGIC, OleFileIO
|
||||
|
||||
|
||||
#
|
||||
|
@ -54,9 +54,9 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
# best way to identify MIC files, but what the... ;-)
|
||||
|
||||
self.images = []
|
||||
for file in self.ole.listdir():
|
||||
if file[1:] and file[0][-4:] == ".ACI" and file[1] == "Image":
|
||||
self.images.append(file)
|
||||
for path in self.ole.listdir():
|
||||
if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image":
|
||||
self.images.append(path)
|
||||
|
||||
# if we didn't find any images, this is probably not
|
||||
# an MIC file.
|
||||
|
@ -71,6 +71,10 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
|
||||
self.seek(0)
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return len(self.images)
|
||||
|
||||
def seek(self, frame):
|
||||
|
||||
try:
|
||||
|
|
|
@ -22,7 +22,7 @@ from PIL._binary import i8
|
|||
#
|
||||
# Bitstream parser
|
||||
|
||||
class BitStream:
|
||||
class BitStream(object):
|
||||
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
|
|
|
@ -62,6 +62,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
def load_seek(self, pos):
|
||||
self.__fp.seek(pos)
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return self.__framecount
|
||||
|
||||
def seek(self, frame):
|
||||
if frame < 0 or frame >= self.__framecount:
|
||||
raise EOFError("no more images in MPO file")
|
||||
|
|
|
@ -49,10 +49,10 @@ class MspImageFile(ImageFile.ImageFile):
|
|||
raise SyntaxError("not an MSP file")
|
||||
|
||||
# Header checksum
|
||||
sum = 0
|
||||
checksum = 0
|
||||
for i in range(0, 32, 2):
|
||||
sum = sum ^ i16(s[i:i+2])
|
||||
if sum != 0:
|
||||
checksum = checksum ^ i16(s[i:i+2])
|
||||
if checksum != 0:
|
||||
raise SyntaxError("bad MSP checksum")
|
||||
|
||||
self.mode = "1"
|
||||
|
@ -83,10 +83,10 @@ def _save(im, fp, filename):
|
|||
header[6], header[7] = 1, 1
|
||||
header[8], header[9] = im.size
|
||||
|
||||
sum = 0
|
||||
checksum = 0
|
||||
for h in header:
|
||||
sum = sum ^ h
|
||||
header[12] = sum # FIXME: is this the right field?
|
||||
checksum = checksum ^ h
|
||||
header[12] = checksum # FIXME: is this the right field?
|
||||
|
||||
# header
|
||||
for h in header:
|
||||
|
|
1138
PIL/OleFileIO.py
1138
PIL/OleFileIO.py
File diff suppressed because it is too large
Load Diff
|
@ -15,15 +15,13 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import EpsImagePlugin
|
||||
|
||||
|
||||
##
|
||||
# Simple Postscript graphics interface.
|
||||
|
||||
class PSDraw:
|
||||
class PSDraw(object):
|
||||
"""
|
||||
Sets up printing to the given file. If **file** is omitted,
|
||||
:py:attr:`sys.stdout` is assumed.
|
||||
|
@ -35,25 +33,31 @@ class PSDraw:
|
|||
fp = sys.stdout
|
||||
self.fp = fp
|
||||
|
||||
def _fp_write(self, to_write):
|
||||
if bytes is str:
|
||||
self.fp.write(to_write)
|
||||
else:
|
||||
self.fp.write(bytes(to_write, 'UTF-8'))
|
||||
|
||||
def begin_document(self, id=None):
|
||||
"""Set up printing of a document. (Write Postscript DSC header.)"""
|
||||
# FIXME: incomplete
|
||||
self.fp.write("%!PS-Adobe-3.0\n"
|
||||
"save\n"
|
||||
"/showpage { } def\n"
|
||||
"%%EndComments\n"
|
||||
"%%BeginDocument\n")
|
||||
# self.fp.write(ERROR_PS) # debugging!
|
||||
self.fp.write(EDROFF_PS)
|
||||
self.fp.write(VDI_PS)
|
||||
self.fp.write("%%EndProlog\n")
|
||||
self._fp_write("%!PS-Adobe-3.0\n"
|
||||
"save\n"
|
||||
"/showpage { } def\n"
|
||||
"%%EndComments\n"
|
||||
"%%BeginDocument\n")
|
||||
# self.fp_write(ERROR_PS) # debugging!
|
||||
self._fp_write(EDROFF_PS)
|
||||
self._fp_write(VDI_PS)
|
||||
self._fp_write("%%EndProlog\n")
|
||||
self.isofont = {}
|
||||
|
||||
def end_document(self):
|
||||
"""Ends printing. (Write Postscript DSC footer.)"""
|
||||
self.fp.write("%%EndDocument\n"
|
||||
"restore showpage\n"
|
||||
"%%End\n")
|
||||
self._fp_write("%%EndDocument\n"
|
||||
"restore showpage\n"
|
||||
"%%End\n")
|
||||
if hasattr(self.fp, "flush"):
|
||||
self.fp.flush()
|
||||
|
||||
|
@ -66,18 +70,11 @@ class PSDraw:
|
|||
"""
|
||||
if font not in self.isofont:
|
||||
# reencode font
|
||||
self.fp.write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %
|
||||
(font, font))
|
||||
self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %
|
||||
(font, font))
|
||||
self.isofont[font] = 1
|
||||
# rough
|
||||
self.fp.write("/F0 %d /PSDraw-%s F\n" % (size, font))
|
||||
|
||||
def setink(self, ink):
|
||||
"""
|
||||
.. warning:: This has been in the PIL API for ages but was never implemented.
|
||||
|
||||
"""
|
||||
print("*** NOT YET IMPLEMENTED ***")
|
||||
self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font))
|
||||
|
||||
def line(self, xy0, xy1):
|
||||
"""
|
||||
|
@ -86,7 +83,7 @@ class PSDraw:
|
|||
left corner of the page).
|
||||
"""
|
||||
xy = xy0 + xy1
|
||||
self.fp.write("%d %d %d %d Vl\n" % xy)
|
||||
self._fp_write("%d %d %d %d Vl\n" % xy)
|
||||
|
||||
def rectangle(self, box):
|
||||
"""
|
||||
|
@ -101,7 +98,7 @@ class PSDraw:
|
|||
|
||||
%d %d M %d %d 0 Vr\n
|
||||
"""
|
||||
self.fp.write("%d %d M %d %d 0 Vr\n" % box)
|
||||
self._fp_write("%d %d M %d %d 0 Vr\n" % box)
|
||||
|
||||
def text(self, xy, text):
|
||||
"""
|
||||
|
@ -111,7 +108,7 @@ class PSDraw:
|
|||
text = "\\(".join(text.split("("))
|
||||
text = "\\)".join(text.split(")"))
|
||||
xy = xy + (text,)
|
||||
self.fp.write("%d %d M (%s) S\n" % xy)
|
||||
self._fp_write("%d %d M (%s) S\n" % xy)
|
||||
|
||||
def image(self, box, im, dpi=None):
|
||||
"""Draw a PIL image, centered in the given box."""
|
||||
|
@ -135,14 +132,14 @@ class PSDraw:
|
|||
y = ymax
|
||||
dx = (xmax - x) / 2 + box[0]
|
||||
dy = (ymax - y) / 2 + box[1]
|
||||
self.fp.write("gsave\n%f %f translate\n" % (dx, dy))
|
||||
self._fp_write("gsave\n%f %f translate\n" % (dx, dy))
|
||||
if (x, y) != im.size:
|
||||
# EpsImagePlugin._save prints the image at (0,0,xsize,ysize)
|
||||
sx = x / im.size[0]
|
||||
sy = y / im.size[1]
|
||||
self.fp.write("%f %f scale\n" % (sx, sy))
|
||||
self._fp_write("%f %f scale\n" % (sx, sy))
|
||||
EpsImagePlugin._save(im, self.fp, None, 0)
|
||||
self.fp.write("\ngrestore\n")
|
||||
self._fp_write("\ngrestore\n")
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Postscript driver
|
||||
|
|
|
@ -19,7 +19,7 @@ from PIL._binary import o8
|
|||
##
|
||||
# File handler for Teragon-style palette files.
|
||||
|
||||
class PaletteFile:
|
||||
class PaletteFile(object):
|
||||
|
||||
rawmode = "RGB"
|
||||
|
||||
|
|
|
@ -52,25 +52,6 @@ class PcdImageFile(ImageFile.ImageFile):
|
|||
self.size = 768, 512 # FIXME: not correct for rotated images!
|
||||
self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)]
|
||||
|
||||
def draft(self, mode, size):
|
||||
|
||||
if len(self.tile) != 1:
|
||||
return
|
||||
|
||||
d, e, o, a = self.tile[0]
|
||||
|
||||
if size:
|
||||
scale = max(self.size[0] / size[0], self.size[1] / size[1])
|
||||
for s, o in [(4, 0*2048), (2, 0*2048), (1, 96*2048)]:
|
||||
if scale >= s:
|
||||
break
|
||||
# e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1]
|
||||
# self.size = ((self.size[0]+s-1)/s, (self.size[1]+s-1)/s)
|
||||
|
||||
self.tile = [(d, e, o, a)]
|
||||
|
||||
return self
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ class PcfFontFile(FontFile.FontFile):
|
|||
for i in range(4):
|
||||
bitmapSizes.append(i32(fp.read(4)))
|
||||
|
||||
byteorder = format & 4 # non-zero => MSB
|
||||
# byteorder = format & 4 # non-zero => MSB
|
||||
bitorder = format & 8 # non-zero => MSB
|
||||
padindex = format & 3
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.6"
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
|
@ -33,6 +33,8 @@ i8 = _binary.i8
|
|||
i16 = _binary.i16le
|
||||
o8 = _binary.o8
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
|
||||
|
@ -58,7 +60,7 @@ class PcxImageFile(ImageFile.ImageFile):
|
|||
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
|
||||
raise SyntaxError("bad PCX image size")
|
||||
if Image.DEBUG:
|
||||
print ("BBox: %s %s %s %s" % bbox)
|
||||
print("BBox: %s %s %s %s" % bbox)
|
||||
|
||||
# format
|
||||
version = i8(s[1])
|
||||
|
@ -66,8 +68,8 @@ class PcxImageFile(ImageFile.ImageFile):
|
|||
planes = i8(s[65])
|
||||
stride = i16(s, 66)
|
||||
if Image.DEBUG:
|
||||
print ("PCX version %s, bits %s, planes %s, stride %s" %
|
||||
(version, bits, planes, stride))
|
||||
print("PCX version %s, bits %s, planes %s, stride %s" %
|
||||
(version, bits, planes, stride))
|
||||
|
||||
self.info["dpi"] = i16(s, 12), i16(s, 14)
|
||||
|
||||
|
@ -106,7 +108,7 @@ class PcxImageFile(ImageFile.ImageFile):
|
|||
|
||||
bbox = (0, 0) + self.size
|
||||
if Image.DEBUG:
|
||||
print ("size: %sx%s" % self.size)
|
||||
print("size: %sx%s" % self.size)
|
||||
|
||||
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
|
||||
|
||||
|
@ -143,7 +145,7 @@ def _save(im, fp, filename, check=0):
|
|||
# gets overwritten.
|
||||
|
||||
if Image.DEBUG:
|
||||
print ("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
|
||||
print("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
|
||||
im.size[0], bits, stride))
|
||||
|
||||
# under windows, we could determine the current screen size with
|
||||
|
|
|
@ -63,7 +63,7 @@ def _save(im, fp, filename):
|
|||
|
||||
xref = [0]*(5+1) # placeholders
|
||||
|
||||
class TextWriter:
|
||||
class TextWriter(object):
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
|
||||
|
|
|
@ -71,12 +71,27 @@ _MODES = {
|
|||
|
||||
|
||||
_simple_palette = re.compile(b'^\xff+\x00\xff*$')
|
||||
_null_palette = re.compile(b'^\x00*$')
|
||||
|
||||
# Maximum decompressed size for a iTXt or zTXt chunk.
|
||||
# Eliminates decompression bombs where compressed chunks can expand 1000x
|
||||
MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK
|
||||
# Set the maximum total text chunk size.
|
||||
MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK
|
||||
|
||||
|
||||
def _safe_zlib_decompress(s):
|
||||
dobj = zlib.decompressobj()
|
||||
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
|
||||
if dobj.unconsumed_tail:
|
||||
raise ValueError("Decompressed Data Too Large")
|
||||
return plaintext
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Support classes. Suitable for PNG and related formats like MNG etc.
|
||||
|
||||
class ChunkStream:
|
||||
class ChunkStream(object):
|
||||
|
||||
def __init__(self, fp):
|
||||
|
||||
|
@ -149,31 +164,56 @@ class ChunkStream:
|
|||
return cids
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Subclass of string to allow iTXt chunks to look like strings while
|
||||
# keeping their extra information
|
||||
|
||||
class iTXt(str):
|
||||
"""
|
||||
Subclass of string to allow iTXt chunks to look like strings while
|
||||
keeping their extra information
|
||||
|
||||
"""
|
||||
@staticmethod
|
||||
def __new__(cls, text, lang, tkey):
|
||||
"""
|
||||
:param value: value for this key
|
||||
:param lang: language code
|
||||
:param tkey: UTF-8 version of the key name
|
||||
"""
|
||||
|
||||
self = str.__new__(cls, text)
|
||||
self.lang = lang
|
||||
self.tkey = tkey
|
||||
return self
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# PNG chunk container (for use with save(pnginfo=))
|
||||
class PngInfo(object):
|
||||
"""
|
||||
PNG chunk container (for use with save(pnginfo=))
|
||||
|
||||
class PngInfo:
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.chunks = []
|
||||
|
||||
def add(self, cid, data):
|
||||
"""Appends an arbitrary chunk. Use with caution.
|
||||
|
||||
:param cid: a byte string, 4 bytes long.
|
||||
:param data: a byte string of the encoded data
|
||||
|
||||
"""
|
||||
|
||||
self.chunks.append((cid, data))
|
||||
|
||||
def add_itxt(self, key, value, lang="", tkey="", zip=False):
|
||||
"""Appends an iTXt chunk.
|
||||
|
||||
:param key: latin-1 encodable text key name
|
||||
:param value: value for this key
|
||||
:param lang: language code
|
||||
:param tkey: UTF-8 version of the key name
|
||||
:param zip: compression flag
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(key, bytes):
|
||||
key = key.encode("latin-1", "strict")
|
||||
if not isinstance(value, bytes):
|
||||
|
@ -184,7 +224,6 @@ class PngInfo:
|
|||
tkey = tkey.encode("utf-8", "strict")
|
||||
|
||||
if zip:
|
||||
import zlib
|
||||
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" +
|
||||
zlib.compress(value))
|
||||
else:
|
||||
|
@ -192,6 +231,14 @@ class PngInfo:
|
|||
value)
|
||||
|
||||
def add_text(self, key, value, zip=0):
|
||||
"""Appends a text chunk.
|
||||
|
||||
:param key: latin-1 encodable text key name
|
||||
:param value: value for this key, text or an
|
||||
:py:class:`PIL.PngImagePlugin.iTXt` instance
|
||||
:param zip: compression flag
|
||||
|
||||
"""
|
||||
if isinstance(value, iTXt):
|
||||
return self.add_itxt(key, value, value.lang, value.tkey, bool(zip))
|
||||
|
||||
|
@ -206,7 +253,6 @@ class PngInfo:
|
|||
key = key.encode('latin-1', 'strict')
|
||||
|
||||
if zip:
|
||||
import zlib
|
||||
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
|
||||
else:
|
||||
self.add(b"tEXt", key + b"\0" + value)
|
||||
|
@ -229,6 +275,14 @@ class PngStream(ChunkStream):
|
|||
self.im_tile = None
|
||||
self.im_palette = None
|
||||
|
||||
self.text_memory = 0
|
||||
|
||||
def check_text_memory(self, chunklen):
|
||||
self.text_memory += chunklen
|
||||
if self.text_memory > MAX_TEXT_MEMORY:
|
||||
raise ValueError("Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" %
|
||||
self.text_memory)
|
||||
|
||||
def chunk_iCCP(self, pos, length):
|
||||
|
||||
# ICC profile
|
||||
|
@ -247,7 +301,7 @@ class PngStream(ChunkStream):
|
|||
raise SyntaxError("Unknown compression method %s in iCCP chunk" %
|
||||
comp_method)
|
||||
try:
|
||||
icc_profile = zlib.decompress(s[i+2:])
|
||||
icc_profile = _safe_zlib_decompress(s[i+2:])
|
||||
except zlib.error:
|
||||
icc_profile = None # FIXME
|
||||
self.im_info["icc_profile"] = icc_profile
|
||||
|
@ -297,6 +351,8 @@ class PngStream(ChunkStream):
|
|||
i = s.find(b"\0")
|
||||
if i >= 0:
|
||||
self.im_info["transparency"] = i
|
||||
elif _null_palette.match(s):
|
||||
self.im_info["transparency"] = 0
|
||||
else:
|
||||
self.im_info["transparency"] = s
|
||||
elif self.im_mode == "L":
|
||||
|
@ -341,6 +397,8 @@ class PngStream(ChunkStream):
|
|||
v = v.decode('latin-1', 'replace')
|
||||
|
||||
self.im_info[k] = self.im_text[k] = v
|
||||
self.check_text_memory(len(v))
|
||||
|
||||
return s
|
||||
|
||||
def chunk_zTXt(self, pos, length):
|
||||
|
@ -359,9 +417,8 @@ class PngStream(ChunkStream):
|
|||
if comp_method != 0:
|
||||
raise SyntaxError("Unknown compression method %s in zTXt chunk" %
|
||||
comp_method)
|
||||
import zlib
|
||||
try:
|
||||
v = zlib.decompress(v[1:])
|
||||
v = _safe_zlib_decompress(v[1:])
|
||||
except zlib.error:
|
||||
v = b""
|
||||
|
||||
|
@ -371,6 +428,8 @@ class PngStream(ChunkStream):
|
|||
v = v.decode('latin-1', 'replace')
|
||||
|
||||
self.im_info[k] = self.im_text[k] = v
|
||||
self.check_text_memory(len(v))
|
||||
|
||||
return s
|
||||
|
||||
def chunk_iTXt(self, pos, length):
|
||||
|
@ -390,9 +449,8 @@ class PngStream(ChunkStream):
|
|||
return s
|
||||
if cf != 0:
|
||||
if cm == 0:
|
||||
import zlib
|
||||
try:
|
||||
v = zlib.decompress(v)
|
||||
v = _safe_zlib_decompress(v)
|
||||
except zlib.error:
|
||||
return s
|
||||
else:
|
||||
|
@ -407,6 +465,7 @@ class PngStream(ChunkStream):
|
|||
return s
|
||||
|
||||
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
|
||||
self.check_text_memory(len(v))
|
||||
|
||||
return s
|
||||
|
||||
|
@ -564,7 +623,7 @@ def putchunk(fp, cid, *data):
|
|||
fp.write(o16(hi) + o16(lo))
|
||||
|
||||
|
||||
class _idat:
|
||||
class _idat(object):
|
||||
# wrap output from the encoder in IDAT chunks
|
||||
|
||||
def __init__(self, fp, chunk):
|
||||
|
@ -674,10 +733,6 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
|||
alpha_bytes = 2**bits
|
||||
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
||||
|
||||
if 0:
|
||||
# FIXME: to be supported some day
|
||||
chunk(fp, b"gAMA", o32(int(gamma * 100000.0)))
|
||||
|
||||
dpi = im.encoderinfo.get("dpi")
|
||||
if dpi:
|
||||
chunk(fp, b"pHYs",
|
||||
|
@ -719,7 +774,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
|||
def getchunks(im, **params):
|
||||
"""Return a list of PNG chunks representing this image."""
|
||||
|
||||
class collector:
|
||||
class collector(object):
|
||||
data = []
|
||||
|
||||
def write(self, data):
|
||||
|
|
|
@ -132,6 +132,10 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
self._fp = self.fp
|
||||
self.frame = 0
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return len(self.layers)
|
||||
|
||||
def seek(self, layer):
|
||||
# seek to given layer (1..max)
|
||||
if layer == self.frame:
|
||||
|
|
|
@ -51,10 +51,10 @@ class PyAccess(object):
|
|||
self.ysize = vals['ysize']
|
||||
|
||||
if DEBUG:
|
||||
print (vals)
|
||||
print(vals)
|
||||
self._post_init()
|
||||
|
||||
def _post_init():
|
||||
def _post_init(self):
|
||||
pass
|
||||
|
||||
def __setitem__(self, xy, color):
|
||||
|
@ -78,6 +78,8 @@ class PyAccess(object):
|
|||
images
|
||||
|
||||
:param xy: The pixel coordinate, given as (x, y).
|
||||
:returns: a pixel value for single band images, a tuple of
|
||||
pixel values for multiband images.
|
||||
"""
|
||||
|
||||
(x, y) = self.check_xy(xy)
|
||||
|
@ -192,7 +194,7 @@ class _PyAccessI16_L(PyAccess):
|
|||
pixel = self.pixels[y][x]
|
||||
try:
|
||||
color = min(color, 65535)
|
||||
except:
|
||||
except TypeError:
|
||||
color = min(color[0], 65535)
|
||||
|
||||
pixel.l = color & 0xFF
|
||||
|
|
|
@ -48,7 +48,7 @@ def isInt(f):
|
|||
return 1
|
||||
else:
|
||||
return 0
|
||||
except:
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
iforms = [1, 3, -11, -12, -21, -22]
|
||||
|
@ -127,12 +127,12 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
if self.istack == 0 and self.imgnumber == 0:
|
||||
# stk=0, img=0: a regular 2D image
|
||||
offset = hdrlen
|
||||
self.nimages = 1
|
||||
self._nimages = 1
|
||||
elif self.istack > 0 and self.imgnumber == 0:
|
||||
# stk>0, img=0: Opening the stack for the first time
|
||||
self.imgbytes = int(h[12]) * int(h[2]) * 4
|
||||
self.hdrlen = hdrlen
|
||||
self.nimages = int(h[26])
|
||||
self._nimages = int(h[26])
|
||||
# Point to the first image in the stack
|
||||
offset = hdrlen * 2
|
||||
self.imgnumber = 1
|
||||
|
@ -154,6 +154,10 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
(self.rawmode, 0, 1))]
|
||||
self.__fp = self.fp # FIXME: hack
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return self._nimages
|
||||
|
||||
# 1st image index is zero (although SPIDER imgnumber starts at 1)
|
||||
def tell(self):
|
||||
if self.imgnumber < 1:
|
||||
|
@ -164,7 +168,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
def seek(self, frame):
|
||||
if self.istack == 0:
|
||||
return
|
||||
if frame >= self.nimages:
|
||||
if frame >= self._nimages:
|
||||
raise EOFError("attempt to seek past end of file")
|
||||
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
||||
self.fp = self.__fp
|
||||
|
@ -173,11 +177,11 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
|
||||
# returns a byte image after rescaling to 0..255
|
||||
def convert2byte(self, depth=255):
|
||||
(min, max) = self.getextrema()
|
||||
(minimum, maximum) = self.getextrema()
|
||||
m = 1
|
||||
if max != min:
|
||||
m = depth / (max-min)
|
||||
b = -m * min
|
||||
if maximum != minimum:
|
||||
m = depth / (maximum-minimum)
|
||||
b = -m * minimum
|
||||
return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
|
||||
|
||||
# returns a ImageTk.PhotoImage object, after rescaling to 0..255
|
||||
|
@ -271,7 +275,7 @@ def _save(im, fp, filename):
|
|||
|
||||
def _save_spider(im, fp, filename):
|
||||
# get the filename extension and register it with Image
|
||||
fn, ext = os.path.splitext(filename)
|
||||
ext = os.path.splitext(filename)[1]
|
||||
Image.register_extension("SPIDER", ext)
|
||||
_save(im, fp, filename)
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ import sys
|
|||
import collections
|
||||
import itertools
|
||||
import os
|
||||
import io
|
||||
|
||||
# Set these to true to force use of libtiff for reading or writing.
|
||||
READ_LIBTIFF = False
|
||||
|
@ -149,6 +150,7 @@ OPEN_INFO = {
|
|||
(II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
|
||||
(II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
|
||||
(II, 1, 1, 1, (1,), ()): ("1", "1"),
|
||||
(II, 1, 1, 1, (4,), ()): ("L", "L;4"),
|
||||
(II, 1, 1, 2, (1,), ()): ("1", "1;R"),
|
||||
(II, 1, 1, 1, (8,), ()): ("L", "L"),
|
||||
(II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
|
||||
|
@ -292,7 +294,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
|||
|
||||
def named(self):
|
||||
"""
|
||||
Returns the complete tag dictionary, with named tags where posible.
|
||||
Returns the complete tag dictionary, with named tags where possible.
|
||||
"""
|
||||
from PIL import TiffTags
|
||||
result = {}
|
||||
|
@ -424,6 +426,11 @@ class ImageFileDirectory(collections.MutableMapping):
|
|||
for i in range(i16(fp.read(2))):
|
||||
|
||||
ifd = fp.read(12)
|
||||
if len(ifd) != 12:
|
||||
warnings.warn("Possibly corrupt EXIF data. "
|
||||
"Expecting to read 12 bytes but only got %d."
|
||||
% (len(ifd)))
|
||||
continue
|
||||
|
||||
tag, typ = i16(ifd), i16(ifd, 2)
|
||||
|
||||
|
@ -449,10 +456,10 @@ class ImageFileDirectory(collections.MutableMapping):
|
|||
if size > 4:
|
||||
here = fp.tell()
|
||||
if Image.DEBUG:
|
||||
print ("Tag Location: %s" %here)
|
||||
print("Tag Location: %s" % here)
|
||||
fp.seek(i32(ifd, 8))
|
||||
if Image.DEBUG:
|
||||
print ("Data Location: %s" %fp.tell())
|
||||
print("Data Location: %s" % fp.tell())
|
||||
data = ImageFile._safe_read(fp, size)
|
||||
fp.seek(here)
|
||||
else:
|
||||
|
@ -474,7 +481,14 @@ class ImageFileDirectory(collections.MutableMapping):
|
|||
else:
|
||||
print("- value:", self[tag])
|
||||
|
||||
self.next = i32(fp.read(4))
|
||||
ifd = fp.read(4)
|
||||
if len(ifd) != 4:
|
||||
warnings.warn("Possibly corrupt EXIF data. "
|
||||
"Expecting to read 4 bytes but only got %d."
|
||||
% (len(ifd)))
|
||||
return
|
||||
|
||||
self.next = i32(ifd)
|
||||
|
||||
# save primitives
|
||||
|
||||
|
@ -504,7 +518,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
|||
typ = self.tagtype[tag]
|
||||
|
||||
if Image.DEBUG:
|
||||
print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
|
||||
print("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
|
||||
|
||||
if typ == 1:
|
||||
# byte data
|
||||
|
@ -515,6 +529,15 @@ class ImageFileDirectory(collections.MutableMapping):
|
|||
elif typ == 7:
|
||||
# untyped data
|
||||
data = value = b"".join(value)
|
||||
elif typ in (11, 12):
|
||||
# float value
|
||||
tmap = {11: 'f', 12: 'd'}
|
||||
if not isinstance(value, tuple):
|
||||
value = (value,)
|
||||
a = array.array(tmap[typ], value)
|
||||
if self.prefix != native_prefix:
|
||||
a.byteswap()
|
||||
data = a.tostring()
|
||||
elif isStringType(value[0]):
|
||||
# string data
|
||||
if isinstance(value, tuple):
|
||||
|
@ -625,54 +648,63 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
self.__first = self.__next = self.ifd.i32(ifh, 4)
|
||||
self.__frame = -1
|
||||
self.__fp = self.fp
|
||||
self._frame_pos = []
|
||||
self._n_frames = None
|
||||
|
||||
if Image.DEBUG:
|
||||
print ("*** TiffImageFile._open ***")
|
||||
print ("- __first:", self.__first)
|
||||
print ("- ifh: ", ifh)
|
||||
print("*** TiffImageFile._open ***")
|
||||
print("- __first:", self.__first)
|
||||
print("- ifh: ", ifh)
|
||||
|
||||
# and load the first frame
|
||||
self._seek(0)
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
if self._n_frames is None:
|
||||
current = self.tell()
|
||||
try:
|
||||
while True:
|
||||
self._seek(self.tell() + 1)
|
||||
except EOFError:
|
||||
self._n_frames = self.tell() + 1
|
||||
self.seek(current)
|
||||
return self._n_frames
|
||||
|
||||
def seek(self, frame):
|
||||
"Select a given frame as current image"
|
||||
if frame < 0:
|
||||
frame = 0
|
||||
self._seek(frame)
|
||||
self._seek(max(frame, 0)) # Questionable backwards compatibility.
|
||||
# Create a new core image object on second and
|
||||
# subsequent frames in the image. Image may be
|
||||
# different size/mode.
|
||||
Image._decompression_bomb_check(self.size)
|
||||
self.im = Image.core.new(self.mode, self.size)
|
||||
|
||||
def tell(self):
|
||||
"Return the current frame number"
|
||||
return self._tell()
|
||||
|
||||
def _seek(self, frame):
|
||||
self.fp = self.__fp
|
||||
if frame < self.__frame:
|
||||
# rewind file
|
||||
self.__frame = -1
|
||||
self.__next = self.__first
|
||||
while self.__frame < frame:
|
||||
while len(self._frame_pos) <= frame:
|
||||
if not self.__next:
|
||||
raise EOFError("no more images in TIFF file")
|
||||
if Image.DEBUG:
|
||||
print("Seeking to frame %s, on frame %s, __next %s, location: %s"%
|
||||
(frame, self.__frame, self.__next, self.fp.tell()))
|
||||
print("Seeking to frame %s, on frame %s, __next %s, location: %s" %
|
||||
(frame, self.__frame, self.__next, self.fp.tell()))
|
||||
# reset python3 buffered io handle in case fp
|
||||
# was passed to libtiff, invalidating the buffer
|
||||
self.fp.tell()
|
||||
self.fp.seek(self.__next)
|
||||
self._frame_pos.append(self.__next)
|
||||
if Image.DEBUG:
|
||||
print("Loading tags, location: %s"%self.fp.tell())
|
||||
print("Loading tags, location: %s" % self.fp.tell())
|
||||
self.tag.load(self.fp)
|
||||
self.__next = self.tag.next
|
||||
self.__frame += 1
|
||||
self.fp.seek(self._frame_pos[frame])
|
||||
self.tag.load(self.fp)
|
||||
self.__frame = frame
|
||||
self._setup()
|
||||
|
||||
def _tell(self):
|
||||
|
||||
def tell(self):
|
||||
"Return the current frame number"
|
||||
return self.__frame
|
||||
|
||||
def _decoder(self, rawmode, layer, tile=None):
|
||||
|
@ -720,8 +752,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
|
||||
# (self._compression, (extents tuple),
|
||||
# 0, (rawmode, self._compression, fp))
|
||||
ignored, extents, ignored_2, args = self.tile[0]
|
||||
args = args + (self.ifd.offset,)
|
||||
extents = self.tile[0][1]
|
||||
args = self.tile[0][3] + (self.ifd.offset,)
|
||||
decoder = Image._getdecoder(self.mode, 'libtiff', args,
|
||||
self.decoderconfig)
|
||||
try:
|
||||
|
@ -738,21 +770,21 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# Rearranging for supporting byteio items, since they have a fileno
|
||||
# that returns an IOError if there's no underlying fp. Easier to
|
||||
# dea. with here by reordering.
|
||||
# deal with here by reordering.
|
||||
if Image.DEBUG:
|
||||
print ("have getvalue. just sending in a string from getvalue")
|
||||
print("have getvalue. just sending in a string from getvalue")
|
||||
n, err = decoder.decode(self.fp.getvalue())
|
||||
elif hasattr(self.fp, "fileno"):
|
||||
# we've got a actual file on disk, pass in the fp.
|
||||
if Image.DEBUG:
|
||||
print ("have fileno, calling fileno version of the decoder.")
|
||||
print("have fileno, calling fileno version of the decoder.")
|
||||
self.fp.seek(0)
|
||||
# 4 bytes, otherwise the trace might error out
|
||||
n, err = decoder.decode(b"fpfp")
|
||||
else:
|
||||
# we have something else.
|
||||
if Image.DEBUG:
|
||||
print ("don't have fileno or getvalue. just reading")
|
||||
print("don't have fileno or getvalue. just reading")
|
||||
# UNDONE -- so much for that buffer size thing.
|
||||
n, err = decoder.decode(self.fp.read())
|
||||
|
||||
|
@ -932,7 +964,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
(0, min(y, ysize), w, min(y+h, ysize)),
|
||||
offsets[i], a))
|
||||
if Image.DEBUG:
|
||||
print ("tiles: ", self.tile)
|
||||
print("tiles: ", self.tile)
|
||||
y = y + h
|
||||
if y >= self.size[1]:
|
||||
x = y = 0
|
||||
|
@ -967,14 +999,14 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
# fixup palette descriptor
|
||||
|
||||
if self.mode == "P":
|
||||
palette = [o8(a // 256) for a in self.tag[COLORMAP]]
|
||||
palette = [o8(b // 256) for b in self.tag[COLORMAP]]
|
||||
self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Write TIFF files
|
||||
|
||||
# little endian is default except for image modes with
|
||||
# explict big endian byte-order
|
||||
# explicit big endian byte-order
|
||||
|
||||
SAVE_INFO = {
|
||||
# mode => rawmode, byteorder, photometrics,
|
||||
|
@ -1066,31 +1098,25 @@ def _save(im, fp, filename):
|
|||
if "icc_profile" in im.info:
|
||||
ifd[ICCPROFILE] = im.info["icc_profile"]
|
||||
|
||||
if "description" in im.encoderinfo:
|
||||
ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"]
|
||||
if "resolution" in im.encoderinfo:
|
||||
ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \
|
||||
= _cvt_res(im.encoderinfo["resolution"])
|
||||
if "x resolution" in im.encoderinfo:
|
||||
ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"])
|
||||
if "y resolution" in im.encoderinfo:
|
||||
ifd[Y_RESOLUTION] = _cvt_res(im.encoderinfo["y resolution"])
|
||||
if "resolution unit" in im.encoderinfo:
|
||||
unit = im.encoderinfo["resolution unit"]
|
||||
if unit == "inch":
|
||||
ifd[RESOLUTION_UNIT] = 2
|
||||
elif unit == "cm" or unit == "centimeter":
|
||||
ifd[RESOLUTION_UNIT] = 3
|
||||
else:
|
||||
ifd[RESOLUTION_UNIT] = 1
|
||||
if "software" in im.encoderinfo:
|
||||
ifd[SOFTWARE] = im.encoderinfo["software"]
|
||||
if "date time" in im.encoderinfo:
|
||||
ifd[DATE_TIME] = im.encoderinfo["date time"]
|
||||
if "artist" in im.encoderinfo:
|
||||
ifd[ARTIST] = im.encoderinfo["artist"]
|
||||
if "copyright" in im.encoderinfo:
|
||||
ifd[COPYRIGHT] = im.encoderinfo["copyright"]
|
||||
for key, name, cvt in [
|
||||
(IMAGEDESCRIPTION, "description", lambda x: x),
|
||||
(X_RESOLUTION, "resolution", _cvt_res),
|
||||
(Y_RESOLUTION, "resolution", _cvt_res),
|
||||
(X_RESOLUTION, "x_resolution", _cvt_res),
|
||||
(Y_RESOLUTION, "y_resolution", _cvt_res),
|
||||
(RESOLUTION_UNIT, "resolution_unit",
|
||||
lambda x: {"inch": 2, "cm": 3, "centimeter": 3}.get(x, 1)),
|
||||
(SOFTWARE, "software", lambda x: x),
|
||||
(DATE_TIME, "date_time", lambda x: x),
|
||||
(ARTIST, "artist", lambda x: x),
|
||||
(COPYRIGHT, "copyright", lambda x: x)]:
|
||||
name_with_spaces = name.replace("_", " ")
|
||||
if "_" in name and name_with_spaces in im.encoderinfo:
|
||||
warnings.warn("%r is deprecated; use %r instead" %
|
||||
(name_with_spaces, name), DeprecationWarning)
|
||||
ifd[key] = cvt(im.encoderinfo[name.replace("_", " ")])
|
||||
if name in im.encoderinfo:
|
||||
ifd[key] = cvt(im.encoderinfo[name])
|
||||
|
||||
dpi = im.encoderinfo.get("dpi")
|
||||
if dpi:
|
||||
|
@ -1123,12 +1149,15 @@ def _save(im, fp, filename):
|
|||
|
||||
if libtiff:
|
||||
if Image.DEBUG:
|
||||
print ("Saving using libtiff encoder")
|
||||
print (ifd.items())
|
||||
print("Saving using libtiff encoder")
|
||||
print(ifd.items())
|
||||
_fp = 0
|
||||
if hasattr(fp, "fileno"):
|
||||
fp.seek(0)
|
||||
_fp = os.dup(fp.fileno())
|
||||
try:
|
||||
fp.seek(0)
|
||||
_fp = os.dup(fp.fileno())
|
||||
except io.UnsupportedOperation:
|
||||
pass
|
||||
|
||||
# ICC Profile crashes.
|
||||
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE]
|
||||
|
@ -1153,8 +1182,11 @@ def _save(im, fp, filename):
|
|||
# following tiffcp.c->cpTag->TIFF_RATIONAL
|
||||
atts[k] = float(v[0][0])/float(v[0][1])
|
||||
continue
|
||||
if type(v) == tuple and len(v) > 2:
|
||||
if (type(v) == tuple and
|
||||
(len(v) > 2 or
|
||||
(len(v) == 2 and v[1] == 0))):
|
||||
# List of ints?
|
||||
# Avoid divide by zero in next if-clause
|
||||
if type(v[0]) in (int, float):
|
||||
atts[k] = list(v)
|
||||
continue
|
||||
|
@ -1175,7 +1207,7 @@ def _save(im, fp, filename):
|
|||
atts[k] = v
|
||||
|
||||
if Image.DEBUG:
|
||||
print (atts)
|
||||
print(atts)
|
||||
|
||||
# libtiff always expects the bytes in native order.
|
||||
# we're storing image byte order. So, if the rawmode
|
||||
|
|
|
@ -37,7 +37,7 @@ def register_handler(handler):
|
|||
if hasattr(Image.core, "drawwmf"):
|
||||
# install default handler (windows only)
|
||||
|
||||
class WmfHandler:
|
||||
class WmfHandler(object):
|
||||
|
||||
def open(self, im):
|
||||
im.mode = "RGB"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
# ;-)
|
||||
|
||||
VERSION = '1.1.7' # PIL version
|
||||
PILLOW_VERSION = '2.6.0' # Pillow
|
||||
PILLOW_VERSION = '2.9.0.dev0' # Pillow
|
||||
|
||||
_plugins = ['BmpImagePlugin',
|
||||
'BufrStubImagePlugin',
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from struct import unpack, pack
|
||||
|
||||
if bytes is str:
|
||||
def i8(c):
|
||||
return ord(c)
|
||||
|
@ -34,7 +36,7 @@ def i16le(c, o=0):
|
|||
c: string containing bytes to convert
|
||||
o: offset of bytes to convert in string
|
||||
"""
|
||||
return i8(c[o]) | (i8(c[o+1]) << 8)
|
||||
return unpack("<H", c[o:o+2])[0]
|
||||
|
||||
|
||||
def i32le(c, o=0):
|
||||
|
@ -44,33 +46,31 @@ def i32le(c, o=0):
|
|||
c: string containing bytes to convert
|
||||
o: offset of bytes to convert in string
|
||||
"""
|
||||
return (i8(c[o]) | (i8(c[o+1]) << 8) | (i8(c[o+2]) << 16) |
|
||||
(i8(c[o+3]) << 24))
|
||||
return unpack("<I", c[o:o+4])[0]
|
||||
|
||||
|
||||
def i16be(c, o=0):
|
||||
return (i8(c[o]) << 8) | i8(c[o+1])
|
||||
return unpack(">H", c[o:o+2])[0]
|
||||
|
||||
|
||||
def i32be(c, o=0):
|
||||
return ((i8(c[o]) << 24) | (i8(c[o+1]) << 16) |
|
||||
(i8(c[o+2]) << 8) | i8(c[o+3]))
|
||||
return unpack(">I", c[o:o+4])[0]
|
||||
|
||||
|
||||
# Output, le = little endian, be = big endian
|
||||
def o16le(i):
|
||||
return o8(i) + o8(i >> 8)
|
||||
return pack("<H", i)
|
||||
|
||||
|
||||
def o32le(i):
|
||||
return o8(i) + o8(i >> 8) + o8(i >> 16) + o8(i >> 24)
|
||||
return pack("<I", i)
|
||||
|
||||
|
||||
def o16be(i):
|
||||
return o8(i >> 8) + o8(i)
|
||||
return pack(">H", i)
|
||||
|
||||
|
||||
def o32be(i):
|
||||
return o8(i >> 24) + o8(i >> 16) + o8(i >> 8) + o8(i)
|
||||
return pack(">I", i)
|
||||
|
||||
# End of file
|
||||
|
|
67
PIL/features.py
Normal file
67
PIL/features.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from PIL import Image
|
||||
|
||||
modules = {
|
||||
"pil": "PIL._imaging",
|
||||
"tkinter": "PIL._imagingtk",
|
||||
"freetype2": "PIL._imagingft",
|
||||
"littlecms2": "PIL._imagingcms",
|
||||
"webp": "PIL._webp",
|
||||
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
|
||||
}
|
||||
|
||||
|
||||
def check_module(feature):
|
||||
if feature not in modules:
|
||||
raise ValueError("Unknown module %s" % feature)
|
||||
|
||||
module = modules[feature]
|
||||
|
||||
method_to_call = None
|
||||
if type(module) is tuple:
|
||||
module, method_to_call = module
|
||||
|
||||
try:
|
||||
imported_module = __import__(module)
|
||||
except ImportError:
|
||||
# If a method is being checked, None means that
|
||||
# rather than the method failing, the module required for the method
|
||||
# failed to be imported first
|
||||
return None if method_to_call else False
|
||||
|
||||
if method_to_call:
|
||||
method = getattr(imported_module, method_to_call)
|
||||
return method() is True
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def get_supported_modules():
|
||||
supported_modules = []
|
||||
for feature in modules:
|
||||
if check_module(feature):
|
||||
supported_modules.append(feature)
|
||||
return supported_modules
|
||||
|
||||
codecs = {
|
||||
"jpg": "jpeg",
|
||||
"jpg_2000": "jpeg2k",
|
||||
"zlib": "zip",
|
||||
"libtiff": "libtiff"
|
||||
}
|
||||
|
||||
|
||||
def check_codec(feature):
|
||||
if feature not in codecs:
|
||||
raise ValueError("Unknown codec %s" % feature)
|
||||
|
||||
codec = codecs[feature]
|
||||
|
||||
return codec + "_encoder" in dir(Image.core)
|
||||
|
||||
|
||||
def get_supported_codecs():
|
||||
supported_codecs = []
|
||||
for feature in codecs:
|
||||
if check_codec(feature):
|
||||
supported_codecs.append(feature)
|
||||
return supported_codecs
|
34
README.rst
34
README.rst
|
@ -1,19 +1,23 @@
|
|||
Pillow
|
||||
======
|
||||
|
||||
*Python Imaging Library (Fork)*
|
||||
Python Imaging Library (Fork)
|
||||
-----------------------------
|
||||
|
||||
Pillow is the "friendly" PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation <http://pillow.readthedocs.org/>`_, `check the changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_ and `find out how to contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_.
|
||||
Pillow is the "friendly PIL fork" by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
||||
|
||||
.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
|
||||
..
|
||||
image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
|
||||
:target: https://travis-ci.org/python-pillow/Pillow
|
||||
:alt: Travis CI build status
|
||||
:alt: Travis CI build status (Linux)
|
||||
|
||||
.. image:: https://pypip.in/v/Pillow/badge.png
|
||||
..
|
||||
image:: https://pypip.in/v/Pillow/badge.png
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
.. image:: https://pypip.in/d/Pillow/badge.png
|
||||
..
|
||||
image:: https://pypip.in/d/Pillow/badge.png
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Number of PyPI downloads
|
||||
|
||||
|
@ -24,3 +28,21 @@ Pillow is the "friendly" PIL fork by `Alex Clark and Contributors <https://githu
|
|||
.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.png
|
||||
:target: https://landscape.io/github/python-pillow/Pillow/master
|
||||
:alt: Code health
|
||||
|
||||
More Information
|
||||
----------------
|
||||
|
||||
- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
|
||||
|
||||
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#02b5---117-1995-2010>`_
|
||||
|
||||
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_
|
||||
|
||||
- `Issues <https://github.com/python-pillow/Pillow/issues>`_
|
||||
|
||||
- `Documentation <http://pillow.readthedocs.org/>`_
|
||||
|
||||
- `About <http://pillow.readthedocs.org/about.html>`_
|
||||
- `Guides <http://pillow.readthedocs.org/guides.html>`_
|
||||
- `Installation <http://pillow.readthedocs.org/installation.html>`_
|
||||
- `Reference <http://pillow.readthedocs.org/reference/index.html>`_
|
||||
|
|
114
RELEASING.md
114
RELEASING.md
|
@ -2,29 +2,62 @@
|
|||
|
||||
## Main Release
|
||||
|
||||
Released quarterly.
|
||||
Released quarterly on the first day of January, April, July, October.
|
||||
|
||||
* [ ] Get master to the appropriate code release state. [Travis CI](https://travis-ci.org/python-pillow/Pillow) should be running cleanly for all merges to master.
|
||||
* [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c`, Update date in `CHANGES.rst`.
|
||||
* [ ] Run pre-release check via `make pre`
|
||||
* [ ] Tag and push to release branch in python-pillow repo.
|
||||
* [ ] Upload binaries.
|
||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174
|
||||
* [ ] Develop and prepare release in ``master`` branch.
|
||||
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
|
||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
|
||||
```
|
||||
PIL/__init__.py setup.py _imaging.c
|
||||
```
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] Run pre-release check via `make pre`.
|
||||
* [ ] Create branch and tag for release e.g.:
|
||||
```
|
||||
$ git branch 2.9.x
|
||||
$ git tag 2.9.0
|
||||
$ git push --all
|
||||
$ git push --tags
|
||||
```
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
```
|
||||
$ make sdistup
|
||||
```
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
* [ ] Manually hide old versions on PyPI as needed, such that only the latest main release is visible when viewing https://pypi.python.org/pypi/Pillow
|
||||
|
||||
## Point Release
|
||||
|
||||
Released as required for security or installation fixes.
|
||||
Released as needed for security, installation or critical bug fixes.
|
||||
|
||||
* [ ] Make necessary changes in master.
|
||||
* [ ] Cherry pick individual commits. Touch up `CHANGES.rst` to reflect reality.
|
||||
* [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c`
|
||||
* [ ] Run pre-release check via `make pre`
|
||||
* [ ] Push to release branch in personal repo. Let Travis run cleanly.
|
||||
* [ ] Tag and push to release branch in python-pillow repo.
|
||||
* [ ] Upload binaries.
|
||||
* [ ] Make necessary changes in ``master`` branch.
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] Cherry pick individual commits from ``master`` branch to 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. ``2.9.x``.
|
||||
* [ ] Checkout release branch e.g.:
|
||||
```
|
||||
git checkout -t remotes/origin/2.9.x
|
||||
```
|
||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
|
||||
```
|
||||
PIL/__init__.py setup.py _imaging.c
|
||||
```
|
||||
* [ ] Run pre-release check via `make pre`.
|
||||
* [ ] Create tag for release e.g.:
|
||||
```
|
||||
$ git tag 2.9.1
|
||||
$ git push --tags
|
||||
```
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
```
|
||||
$ make sdistup
|
||||
```
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
|
||||
## Embargoed Release
|
||||
|
||||
Security fixes that need to be pushed to the distros prior to public release.
|
||||
Released as needed privately to individual vendors for critical security-related bug fixes.
|
||||
|
||||
* [ ] Prepare patch for all versions that will get a fix. Test against local installations.
|
||||
* [ ] Commit against master, cherry pick to affected release branches.
|
||||
|
@ -34,26 +67,39 @@ Security fixes that need to be pushed to the distros prior to public release.
|
|||
* [ ] Amend any commits with the CVE #
|
||||
* [ ] On release date, tag and push to GitHub.
|
||||
```
|
||||
git checkout 2.5.x
|
||||
git tag 2.5.3
|
||||
git push origin 2.5.x
|
||||
git push origin --tags
|
||||
git checkout 2.5.x
|
||||
git tag 2.5.3
|
||||
git push origin 2.5.x
|
||||
git push origin --tags
|
||||
```
|
||||
* [ ] Upload binaries
|
||||
|
||||
|
||||
## Binary Upload Process
|
||||
|
||||
* [ ] Ping cgohlke for Windows binaries
|
||||
* [ ] From a clean source directory with no extra temp files:
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
```
|
||||
python setup.py register
|
||||
python setup.py sdist --format=zip upload
|
||||
python setup.py sdist upload
|
||||
$ make sdistup
|
||||
```
|
||||
(Debian requests a tarball, everyone else would just prefer that we choose one and stick to it. So both it is)
|
||||
* [ ] Push a commit to https://github.com/python-pillow/pillow-wheels to build OSX versions (UNDONE latest tag or specific release???)
|
||||
* [ ] Retrieve the OS X Wheels from Rackspace files, upload to PyPi (Twine?)
|
||||
* [ ] Grab Windows binaries, `twine upload dist/*.[whl|egg]`. Manually upload .exe installers.
|
||||
* [ ] Announce release availability. [Twitter](https://twitter.com/pythonpillow), web.
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
|
||||
## Binary Distributions
|
||||
|
||||
### Windows
|
||||
* [ ] Contact @cgohlke for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174.
|
||||
* [ ] Download and extract tarball from @cgohlke and ``twine upload *``.
|
||||
|
||||
### OS X
|
||||
* [ ] Use the [Pillow OS X Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
||||
```
|
||||
$ git checkout https://github.com/python-pillow/pillow-wheels
|
||||
$ cd pillow-wheels
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
$ cd Pillow
|
||||
$ git fetch --all
|
||||
$ git commit -a -m "Pillow -> 2.9.0"
|
||||
$ git push
|
||||
```
|
||||
* [ ] Download distributions from the [Pillow OS X Wheel Builder container](http://cdf58691c5cf45771290-6a3b6a0f5f6ab91aadc447b2a897dd9a.r50.cf2.rackcdn.com/) and ``twine upload *``.
|
||||
|
||||
### Linux
|
||||
|
||||
## Publicize Release
|
||||
|
||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328.
|
||||
|
|
|
@ -63,20 +63,3 @@ explode.py
|
|||
--------------------------------------------------------------------
|
||||
|
||||
Split a sequence file into individual frames.
|
||||
|
||||
image2py.py
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Convert an image to a Python module containing an IMAGE variable.
|
||||
Note that the module using the module must include JPEG and ZIP
|
||||
decoders, unless the -u option is used.
|
||||
|
||||
olesummary.py
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Uses the OleFileIO module to dump the summary information from an OLE
|
||||
structured storage file. This works with most OLE files, including
|
||||
Word documents, FlashPix images, etc.
|
||||
|
||||
Note that datetime fields currently show the number of seconds since
|
||||
January 1st, 1601.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from __future__ import print_function
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
#
|
||||
|
||||
try:
|
||||
from tkinter import *
|
||||
from tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
|
||||
except ImportError:
|
||||
from Tkinter import *
|
||||
from Tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
|
||||
|
||||
from PIL import Image, ImageTk, ImageEnhance
|
||||
import sys
|
||||
|
@ -18,6 +18,7 @@ import sys
|
|||
#
|
||||
# enhancer widget
|
||||
|
||||
|
||||
class Enhance(Frame):
|
||||
def __init__(self, master, image, name, enhancer, lo, hi):
|
||||
Frame.__init__(self, master)
|
||||
|
@ -25,7 +26,7 @@ class Enhance(Frame):
|
|||
# set up the image
|
||||
self.tkim = ImageTk.PhotoImage(image.mode, image.size)
|
||||
self.enhancer = enhancer(image)
|
||||
self.update("1.0") # normalize
|
||||
self.update("1.0") # normalize
|
||||
|
||||
# image window
|
||||
Label(self, image=self.tkim).pack()
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from PIL import Image
|
||||
import os, sys
|
||||
import os
|
||||
import sys
|
||||
|
||||
class Interval:
|
||||
|
||||
def __init__(self, interval = "0"):
|
||||
class Interval(object):
|
||||
|
||||
def __init__(self, interval="0"):
|
||||
|
||||
self.setinterval(interval)
|
||||
|
||||
|
|
|
@ -49,20 +49,23 @@ from PIL.GifImagePlugin import getheader, getdata
|
|||
# --------------------------------------------------------------------
|
||||
# sequence iterator
|
||||
|
||||
class image_sequence:
|
||||
|
||||
class image_sequence(object):
|
||||
def __init__(self, im):
|
||||
self.im = im
|
||||
|
||||
def __getitem__(self, ix):
|
||||
try:
|
||||
if ix:
|
||||
self.im.seek(ix)
|
||||
return self.im
|
||||
except EOFError:
|
||||
raise IndexError # end of sequence
|
||||
raise IndexError # end of sequence
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# straightforward delta encoding
|
||||
|
||||
|
||||
def makedelta(fp, sequence):
|
||||
"""Convert list of image frames to a GIF animation file"""
|
||||
|
||||
|
@ -72,13 +75,13 @@ def makedelta(fp, sequence):
|
|||
|
||||
for im in sequence:
|
||||
|
||||
#
|
||||
# FIXME: write graphics control block before each frame
|
||||
# To specify duration, add the time in milliseconds to getdata(),
|
||||
# e.g. getdata(im, duration=1000)
|
||||
|
||||
if not previous:
|
||||
|
||||
# global header
|
||||
for s in getheader(im) + getdata(im):
|
||||
for s in getheader(im)[0] + getdata(im):
|
||||
fp.write(s)
|
||||
|
||||
else:
|
||||
|
@ -91,7 +94,7 @@ def makedelta(fp, sequence):
|
|||
if bbox:
|
||||
|
||||
# compress difference
|
||||
for s in getdata(im.crop(bbox), offset = bbox[:2]):
|
||||
for s in getdata(im.crop(bbox), offset=bbox[:2]):
|
||||
fp.write(s)
|
||||
|
||||
else:
|
||||
|
@ -109,6 +112,7 @@ def makedelta(fp, sequence):
|
|||
# --------------------------------------------------------------------
|
||||
# main hack
|
||||
|
||||
|
||||
def compress(infile, outfile):
|
||||
|
||||
# open input image, and force loading of first frame
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
#
|
||||
|
||||
try:
|
||||
from tkinter import *
|
||||
from tkinter import Tk, Canvas, NW
|
||||
except ImportError:
|
||||
from Tkinter import *
|
||||
from Tkinter import Tk, Canvas, NW
|
||||
|
||||
from PIL import Image, ImageTk
|
||||
import sys
|
||||
|
@ -20,6 +20,7 @@ import sys
|
|||
#
|
||||
# painter widget
|
||||
|
||||
|
||||
class PaintCanvas(Canvas):
|
||||
def __init__(self, master, image):
|
||||
Canvas.__init__(self, master, width=image.size[0], height=image.size[1])
|
||||
|
@ -33,7 +34,7 @@ class PaintCanvas(Canvas):
|
|||
box = x, y, min(xsize, x+tilesize), min(ysize, y+tilesize)
|
||||
tile = ImageTk.PhotoImage(image.crop(box))
|
||||
self.create_image(x, y, image=tile, anchor=NW)
|
||||
self.tile[(x,y)] = box, tile
|
||||
self.tile[(x, y)] = box, tile
|
||||
|
||||
self.image = image
|
||||
|
||||
|
@ -59,7 +60,7 @@ class PaintCanvas(Canvas):
|
|||
xy, tile = self.tile[(x, y)]
|
||||
tile.paste(self.image.crop(xy))
|
||||
except KeyError:
|
||||
pass # outside the image
|
||||
pass # outside the image
|
||||
self.update_idletasks()
|
||||
|
||||
#
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
import site
|
||||
import getopt, string, sys
|
||||
import getopt
|
||||
import string
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def usage():
|
||||
print("PIL Convert 0.5/1998-12-30 -- convert image files")
|
||||
print("Usage: pilconvert [option] infile outfile")
|
||||
|
@ -47,10 +49,10 @@ except getopt.error as v:
|
|||
print(v)
|
||||
sys.exit(1)
|
||||
|
||||
format = None
|
||||
output_format = None
|
||||
convert = None
|
||||
|
||||
options = { }
|
||||
options = {}
|
||||
|
||||
for o, a in opt:
|
||||
|
||||
|
@ -66,7 +68,7 @@ for o, a in opt:
|
|||
sys.exit(1)
|
||||
|
||||
elif o == "-c":
|
||||
format = a
|
||||
output_format = a
|
||||
|
||||
if o == "-g":
|
||||
convert = "L"
|
||||
|
@ -88,8 +90,8 @@ try:
|
|||
if convert and im.mode != convert:
|
||||
im.draft(convert, im.size)
|
||||
im = im.convert(convert)
|
||||
if format:
|
||||
im.save(argv[1], format, **options)
|
||||
if output_format:
|
||||
im.save(argv[1], output_format, **options)
|
||||
else:
|
||||
im.save(argv[1], **options)
|
||||
except:
|
||||
|
|
|
@ -52,7 +52,8 @@ from __future__ import print_function
|
|||
|
||||
from PIL import Image
|
||||
|
||||
class PILDriver:
|
||||
|
||||
class PILDriver(object):
|
||||
|
||||
verbose = 0
|
||||
|
||||
|
@ -497,10 +498,6 @@ class PILDriver:
|
|||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
pass # not available on all platforms
|
||||
|
||||
# If we see command-line arguments, interpret them as a stack state
|
||||
# and execute. Otherwise go interactive.
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
import site
|
||||
import getopt, glob, sys
|
||||
import getopt
|
||||
import glob
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
@ -59,6 +60,7 @@ for o, a in opt:
|
|||
elif o == "-D":
|
||||
Image.DEBUG += 1
|
||||
|
||||
|
||||
def globfix(files):
|
||||
# expand wildcards where necessary
|
||||
if sys.platform == "win32":
|
||||
|
|
|
@ -14,7 +14,8 @@ from __future__ import print_function
|
|||
|
||||
VERSION = "0.4"
|
||||
|
||||
import glob, sys
|
||||
import glob
|
||||
import sys
|
||||
|
||||
# drivers
|
||||
from PIL import BdfFontFile
|
||||
|
|
|
@ -12,24 +12,25 @@
|
|||
#
|
||||
|
||||
from __future__ import print_function
|
||||
import getopt
|
||||
import os
|
||||
import sys
|
||||
|
||||
VERSION = "pilprint 0.3/2003-05-05"
|
||||
|
||||
from PIL import Image
|
||||
from PIL import PSDraw
|
||||
|
||||
letter = ( 1.0*72, 1.0*72, 7.5*72, 10.0*72 )
|
||||
letter = (1.0*72, 1.0*72, 7.5*72, 10.0*72)
|
||||
|
||||
def description(file, image):
|
||||
import os
|
||||
title = os.path.splitext(os.path.split(file)[1])[0]
|
||||
|
||||
def description(filepath, image):
|
||||
title = os.path.splitext(os.path.split(filepath)[1])[0]
|
||||
format = " (%dx%d "
|
||||
if image.format:
|
||||
format = " (" + image.format + " %dx%d "
|
||||
return title + format % image.size + image.mode + ")"
|
||||
|
||||
import getopt, os, sys
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
print("PIL Print 0.2a1/96-10-04 -- print image files")
|
||||
print("Usage: pilprint files...")
|
||||
|
@ -45,8 +46,8 @@ except getopt.error as v:
|
|||
print(v)
|
||||
sys.exit(1)
|
||||
|
||||
printer = None # print to stdout
|
||||
monochrome = 1 # reduce file size for most common case
|
||||
printer = None # print to stdout
|
||||
monochrome = 1 # reduce file size for most common case
|
||||
|
||||
for o, a in opt:
|
||||
if o == "-d":
|
||||
|
@ -64,12 +65,12 @@ for o, a in opt:
|
|||
# printer channel
|
||||
printer = "lpr -P%s" % a
|
||||
|
||||
for file in argv:
|
||||
for filepath in argv:
|
||||
try:
|
||||
|
||||
im = Image.open(file)
|
||||
im = Image.open(filepath)
|
||||
|
||||
title = description(file, im)
|
||||
title = description(filepath, im)
|
||||
|
||||
if monochrome and im.mode not in ["1", "L"]:
|
||||
im.draft("L", im.size)
|
||||
|
|
|
@ -56,7 +56,7 @@ class UI(Label):
|
|||
del self.im[0]
|
||||
self.image.paste(im)
|
||||
except IndexError:
|
||||
return # end of list
|
||||
return # end of list
|
||||
|
||||
else:
|
||||
|
||||
|
@ -65,7 +65,7 @@ class UI(Label):
|
|||
im.seek(im.tell() + 1)
|
||||
self.image.paste(im)
|
||||
except EOFError:
|
||||
return # end of file
|
||||
return # end of file
|
||||
|
||||
try:
|
||||
duration = im.info["duration"]
|
||||
|
|
|
@ -18,8 +18,9 @@ import sys
|
|||
#
|
||||
# an image viewer
|
||||
|
||||
|
||||
class UI(Frame):
|
||||
def __init__(self, master, im, value = 128):
|
||||
def __init__(self, master, im, value=128):
|
||||
Frame.__init__(self, master)
|
||||
|
||||
self.image = im
|
||||
|
@ -31,7 +32,7 @@ class UI(Frame):
|
|||
self.canvas.pack()
|
||||
|
||||
scale = Scale(self, orient=HORIZONTAL, from_=0, to=255,
|
||||
resolution=1, command=self.update, length=256)
|
||||
resolution=1, command=self.update_scale, length=256)
|
||||
scale.set(value)
|
||||
scale.bind("<ButtonRelease-1>", self.redraw)
|
||||
scale.pack()
|
||||
|
@ -40,21 +41,21 @@ class UI(Frame):
|
|||
# be too slow on some platforms)
|
||||
# self.redraw()
|
||||
|
||||
def update(self, value):
|
||||
def update_scale(self, value):
|
||||
self.value = eval(value)
|
||||
|
||||
self.redraw()
|
||||
|
||||
def redraw(self, event = None):
|
||||
def redraw(self, event=None):
|
||||
|
||||
# create overlay (note the explicit conversion to mode "1")
|
||||
im = self.image.point(lambda v,t=self.value: v>=t, "1")
|
||||
im = self.image.point(lambda v, t=self.value: v >= t, "1")
|
||||
self.overlay = ImageTk.BitmapImage(im, foreground="green")
|
||||
|
||||
# update canvas
|
||||
self.canvas.delete("overlay")
|
||||
self.canvas.create_image(0, 0, image=self.overlay, anchor=NW,
|
||||
tags="overlay")
|
||||
tags="overlay")
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# main
|
||||
|
|
|
@ -7,15 +7,16 @@
|
|||
from __future__ import print_function
|
||||
|
||||
try:
|
||||
from tkinter import *
|
||||
from tkinter import Tk, Label
|
||||
except ImportError:
|
||||
from Tkinter import *
|
||||
from Tkinter import Tk, Label
|
||||
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
#
|
||||
# an image viewer
|
||||
|
||||
|
||||
class UI(Label):
|
||||
|
||||
def __init__(self, master, im):
|
||||
|
|
|
@ -3,7 +3,7 @@ Pillow Tests
|
|||
|
||||
Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``.
|
||||
|
||||
Depedencies
|
||||
Dependencies
|
||||
-----------
|
||||
|
||||
Install::
|
||||
|
|
|
@ -47,7 +47,7 @@ class BenchCffiAccess(PillowTestCase):
|
|||
|
||||
self.assertEqual(caccess[(0, 0)], access[(0, 0)])
|
||||
|
||||
print ("Size: %sx%s" % im.size)
|
||||
print("Size: %sx%s" % im.size)
|
||||
timer(iterate_get, 'PyAccess - get', im.size, access)
|
||||
timer(iterate_set, 'PyAccess - set', im.size, access)
|
||||
timer(iterate_get, 'C-api - get', im.size, caccess)
|
||||
|
|
|
@ -10,7 +10,7 @@ def bench(mode):
|
|||
get = im.im.getpixel
|
||||
xy = 50, 50 # position shouldn't really matter
|
||||
t0 = timeit.default_timer()
|
||||
for i in range(1000000):
|
||||
for _ in range(1000000):
|
||||
get(xy)
|
||||
print(mode, timeit.default_timer() - t0, "us")
|
||||
|
||||
|
|
43
Tests/check_imaging_leaks.py
Normal file
43
Tests/check_imaging_leaks.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import division
|
||||
from helper import unittest, PillowTestCase
|
||||
import sys
|
||||
from PIL import Image, ImageFilter
|
||||
|
||||
min_iterations = 100
|
||||
max_iterations = 10000
|
||||
|
||||
|
||||
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
|
||||
class TestImagingLeaks(PillowTestCase):
|
||||
|
||||
def _get_mem_usage(self):
|
||||
from resource import getpagesize, getrusage, RUSAGE_SELF
|
||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||
return mem * getpagesize() / 1024 / 1024
|
||||
|
||||
def _test_leak(self, min_iterations, max_iterations, fn, *args, **kwargs):
|
||||
mem_limit = None
|
||||
for i in range(max_iterations):
|
||||
fn(*args, **kwargs)
|
||||
mem = self._get_mem_usage()
|
||||
if i < min_iterations:
|
||||
mem_limit = mem + 1
|
||||
continue
|
||||
self.assertLessEqual(mem, mem_limit,
|
||||
msg='memory usage limit exceeded after %d iterations'
|
||||
% (i + 1))
|
||||
|
||||
def test_leak_putdata(self):
|
||||
im = Image.new('RGB', (25, 25))
|
||||
self._test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
|
||||
|
||||
def test_leak_getlist(self):
|
||||
im = Image.new('P', (25, 25))
|
||||
self._test_leak(min_iterations, max_iterations,
|
||||
# Pass a new list at each iteration.
|
||||
lambda: im.point(range(256)))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -21,7 +21,7 @@ class TestJpegLeaks(PillowTestCase):
|
|||
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
|
||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
|
||||
for count in range(iterations):
|
||||
for _ in range(iterations):
|
||||
with Image.open(test_file) as im:
|
||||
im.load()
|
||||
|
||||
|
@ -29,13 +29,13 @@ class TestJpegLeaks(PillowTestCase):
|
|||
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
|
||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
|
||||
for count in range(iterations):
|
||||
for _ in range(iterations):
|
||||
with Image.open(test_file) as im:
|
||||
im.load()
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG2000")
|
||||
test_output.seek(0)
|
||||
output = test_output.read()
|
||||
test_output.read()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from helper import unittest, PillowTestCase, hopper
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
import sys
|
||||
|
||||
|
@ -71,9 +70,8 @@ post-patch:
|
|||
| :@:@@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
|
||||
0 +----------------------------------------------------------------------->Gi
|
||||
0 8.421
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
def test_qtables_leak(self):
|
||||
im = hopper('RGB')
|
||||
|
@ -103,8 +101,7 @@ post-patch:
|
|||
qtables = [standard_l_qtable,
|
||||
standard_chrominance_qtable]
|
||||
|
||||
|
||||
for count in range(iterations):
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", qtables=qtables)
|
||||
|
||||
|
@ -135,7 +132,7 @@ pre patch:
|
|||
0 +----------------------------------------------------------------------->Gi
|
||||
0 11.37
|
||||
|
||||
|
||||
|
||||
post patch:
|
||||
|
||||
MB
|
||||
|
@ -168,12 +165,12 @@ post patch:
|
|||
im = hopper('RGB')
|
||||
exif = b'12345678'*4096
|
||||
|
||||
for count in range(iterations):
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", exif=exif)
|
||||
|
||||
|
||||
"""
|
||||
def test_base_save(self):
|
||||
"""
|
||||
base case:
|
||||
MB
|
||||
20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@:::
|
||||
|
@ -199,11 +196,9 @@ base case:
|
|||
0 +----------------------------------------------------------------------->Gi
|
||||
0 7.882
|
||||
"""
|
||||
|
||||
def test_base_save(self):
|
||||
im = hopper('RGB')
|
||||
|
||||
for count in range(iterations):
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG")
|
||||
|
||||
|
|
48
Tests/check_png_dos.py
Normal file
48
Tests/check_png_dos.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from helper import unittest, PillowTestCase
|
||||
from PIL import Image, PngImagePlugin
|
||||
from io import BytesIO
|
||||
import zlib
|
||||
|
||||
TEST_FILE = "Tests/images/png_decompression_dos.png"
|
||||
|
||||
|
||||
class TestPngDos(PillowTestCase):
|
||||
def test_dos_text(self):
|
||||
|
||||
try:
|
||||
im = Image.open(TEST_FILE)
|
||||
im.load()
|
||||
except ValueError as msg:
|
||||
self.assertTrue(msg, "Decompressed Data Too Large")
|
||||
return
|
||||
|
||||
for s in im.text.values():
|
||||
self.assertLess(len(s), 1024*1024, "Text chunk larger than 1M")
|
||||
|
||||
def test_dos_total_memory(self):
|
||||
im = Image.new('L', (1, 1))
|
||||
compressed_data = zlib.compress('a'*1024*1023)
|
||||
|
||||
info = PngImagePlugin.PngInfo()
|
||||
|
||||
for x in range(64):
|
||||
info.add_text('t%s' % x, compressed_data, 1)
|
||||
info.add_itxt('i%s' % x, compressed_data, zip=True)
|
||||
|
||||
b = BytesIO()
|
||||
im.save(b, 'PNG', pnginfo=info)
|
||||
b.seek(0)
|
||||
|
||||
try:
|
||||
im2 = Image.open(b)
|
||||
except ValueError as msg:
|
||||
self.assertIn("Too much memory", msg)
|
||||
return
|
||||
|
||||
total_len = 0
|
||||
for txt in im2.text.values():
|
||||
total_len += len(txt)
|
||||
self.assertLess(total_len, 64*1024*1024, "Total text chunks greater than 64M")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
38
Tests/check_webp_leaks.py
Normal file
38
Tests/check_webp_leaks.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from __future__ import division
|
||||
from helper import unittest, PillowTestCase
|
||||
import sys
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
|
||||
# Limits for testing the leak
|
||||
mem_limit = 16 # max increase in MB
|
||||
iterations = 5000
|
||||
test_file = "Tests/images/hopper.webp"
|
||||
|
||||
|
||||
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
|
||||
class TestWebPLeaks(PillowTestCase):
|
||||
|
||||
def setUp(self):
|
||||
try:
|
||||
from PIL import _webp
|
||||
except ImportError:
|
||||
self.skipTest('WebP support not installed')
|
||||
|
||||
def _get_mem_usage(self):
|
||||
from resource import getpagesize, getrusage, RUSAGE_SELF
|
||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||
return mem * getpagesize() / 1024 / 1024
|
||||
|
||||
def test_leak_load(self):
|
||||
with open(test_file, 'rb') as f:
|
||||
im_data = f.read()
|
||||
start_mem = self._get_mem_usage()
|
||||
for _ in range(iterations):
|
||||
with Image.open(BytesIO(im_data)) as im:
|
||||
im.load()
|
||||
mem = (self._get_mem_usage() - start_mem)
|
||||
self.assertLess(mem, mem_limit, msg='memory usage limit exceeded')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
BIN
Tests/fonts/DejaVuSans-bitmap.ttf
Normal file
BIN
Tests/fonts/DejaVuSans-bitmap.ttf
Normal file
Binary file not shown.
BIN
Tests/fonts/DejaVuSans.ttf
Normal file
BIN
Tests/fonts/DejaVuSans.ttf
Normal file
Binary file not shown.
|
@ -130,7 +130,7 @@ class PillowTestCase(unittest.TestCase):
|
|||
# Skip if platform/travis matches, and
|
||||
# PILLOW_RUN_KNOWN_BAD is not true in the environment.
|
||||
if bool(os.environ.get('PILLOW_RUN_KNOWN_BAD', False)):
|
||||
print (os.environ.get('PILLOW_RUN_KNOWN_BAD', False))
|
||||
print(os.environ.get('PILLOW_RUN_KNOWN_BAD', False))
|
||||
return
|
||||
|
||||
skip = True
|
||||
|
@ -165,7 +165,6 @@ class PillowTestCase(unittest.TestCase):
|
|||
|
||||
# helpers
|
||||
|
||||
import sys
|
||||
py3 = (sys.version_info >= (3, 0))
|
||||
|
||||
|
||||
|
@ -175,31 +174,33 @@ def fromstring(data):
|
|||
return Image.open(BytesIO(data))
|
||||
|
||||
|
||||
def tostring(im, format, **options):
|
||||
def tostring(im, string_format, **options):
|
||||
from io import BytesIO
|
||||
out = BytesIO()
|
||||
im.save(out, format, **options)
|
||||
im.save(out, string_format, **options)
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
def hopper(mode="RGB", cache={}):
|
||||
def hopper(mode=None, cache={}):
|
||||
from PIL import Image
|
||||
im = None
|
||||
# FIXME: Implement caching to reduce reading from disk but so an original
|
||||
# copy is returned each time and the cached image isn't modified by tests
|
||||
if mode is None:
|
||||
# Always return fresh not-yet-loaded version of image.
|
||||
# Operations on not-yet-loaded images is separate class of errors
|
||||
# what we should catch.
|
||||
return Image.open("Tests/images/hopper.ppm")
|
||||
# Use caching to reduce reading from disk but so an original copy is
|
||||
# returned each time and the cached image isn't modified by tests
|
||||
# (for fast, isolated, repeatable tests).
|
||||
# im = cache.get(mode)
|
||||
im = cache.get(mode)
|
||||
if im is None:
|
||||
if mode == "RGB":
|
||||
im = Image.open("Tests/images/hopper.ppm")
|
||||
elif mode == "F":
|
||||
if mode == "F":
|
||||
im = hopper("L").convert(mode)
|
||||
elif mode[:4] == "I;16":
|
||||
im = hopper("I").convert(mode)
|
||||
else:
|
||||
im = hopper("RGB").convert(mode)
|
||||
# cache[mode] = im
|
||||
return im
|
||||
im = hopper().convert(mode)
|
||||
cache[mode] = im
|
||||
return im.copy()
|
||||
|
||||
|
||||
def command_succeeds(cmd):
|
||||
|
@ -207,7 +208,6 @@ def command_succeeds(cmd):
|
|||
Runs the command, which must be a list of strings. Returns True if the
|
||||
command succeeds, or False if an OSError was raised by subprocess.Popen.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
with open(os.devnull, 'w') as f:
|
||||
try:
|
||||
|
|
BIN
Tests/images/color_snakes.png
Normal file
BIN
Tests/images/color_snakes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
Tests/images/hopper.im
Normal file
BIN
Tests/images/hopper.im
Normal file
Binary file not shown.
BIN
Tests/images/hopper.msp
Normal file
BIN
Tests/images/hopper.msp
Normal file
Binary file not shown.
BIN
Tests/images/hopper_bad_exif.jpg
Normal file
BIN
Tests/images/hopper_bad_exif.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
BIN
Tests/images/hopper_gray_4bpp.tif
Normal file
BIN
Tests/images/hopper_gray_4bpp.tif
Normal file
Binary file not shown.
BIN
Tests/images/hopper_merged.psd
Normal file
BIN
Tests/images/hopper_merged.psd
Normal file
Binary file not shown.
BIN
Tests/images/illu10_no_preview.eps
Normal file
BIN
Tests/images/illu10_no_preview.eps
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user