Merge from master
|
@ -9,3 +9,6 @@ exclude_lines =
|
||||||
# Don't complain if non-runnable code isn't run:
|
# Don't complain if non-runnable code isn't run:
|
||||||
if 0:
|
if 0:
|
||||||
if __name__ == .__main__.:
|
if __name__ == .__main__.:
|
||||||
|
# Don't complain about debug code
|
||||||
|
if Image.DEBUG:
|
||||||
|
if DEBUG:
|
||||||
|
|
6
.gitignore
vendored
|
@ -60,3 +60,9 @@ docs/_build/
|
||||||
\#*#
|
\#*#
|
||||||
.#*
|
.#*
|
||||||
|
|
||||||
|
#Komodo
|
||||||
|
*.komodoproject
|
||||||
|
|
||||||
|
#OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
|
47
.travis.yml
|
@ -3,18 +3,25 @@ language: python
|
||||||
notifications:
|
notifications:
|
||||||
irc: "chat.freenode.net#pil"
|
irc: "chat.freenode.net#pil"
|
||||||
|
|
||||||
|
env: MAX_CONCURRENCY=4
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- "pypy"
|
- "pypy"
|
||||||
|
- "pypy3"
|
||||||
- 2.6
|
- 2.6
|
||||||
- 2.7
|
- 2.7
|
||||||
|
- "2.7_with_system_site_packages" # For PyQt4
|
||||||
- 3.2
|
- 3.2
|
||||||
- 3.3
|
- 3.3
|
||||||
- 3.4
|
- 3.4
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake"
|
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick lcov"
|
||||||
- "pip install cffi"
|
- "pip install cffi"
|
||||||
- "pip install coveralls"
|
- "pip install coveralls nose coveralls-merge"
|
||||||
|
- "gem install coveralls-lcov"
|
||||||
|
- travis_retry pip install pyroma
|
||||||
|
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi
|
||||||
|
|
||||||
# webp
|
# webp
|
||||||
- pushd depends && ./install_webp.sh && popd
|
- pushd depends && ./install_webp.sh && popd
|
||||||
|
@ -25,21 +32,31 @@ install:
|
||||||
script:
|
script:
|
||||||
- coverage erase
|
- coverage erase
|
||||||
- python setup.py clean
|
- python setup.py clean
|
||||||
- python setup.py build_ext --inplace
|
- CFLAGS="-coverage" python setup.py build_ext --inplace
|
||||||
|
|
||||||
# Don't cover PyPy: it fails intermittently and is x5.8 slower (#640)
|
- coverage run --append --include=PIL/* selftest.py
|
||||||
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi
|
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
|
||||||
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi
|
|
||||||
|
|
||||||
# Cover the others
|
|
||||||
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi
|
|
||||||
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi
|
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
|
# gather the coverage data
|
||||||
|
- lcov --capture --directory . -b . --output-file coverage.info
|
||||||
|
# filter to remove system headers
|
||||||
|
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
|
||||||
|
# convert to json
|
||||||
|
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
|
||||||
|
|
||||||
- coverage report
|
- coverage report
|
||||||
- coveralls
|
- coveralls-merge coverage.c.json
|
||||||
|
|
||||||
|
|
||||||
- pip install pep8 pyflakes
|
- pip install pep8 pyflakes
|
||||||
- pep8 PIL/*.py
|
- pep8 --statistics --count PIL/*.py
|
||||||
- pyflakes PIL/*.py
|
- pep8 --statistics --count Tests/*.py
|
||||||
- pep8 Tests/*.py
|
- pyflakes PIL/*.py | tee >(wc -l)
|
||||||
- pyflakes Tests/*.py
|
- 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
|
||||||
|
|
199
CHANGES.rst
|
@ -1,8 +1,164 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
2.5.0 (unreleased)
|
2.6.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport)
|
||||||
|
[Andrew Drake]
|
||||||
|
|
||||||
|
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin
|
||||||
|
[Andrew Drake]
|
||||||
|
|
||||||
|
- setup.py: Close open file handle before deleting #844
|
||||||
|
[divergentdave]
|
||||||
|
|
||||||
|
- Return Profile with Transformed Images #837
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Changed docstring to refer to the correct function #836
|
||||||
|
[MatMoore]
|
||||||
|
|
||||||
|
- Adding coverage support for C code tests #833
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- PyPy performance improvements #821
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Added support for reading MPO files
|
||||||
|
[Feneric]
|
||||||
|
|
||||||
|
- Added support for encoding and decoding iTXt chunks #818
|
||||||
|
[dolda2000]
|
||||||
|
|
||||||
|
- HSV Support #816
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Removed unusable ImagePalette.new()
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Fix Scrambled XPM #808
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Doc cleanup
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix `ImageStat` docs
|
||||||
|
[akx]
|
||||||
|
|
||||||
|
- Added docs for ExifTags
|
||||||
|
[Wintermute3]
|
||||||
|
|
||||||
|
- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Fix return value of FreeTypeFont.textsize() does not include font offsets
|
||||||
|
[tk0miya]
|
||||||
|
|
||||||
|
- Fix dispose calculations for animated GIFs #765
|
||||||
|
[larsjsol]
|
||||||
|
|
||||||
|
- Added class checking to Image __eq__ function #775
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Test PalmImagePlugin and method to skip known bad tests #776
|
||||||
|
[hugovk, wiredfool]
|
||||||
|
|
||||||
|
2.5.3 (2014-08-18)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport)
|
||||||
|
[Andrew Drake]
|
||||||
|
|
||||||
|
|
||||||
|
2.5.2 (2014-08-13)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport)
|
||||||
|
[Andrew Drake]
|
||||||
|
|
||||||
|
|
||||||
|
2.5.1 (2014-07-10)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Fixed install issue if Multiprocessing.Pool is not available
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- 32bit mult overflow fix #782
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
2.5.0 (2014-07-01)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Imagedraw rewrite
|
||||||
|
[terseus, wiredfool]
|
||||||
|
|
||||||
|
- Add support for multithreaded test execution
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Prevent shell injection #748
|
||||||
|
[mbrown1413, wiredfool]
|
||||||
|
|
||||||
|
- Support for Resolution in BMP files #734
|
||||||
|
[gcq]
|
||||||
|
|
||||||
|
- Fix error in setup.py for Python 3
|
||||||
|
[matthew-brett]
|
||||||
|
|
||||||
|
- Pyroma fix and add Python 3.4 to setup metadata #742
|
||||||
|
[wirefool]
|
||||||
|
|
||||||
|
- Top level flake8 fixes #741
|
||||||
|
[aclark]
|
||||||
|
|
||||||
|
- Remove obsolete Animated Raster Graphics (ARG) support
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Fix test_imagedraw failures #727
|
||||||
|
[cgohlke]
|
||||||
|
|
||||||
|
- Fix AttributeError: class Image has no attribute 'DEBUG' #726
|
||||||
|
[cgohlke]
|
||||||
|
|
||||||
|
- Fix msvc warning: 'inline' : macro redefinition #725
|
||||||
|
[cgohlke]
|
||||||
|
|
||||||
|
- Cleanup #654
|
||||||
|
[dvska, hugovk, wiredfool]
|
||||||
|
|
||||||
|
- 16-bit monochrome support for JPEG2000
|
||||||
|
[videan42]
|
||||||
|
|
||||||
|
- Fixed ImagePalette.save
|
||||||
|
[brightpisces]
|
||||||
|
|
||||||
|
- Support JPEG qtables
|
||||||
|
[csinchok]
|
||||||
|
|
||||||
|
- Add binary morphology addon
|
||||||
|
[dov, wiredfool]
|
||||||
|
|
||||||
|
- Decompression bomb protection
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Put images in a single directory
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Support OpenJpeg 2.1
|
||||||
|
[al45tair]
|
||||||
|
|
||||||
|
- Remove unistd.h #include for all platforms
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Use unittest for tests
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- ImageCms fixes
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Added more ImageDraw tests
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
- Added tests for Spider files
|
- Added tests for Spider files
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
|
@ -150,6 +306,12 @@ Changelog (Pillow)
|
||||||
- Prefer homebrew freetype over X11 freetype (but still allow both)
|
- Prefer homebrew freetype over X11 freetype (but still allow both)
|
||||||
[dmckeone]
|
[dmckeone]
|
||||||
|
|
||||||
|
2.3.2 (2014-08-13)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport)
|
||||||
|
[Andrew Drake]
|
||||||
|
|
||||||
2.3.1 (2014-03-14)
|
2.3.1 (2014-03-14)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -431,10 +593,14 @@ Changelog (Pillow)
|
||||||
2.0.0 (2013-03-15)
|
2.0.0 (2013-03-15)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
.. Note:: Special thanks to Christoph Gohlke and Eric Soroos for assisting with a pre-PyCon 2013 release!
|
||||||
|
|
||||||
|
- Many other bug fixes and enhancements by many other people.
|
||||||
|
|
||||||
- Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.)
|
- Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.)
|
||||||
[fluggo]
|
[fluggo]
|
||||||
|
|
||||||
- Add PyPy support (experimental, please see: https://github.com/python-imaging/Pillow/issues/67)
|
- Add PyPy support (experimental, please see: https://github.com/python-pillow/Pillow/issues/67)
|
||||||
|
|
||||||
- Add WebP support.
|
- Add WebP support.
|
||||||
[lqs]
|
[lqs]
|
||||||
|
@ -454,10 +620,6 @@ Changelog (Pillow)
|
||||||
- Added support for PNG images with transparency palette.
|
- Added support for PNG images with transparency palette.
|
||||||
[d-schmidt]
|
[d-schmidt]
|
||||||
|
|
||||||
- Many other bug fixes and enhancements by many other people (see commit log and/or docs/CONTRIBUTORS.txt).
|
|
||||||
|
|
||||||
- Special thanks to Christoph Gohlke and Eric Soroos for rallying around the effort to get a release out for PyCon 2013.
|
|
||||||
|
|
||||||
1.7.8 (2012-11-01)
|
1.7.8 (2012-11-01)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -530,44 +692,55 @@ Changelog (Pillow)
|
||||||
[elro]
|
[elro]
|
||||||
|
|
||||||
- Doc fixes
|
- Doc fixes
|
||||||
|
[aclark]
|
||||||
|
|
||||||
1.5 (11/28/2010)
|
1.5 (11/28/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Module and package fixes
|
- Module and package fixes
|
||||||
|
[aclark]
|
||||||
|
|
||||||
1.4 (11/28/2010)
|
1.4 (11/28/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Doc fixes
|
- Doc fixes
|
||||||
|
[aclark]
|
||||||
|
|
||||||
1.3 (11/28/2010)
|
1.3 (11/28/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Add support for /lib64 and /usr/lib64 library directories on Linux
|
- Add support for /lib64 and /usr/lib64 library directories on Linux
|
||||||
|
[aclark]
|
||||||
|
|
||||||
- Doc fixes
|
- Doc fixes
|
||||||
|
[aclark]
|
||||||
|
|
||||||
1.2 (08/02/2010)
|
1.2 (08/02/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- On OS X also check for freetype2 in the X11 path [jezdez]
|
- On OS X also check for freetype2 in the X11 path
|
||||||
- Doc fixes [aclark]
|
[jezdez]
|
||||||
|
|
||||||
|
- Doc fixes
|
||||||
|
[aclark]
|
||||||
|
|
||||||
1.1 (07/31/2010)
|
1.1 (07/31/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Removed setuptools_hg requirement
|
- Removed setuptools_hg requirement
|
||||||
|
[aclark]
|
||||||
|
|
||||||
- Doc fixes
|
- Doc fixes
|
||||||
|
[aclark]
|
||||||
|
|
||||||
1.0 (07/30/2010)
|
1.0 (07/30/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Forked PIL based on Hanno Schlichting's re-packaging
|
- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required.
|
||||||
(http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz)
|
- Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
|
||||||
|
[aclark]
|
||||||
|
|
||||||
- Remove support for importing from the standard namespace
|
.. Note:: What follows is the original PIL 1.1.7 CHANGES
|
||||||
|
|
||||||
.. Note:: What follows is the original PIL 1.1.7 CHANGES file contents
|
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|
26
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Fixes, Features and Changes
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
- Push to your fork, and make a pull request.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Bugs
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Let us know:
|
||||||
|
- What did you do?
|
||||||
|
- What did you expect to happen?
|
||||||
|
- What actually happened?
|
||||||
|
- What versions of Pillow and Python are you using?
|
34
MANIFEST.in
|
@ -1,7 +1,11 @@
|
||||||
|
|
||||||
include *.c
|
include *.c
|
||||||
include *.h
|
include *.h
|
||||||
|
include *.md
|
||||||
include *.py
|
include *.py
|
||||||
include *.rst
|
include *.rst
|
||||||
|
include *.txt
|
||||||
|
include .coveragerc
|
||||||
include .gitattributes
|
include .gitattributes
|
||||||
include .travis.yml
|
include .travis.yml
|
||||||
include Makefile
|
include Makefile
|
||||||
|
@ -23,33 +27,59 @@ recursive-include Images *.xpm
|
||||||
recursive-include PIL *.md
|
recursive-include PIL *.md
|
||||||
recursive-include Sane *.c
|
recursive-include Sane *.c
|
||||||
recursive-include Sane *.py
|
recursive-include Sane *.py
|
||||||
|
recursive-include Sane *.rst
|
||||||
recursive-include Sane *.txt
|
recursive-include Sane *.txt
|
||||||
recursive-include Sane CHANGES
|
recursive-include Sane CHANGES
|
||||||
recursive-include Sane README
|
recursive-include Sane README
|
||||||
recursive-include Scripts *.py
|
recursive-include Scripts *.py
|
||||||
|
recursive-include Scripts *.rst
|
||||||
|
recursive-include Scripts *.sh
|
||||||
recursive-include Scripts README
|
recursive-include Scripts README
|
||||||
|
recursive-include Tests *.bdf
|
||||||
recursive-include Tests *.bin
|
recursive-include Tests *.bin
|
||||||
recursive-include Tests *.bmp
|
recursive-include Tests *.bmp
|
||||||
|
recursive-include Tests *.bw
|
||||||
|
recursive-include Tests *.cur
|
||||||
|
recursive-include Tests *.dcx
|
||||||
|
recursive-include Tests *.doc
|
||||||
recursive-include Tests *.eps
|
recursive-include Tests *.eps
|
||||||
|
recursive-include Tests *.fli
|
||||||
recursive-include Tests *.gif
|
recursive-include Tests *.gif
|
||||||
recursive-include Tests *.gnuplot
|
recursive-include Tests *.gnuplot
|
||||||
recursive-include Tests *.html
|
recursive-include Tests *.html
|
||||||
recursive-include Tests *.icm
|
recursive-include Tests *.icm
|
||||||
recursive-include Tests *.icns
|
recursive-include Tests *.icns
|
||||||
recursive-include Tests *.ico
|
recursive-include Tests *.ico
|
||||||
|
recursive-include Tests *.j2k
|
||||||
recursive-include Tests *.jp2
|
recursive-include Tests *.jp2
|
||||||
recursive-include Tests *.jpg
|
recursive-include Tests *.jpg
|
||||||
|
recursive-include Tests *.lut
|
||||||
|
recursive-include Tests *.mpo
|
||||||
|
recursive-include Tests *.pbm
|
||||||
recursive-include Tests *.pcf
|
recursive-include Tests *.pcf
|
||||||
recursive-include Tests *.pcx
|
recursive-include Tests *.pcx
|
||||||
|
recursive-include Tests *.pgm
|
||||||
|
recursive-include Tests *.pil
|
||||||
recursive-include Tests *.png
|
recursive-include Tests *.png
|
||||||
recursive-include Tests *.ppm
|
recursive-include Tests *.ppm
|
||||||
|
recursive-include Tests *.psd
|
||||||
recursive-include Tests *.py
|
recursive-include Tests *.py
|
||||||
|
recursive-include Tests *.ras
|
||||||
|
recursive-include Tests *.rgb
|
||||||
|
recursive-include Tests *.rst
|
||||||
|
recursive-include Tests *.sgi
|
||||||
|
recursive-include Tests *.spider
|
||||||
|
recursive-include Tests *.tar
|
||||||
recursive-include Tests *.tif
|
recursive-include Tests *.tif
|
||||||
recursive-include Tests *.tiff
|
recursive-include Tests *.tiff
|
||||||
recursive-include Tests *.ttf
|
recursive-include Tests *.ttf
|
||||||
recursive-include Tests *.txt
|
recursive-include Tests *.txt
|
||||||
|
recursive-include Tests *.webp
|
||||||
|
recursive-include Tests *.xpm
|
||||||
recursive-include Tk *.c
|
recursive-include Tk *.c
|
||||||
|
recursive-include Tk *.rst
|
||||||
recursive-include Tk *.txt
|
recursive-include Tk *.txt
|
||||||
|
recursive-include depends *.rst
|
||||||
recursive-include depends *.sh
|
recursive-include depends *.sh
|
||||||
recursive-include docs *.bat
|
recursive-include docs *.bat
|
||||||
recursive-include docs *.gitignore
|
recursive-include docs *.gitignore
|
||||||
|
@ -57,10 +87,10 @@ recursive-include docs *.html
|
||||||
recursive-include docs *.py
|
recursive-include docs *.py
|
||||||
recursive-include docs *.rst
|
recursive-include docs *.rst
|
||||||
recursive-include docs *.txt
|
recursive-include docs *.txt
|
||||||
recursive-include docs Guardfile
|
|
||||||
recursive-include docs Makefile
|
|
||||||
recursive-include docs BUILDME
|
recursive-include docs BUILDME
|
||||||
recursive-include docs COPYING
|
recursive-include docs COPYING
|
||||||
|
recursive-include docs Guardfile
|
||||||
recursive-include docs LICENSE
|
recursive-include docs LICENSE
|
||||||
|
recursive-include docs Makefile
|
||||||
recursive-include libImaging *.c
|
recursive-include libImaging *.c
|
||||||
recursive-include libImaging *.h
|
recursive-include libImaging *.h
|
||||||
|
|
56
Makefile
|
@ -1,7 +1,61 @@
|
||||||
|
.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:
|
pre:
|
||||||
|
virtualenv .
|
||||||
|
bin/pip install -r requirements.txt
|
||||||
bin/python setup.py develop
|
bin/python setup.py develop
|
||||||
bin/python selftest.py
|
bin/python selftest.py
|
||||||
bin/python Tests/run.py
|
bin/nosetests Tests/test_*.py
|
||||||
|
bin/python setup.py install
|
||||||
|
bin/python test-installed.py
|
||||||
check-manifest
|
check-manifest
|
||||||
pyroma .
|
pyroma .
|
||||||
viewdoc
|
viewdoc
|
||||||
|
|
||||||
|
clean:
|
||||||
|
python setup.py clean
|
||||||
|
rm PIL/*.so || true
|
||||||
|
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
|
||||||
|
rm -r htmlcov || true
|
||||||
|
coverage combine
|
||||||
|
coverage report
|
||||||
|
|
||||||
|
test-dep:
|
||||||
|
pip install coveralls nose nose-cov pep8 pyflakes
|
||||||
|
|
||||||
|
docs:
|
||||||
|
$(MAKE) -C docs html
|
||||||
|
|
||||||
|
docserver:
|
||||||
|
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
|
|
@ -1,506 +0,0 @@
|
||||||
#
|
|
||||||
# THIS IS WORK IN PROGRESS
|
|
||||||
#
|
|
||||||
# The Python Imaging Library.
|
|
||||||
# $Id$
|
|
||||||
#
|
|
||||||
# ARG animation support code
|
|
||||||
#
|
|
||||||
# history:
|
|
||||||
# 1996-12-30 fl Created
|
|
||||||
# 1996-01-06 fl Added safe scripting environment
|
|
||||||
# 1996-01-10 fl Added JHDR, UHDR and sYNC support
|
|
||||||
# 2005-03-02 fl Removed AAPP and ARUN support
|
|
||||||
#
|
|
||||||
# Copyright (c) Secret Labs AB 1997.
|
|
||||||
# Copyright (c) Fredrik Lundh 1996-97.
|
|
||||||
#
|
|
||||||
# See the README file for information on usage and redistribution.
|
|
||||||
#
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
__version__ = "0.4"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette
|
|
||||||
|
|
||||||
from PIL.PngImagePlugin import i8, i16, i32, ChunkStream, _MODES
|
|
||||||
|
|
||||||
MAGIC = b"\212ARG\r\n\032\n"
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# ARG parser
|
|
||||||
|
|
||||||
class ArgStream(ChunkStream):
|
|
||||||
"Parser callbacks for ARG data"
|
|
||||||
|
|
||||||
def __init__(self, fp):
|
|
||||||
|
|
||||||
ChunkStream.__init__(self, fp)
|
|
||||||
|
|
||||||
self.eof = 0
|
|
||||||
|
|
||||||
self.im = None
|
|
||||||
self.palette = None
|
|
||||||
|
|
||||||
self.__reset()
|
|
||||||
|
|
||||||
def __reset(self):
|
|
||||||
|
|
||||||
# reset decoder state (called on init and sync)
|
|
||||||
|
|
||||||
self.count = 0
|
|
||||||
self.id = None
|
|
||||||
self.action = ("NONE",)
|
|
||||||
|
|
||||||
self.images = {}
|
|
||||||
self.names = {}
|
|
||||||
|
|
||||||
|
|
||||||
def chunk_AHDR(self, offset, bytes):
|
|
||||||
"AHDR -- animation header"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count != 0:
|
|
||||||
raise SyntaxError("misplaced AHDR chunk")
|
|
||||||
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
self.size = i32(s), i32(s[4:])
|
|
||||||
try:
|
|
||||||
self.mode, self.rawmode = _MODES[(i8(s[8]), i8(s[9]))]
|
|
||||||
except:
|
|
||||||
raise SyntaxError("unknown ARG mode")
|
|
||||||
|
|
||||||
if Image.DEBUG:
|
|
||||||
print("AHDR size", self.size)
|
|
||||||
print("AHDR mode", self.mode, self.rawmode)
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_AFRM(self, offset, bytes):
|
|
||||||
"AFRM -- next frame follows"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count != 0:
|
|
||||||
raise SyntaxError("misplaced AFRM chunk")
|
|
||||||
|
|
||||||
self.show = 1
|
|
||||||
self.id = 0
|
|
||||||
self.count = 1
|
|
||||||
self.repair = None
|
|
||||||
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
if len(s) >= 2:
|
|
||||||
self.id = i16(s)
|
|
||||||
if len(s) >= 4:
|
|
||||||
self.count = i16(s[2:4])
|
|
||||||
if len(s) >= 6:
|
|
||||||
self.repair = i16(s[4:6])
|
|
||||||
else:
|
|
||||||
self.repair = None
|
|
||||||
|
|
||||||
if Image.DEBUG:
|
|
||||||
print("AFRM", self.id, self.count)
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_ADEF(self, offset, bytes):
|
|
||||||
"ADEF -- store image"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count != 0:
|
|
||||||
raise SyntaxError("misplaced ADEF chunk")
|
|
||||||
|
|
||||||
self.show = 0
|
|
||||||
self.id = 0
|
|
||||||
self.count = 1
|
|
||||||
self.repair = None
|
|
||||||
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
if len(s) >= 2:
|
|
||||||
self.id = i16(s)
|
|
||||||
if len(s) >= 4:
|
|
||||||
self.count = i16(s[2:4])
|
|
||||||
|
|
||||||
if Image.DEBUG:
|
|
||||||
print("ADEF", self.id, self.count)
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_NAME(self, offset, bytes):
|
|
||||||
"NAME -- name the current image"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count == 0:
|
|
||||||
raise SyntaxError("misplaced NAME chunk")
|
|
||||||
|
|
||||||
name = self.fp.read(bytes)
|
|
||||||
self.names[self.id] = name
|
|
||||||
|
|
||||||
return name
|
|
||||||
|
|
||||||
def chunk_AEND(self, offset, bytes):
|
|
||||||
"AEND -- end of animation"
|
|
||||||
|
|
||||||
if Image.DEBUG:
|
|
||||||
print("AEND")
|
|
||||||
|
|
||||||
self.eof = 1
|
|
||||||
|
|
||||||
raise EOFError("end of ARG file")
|
|
||||||
|
|
||||||
def __getmodesize(self, s, full=1):
|
|
||||||
|
|
||||||
size = i32(s), i32(s[4:])
|
|
||||||
|
|
||||||
try:
|
|
||||||
mode, rawmode = _MODES[(i8(s[8]), i8(s[9]))]
|
|
||||||
except:
|
|
||||||
raise SyntaxError("unknown image mode")
|
|
||||||
|
|
||||||
if full:
|
|
||||||
if i8(s[12]):
|
|
||||||
pass # interlace not yet supported
|
|
||||||
if i8(s[11]):
|
|
||||||
raise SyntaxError("unknown filter category")
|
|
||||||
|
|
||||||
return size, mode, rawmode
|
|
||||||
|
|
||||||
def chunk_PAST(self, offset, bytes):
|
|
||||||
"PAST -- paste one image into another"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count == 0:
|
|
||||||
raise SyntaxError("misplaced PAST chunk")
|
|
||||||
|
|
||||||
if self.repair is not None:
|
|
||||||
# we must repair the target image before we
|
|
||||||
# start pasting
|
|
||||||
|
|
||||||
# brute force; a better solution would be to
|
|
||||||
# update only the dirty rectangles in images[id].
|
|
||||||
# note that if images[id] doesn't exist, it must
|
|
||||||
# be created
|
|
||||||
|
|
||||||
self.images[self.id] = self.images[self.repair].copy()
|
|
||||||
self.repair = None
|
|
||||||
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
im = self.images[i16(s)]
|
|
||||||
x, y = i32(s[2:6]), i32(s[6:10])
|
|
||||||
bbox = x, y, im.size[0]+x, im.size[1]+y
|
|
||||||
|
|
||||||
if im.mode in ["RGBA"]:
|
|
||||||
# paste with transparency
|
|
||||||
# FIXME: should handle P+transparency as well
|
|
||||||
self.images[self.id].paste(im, bbox, im)
|
|
||||||
else:
|
|
||||||
# paste without transparency
|
|
||||||
self.images[self.id].paste(im, bbox)
|
|
||||||
|
|
||||||
self.action = ("PAST",)
|
|
||||||
self.__store()
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_BLNK(self, offset, bytes):
|
|
||||||
"BLNK -- create blank image"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count == 0:
|
|
||||||
raise SyntaxError("misplaced BLNK chunk")
|
|
||||||
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
size, mode, rawmode = self.__getmodesize(s, 0)
|
|
||||||
|
|
||||||
# store image (FIXME: handle colour)
|
|
||||||
self.action = ("BLNK",)
|
|
||||||
self.im = Image.core.fill(mode, size, 0)
|
|
||||||
self.__store()
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_IHDR(self, offset, bytes):
|
|
||||||
"IHDR -- full image follows"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count == 0:
|
|
||||||
raise SyntaxError("misplaced IHDR chunk")
|
|
||||||
|
|
||||||
# image header
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
size, mode, rawmode = self.__getmodesize(s)
|
|
||||||
|
|
||||||
# decode and store image
|
|
||||||
self.action = ("IHDR",)
|
|
||||||
self.im = Image.core.new(mode, size)
|
|
||||||
self.decoder = Image.core.zip_decoder(rawmode)
|
|
||||||
self.decoder.setimage(self.im, (0,0) + size)
|
|
||||||
self.data = b""
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_DHDR(self, offset, bytes):
|
|
||||||
"DHDR -- delta image follows"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count == 0:
|
|
||||||
raise SyntaxError("misplaced DHDR chunk")
|
|
||||||
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
|
|
||||||
size, mode, rawmode = self.__getmodesize(s)
|
|
||||||
|
|
||||||
# delta header
|
|
||||||
diff = i8(s[13])
|
|
||||||
offs = i32(s[14:18]), i32(s[18:22])
|
|
||||||
|
|
||||||
bbox = offs + (offs[0]+size[0], offs[1]+size[1])
|
|
||||||
|
|
||||||
if Image.DEBUG:
|
|
||||||
print("DHDR", diff, bbox)
|
|
||||||
|
|
||||||
# FIXME: decode and apply image
|
|
||||||
self.action = ("DHDR", diff, bbox)
|
|
||||||
|
|
||||||
# setup decoder
|
|
||||||
self.im = Image.core.new(mode, size)
|
|
||||||
|
|
||||||
self.decoder = Image.core.zip_decoder(rawmode)
|
|
||||||
self.decoder.setimage(self.im, (0,0) + size)
|
|
||||||
|
|
||||||
self.data = b""
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_JHDR(self, offset, bytes):
|
|
||||||
"JHDR -- JPEG image follows"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count == 0:
|
|
||||||
raise SyntaxError("misplaced JHDR chunk")
|
|
||||||
|
|
||||||
# image header
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
size, mode, rawmode = self.__getmodesize(s, 0)
|
|
||||||
|
|
||||||
# decode and store image
|
|
||||||
self.action = ("JHDR",)
|
|
||||||
self.im = Image.core.new(mode, size)
|
|
||||||
self.decoder = Image.core.jpeg_decoder(rawmode)
|
|
||||||
self.decoder.setimage(self.im, (0,0) + size)
|
|
||||||
self.data = b""
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_UHDR(self, offset, bytes):
|
|
||||||
"UHDR -- uncompressed image data follows (EXPERIMENTAL)"
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
if self.count == 0:
|
|
||||||
raise SyntaxError("misplaced UHDR chunk")
|
|
||||||
|
|
||||||
# image header
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
size, mode, rawmode = self.__getmodesize(s, 0)
|
|
||||||
|
|
||||||
# decode and store image
|
|
||||||
self.action = ("UHDR",)
|
|
||||||
self.im = Image.core.new(mode, size)
|
|
||||||
self.decoder = Image.core.raw_decoder(rawmode)
|
|
||||||
self.decoder.setimage(self.im, (0,0) + size)
|
|
||||||
self.data = b""
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_IDAT(self, offset, bytes):
|
|
||||||
"IDAT -- image data block"
|
|
||||||
|
|
||||||
# pass compressed chunks through the decoder
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
self.data = self.data + s
|
|
||||||
n, e = self.decoder.decode(self.data)
|
|
||||||
if n < 0:
|
|
||||||
# end of image
|
|
||||||
if e < 0:
|
|
||||||
raise IOError("decoder error %d" % e)
|
|
||||||
else:
|
|
||||||
self.data = self.data[n:]
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_DEND(self, offset, bytes):
|
|
||||||
return self.chunk_IEND(offset, bytes)
|
|
||||||
|
|
||||||
def chunk_JEND(self, offset, bytes):
|
|
||||||
return self.chunk_IEND(offset, bytes)
|
|
||||||
|
|
||||||
def chunk_UEND(self, offset, bytes):
|
|
||||||
return self.chunk_IEND(offset, bytes)
|
|
||||||
|
|
||||||
def chunk_IEND(self, offset, bytes):
|
|
||||||
"IEND -- end of image"
|
|
||||||
|
|
||||||
# we now have a new image. carry out the operation
|
|
||||||
# defined by the image header.
|
|
||||||
|
|
||||||
# won't need these anymore
|
|
||||||
del self.decoder
|
|
||||||
del self.data
|
|
||||||
|
|
||||||
self.__store()
|
|
||||||
|
|
||||||
return self.fp.read(bytes)
|
|
||||||
|
|
||||||
def __store(self):
|
|
||||||
|
|
||||||
# apply operation
|
|
||||||
cid = self.action[0]
|
|
||||||
|
|
||||||
if cid in ["BLNK", "IHDR", "JHDR", "UHDR"]:
|
|
||||||
# store
|
|
||||||
self.images[self.id] = self.im
|
|
||||||
|
|
||||||
elif cid == "DHDR":
|
|
||||||
# paste
|
|
||||||
cid, mode, bbox = self.action
|
|
||||||
im0 = self.images[self.id]
|
|
||||||
im1 = self.im
|
|
||||||
if mode == 0:
|
|
||||||
im1 = im1.chop_add_modulo(im0.crop(bbox))
|
|
||||||
im0.paste(im1, bbox)
|
|
||||||
|
|
||||||
self.count = self.count - 1
|
|
||||||
|
|
||||||
if self.count == 0 and self.show:
|
|
||||||
self.im = self.images[self.id]
|
|
||||||
raise EOFError # end of this frame
|
|
||||||
|
|
||||||
def chunk_PLTE(self, offset, bytes):
|
|
||||||
"PLTE -- palette data"
|
|
||||||
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
if self.mode == "P":
|
|
||||||
self.palette = ImagePalette.raw("RGB", s)
|
|
||||||
return s
|
|
||||||
|
|
||||||
def chunk_sYNC(self, offset, bytes):
|
|
||||||
"SYNC -- reset decoder"
|
|
||||||
|
|
||||||
if self.count != 0:
|
|
||||||
raise SyntaxError("misplaced sYNC chunk")
|
|
||||||
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
self.__reset()
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# ARG reader
|
|
||||||
|
|
||||||
def _accept(prefix):
|
|
||||||
return prefix[:8] == MAGIC
|
|
||||||
|
|
||||||
##
|
|
||||||
# Image plugin for the experimental Animated Raster Graphics format.
|
|
||||||
|
|
||||||
class ArgImageFile(ImageFile.ImageFile):
|
|
||||||
|
|
||||||
format = "ARG"
|
|
||||||
format_description = "Animated raster graphics"
|
|
||||||
|
|
||||||
def _open(self):
|
|
||||||
|
|
||||||
if Image.warnings:
|
|
||||||
Image.warnings.warn(
|
|
||||||
"The ArgImagePlugin driver is obsolete, and will be removed "
|
|
||||||
"from a future release of PIL. If you rely on this module, "
|
|
||||||
"please contact the PIL authors.",
|
|
||||||
RuntimeWarning
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.fp.read(8) != MAGIC:
|
|
||||||
raise SyntaxError("not an ARG file")
|
|
||||||
|
|
||||||
self.arg = ArgStream(self.fp)
|
|
||||||
|
|
||||||
# read and process the first chunk (AHDR)
|
|
||||||
|
|
||||||
cid, offset, bytes = self.arg.read()
|
|
||||||
|
|
||||||
if cid != "AHDR":
|
|
||||||
raise SyntaxError("expected an AHDR chunk")
|
|
||||||
|
|
||||||
s = self.arg.call(cid, offset, bytes)
|
|
||||||
|
|
||||||
self.arg.crc(cid, s)
|
|
||||||
|
|
||||||
# image characteristics
|
|
||||||
self.mode = self.arg.mode
|
|
||||||
self.size = self.arg.size
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
|
|
||||||
if self.arg.im is None:
|
|
||||||
self.seek(0)
|
|
||||||
|
|
||||||
# image data
|
|
||||||
self.im = self.arg.im
|
|
||||||
self.palette = self.arg.palette
|
|
||||||
|
|
||||||
# set things up for further processing
|
|
||||||
Image.Image.load(self)
|
|
||||||
|
|
||||||
def seek(self, frame):
|
|
||||||
|
|
||||||
if self.arg.eof:
|
|
||||||
raise EOFError("end of animation")
|
|
||||||
|
|
||||||
self.fp = self.arg.fp
|
|
||||||
|
|
||||||
while True:
|
|
||||||
|
|
||||||
#
|
|
||||||
# process chunks
|
|
||||||
|
|
||||||
cid, offset, bytes = self.arg.read()
|
|
||||||
|
|
||||||
if self.arg.eof:
|
|
||||||
raise EOFError("end of animation")
|
|
||||||
|
|
||||||
try:
|
|
||||||
s = self.arg.call(cid, offset, bytes)
|
|
||||||
except EOFError:
|
|
||||||
break
|
|
||||||
|
|
||||||
except "glurk": # AttributeError
|
|
||||||
if Image.DEBUG:
|
|
||||||
print(cid, bytes, "(unknown)")
|
|
||||||
s = self.fp.read(bytes)
|
|
||||||
|
|
||||||
self.arg.crc(cid, s)
|
|
||||||
|
|
||||||
self.fp.read(4) # ship extra CRC
|
|
||||||
|
|
||||||
def tell(self):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def verify(self):
|
|
||||||
"Verify ARG file"
|
|
||||||
|
|
||||||
# back up to first chunk
|
|
||||||
self.fp.seek(8)
|
|
||||||
|
|
||||||
self.arg.verify(self)
|
|
||||||
self.arg.close()
|
|
||||||
|
|
||||||
self.fp = None
|
|
||||||
|
|
||||||
#
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
|
|
||||||
Image.register_open("ARG", ArgImageFile, _accept)
|
|
||||||
|
|
||||||
Image.register_extension("ARG", ".arg")
|
|
||||||
|
|
||||||
Image.register_mime("ARG", "video/x-arg")
|
|
|
@ -128,5 +128,5 @@ class BdfFontFile(FontFile.FontFile):
|
||||||
if not c:
|
if not c:
|
||||||
break
|
break
|
||||||
id, ch, (xy, dst, src), im = c
|
id, ch, (xy, dst, src), im = c
|
||||||
if ch >= 0 and ch < len(self.glyph):
|
if 0 <= ch < len(self.glyph):
|
||||||
self.glyph[ch] = xy, dst, src, im
|
self.glyph[ch] = xy, dst, src, im
|
||||||
|
|
|
@ -28,6 +28,7 @@ __version__ = "0.7"
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
import math
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
|
@ -88,6 +89,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
bits = i16(s[14:])
|
bits = i16(s[14:])
|
||||||
self.size = i32(s[4:]), i32(s[8:])
|
self.size = i32(s[4:]), i32(s[8:])
|
||||||
compression = i32(s[16:])
|
compression = i32(s[16:])
|
||||||
|
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
|
||||||
lutsize = 4
|
lutsize = 4
|
||||||
colors = i32(s[32:])
|
colors = i32(s[32:])
|
||||||
direction = -1
|
direction = -1
|
||||||
|
@ -96,6 +98,8 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
self.size = self.size[0], 2**32 - self.size[1]
|
self.size = self.size[0], 2**32 - self.size[1]
|
||||||
direction = 0
|
direction = 0
|
||||||
|
|
||||||
|
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise IOError("Unsupported BMP header type (%d)" % len(s))
|
raise IOError("Unsupported BMP header type (%d)" % len(s))
|
||||||
|
|
||||||
|
@ -203,6 +207,13 @@ def _save(im, fp, filename, check=0):
|
||||||
if check:
|
if check:
|
||||||
return check
|
return check
|
||||||
|
|
||||||
|
info = im.encoderinfo
|
||||||
|
|
||||||
|
dpi = info.get("dpi", (96, 96))
|
||||||
|
|
||||||
|
# 1 meter == 39.3701 inches
|
||||||
|
ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
|
||||||
|
|
||||||
stride = ((im.size[0]*bits+7)//8+3)&(~3)
|
stride = ((im.size[0]*bits+7)//8+3)&(~3)
|
||||||
header = 40 # or 64 for OS/2 version 2
|
header = 40 # or 64 for OS/2 version 2
|
||||||
offset = 14 + header + colors * 4
|
offset = 14 + header + colors * 4
|
||||||
|
@ -222,7 +233,7 @@ def _save(im, fp, filename, check=0):
|
||||||
o16(bits) + # depth
|
o16(bits) + # depth
|
||||||
o32(0) + # compression (0=uncompressed)
|
o32(0) + # compression (0=uncompressed)
|
||||||
o32(image) + # size of bitmap
|
o32(image) + # size of bitmap
|
||||||
o32(1) + o32(1) + # resolution
|
o32(ppm[0]) + o32(ppm[1]) + # resolution
|
||||||
o32(colors) + # colors used
|
o32(colors) + # colors used
|
||||||
o32(colors)) # colors important
|
o32(colors)) # colors important
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ i32 = _binary.i32le
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] == b"\0\0\2\0"
|
return prefix[:4] == b"\0\0\2\0"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Windows Cursor files.
|
# Image plugin for Windows Cursor files.
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
# check magic
|
# check magic
|
||||||
s = self.fp.read(6)
|
s = self.fp.read(6)
|
||||||
if not _accept(s):
|
if not _accept(s):
|
||||||
raise SyntaxError("not an CUR file")
|
raise SyntaxError("not a CUR file")
|
||||||
|
|
||||||
# pick the largest cursor in the file
|
# pick the largest cursor in the file
|
||||||
m = b""
|
m = b""
|
||||||
|
|
|
@ -31,9 +31,11 @@ MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
||||||
|
|
||||||
i32 = _binary.i32le
|
i32 = _binary.i32le
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i32(prefix) == MAGIC
|
return i32(prefix) == MAGIC
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for the Intel DCX format.
|
# Image plugin for the Intel DCX format.
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
s = fp.read(min(lengthfile, 100*1024))
|
s = fp.read(min(lengthfile, 100*1024))
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
lengthfile = lengthfile - len(s)
|
length -= len(s)
|
||||||
f.write(s)
|
f.write(s)
|
||||||
|
|
||||||
# Build ghostscript command
|
# Build ghostscript command
|
||||||
|
@ -161,7 +161,7 @@ class PSFile:
|
||||||
def tell(self):
|
def tell(self):
|
||||||
pos = self.fp.tell()
|
pos = self.fp.tell()
|
||||||
if self.char:
|
if self.char:
|
||||||
pos = pos - 1
|
pos -= 1
|
||||||
return pos
|
return pos
|
||||||
def readline(self):
|
def readline(self):
|
||||||
s = b""
|
s = b""
|
||||||
|
|
|
@ -63,15 +63,12 @@ TAGS = {
|
||||||
0x0201: "JpegIFOffset",
|
0x0201: "JpegIFOffset",
|
||||||
0x0202: "JpegIFByteCount",
|
0x0202: "JpegIFByteCount",
|
||||||
0x0211: "YCbCrCoefficients",
|
0x0211: "YCbCrCoefficients",
|
||||||
0x0211: "YCbCrCoefficients",
|
|
||||||
0x0212: "YCbCrSubSampling",
|
0x0212: "YCbCrSubSampling",
|
||||||
0x0213: "YCbCrPositioning",
|
0x0213: "YCbCrPositioning",
|
||||||
0x0213: "YCbCrPositioning",
|
|
||||||
0x0214: "ReferenceBlackWhite",
|
|
||||||
0x0214: "ReferenceBlackWhite",
|
0x0214: "ReferenceBlackWhite",
|
||||||
0x1000: "RelatedImageFileFormat",
|
0x1000: "RelatedImageFileFormat",
|
||||||
0x1001: "RelatedImageLength",
|
0x1001: "RelatedImageLength", # FIXME / Dictionary contains duplicate keys
|
||||||
0x1001: "RelatedImageWidth",
|
0x1001: "RelatedImageWidth", # FIXME \ Dictionary contains duplicate keys
|
||||||
0x828d: "CFARepeatPatternDim",
|
0x828d: "CFARepeatPatternDim",
|
||||||
0x828e: "CFAPattern",
|
0x828e: "CFAPattern",
|
||||||
0x828f: "BatteryLevel",
|
0x828f: "BatteryLevel",
|
||||||
|
|
|
@ -105,7 +105,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
g = i8(s[n+1]) << shift
|
g = i8(s[n+1]) << shift
|
||||||
b = i8(s[n+2]) << shift
|
b = i8(s[n+2]) << shift
|
||||||
palette[i] = (r, g, b)
|
palette[i] = (r, g, b)
|
||||||
i = i + 1
|
i += 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
import os
|
import os
|
||||||
from PIL import Image, _binary
|
from PIL import Image, _binary
|
||||||
|
|
||||||
import marshal
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zlib
|
import zlib
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -26,13 +24,15 @@ except ImportError:
|
||||||
|
|
||||||
WIDTH = 800
|
WIDTH = 800
|
||||||
|
|
||||||
|
|
||||||
def puti16(fp, values):
|
def puti16(fp, values):
|
||||||
# write network order (big-endian) 16-bit sequence
|
# write network order (big-endian) 16-bit sequence
|
||||||
for v in values:
|
for v in values:
|
||||||
if v < 0:
|
if v < 0:
|
||||||
v = v + 65536
|
v += 65536
|
||||||
fp.write(_binary.o16be(v))
|
fp.write(_binary.o16be(v))
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Base class for raster font file handlers.
|
# Base class for raster font file handlers.
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ class FontFile:
|
||||||
h = max(h, src[3] - src[1])
|
h = max(h, src[3] - src[1])
|
||||||
w = w + (src[2] - src[0])
|
w = w + (src[2] - src[0])
|
||||||
if w > WIDTH:
|
if w > WIDTH:
|
||||||
lines = lines + 1
|
lines += 1
|
||||||
w = (src[2] - src[0])
|
w = (src[2] - src[0])
|
||||||
maxwidth = max(maxwidth, w)
|
maxwidth = max(maxwidth, w)
|
||||||
|
|
||||||
|
@ -95,9 +95,8 @@ class FontFile:
|
||||||
# print chr(i), dst, s
|
# print chr(i), dst, s
|
||||||
self.metrics[i] = d, dst, s
|
self.metrics[i] = d, dst, s
|
||||||
|
|
||||||
|
def save(self, filename):
|
||||||
def save1(self, filename):
|
"Save font"
|
||||||
"Save font in version 1 format"
|
|
||||||
|
|
||||||
self.compile()
|
self.compile()
|
||||||
|
|
||||||
|
@ -117,30 +116,4 @@ class FontFile:
|
||||||
puti16(fp, m[0] + m[1] + m[2])
|
puti16(fp, m[0] + m[1] + m[2])
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
|
# End of file
|
||||||
def save2(self, filename):
|
|
||||||
"Save font in version 2 format"
|
|
||||||
|
|
||||||
# THIS IS WORK IN PROGRESS
|
|
||||||
|
|
||||||
self.compile()
|
|
||||||
|
|
||||||
data = marshal.dumps((self.metrics, self.info))
|
|
||||||
|
|
||||||
if zlib:
|
|
||||||
data = b"z" + zlib.compress(data, 9)
|
|
||||||
else:
|
|
||||||
data = b"u" + data
|
|
||||||
|
|
||||||
fp = open(os.path.splitext(filename)[0] + ".pil", "wb")
|
|
||||||
|
|
||||||
fp.write(b"PILfont2\n" + self.name + "\n" + "DATA\n")
|
|
||||||
|
|
||||||
fp.write(data)
|
|
||||||
|
|
||||||
self.bitmap.save(fp, "PNG")
|
|
||||||
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
|
|
||||||
save = save1 # for now
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
i = 1
|
i = 1
|
||||||
while size > 64:
|
while size > 64:
|
||||||
size = size / 2
|
size = size / 2
|
||||||
i = i + 1
|
i += 1
|
||||||
self.maxid = i - 1
|
self.maxid = i - 1
|
||||||
|
|
||||||
# mode. instead of using a single field for this, flashpix
|
# mode. instead of using a single field for this, flashpix
|
||||||
|
|
|
@ -96,8 +96,15 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
# rewind
|
# rewind
|
||||||
self.__offset = 0
|
self.__offset = 0
|
||||||
self.dispose = None
|
self.dispose = None
|
||||||
|
self.dispose_extent = [0, 0, 0, 0] #x0, y0, x1, y1
|
||||||
self.__frame = -1
|
self.__frame = -1
|
||||||
self.__fp.seek(self.__rewind)
|
self.__fp.seek(self.__rewind)
|
||||||
|
self._prev_im = None
|
||||||
|
self.disposal_method = 0
|
||||||
|
else:
|
||||||
|
# ensure that the previous frame was loaded
|
||||||
|
if not self.im:
|
||||||
|
self.load()
|
||||||
|
|
||||||
if frame != self.__frame + 1:
|
if frame != self.__frame + 1:
|
||||||
raise ValueError("cannot seek to frame %d" % frame)
|
raise ValueError("cannot seek to frame %d" % frame)
|
||||||
|
@ -114,8 +121,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.__offset = 0
|
self.__offset = 0
|
||||||
|
|
||||||
if self.dispose:
|
if self.dispose:
|
||||||
self.im = self.dispose
|
self.im.paste(self.dispose, self.dispose_extent)
|
||||||
self.dispose = None
|
|
||||||
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
self.palette = copy(self.global_palette)
|
self.palette = copy(self.global_palette)
|
||||||
|
@ -140,17 +146,16 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if flags & 1:
|
if flags & 1:
|
||||||
self.info["transparency"] = i8(block[3])
|
self.info["transparency"] = i8(block[3])
|
||||||
self.info["duration"] = i16(block[1:3]) * 10
|
self.info["duration"] = i16(block[1:3]) * 10
|
||||||
try:
|
|
||||||
# disposal methods
|
# disposal method - find the value of bits 4 - 6
|
||||||
if flags & 8:
|
dispose_bits = 0b00011100 & flags
|
||||||
# replace with background colour
|
dispose_bits = dispose_bits >> 2
|
||||||
self.dispose = Image.core.fill("P", self.size,
|
if dispose_bits:
|
||||||
self.info["background"])
|
# only set the dispose if it is not
|
||||||
elif flags & 16:
|
# unspecified. I'm not sure if this is
|
||||||
# replace with previous contents
|
# correct, but it seems to prevent the last
|
||||||
self.dispose = self.im.copy()
|
# frame from looking odd for some animations
|
||||||
except (AttributeError, KeyError):
|
self.disposal_method = dispose_bits
|
||||||
pass
|
|
||||||
elif i8(s) == 255:
|
elif i8(s) == 255:
|
||||||
#
|
#
|
||||||
# application extension
|
# application extension
|
||||||
|
@ -172,6 +177,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
# extent
|
# extent
|
||||||
x0, y0 = i16(s[0:]), i16(s[2:])
|
x0, y0 = i16(s[0:]), i16(s[2:])
|
||||||
x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:])
|
x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:])
|
||||||
|
self.dispose_extent = x0, y0, x1, y1
|
||||||
flags = i8(s[8])
|
flags = i8(s[8])
|
||||||
|
|
||||||
interlace = (flags & 64) != 0
|
interlace = (flags & 64) != 0
|
||||||
|
@ -194,6 +200,26 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
pass
|
pass
|
||||||
# raise IOError, "illegal GIF tag `%x`" % i8(s)
|
# raise IOError, "illegal GIF tag `%x`" % i8(s)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.disposal_method < 2:
|
||||||
|
# do not dispose or none specified
|
||||||
|
self.dispose = None
|
||||||
|
elif self.disposal_method == 2:
|
||||||
|
# replace with background colour
|
||||||
|
self.dispose = Image.core.fill("P", self.size,
|
||||||
|
self.info["background"])
|
||||||
|
else:
|
||||||
|
# replace with previous contents
|
||||||
|
if self.im:
|
||||||
|
self.dispose = self.im.copy()
|
||||||
|
|
||||||
|
# only dispose the extent in this frame
|
||||||
|
if self.dispose:
|
||||||
|
self.dispose = self.dispose.crop(self.dispose_extent)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if not self.tile:
|
if not self.tile:
|
||||||
# self.__fp = None
|
# self.__fp = None
|
||||||
raise EOFError("no more images in GIF file")
|
raise EOFError("no more images in GIF file")
|
||||||
|
@ -205,6 +231,18 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
|
def load_end(self):
|
||||||
|
ImageFile.ImageFile.load_end(self)
|
||||||
|
|
||||||
|
# if the disposal method is 'do not dispose', transparent
|
||||||
|
# pixels should show the content of the previous frame
|
||||||
|
if self._prev_im and self.disposal_method == 1:
|
||||||
|
# we do this by pasting the updated area onto the previous
|
||||||
|
# frame which we then use as the current image content
|
||||||
|
updated = self.im.crop(self.dispose_extent)
|
||||||
|
self._prev_im.paste(updated, self.dispose_extent, updated.convert('RGBA'))
|
||||||
|
self.im = self._prev_im
|
||||||
|
self._prev_im = self.im.copy()
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Write GIF files
|
# Write GIF files
|
||||||
|
@ -230,10 +268,9 @@ def _save(im, fp, filename):
|
||||||
except IOError:
|
except IOError:
|
||||||
pass # write uncompressed file
|
pass # write uncompressed file
|
||||||
|
|
||||||
try:
|
if im.mode in RAWMODE:
|
||||||
rawmode = RAWMODE[im.mode]
|
|
||||||
imOut = im
|
imOut = im
|
||||||
except KeyError:
|
else:
|
||||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
||||||
# should automatically convert images on save...)
|
# should automatically convert images on save...)
|
||||||
if Image.getmodebase(im.mode) == "RGB":
|
if Image.getmodebase(im.mode) == "RGB":
|
||||||
|
@ -241,10 +278,8 @@ def _save(im, fp, filename):
|
||||||
if im.palette:
|
if im.palette:
|
||||||
palette_size = len(im.palette.getdata()[1]) // 3
|
palette_size = len(im.palette.getdata()[1]) // 3
|
||||||
imOut = im.convert("P", palette=1, colors=palette_size)
|
imOut = im.convert("P", palette=1, colors=palette_size)
|
||||||
rawmode = "P"
|
|
||||||
else:
|
else:
|
||||||
imOut = im.convert("L")
|
imOut = im.convert("L")
|
||||||
rawmode = "L"
|
|
||||||
|
|
||||||
# header
|
# header
|
||||||
try:
|
try:
|
||||||
|
@ -252,12 +287,6 @@ def _save(im, fp, filename):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
palette = None
|
palette = None
|
||||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||||
if im.encoderinfo["optimize"]:
|
|
||||||
# When the mode is L, and we optimize, we end up with
|
|
||||||
# im.mode == P and rawmode = L, which fails.
|
|
||||||
# If we're optimizing the palette, we're going to be
|
|
||||||
# in a rawmode of P anyway.
|
|
||||||
rawmode = 'P'
|
|
||||||
|
|
||||||
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
|
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
|
||||||
for s in header:
|
for s in header:
|
||||||
|
@ -314,7 +343,7 @@ def _save(im, fp, filename):
|
||||||
o8(8)) # bits
|
o8(8)) # bits
|
||||||
|
|
||||||
imOut.encoderconfig = (8, interlace)
|
imOut.encoderconfig = (8, interlace)
|
||||||
ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, rawmode)])
|
ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, RAWMODE[imOut.mode])])
|
||||||
|
|
||||||
fp.write(b"\0") # end of image data
|
fp.write(b"\0") # end of image data
|
||||||
|
|
||||||
|
@ -333,13 +362,41 @@ def _save_netpbm(im, fp, filename):
|
||||||
# below for information on how to enable this.
|
# below for information on how to enable this.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from subprocess import Popen, check_call, PIPE, CalledProcessError
|
||||||
|
import tempfile
|
||||||
file = im._dump()
|
file = im._dump()
|
||||||
|
|
||||||
if im.mode != "RGB":
|
if im.mode != "RGB":
|
||||||
os.system("ppmtogif %s >%s" % (file, filename))
|
with open(filename, 'wb') as f:
|
||||||
|
stderr = tempfile.TemporaryFile()
|
||||||
|
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
|
||||||
else:
|
else:
|
||||||
os.system("ppmquant 256 %s | ppmtogif >%s" % (file, filename))
|
with open(filename, 'wb') as f:
|
||||||
try: os.unlink(file)
|
|
||||||
except: pass
|
# Pipe ppmquant output into ppmtogif
|
||||||
|
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
|
||||||
|
quant_cmd = ["ppmquant", "256", file]
|
||||||
|
togif_cmd = ["ppmtogif"]
|
||||||
|
stderr = tempfile.TemporaryFile()
|
||||||
|
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
|
||||||
|
stderr = tempfile.TemporaryFile()
|
||||||
|
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=stderr)
|
||||||
|
|
||||||
|
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
||||||
|
quant_proc.stdout.close()
|
||||||
|
|
||||||
|
retcode = quant_proc.wait()
|
||||||
|
if retcode:
|
||||||
|
raise CalledProcessError(retcode, quant_cmd)
|
||||||
|
|
||||||
|
retcode = togif_proc.wait()
|
||||||
|
if retcode:
|
||||||
|
raise CalledProcessError(retcode, togif_cmd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.unlink(file)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -68,7 +68,7 @@ class GradientFile:
|
||||||
x = i / float(entries-1)
|
x = i / float(entries-1)
|
||||||
|
|
||||||
while x1 < x:
|
while x1 < x:
|
||||||
ix = ix + 1
|
ix += 1
|
||||||
x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
|
x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
|
||||||
|
|
||||||
w = x1 - x0
|
w = x1 - x0
|
||||||
|
|
|
@ -52,7 +52,7 @@ class GimpPaletteFile:
|
||||||
if 0 <= i <= 255:
|
if 0 <= i <= 255:
|
||||||
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
|
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
|
||||||
|
|
||||||
i = i + 1
|
i += 1
|
||||||
|
|
||||||
self.palette = b"".join(self.palette)
|
self.palette = b"".join(self.palette)
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ def read_32(fobj, start_length, size):
|
||||||
else:
|
else:
|
||||||
blocksize = byte + 1
|
blocksize = byte + 1
|
||||||
data.append(fobj.read(blocksize))
|
data.append(fobj.read(blocksize))
|
||||||
bytesleft = bytesleft - blocksize
|
bytesleft -= blocksize
|
||||||
if bytesleft <= 0:
|
if bytesleft <= 0:
|
||||||
break
|
break
|
||||||
if bytesleft != 0:
|
if bytesleft != 0:
|
||||||
|
@ -179,11 +179,13 @@ class IcnsFile:
|
||||||
i = HEADERSIZE
|
i = HEADERSIZE
|
||||||
while i < filesize:
|
while i < filesize:
|
||||||
sig, blocksize = nextheader(fobj)
|
sig, blocksize = nextheader(fobj)
|
||||||
i = i + HEADERSIZE
|
if blocksize <= 0:
|
||||||
blocksize = blocksize - HEADERSIZE
|
raise SyntaxError('invalid block header')
|
||||||
|
i += HEADERSIZE
|
||||||
|
blocksize -= HEADERSIZE
|
||||||
dct[sig] = (i, blocksize)
|
dct[sig] = (i, blocksize)
|
||||||
fobj.seek(blocksize, 1)
|
fobj.seek(blocksize, 1)
|
||||||
i = i + blocksize
|
i += blocksize
|
||||||
|
|
||||||
def itersizes(self):
|
def itersizes(self):
|
||||||
sizes = []
|
sizes = []
|
||||||
|
|
|
@ -182,7 +182,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
self.info[k] = v
|
self.info[k] = v
|
||||||
|
|
||||||
if k in TAGS:
|
if k in TAGS:
|
||||||
n = n + 1
|
n += 1
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
|
41
PIL/Image.py
|
@ -31,11 +31,18 @@ from PIL import VERSION, PILLOW_VERSION, _plugins
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
|
class DecompressionBombWarning(RuntimeWarning):
|
||||||
|
pass
|
||||||
|
|
||||||
class _imaging_not_installed:
|
class _imaging_not_installed:
|
||||||
# module placeholder
|
# module placeholder
|
||||||
def __getattr__(self, id):
|
def __getattr__(self, id):
|
||||||
raise ImportError("The _imaging C module is not installed")
|
raise ImportError("The _imaging C module is not installed")
|
||||||
|
|
||||||
|
|
||||||
|
# Limit to around a quarter gigabyte for a 24 bit (3 bpp) image
|
||||||
|
MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 / 4 / 3)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# give Tk a chance to set up the environment, in case we're
|
# give Tk a chance to set up the environment, in case we're
|
||||||
# using an _imaging module linked against libtcl/libtk (use
|
# using an _imaging module linked against libtcl/libtk (use
|
||||||
|
@ -213,6 +220,7 @@ _MODEINFO = {
|
||||||
"CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
|
"CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
|
||||||
"YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
|
"YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
|
||||||
"LAB": ("RGB", "L", ("L", "A", "B")),
|
"LAB": ("RGB", "L", ("L", "A", "B")),
|
||||||
|
"HSV": ("RGB", "L", ("H", "S", "V")),
|
||||||
|
|
||||||
# Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and
|
# Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and
|
||||||
# BGR;24. Use these modes only if you know exactly what you're
|
# BGR;24. Use these modes only if you know exactly what you're
|
||||||
|
@ -448,7 +456,7 @@ def _getscaleoffset(expr):
|
||||||
(a, b, c) = data # simplified syntax
|
(a, b, c) = data # simplified syntax
|
||||||
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)):
|
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)):
|
||||||
return c, 0.0
|
return c, 0.0
|
||||||
if (a is stub and b == "__add__" and isinstance(c, numbers.Number)):
|
if a is stub and b == "__add__" and isinstance(c, numbers.Number):
|
||||||
return 1.0, c
|
return 1.0, c
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
@ -532,7 +540,7 @@ class Image:
|
||||||
try:
|
try:
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
except Exception as msg:
|
except Exception as msg:
|
||||||
if Image.DEBUG:
|
if DEBUG:
|
||||||
print ("Error closing: %s" % msg)
|
print ("Error closing: %s" % msg)
|
||||||
|
|
||||||
# Instead of simply setting to None, we're setting up a
|
# Instead of simply setting to None, we're setting up a
|
||||||
|
@ -547,7 +555,6 @@ class Image:
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
|
|
||||||
def _dump(self, file=None, format=None):
|
def _dump(self, file=None, format=None):
|
||||||
import os
|
|
||||||
import tempfile
|
import tempfile
|
||||||
suffix = ''
|
suffix = ''
|
||||||
if format:
|
if format:
|
||||||
|
@ -566,6 +573,8 @@ class Image:
|
||||||
return file
|
return file
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
if self.__class__.__name__ != other.__class__.__name__:
|
||||||
|
return False
|
||||||
a = (self.mode == other.mode)
|
a = (self.mode == other.mode)
|
||||||
b = (self.size == other.size)
|
b = (self.size == other.size)
|
||||||
c = (self.getpalette() == other.getpalette())
|
c = (self.getpalette() == other.getpalette())
|
||||||
|
@ -1529,7 +1538,7 @@ class Image:
|
||||||
clockwise around its centre.
|
clockwise around its centre.
|
||||||
|
|
||||||
:param angle: In degrees counter clockwise.
|
:param angle: In degrees counter clockwise.
|
||||||
:param filter: An optional resampling filter. This can be
|
:param resample: An optional resampling filter. This can be
|
||||||
one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
|
one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
|
||||||
:py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
|
:py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
|
||||||
environment), or :py:attr:`PIL.Image.BICUBIC`
|
environment), or :py:attr:`PIL.Image.BICUBIC`
|
||||||
|
@ -1551,7 +1560,6 @@ class Image:
|
||||||
-math.sin(angle), math.cos(angle), 0.0
|
-math.sin(angle), math.cos(angle), 0.0
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def transform(x, y, matrix=matrix):
|
def transform(x, y, matrix=matrix):
|
||||||
(a, b, c, d, e, f) = matrix
|
(a, b, c, d, e, f) = matrix
|
||||||
return a*x + b*y + c, d*x + e*y + f
|
return a*x + b*y + c, d*x + e*y + f
|
||||||
|
@ -2075,7 +2083,6 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
|
||||||
|
|
||||||
.. versionadded:: 1.1.4
|
.. versionadded:: 1.1.4
|
||||||
"""
|
"""
|
||||||
"Load image from bytes or buffer"
|
|
||||||
|
|
||||||
# may pass tuple instead of argument list
|
# may pass tuple instead of argument list
|
||||||
if len(args) == 1 and isinstance(args[0], tuple):
|
if len(args) == 1 and isinstance(args[0], tuple):
|
||||||
|
@ -2173,6 +2180,20 @@ _fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I")
|
||||||
_fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F")
|
_fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F")
|
||||||
|
|
||||||
|
|
||||||
|
def _decompression_bomb_check(size):
|
||||||
|
if MAX_IMAGE_PIXELS is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
pixels = size[0] * size[1]
|
||||||
|
|
||||||
|
if pixels > MAX_IMAGE_PIXELS:
|
||||||
|
warnings.warn(
|
||||||
|
"Image size (%d pixels) exceeds limit of %d pixels, "
|
||||||
|
"could be decompression bomb DOS attack." %
|
||||||
|
(pixels, MAX_IMAGE_PIXELS),
|
||||||
|
DecompressionBombWarning)
|
||||||
|
|
||||||
|
|
||||||
def open(fp, mode="r"):
|
def open(fp, mode="r"):
|
||||||
"""
|
"""
|
||||||
Opens and identifies the given image file.
|
Opens and identifies the given image file.
|
||||||
|
@ -2210,7 +2231,9 @@ def open(fp, mode="r"):
|
||||||
factory, accept = OPEN[i]
|
factory, accept = OPEN[i]
|
||||||
if not accept or accept(prefix):
|
if not accept or accept(prefix):
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
return factory(fp, filename)
|
im = factory(fp, filename)
|
||||||
|
_decompression_bomb_check(im.size)
|
||||||
|
return im
|
||||||
except (SyntaxError, IndexError, TypeError):
|
except (SyntaxError, IndexError, TypeError):
|
||||||
# import traceback
|
# import traceback
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
|
@ -2223,7 +2246,9 @@ def open(fp, mode="r"):
|
||||||
factory, accept = OPEN[i]
|
factory, accept = OPEN[i]
|
||||||
if not accept or accept(prefix):
|
if not accept or accept(prefix):
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
return factory(fp, filename)
|
im = factory(fp, filename)
|
||||||
|
_decompression_bomb_check(im.size)
|
||||||
|
return im
|
||||||
except (SyntaxError, IndexError, TypeError):
|
except (SyntaxError, IndexError, TypeError):
|
||||||
# import traceback
|
# import traceback
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
|
|
331
PIL/ImageCms.py
|
@ -1,19 +1,19 @@
|
||||||
#
|
## The Python Imaging Library.
|
||||||
# The Python Imaging Library.
|
## $Id$
|
||||||
# $Id$
|
|
||||||
#
|
## Optional color managment support, based on Kevin Cazabon's PyCMS
|
||||||
# optional color managment support, based on Kevin Cazabon's PyCMS
|
## library.
|
||||||
# library.
|
|
||||||
#
|
## History:
|
||||||
# History:
|
|
||||||
# 2009-03-08 fl Added to PIL.
|
## 2009-03-08 fl Added to PIL.
|
||||||
#
|
|
||||||
# Copyright (C) 2002-2003 Kevin Cazabon
|
## Copyright (C) 2002-2003 Kevin Cazabon
|
||||||
# Copyright (c) 2009 by Fredrik Lundh
|
## Copyright (c) 2009 by Fredrik Lundh
|
||||||
#
|
## Copyright (c) 2013 by Eric Soroos
|
||||||
# See the README file for information on usage and redistribution. See
|
|
||||||
# below for the original description.
|
## See the README file for information on usage and redistribution. See
|
||||||
#
|
## below for the original description.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
@ -66,7 +66,8 @@ pyCMS
|
||||||
|
|
||||||
Added try/except statements arount type() checks of
|
Added try/except statements arount type() checks of
|
||||||
potential CObjects... Python won't let you use type()
|
potential CObjects... Python won't let you use type()
|
||||||
on them, and raises a TypeError (stupid, if you ask me!)
|
on them, and raises a TypeError (stupid, if you ask
|
||||||
|
me!)
|
||||||
|
|
||||||
Added buildProofTransformFromOpenProfiles() function.
|
Added buildProofTransformFromOpenProfiles() function.
|
||||||
Additional fixes in DLL, see DLL code for details.
|
Additional fixes in DLL, see DLL code for details.
|
||||||
|
@ -89,8 +90,8 @@ try:
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
# Allow error import for doc purposes, but error out when accessing
|
# Allow error import for doc purposes, but error out when accessing
|
||||||
# anything in core.
|
# anything in core.
|
||||||
from _util import deferred_error
|
from _util import import_err
|
||||||
_imagingcms = deferred_error(ex)
|
_imagingcms = import_err(ex)
|
||||||
from PIL._util import isStringType
|
from PIL._util import isStringType
|
||||||
|
|
||||||
core = _imagingcms
|
core = _imagingcms
|
||||||
|
@ -115,7 +116,9 @@ FLAGS = {
|
||||||
"MATRIXOUTPUT": 2,
|
"MATRIXOUTPUT": 2,
|
||||||
"MATRIXONLY": (1 | 2),
|
"MATRIXONLY": (1 | 2),
|
||||||
"NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
|
"NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
|
||||||
"NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use)
|
# Don't create prelinearization tables on precalculated transforms
|
||||||
|
# (internal use):
|
||||||
|
"NOPRELINEARIZATION": 16,
|
||||||
"GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
|
"GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
|
||||||
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
||||||
"NOTPRECALC": 256,
|
"NOTPRECALC": 256,
|
||||||
|
@ -136,6 +139,7 @@ for flag in FLAGS.values():
|
||||||
if isinstance(flag, int):
|
if isinstance(flag, int):
|
||||||
_MAX_FLAG = _MAX_FLAG | flag
|
_MAX_FLAG = _MAX_FLAG | flag
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
# Experimental PIL-level API
|
# Experimental PIL-level API
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
|
@ -146,8 +150,13 @@ for flag in FLAGS.values():
|
||||||
class ImageCmsProfile:
|
class ImageCmsProfile:
|
||||||
|
|
||||||
def __init__(self, profile):
|
def __init__(self, profile):
|
||||||
# accepts a string (filename), a file-like object, or a low-level
|
"""
|
||||||
# profile object
|
:param profile: Either a string representing a filename,
|
||||||
|
a file like object containing a profile or a
|
||||||
|
low-level profile object
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
if isStringType(profile):
|
if isStringType(profile):
|
||||||
self._set(core.profile_open(profile), profile)
|
self._set(core.profile_open(profile), profile)
|
||||||
elif hasattr(profile, "read"):
|
elif hasattr(profile, "read"):
|
||||||
|
@ -165,14 +174,27 @@ class ImageCmsProfile:
|
||||||
self.product_name = None
|
self.product_name = None
|
||||||
self.product_info = None
|
self.product_info = None
|
||||||
|
|
||||||
class ImageCmsTransform(Image.ImagePointHandler):
|
def tobytes(self):
|
||||||
"""Transform. This can be used with the procedural API, or with the
|
"""
|
||||||
standard Image.point() method.
|
Returns the profile in a format suitable for embedding in
|
||||||
|
saved images.
|
||||||
|
|
||||||
|
:returns: a bytes object containing the ICC profile.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
return core.profile_tobytes(self.profile)
|
||||||
|
|
||||||
|
class ImageCmsTransform(Image.ImagePointHandler):
|
||||||
|
|
||||||
|
# Transform. This can be used with the procedural API, or with the
|
||||||
|
# standard Image.point() method.
|
||||||
|
#
|
||||||
|
# Will return the output profile in the output.info['icc_profile'].
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, input, output, input_mode, output_mode,
|
def __init__(self, input, output, input_mode, output_mode,
|
||||||
intent=INTENT_PERCEPTUAL,
|
intent=INTENT_PERCEPTUAL, proof=None,
|
||||||
proof=None, proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
|
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
|
||||||
if proof is None:
|
if proof is None:
|
||||||
self.transform = core.buildTransform(
|
self.transform = core.buildTransform(
|
||||||
input.profile, output.profile,
|
input.profile, output.profile,
|
||||||
|
@ -191,6 +213,8 @@ class ImageCmsTransform(Image.ImagePointHandler):
|
||||||
self.input_mode = self.inputMode = input_mode
|
self.input_mode = self.inputMode = input_mode
|
||||||
self.output_mode = self.outputMode = output_mode
|
self.output_mode = self.outputMode = output_mode
|
||||||
|
|
||||||
|
self.output_profile = output
|
||||||
|
|
||||||
def point(self, im):
|
def point(self, im):
|
||||||
return self.apply(im)
|
return self.apply(im)
|
||||||
|
|
||||||
|
@ -198,16 +222,19 @@ class ImageCmsTransform(Image.ImagePointHandler):
|
||||||
im.load()
|
im.load()
|
||||||
if imOut is None:
|
if imOut is None:
|
||||||
imOut = Image.new(self.output_mode, im.size, None)
|
imOut = Image.new(self.output_mode, im.size, None)
|
||||||
result = self.transform.apply(im.im.id, imOut.im.id)
|
self.transform.apply(im.im.id, imOut.im.id)
|
||||||
|
imOut.info['icc_profile'] = self.output_profile.tobytes()
|
||||||
return imOut
|
return imOut
|
||||||
|
|
||||||
def apply_in_place(self, im):
|
def apply_in_place(self, im):
|
||||||
im.load()
|
im.load()
|
||||||
if im.mode != self.output_mode:
|
if im.mode != self.output_mode:
|
||||||
raise ValueError("mode mismatch") # wrong output mode
|
raise ValueError("mode mismatch") # wrong output mode
|
||||||
result = self.transform.apply(im.im.id, im.im.id)
|
self.transform.apply(im.im.id, im.im.id)
|
||||||
|
im.info['icc_profile'] = self.output_profile.tobytes()
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
def get_display_profile(handle=None):
|
def get_display_profile(handle=None):
|
||||||
""" (experimental) Fetches the profile for the current display device.
|
""" (experimental) Fetches the profile for the current display device.
|
||||||
:returns: None if the profile is not known.
|
:returns: None if the profile is not known.
|
||||||
|
@ -229,15 +256,21 @@ def get_display_profile(handle=None):
|
||||||
profile = get()
|
profile = get()
|
||||||
return ImageCmsProfile(profile)
|
return ImageCmsProfile(profile)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
# pyCMS compatible layer
|
# pyCMS compatible layer
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
|
|
||||||
class PyCMSError(Exception):
|
class PyCMSError(Exception):
|
||||||
""" (pyCMS) Exception class. This is used for all errors in the pyCMS API. """
|
|
||||||
|
""" (pyCMS) Exception class.
|
||||||
|
This is used for all errors in the pyCMS API. """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, outputMode=None, inPlace=0, flags=0):
|
|
||||||
|
def profileToProfile(
|
||||||
|
im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL,
|
||||||
|
outputMode=None, inPlace=0, flags=0):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Applies an ICC transformation to a given image, mapping from
|
(pyCMS) Applies an ICC transformation to a given image, mapping from
|
||||||
inputProfile to outputProfile.
|
inputProfile to outputProfile.
|
||||||
|
@ -259,29 +292,33 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER
|
||||||
profiles, the input profile must handle RGB data, and the output
|
profiles, the input profile must handle RGB data, and the output
|
||||||
profile must handle CMYK data.
|
profile must handle CMYK data.
|
||||||
|
|
||||||
:param im: An open PIL image object (i.e. Image.new(...) or Image.open(...), etc.)
|
:param im: An open PIL image object (i.e. Image.new(...) or
|
||||||
:param inputProfile: String, as a valid filename path to the ICC input profile
|
Image.open(...), etc.)
|
||||||
you wish to use for this image, or a profile object
|
:param inputProfile: String, as a valid filename path to the ICC input
|
||||||
|
profile you wish to use for this image, or a profile object
|
||||||
:param outputProfile: String, as a valid filename path to the ICC output
|
:param outputProfile: String, as a valid filename path to the ICC output
|
||||||
profile you wish to use for this image, or a profile object
|
profile you wish to use for this image, or a profile object
|
||||||
:param renderingIntent: Integer (0-3) specifying the rendering intent you wish
|
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
||||||
to use for the transform
|
wish to use for the transform
|
||||||
|
|
||||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
:param outputMode: A valid PIL mode for the output image (i.e. "RGB", "CMYK",
|
they do.
|
||||||
etc.). Note: if rendering the image "inPlace", outputMode MUST be the
|
:param outputMode: A valid PIL mode for the output image (i.e. "RGB",
|
||||||
same mode as the input, or omitted completely. If omitted, the outputMode
|
"CMYK", etc.). Note: if rendering the image "inPlace", outputMode
|
||||||
will be the same as the mode of the input image (im.mode)
|
MUST be the same mode as the input, or omitted completely. If
|
||||||
:param inPlace: Boolean (1 = True, None or 0 = False). If True, the original
|
omitted, the outputMode will be the same as the mode of the input
|
||||||
image is modified in-place, and None is returned. If False (default), a
|
image (im.mode)
|
||||||
new Image object is returned with the transform applied.
|
:param inPlace: Boolean (1 = True, None or 0 = False). If True, the
|
||||||
|
original image is modified in-place, and None is returned. If False
|
||||||
|
(default), a new Image object is returned with the transform applied.
|
||||||
:param flags: Integer (0-...) specifying additional flags
|
:param flags: Integer (0-...) specifying additional flags
|
||||||
:returns: Either None or a new PIL image object, depending on value of inPlace
|
:returns: Either None or a new PIL image object, depending on value of
|
||||||
|
inPlace
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -292,7 +329,8 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER
|
||||||
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
||||||
|
|
||||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||||
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
|
raise PyCMSError(
|
||||||
|
"flags must be an integer between 0 and %s" + _MAX_FLAG)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not isinstance(inputProfile, ImageCmsProfile):
|
if not isinstance(inputProfile, ImageCmsProfile):
|
||||||
|
@ -300,7 +338,8 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER
|
||||||
if not isinstance(outputProfile, ImageCmsProfile):
|
if not isinstance(outputProfile, ImageCmsProfile):
|
||||||
outputProfile = ImageCmsProfile(outputProfile)
|
outputProfile = ImageCmsProfile(outputProfile)
|
||||||
transform = ImageCmsTransform(
|
transform = ImageCmsTransform(
|
||||||
inputProfile, outputProfile, im.mode, outputMode, renderingIntent, flags=flags
|
inputProfile, outputProfile, im.mode, outputMode,
|
||||||
|
renderingIntent, flags=flags
|
||||||
)
|
)
|
||||||
if inPlace:
|
if inPlace:
|
||||||
transform.apply_in_place(im)
|
transform.apply_in_place(im)
|
||||||
|
@ -323,8 +362,8 @@ def getOpenProfile(profileFilename):
|
||||||
If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
|
If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
|
||||||
will be raised.
|
will be raised.
|
||||||
|
|
||||||
:param profileFilename: String, as a valid filename path to the ICC profile you
|
:param profileFilename: String, as a valid filename path to the ICC profile
|
||||||
wish to open, or a file-like object.
|
you wish to open, or a file-like object.
|
||||||
:returns: A CmsProfile class object.
|
:returns: A CmsProfile class object.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
@ -334,7 +373,10 @@ def getOpenProfile(profileFilename):
|
||||||
except (IOError, TypeError, ValueError) as v:
|
except (IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, flags=0):
|
|
||||||
|
def buildTransform(
|
||||||
|
inputProfile, outputProfile, inMode, outMode,
|
||||||
|
renderingIntent=INTENT_PERCEPTUAL, flags=0):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
|
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
|
||||||
outputProfile. Use applyTransform to apply the transform to a given
|
outputProfile. Use applyTransform to apply the transform to a given
|
||||||
|
@ -367,14 +409,14 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent
|
||||||
manually overridden if you really want to, but I don't know of any
|
manually overridden if you really want to, but I don't know of any
|
||||||
time that would be of use, or would even work).
|
time that would be of use, or would even work).
|
||||||
|
|
||||||
:param inputProfile: String, as a valid filename path to the ICC input profile
|
:param inputProfile: String, as a valid filename path to the ICC input
|
||||||
you wish to use for this transform, or a profile object
|
profile you wish to use for this transform, or a profile object
|
||||||
:param outputProfile: String, as a valid filename path to the ICC output
|
:param outputProfile: String, as a valid filename path to the ICC output
|
||||||
profile you wish to use for this transform, or a profile object
|
profile you wish to use for this transform, or a profile object
|
||||||
:param inMode: String, as a valid PIL mode that the appropriate profile also
|
:param inMode: String, as a valid PIL mode that the appropriate profile
|
||||||
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||||
:param outMode: String, as a valid PIL mode that the appropriate profile also
|
:param outMode: String, as a valid PIL mode that the appropriate profile
|
||||||
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||||
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
||||||
wish to use for the transform
|
wish to use for the transform
|
||||||
|
|
||||||
|
@ -383,7 +425,8 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
|
they do.
|
||||||
:param flags: Integer (0-...) specifying additional flags
|
:param flags: Integer (0-...) specifying additional flags
|
||||||
:returns: A CmsTransform class object.
|
:returns: A CmsTransform class object.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
|
@ -393,18 +436,26 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent
|
||||||
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
||||||
|
|
||||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||||
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
|
raise PyCMSError(
|
||||||
|
"flags must be an integer between 0 and %s" + _MAX_FLAG)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not isinstance(inputProfile, ImageCmsProfile):
|
if not isinstance(inputProfile, ImageCmsProfile):
|
||||||
inputProfile = ImageCmsProfile(inputProfile)
|
inputProfile = ImageCmsProfile(inputProfile)
|
||||||
if not isinstance(outputProfile, ImageCmsProfile):
|
if not isinstance(outputProfile, ImageCmsProfile):
|
||||||
outputProfile = ImageCmsProfile(outputProfile)
|
outputProfile = ImageCmsProfile(outputProfile)
|
||||||
return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags)
|
return ImageCmsTransform(
|
||||||
|
inputProfile, outputProfile, inMode, outMode,
|
||||||
|
renderingIntent, flags=flags)
|
||||||
except (IOError, TypeError, ValueError) as v:
|
except (IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, flags=FLAGS["SOFTPROOFING"]):
|
|
||||||
|
def buildProofTransform(
|
||||||
|
inputProfile, outputProfile, proofProfile, inMode, outMode,
|
||||||
|
renderingIntent=INTENT_PERCEPTUAL,
|
||||||
|
proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC,
|
||||||
|
flags=FLAGS["SOFTPROOFING"]):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
|
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
|
||||||
outputProfile, but tries to simulate the result that would be
|
outputProfile, but tries to simulate the result that would be
|
||||||
|
@ -443,17 +494,17 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
|
||||||
when the simulated device has a much wider gamut than the output
|
when the simulated device has a much wider gamut than the output
|
||||||
device, you may obtain marginal results.
|
device, you may obtain marginal results.
|
||||||
|
|
||||||
:param inputProfile: String, as a valid filename path to the ICC input profile
|
:param inputProfile: String, as a valid filename path to the ICC input
|
||||||
you wish to use for this transform, or a profile object
|
profile you wish to use for this transform, or a profile object
|
||||||
:param outputProfile: String, as a valid filename path to the ICC output
|
:param outputProfile: String, as a valid filename path to the ICC output
|
||||||
(monitor, usually) profile you wish to use for this transform, or a
|
(monitor, usually) profile you wish to use for this transform, or a
|
||||||
profile object
|
profile object
|
||||||
:param proofProfile: String, as a valid filename path to the ICC proof profile
|
:param proofProfile: String, as a valid filename path to the ICC proof
|
||||||
you wish to use for this transform, or a profile object
|
profile you wish to use for this transform, or a profile object
|
||||||
:param inMode: String, as a valid PIL mode that the appropriate profile also
|
:param inMode: String, as a valid PIL mode that the appropriate profile
|
||||||
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||||
:param outMode: String, as a valid PIL mode that the appropriate profile also
|
:param outMode: String, as a valid PIL mode that the appropriate profile
|
||||||
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||||
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
||||||
wish to use for the input->proof (simulated) transform
|
wish to use for the input->proof (simulated) transform
|
||||||
|
|
||||||
|
@ -462,7 +513,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
|
they do.
|
||||||
:param proofRenderingIntent: Integer (0-3) specifying the rendering intent you
|
:param proofRenderingIntent: Integer (0-3) specifying the rendering intent you
|
||||||
wish to use for proof->output transform
|
wish to use for proof->output transform
|
||||||
|
|
||||||
|
@ -471,7 +523,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
|
they do.
|
||||||
:param flags: Integer (0-...) specifying additional flags
|
:param flags: Integer (0-...) specifying additional flags
|
||||||
:returns: A CmsTransform class object.
|
:returns: A CmsTransform class object.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
|
@ -481,7 +534,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
|
||||||
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
||||||
|
|
||||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||||
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
|
raise PyCMSError(
|
||||||
|
"flags must be an integer between 0 and %s" + _MAX_FLAG)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not isinstance(inputProfile, ImageCmsProfile):
|
if not isinstance(inputProfile, ImageCmsProfile):
|
||||||
|
@ -490,13 +544,16 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
|
||||||
outputProfile = ImageCmsProfile(outputProfile)
|
outputProfile = ImageCmsProfile(outputProfile)
|
||||||
if not isinstance(proofProfile, ImageCmsProfile):
|
if not isinstance(proofProfile, ImageCmsProfile):
|
||||||
proofProfile = ImageCmsProfile(proofProfile)
|
proofProfile = ImageCmsProfile(proofProfile)
|
||||||
return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, proofProfile, proofRenderingIntent, flags)
|
return ImageCmsTransform(
|
||||||
|
inputProfile, outputProfile, inMode, outMode, renderingIntent,
|
||||||
|
proofProfile, proofRenderingIntent, flags)
|
||||||
except (IOError, TypeError, ValueError) as v:
|
except (IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
buildTransformFromOpenProfiles = buildTransform
|
buildTransformFromOpenProfiles = buildTransform
|
||||||
buildProofTransformFromOpenProfiles = buildProofTransform
|
buildProofTransformFromOpenProfiles = buildProofTransform
|
||||||
|
|
||||||
|
|
||||||
def applyTransform(im, transform, inPlace=0):
|
def applyTransform(im, transform, inPlace=0):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Applies a transform to a given image.
|
(pyCMS) Applies a transform to a given image.
|
||||||
|
@ -514,8 +571,8 @@ def applyTransform(im, transform, inPlace=0):
|
||||||
is raised.
|
is raised.
|
||||||
|
|
||||||
This function applies a pre-calculated transform (from
|
This function applies a pre-calculated transform (from
|
||||||
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) to an
|
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
|
||||||
image. The transform can be used for multiple images, saving
|
to an image. The transform can be used for multiple images, saving
|
||||||
considerable calcuation time if doing the same conversion multiple times.
|
considerable calcuation time if doing the same conversion multiple times.
|
||||||
|
|
||||||
If you want to modify im in-place instead of receiving a new image as
|
If you want to modify im in-place instead of receiving a new image as
|
||||||
|
@ -528,10 +585,12 @@ def applyTransform(im, transform, inPlace=0):
|
||||||
:param im: A PIL Image object, and im.mode must be the same as the inMode
|
:param im: A PIL Image object, and im.mode must be the same as the inMode
|
||||||
supported by the transform.
|
supported by the transform.
|
||||||
:param transform: A valid CmsTransform class object
|
:param transform: A valid CmsTransform class object
|
||||||
:param inPlace: Bool (1 == True, 0 or None == False). If True, im is modified
|
:param inPlace: Bool (1 == True, 0 or None == False). If True, im is
|
||||||
in place and None is returned, if False, a new Image object with the
|
modified in place and None is returned, if False, a new Image object
|
||||||
transform applied is returned (and im is not changed). The default is False.
|
with the transform applied is returned (and im is not changed). The
|
||||||
:returns: Either None, or a new PIL Image object, depending on the value of inPlace
|
default is False.
|
||||||
|
:returns: Either None, or a new PIL Image object, depending on the value of
|
||||||
|
inPlace. The profile will be returned in the image's info['icc_profile'].
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -546,6 +605,7 @@ def applyTransform(im, transform, inPlace=0):
|
||||||
|
|
||||||
return imOut
|
return imOut
|
||||||
|
|
||||||
|
|
||||||
def createProfile(colorSpace, colorTemp=-1):
|
def createProfile(colorSpace, colorTemp=-1):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Creates a profile.
|
(pyCMS) Creates a profile.
|
||||||
|
@ -562,30 +622,36 @@ def createProfile(colorSpace, colorTemp=-1):
|
||||||
ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
|
ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
|
||||||
to images.
|
to images.
|
||||||
|
|
||||||
:param colorSpace: String, the color space of the profile you wish to create.
|
:param colorSpace: String, the color space of the profile you wish to
|
||||||
|
create.
|
||||||
Currently only "LAB", "XYZ", and "sRGB" are supported.
|
Currently only "LAB", "XYZ", and "sRGB" are supported.
|
||||||
:param colorTemp: Positive integer for the white point for the profile, in
|
:param colorTemp: Positive integer for the white point for the profile, in
|
||||||
degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
|
degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
|
||||||
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB profiles,
|
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
|
||||||
and is ignored for XYZ and sRGB.
|
profiles, and is ignored for XYZ and sRGB.
|
||||||
:returns: A CmsProfile class object
|
:returns: A CmsProfile class object
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if colorSpace not in ["LAB", "XYZ", "sRGB"]:
|
if colorSpace not in ["LAB", "XYZ", "sRGB"]:
|
||||||
raise PyCMSError("Color space not supported for on-the-fly profile creation (%s)" % colorSpace)
|
raise PyCMSError(
|
||||||
|
"Color space not supported for on-the-fly profile creation (%s)"
|
||||||
|
% colorSpace)
|
||||||
|
|
||||||
if colorSpace == "LAB":
|
if colorSpace == "LAB":
|
||||||
try:
|
try:
|
||||||
colorTemp = float(colorTemp)
|
colorTemp = float(colorTemp)
|
||||||
except:
|
except:
|
||||||
raise PyCMSError("Color temperature must be numeric, \"%s\" not valid" % colorTemp)
|
raise PyCMSError(
|
||||||
|
"Color temperature must be numeric, \"%s\" not valid"
|
||||||
|
% colorTemp)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return core.createProfile(colorSpace, colorTemp)
|
return core.createProfile(colorSpace, colorTemp)
|
||||||
except (TypeError, ValueError) as v:
|
except (TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileName(profile):
|
def getProfileName(profile):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -600,10 +666,10 @@ def getProfileName(profile):
|
||||||
profile was originally created. Sometimes this tag also contains
|
profile was originally created. Sometimes this tag also contains
|
||||||
additional information supplied by the creator.
|
additional information supplied by the creator.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal name of the profile as stored in an
|
:returns: A string containing the internal name of the profile as stored
|
||||||
ICC tag.
|
in an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -627,6 +693,7 @@ def getProfileName(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileInfo(profile):
|
def getProfileInfo(profile):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Gets the internal product information for the given profile.
|
(pyCMS) Gets the internal product information for the given profile.
|
||||||
|
@ -641,10 +708,10 @@ def getProfileInfo(profile):
|
||||||
info tag. This often contains details about the profile, and how it
|
info tag. This often contains details about the profile, and how it
|
||||||
was created, as supplied by the creator.
|
was created, as supplied by the creator.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in
|
||||||
tag.
|
an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -652,7 +719,8 @@ def getProfileInfo(profile):
|
||||||
if not isinstance(profile, ImageCmsProfile):
|
if not isinstance(profile, ImageCmsProfile):
|
||||||
profile = ImageCmsProfile(profile)
|
profile = ImageCmsProfile(profile)
|
||||||
# add an extra newline to preserve pyCMS compatibility
|
# add an extra newline to preserve pyCMS compatibility
|
||||||
# Python, not C. the white point bits weren't working well, so skipping.
|
# Python, not C. the white point bits weren't working well,
|
||||||
|
# so skipping.
|
||||||
# // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
|
# // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
|
||||||
description = profile.profile.product_description
|
description = profile.profile.product_description
|
||||||
cpright = profile.profile.product_copyright
|
cpright = profile.profile.product_copyright
|
||||||
|
@ -679,10 +747,10 @@ def getProfileCopyright(profile):
|
||||||
Use this function to obtain the information stored in the profile's
|
Use this function to obtain the information stored in the profile's
|
||||||
copyright tag.
|
copyright tag.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in
|
||||||
tag.
|
an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
@ -693,6 +761,7 @@ def getProfileCopyright(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileManufacturer(profile):
|
def getProfileManufacturer(profile):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Gets the manufacturer for the given profile.
|
(pyCMS) Gets the manufacturer for the given profile.
|
||||||
|
@ -700,16 +769,16 @@ def getProfileManufacturer(profile):
|
||||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||||
a PyCMSError is raised.
|
a PyCMSError is raised.
|
||||||
|
|
||||||
If an error occurs while trying to obtain the manufacturer tag, a PyCMSError
|
If an error occurs while trying to obtain the manufacturer tag, a
|
||||||
is raised
|
PyCMSError is raised
|
||||||
|
|
||||||
Use this function to obtain the information stored in the profile's
|
Use this function to obtain the information stored in the profile's
|
||||||
manufacturer tag.
|
manufacturer tag.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in
|
||||||
tag.
|
an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
@ -720,6 +789,7 @@ def getProfileManufacturer(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileModel(profile):
|
def getProfileModel(profile):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Gets the model for the given profile.
|
(pyCMS) Gets the model for the given profile.
|
||||||
|
@ -733,10 +803,10 @@ def getProfileModel(profile):
|
||||||
Use this function to obtain the information stored in the profile's
|
Use this function to obtain the information stored in the profile's
|
||||||
model tag.
|
model tag.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in
|
||||||
tag.
|
an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -748,6 +818,7 @@ def getProfileModel(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileDescription(profile):
|
def getProfileDescription(profile):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Gets the description for the given profile.
|
(pyCMS) Gets the description for the given profile.
|
||||||
|
@ -761,10 +832,10 @@ def getProfileDescription(profile):
|
||||||
Use this function to obtain the information stored in the profile's
|
Use this function to obtain the information stored in the profile's
|
||||||
description tag.
|
description tag.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in an
|
||||||
tag.
|
ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -793,16 +864,18 @@ def getDefaultIntent(profile):
|
||||||
If you wish to use a different intent than returned, use
|
If you wish to use a different intent than returned, use
|
||||||
ImageCms.isIntentSupported() to verify it will work first.
|
ImageCms.isIntentSupported() to verify it will work first.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: Integer 0-3 specifying the default rendering intent for this profile.
|
:returns: Integer 0-3 specifying the default rendering intent for this
|
||||||
|
profile.
|
||||||
|
|
||||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
|
they do.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -813,6 +886,7 @@ def getDefaultIntent(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def isIntentSupported(profile, intent, direction):
|
def isIntentSupported(profile, intent, direction):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Checks if a given intent is supported.
|
(pyCMS) Checks if a given intent is supported.
|
||||||
|
@ -828,17 +902,18 @@ def isIntentSupported(profile, intent, direction):
|
||||||
potential PyCMSError that will occur if they don't support the modes
|
potential PyCMSError that will occur if they don't support the modes
|
||||||
you select.
|
you select.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:param intent: Integer (0-3) specifying the rendering intent you wish to use
|
:param intent: Integer (0-3) specifying the rendering intent you wish to
|
||||||
with this profile
|
use with this profile
|
||||||
|
|
||||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
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 specifing if the profile is to be used for input,
|
||||||
output, or proof
|
output, or proof
|
||||||
|
|
||||||
|
@ -862,6 +937,7 @@ def isIntentSupported(profile, intent, direction):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def versions():
|
def versions():
|
||||||
"""
|
"""
|
||||||
(pyCMS) Fetches versions.
|
(pyCMS) Fetches versions.
|
||||||
|
@ -869,7 +945,8 @@ def versions():
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
return (
|
return (
|
||||||
VERSION, core.littlecms_version, sys.version.split()[0], Image.VERSION
|
VERSION, core.littlecms_version,
|
||||||
|
sys.version.split()[0], Image.VERSION
|
||||||
)
|
)
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -880,14 +957,16 @@ if __name__ == "__main__":
|
||||||
from PIL import ImageCms
|
from PIL import ImageCms
|
||||||
print(__doc__)
|
print(__doc__)
|
||||||
|
|
||||||
for f in dir(pyCMS):
|
for f in dir(ImageCms):
|
||||||
print("="*80)
|
doc = None
|
||||||
print("%s" %f)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
exec ("doc = ImageCms.%s.__doc__" %(f))
|
exec("doc = %s.__doc__" % (f))
|
||||||
if "pyCMS" in doc:
|
if "pyCMS" in doc:
|
||||||
# so we don't get the __doc__ string for imported modules
|
# so we don't get the __doc__ string for imported modules
|
||||||
|
print("=" * 80)
|
||||||
|
print("%s" % f)
|
||||||
print(doc)
|
print(doc)
|
||||||
except AttributeError:
|
except (AttributeError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# End of file
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#
|
#
|
||||||
# For a background, see "Image Processing By Interpolation and
|
# For a background, see "Image Processing By Interpolation and
|
||||||
# Extrapolation", Paul Haeberli and Douglas Voorhies. Available
|
# Extrapolation", Paul Haeberli and Douglas Voorhies. Available
|
||||||
# at http://www.sgi.com/grafica/interp/index.html
|
# at http://www.graficaobscura.com/interp/index.html
|
||||||
#
|
#
|
||||||
# History:
|
# History:
|
||||||
# 1996-03-23 fl Created
|
# 1996-03-23 fl Created
|
||||||
|
|
|
@ -133,11 +133,27 @@ class ImageFile(Image.Image):
|
||||||
return pixel
|
return pixel
|
||||||
|
|
||||||
self.map = None
|
self.map = None
|
||||||
|
use_mmap = self.filename and len(self.tile) == 1
|
||||||
|
# As of pypy 2.1.0, memory mapping was failing here.
|
||||||
|
use_mmap = use_mmap and not hasattr(sys, 'pypy_version_info')
|
||||||
|
|
||||||
readonly = 0
|
readonly = 0
|
||||||
|
|
||||||
if self.filename and len(self.tile) == 1 and not hasattr(sys, 'pypy_version_info'):
|
# look for read/seek overrides
|
||||||
# As of pypy 2.1.0, memory mapping was failing here.
|
try:
|
||||||
|
read = self.load_read
|
||||||
|
# don't use mmap if there are custom read/seek functions
|
||||||
|
use_mmap = False
|
||||||
|
except AttributeError:
|
||||||
|
read = self.fp.read
|
||||||
|
|
||||||
|
try:
|
||||||
|
seek = self.load_seek
|
||||||
|
use_mmap = False
|
||||||
|
except AttributeError:
|
||||||
|
seek = self.fp.seek
|
||||||
|
|
||||||
|
if use_mmap:
|
||||||
# try memory mapping
|
# try memory mapping
|
||||||
d, e, o, a = self.tile[0]
|
d, e, o, a = self.tile[0]
|
||||||
if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
|
if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
|
||||||
|
@ -165,19 +181,7 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
self.load_prepare()
|
self.load_prepare()
|
||||||
|
|
||||||
# look for read/seek overrides
|
|
||||||
try:
|
|
||||||
read = self.load_read
|
|
||||||
except AttributeError:
|
|
||||||
read = self.fp.read
|
|
||||||
|
|
||||||
try:
|
|
||||||
seek = self.load_seek
|
|
||||||
except AttributeError:
|
|
||||||
seek = self.fp.seek
|
|
||||||
|
|
||||||
if not self.map:
|
if not self.map:
|
||||||
|
|
||||||
# sort tiles in file order
|
# sort tiles in file order
|
||||||
self.tile.sort(key=_tilesort)
|
self.tile.sort(key=_tilesort)
|
||||||
|
|
||||||
|
@ -502,5 +506,5 @@ def _safe_read(fp, size):
|
||||||
if not block:
|
if not block:
|
||||||
break
|
break
|
||||||
data.append(block)
|
data.append(block)
|
||||||
size = size - len(block)
|
size -= len(block)
|
||||||
return b"".join(data)
|
return b"".join(data)
|
||||||
|
|
|
@ -29,13 +29,15 @@ from __future__ import print_function
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL._util import isDirectory, isPath
|
from PIL._util import isDirectory, isPath
|
||||||
import os, sys
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import warnings
|
import warnings
|
||||||
except ImportError:
|
except ImportError:
|
||||||
warnings = None
|
warnings = None
|
||||||
|
|
||||||
|
|
||||||
class _imagingft_not_installed:
|
class _imagingft_not_installed:
|
||||||
# module placeholder
|
# module placeholder
|
||||||
def __getattr__(self, id):
|
def __getattr__(self, id):
|
||||||
|
@ -90,7 +92,7 @@ class ImageFont:
|
||||||
# read PILfont header
|
# read PILfont header
|
||||||
if file.readline() != b"PILfont\n":
|
if file.readline() != b"PILfont\n":
|
||||||
raise SyntaxError("Not a PILfont file")
|
raise SyntaxError("Not a PILfont file")
|
||||||
d = file.readline().split(b";")
|
file.readline().split(b";")
|
||||||
self.info = [] # FIXME: should be a dictionary
|
self.info = [] # FIXME: should be a dictionary
|
||||||
while True:
|
while True:
|
||||||
s = file.readline()
|
s = file.readline()
|
||||||
|
@ -113,6 +115,7 @@ class ImageFont:
|
||||||
self.getsize = self.font.getsize
|
self.getsize = self.font.getsize
|
||||||
self.getmask = self.font.getmask
|
self.getmask = self.font.getmask
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Wrapper for FreeType fonts. Application code should use the
|
# Wrapper for FreeType fonts. Application code should use the
|
||||||
# <b>truetype</b> factory function to create font objects.
|
# <b>truetype</b> factory function to create font objects.
|
||||||
|
@ -124,14 +127,18 @@ class FreeTypeFont:
|
||||||
# FIXME: use service provider instead
|
# FIXME: use service provider instead
|
||||||
if file:
|
if file:
|
||||||
if warnings:
|
if warnings:
|
||||||
warnings.warn('file parameter deprecated, please use font parameter instead.', DeprecationWarning)
|
warnings.warn(
|
||||||
|
'file parameter deprecated, '
|
||||||
|
'please use font parameter instead.',
|
||||||
|
DeprecationWarning)
|
||||||
font = file
|
font = file
|
||||||
|
|
||||||
if isPath(font):
|
if isPath(font):
|
||||||
self.font = core.getfont(font, size, index, encoding)
|
self.font = core.getfont(font, size, index, encoding)
|
||||||
else:
|
else:
|
||||||
self.font_bytes = font.read()
|
self.font_bytes = font.read()
|
||||||
self.font = core.getfont("", size, index, encoding, self.font_bytes)
|
self.font = core.getfont(
|
||||||
|
"", size, index, encoding, self.font_bytes)
|
||||||
|
|
||||||
def getname(self):
|
def getname(self):
|
||||||
return self.font.family, self.font.style
|
return self.font.family, self.font.style
|
||||||
|
@ -140,7 +147,8 @@ class FreeTypeFont:
|
||||||
return self.font.ascent, self.font.descent
|
return self.font.ascent, self.font.descent
|
||||||
|
|
||||||
def getsize(self, text):
|
def getsize(self, text):
|
||||||
return self.font.getsize(text)[0]
|
size, offset = self.font.getsize(text)
|
||||||
|
return (size[0] + offset[0], size[1] + offset[1])
|
||||||
|
|
||||||
def getoffset(self, text):
|
def getoffset(self, text):
|
||||||
return self.font.getsize(text)[1]
|
return self.font.getsize(text)[1]
|
||||||
|
@ -163,6 +171,7 @@ class FreeTypeFont:
|
||||||
# be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM,
|
# be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM,
|
||||||
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
||||||
|
|
||||||
|
|
||||||
class TransposedFont:
|
class TransposedFont:
|
||||||
"Wrapper for writing rotated or mirrored text"
|
"Wrapper for writing rotated or mirrored text"
|
||||||
|
|
||||||
|
@ -221,7 +230,10 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
||||||
|
|
||||||
if filename:
|
if filename:
|
||||||
if warnings:
|
if warnings:
|
||||||
warnings.warn('filename parameter deprecated, please use font parameter instead.', DeprecationWarning)
|
warnings.warn(
|
||||||
|
'filename parameter deprecated, '
|
||||||
|
'please use font parameter instead.',
|
||||||
|
DeprecationWarning)
|
||||||
font = filename
|
font = filename
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -392,15 +404,4 @@ w7IkEbzhVQAAAABJRU5ErkJggg==
|
||||||
'''))))
|
'''))))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
# End of file
|
||||||
if __name__ == "__main__":
|
|
||||||
# create font data chunk for embedding
|
|
||||||
import base64, os, sys
|
|
||||||
font = "../Images/courB08"
|
|
||||||
print(" f._load_pilfont_data(")
|
|
||||||
print(" # %s" % os.path.basename(font))
|
|
||||||
print(" BytesIO(base64.decodestring(b'''")
|
|
||||||
base64.encode(open(font + ".pil", "rb"), sys.stdout)
|
|
||||||
print("''')), Image.open(BytesIO(base64.decodestring(b'''")
|
|
||||||
base64.encode(open(font + ".pbm", "rb"), sys.stdout)
|
|
||||||
print("'''))))")
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL import _imagingmath
|
from PIL import _imagingmath
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import builtins
|
import builtins
|
||||||
|
@ -27,9 +26,11 @@ except ImportError:
|
||||||
|
|
||||||
VERBOSE = 0
|
VERBOSE = 0
|
||||||
|
|
||||||
|
|
||||||
def _isconstant(v):
|
def _isconstant(v):
|
||||||
return isinstance(v, int) or isinstance(v, float)
|
return isinstance(v, int) or isinstance(v, float)
|
||||||
|
|
||||||
|
|
||||||
class _Operand:
|
class _Operand:
|
||||||
# wraps an image operand, providing standard operators
|
# wraps an image operand, providing standard operators
|
||||||
|
|
||||||
|
@ -69,20 +70,25 @@ class _Operand:
|
||||||
im2 = self.__fixup(im2)
|
im2 = self.__fixup(im2)
|
||||||
if im1.mode != im2.mode:
|
if im1.mode != im2.mode:
|
||||||
# convert both arguments to floating point
|
# convert both arguments to floating point
|
||||||
if im1.mode != "F": im1 = im1.convert("F")
|
if im1.mode != "F":
|
||||||
if im2.mode != "F": im2 = im2.convert("F")
|
im1 = im1.convert("F")
|
||||||
|
if im2.mode != "F":
|
||||||
|
im2 = im2.convert("F")
|
||||||
if im1.mode != im2.mode:
|
if im1.mode != im2.mode:
|
||||||
raise ValueError("mode mismatch")
|
raise ValueError("mode mismatch")
|
||||||
if im1.size != im2.size:
|
if im1.size != im2.size:
|
||||||
# crop both arguments to a common size
|
# crop both arguments to a common size
|
||||||
size = (min(im1.size[0], im2.size[0]),
|
size = (min(im1.size[0], im2.size[0]),
|
||||||
min(im1.size[1], im2.size[1]))
|
min(im1.size[1], im2.size[1]))
|
||||||
if im1.size != size: im1 = im1.crop((0, 0) + size)
|
if im1.size != size:
|
||||||
if im2.size != size: im2 = im2.crop((0, 0) + size)
|
im1 = im1.crop((0, 0) + size)
|
||||||
|
if im2.size != size:
|
||||||
|
im2 = im2.crop((0, 0) + size)
|
||||||
out = Image.new(mode or im1.mode, size, None)
|
out = Image.new(mode or im1.mode, size, None)
|
||||||
else:
|
else:
|
||||||
out = Image.new(mode or im1.mode, im1.size, None)
|
out = Image.new(mode or im1.mode, im1.size, None)
|
||||||
im1.load(); im2.load()
|
im1.load()
|
||||||
|
im2.load()
|
||||||
try:
|
try:
|
||||||
op = getattr(_imagingmath, op+"_"+im1.mode)
|
op = getattr(_imagingmath, op+"_"+im1.mode)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -102,34 +108,47 @@ class _Operand:
|
||||||
|
|
||||||
def __abs__(self):
|
def __abs__(self):
|
||||||
return self.apply("abs", self)
|
return self.apply("abs", self)
|
||||||
|
|
||||||
def __pos__(self):
|
def __pos__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __neg__(self):
|
def __neg__(self):
|
||||||
return self.apply("neg", self)
|
return self.apply("neg", self)
|
||||||
|
|
||||||
# binary operators
|
# binary operators
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return self.apply("add", self, other)
|
return self.apply("add", self, other)
|
||||||
|
|
||||||
def __radd__(self, other):
|
def __radd__(self, other):
|
||||||
return self.apply("add", other, self)
|
return self.apply("add", other, self)
|
||||||
|
|
||||||
def __sub__(self, other):
|
def __sub__(self, other):
|
||||||
return self.apply("sub", self, other)
|
return self.apply("sub", self, other)
|
||||||
|
|
||||||
def __rsub__(self, other):
|
def __rsub__(self, other):
|
||||||
return self.apply("sub", other, self)
|
return self.apply("sub", other, self)
|
||||||
|
|
||||||
def __mul__(self, other):
|
def __mul__(self, other):
|
||||||
return self.apply("mul", self, other)
|
return self.apply("mul", self, other)
|
||||||
|
|
||||||
def __rmul__(self, other):
|
def __rmul__(self, other):
|
||||||
return self.apply("mul", other, self)
|
return self.apply("mul", other, self)
|
||||||
|
|
||||||
def __truediv__(self, other):
|
def __truediv__(self, other):
|
||||||
return self.apply("div", self, other)
|
return self.apply("div", self, other)
|
||||||
|
|
||||||
def __rtruediv__(self, other):
|
def __rtruediv__(self, other):
|
||||||
return self.apply("div", other, self)
|
return self.apply("div", other, self)
|
||||||
|
|
||||||
def __mod__(self, other):
|
def __mod__(self, other):
|
||||||
return self.apply("mod", self, other)
|
return self.apply("mod", self, other)
|
||||||
|
|
||||||
def __rmod__(self, other):
|
def __rmod__(self, other):
|
||||||
return self.apply("mod", other, self)
|
return self.apply("mod", other, self)
|
||||||
|
|
||||||
def __pow__(self, other):
|
def __pow__(self, other):
|
||||||
return self.apply("pow", self, other)
|
return self.apply("pow", self, other)
|
||||||
|
|
||||||
def __rpow__(self, other):
|
def __rpow__(self, other):
|
||||||
return self.apply("pow", other, self)
|
return self.apply("pow", other, self)
|
||||||
|
|
||||||
|
@ -143,54 +162,77 @@ class _Operand:
|
||||||
# bitwise
|
# bitwise
|
||||||
def __invert__(self):
|
def __invert__(self):
|
||||||
return self.apply("invert", self)
|
return self.apply("invert", self)
|
||||||
|
|
||||||
def __and__(self, other):
|
def __and__(self, other):
|
||||||
return self.apply("and", self, other)
|
return self.apply("and", self, other)
|
||||||
|
|
||||||
def __rand__(self, other):
|
def __rand__(self, other):
|
||||||
return self.apply("and", other, self)
|
return self.apply("and", other, self)
|
||||||
|
|
||||||
def __or__(self, other):
|
def __or__(self, other):
|
||||||
return self.apply("or", self, other)
|
return self.apply("or", self, other)
|
||||||
|
|
||||||
def __ror__(self, other):
|
def __ror__(self, other):
|
||||||
return self.apply("or", other, self)
|
return self.apply("or", other, self)
|
||||||
|
|
||||||
def __xor__(self, other):
|
def __xor__(self, other):
|
||||||
return self.apply("xor", self, other)
|
return self.apply("xor", self, other)
|
||||||
|
|
||||||
def __rxor__(self, other):
|
def __rxor__(self, other):
|
||||||
return self.apply("xor", other, self)
|
return self.apply("xor", other, self)
|
||||||
|
|
||||||
def __lshift__(self, other):
|
def __lshift__(self, other):
|
||||||
return self.apply("lshift", self, other)
|
return self.apply("lshift", self, other)
|
||||||
|
|
||||||
def __rshift__(self, other):
|
def __rshift__(self, other):
|
||||||
return self.apply("rshift", self, other)
|
return self.apply("rshift", self, other)
|
||||||
|
|
||||||
# logical
|
# logical
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.apply("eq", self, other)
|
return self.apply("eq", self, other)
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return self.apply("ne", self, other)
|
return self.apply("ne", self, other)
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.apply("lt", self, other)
|
return self.apply("lt", self, other)
|
||||||
|
|
||||||
def __le__(self, other):
|
def __le__(self, other):
|
||||||
return self.apply("le", self, other)
|
return self.apply("le", self, other)
|
||||||
|
|
||||||
def __gt__(self, other):
|
def __gt__(self, other):
|
||||||
return self.apply("gt", self, other)
|
return self.apply("gt", self, other)
|
||||||
|
|
||||||
def __ge__(self, other):
|
def __ge__(self, other):
|
||||||
return self.apply("ge", self, other)
|
return self.apply("ge", self, other)
|
||||||
|
|
||||||
|
|
||||||
# conversions
|
# conversions
|
||||||
def imagemath_int(self):
|
def imagemath_int(self):
|
||||||
return _Operand(self.im.convert("I"))
|
return _Operand(self.im.convert("I"))
|
||||||
|
|
||||||
|
|
||||||
def imagemath_float(self):
|
def imagemath_float(self):
|
||||||
return _Operand(self.im.convert("F"))
|
return _Operand(self.im.convert("F"))
|
||||||
|
|
||||||
|
|
||||||
# logical
|
# logical
|
||||||
def imagemath_equal(self, other):
|
def imagemath_equal(self, other):
|
||||||
return self.apply("eq", self, other, mode="I")
|
return self.apply("eq", self, other, mode="I")
|
||||||
|
|
||||||
|
|
||||||
def imagemath_notequal(self, other):
|
def imagemath_notequal(self, other):
|
||||||
return self.apply("ne", self, other, mode="I")
|
return self.apply("ne", self, other, mode="I")
|
||||||
|
|
||||||
|
|
||||||
def imagemath_min(self, other):
|
def imagemath_min(self, other):
|
||||||
return self.apply("min", self, other)
|
return self.apply("min", self, other)
|
||||||
|
|
||||||
|
|
||||||
def imagemath_max(self, other):
|
def imagemath_max(self, other):
|
||||||
return self.apply("max", self, other)
|
return self.apply("max", self, other)
|
||||||
|
|
||||||
|
|
||||||
def imagemath_convert(self, mode):
|
def imagemath_convert(self, mode):
|
||||||
return _Operand(self.im.convert(mode))
|
return _Operand(self.im.convert(mode))
|
||||||
|
|
||||||
|
|
245
PIL/ImageMorph.py
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
# A binary morphology add-on for the Python Imaging Library
|
||||||
|
#
|
||||||
|
# History:
|
||||||
|
# 2014-06-04 Initial version.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import _imagingmorph
|
||||||
|
import re
|
||||||
|
|
||||||
|
LUT_SIZE = 1 << 9
|
||||||
|
|
||||||
|
|
||||||
|
class LutBuilder:
|
||||||
|
"""A class for building a MorphLut from a descriptive language
|
||||||
|
|
||||||
|
The input patterns is a list of a strings sequences like these::
|
||||||
|
|
||||||
|
4:(...
|
||||||
|
.1.
|
||||||
|
111)->1
|
||||||
|
|
||||||
|
(whitespaces including linebreaks are ignored). The option 4
|
||||||
|
describes a series of symmetry operations (in this case a
|
||||||
|
4-rotation), the pattern is described by:
|
||||||
|
|
||||||
|
- . or X - Ignore
|
||||||
|
- 1 - Pixel is on
|
||||||
|
- 0 - Pixel is off
|
||||||
|
|
||||||
|
The result of the operation is described after "->" string.
|
||||||
|
|
||||||
|
The default is to return the current pixel value, which is
|
||||||
|
returned if no other match is found.
|
||||||
|
|
||||||
|
Operations:
|
||||||
|
|
||||||
|
- 4 - 4 way rotation
|
||||||
|
- N - Negate
|
||||||
|
- 1 - Dummy op for no other operation (an op must always be given)
|
||||||
|
- M - Mirroring
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
lb = LutBuilder(patterns = ["4:(... .1. 111)->1"])
|
||||||
|
lut = lb.build_lut()
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, patterns=None, op_name=None):
|
||||||
|
if patterns is not None:
|
||||||
|
self.patterns = patterns
|
||||||
|
else:
|
||||||
|
self.patterns = []
|
||||||
|
self.lut = None
|
||||||
|
if op_name is not None:
|
||||||
|
known_patterns = {
|
||||||
|
'corner': ['1:(... ... ...)->0',
|
||||||
|
'4:(00. 01. ...)->1'],
|
||||||
|
'dilation4': ['4:(... .0. .1.)->1'],
|
||||||
|
'dilation8': ['4:(... .0. .1.)->1',
|
||||||
|
'4:(... .0. ..1)->1'],
|
||||||
|
'erosion4': ['4:(... .1. .0.)->0'],
|
||||||
|
'erosion8': ['4:(... .1. .0.)->0',
|
||||||
|
'4:(... .1. ..0)->0'],
|
||||||
|
'edge': ['1:(... ... ...)->0',
|
||||||
|
'4:(.0. .1. ...)->1',
|
||||||
|
'4:(01. .1. ...)->1']
|
||||||
|
}
|
||||||
|
if op_name not in known_patterns:
|
||||||
|
raise Exception('Unknown pattern '+op_name+'!')
|
||||||
|
|
||||||
|
self.patterns = known_patterns[op_name]
|
||||||
|
|
||||||
|
def add_patterns(self, patterns):
|
||||||
|
self.patterns += patterns
|
||||||
|
|
||||||
|
def build_default_lut(self):
|
||||||
|
symbols = [0, 1]
|
||||||
|
m = 1 << 4 # pos of current pixel
|
||||||
|
self.lut = bytearray([symbols[(i & m) > 0] for i in range(LUT_SIZE)])
|
||||||
|
|
||||||
|
def get_lut(self):
|
||||||
|
return self.lut
|
||||||
|
|
||||||
|
def _string_permute(self, pattern, permutation):
|
||||||
|
"""string_permute takes a pattern and a permutation and returns the
|
||||||
|
string permuted according to the permutation list.
|
||||||
|
"""
|
||||||
|
assert(len(permutation) == 9)
|
||||||
|
return ''.join([pattern[p] for p in permutation])
|
||||||
|
|
||||||
|
def _pattern_permute(self, basic_pattern, options, basic_result):
|
||||||
|
"""pattern_permute takes a basic pattern and its result and clones
|
||||||
|
the pattern according to the modifications described in the $options
|
||||||
|
parameter. It returns a list of all cloned patterns."""
|
||||||
|
patterns = [(basic_pattern, basic_result)]
|
||||||
|
|
||||||
|
# rotations
|
||||||
|
if '4' in options:
|
||||||
|
res = patterns[-1][1]
|
||||||
|
for i in range(4):
|
||||||
|
patterns.append(
|
||||||
|
(self._string_permute(patterns[-1][0], [6, 3, 0,
|
||||||
|
7, 4, 1,
|
||||||
|
8, 5, 2]), res))
|
||||||
|
# mirror
|
||||||
|
if 'M' in options:
|
||||||
|
n = len(patterns)
|
||||||
|
for pattern, res in patterns[0:n]:
|
||||||
|
patterns.append(
|
||||||
|
(self._string_permute(pattern, [2, 1, 0,
|
||||||
|
5, 4, 3,
|
||||||
|
8, 7, 6]), res))
|
||||||
|
|
||||||
|
# negate
|
||||||
|
if 'N' in options:
|
||||||
|
n = len(patterns)
|
||||||
|
for pattern, res in patterns[0:n]:
|
||||||
|
# Swap 0 and 1
|
||||||
|
pattern = (pattern
|
||||||
|
.replace('0', 'Z')
|
||||||
|
.replace('1', '0')
|
||||||
|
.replace('Z', '1'))
|
||||||
|
res = '%d' % (1-int(res))
|
||||||
|
patterns.append((pattern, res))
|
||||||
|
|
||||||
|
return patterns
|
||||||
|
|
||||||
|
def build_lut(self):
|
||||||
|
"""Compile all patterns into a morphology lut.
|
||||||
|
|
||||||
|
TBD :Build based on (file) morphlut:modify_lut
|
||||||
|
"""
|
||||||
|
self.build_default_lut()
|
||||||
|
patterns = []
|
||||||
|
|
||||||
|
# Parse and create symmetries of the patterns strings
|
||||||
|
for p in self.patterns:
|
||||||
|
m = re.search(
|
||||||
|
r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n', ''))
|
||||||
|
if not m:
|
||||||
|
raise Exception('Syntax error in pattern "'+p+'"')
|
||||||
|
options = m.group(1)
|
||||||
|
pattern = m.group(2)
|
||||||
|
result = int(m.group(3))
|
||||||
|
|
||||||
|
# Get rid of spaces
|
||||||
|
pattern = pattern.replace(' ', '').replace('\n', '')
|
||||||
|
|
||||||
|
patterns += self._pattern_permute(pattern, options, result)
|
||||||
|
|
||||||
|
# # Debugging
|
||||||
|
# for p,r in patterns:
|
||||||
|
# print p,r
|
||||||
|
# print '--'
|
||||||
|
|
||||||
|
# compile the patterns into regular expressions for speed
|
||||||
|
for i in range(len(patterns)):
|
||||||
|
p = patterns[i][0].replace('.', 'X').replace('X', '[01]')
|
||||||
|
p = re.compile(p)
|
||||||
|
patterns[i] = (p, patterns[i][1])
|
||||||
|
|
||||||
|
# Step through table and find patterns that match.
|
||||||
|
# Note that all the patterns are searched. The last one
|
||||||
|
# caught overrides
|
||||||
|
for i in range(LUT_SIZE):
|
||||||
|
# Build the bit pattern
|
||||||
|
bitpattern = bin(i)[2:]
|
||||||
|
bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1]
|
||||||
|
|
||||||
|
for p, r in patterns:
|
||||||
|
if p.match(bitpattern):
|
||||||
|
self.lut[i] = [0, 1][r]
|
||||||
|
|
||||||
|
return self.lut
|
||||||
|
|
||||||
|
|
||||||
|
class MorphOp:
|
||||||
|
"""A class for binary morphological operators"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
lut=None,
|
||||||
|
op_name=None,
|
||||||
|
patterns=None):
|
||||||
|
"""Create a binary morphological operator"""
|
||||||
|
self.lut = lut
|
||||||
|
if op_name is not None:
|
||||||
|
self.lut = LutBuilder(op_name=op_name).build_lut()
|
||||||
|
elif patterns is not None:
|
||||||
|
self.lut = LutBuilder(patterns=patterns).build_lut()
|
||||||
|
|
||||||
|
def apply(self, image):
|
||||||
|
"""Run a single morphological operation on an image
|
||||||
|
|
||||||
|
Returns a tuple of the number of changed pixels and the
|
||||||
|
morphed image"""
|
||||||
|
if self.lut is None:
|
||||||
|
raise Exception('No operator loaded')
|
||||||
|
|
||||||
|
outimage = Image.new(image.mode, image.size, None)
|
||||||
|
count = _imagingmorph.apply(
|
||||||
|
bytes(self.lut), image.im.id, outimage.im.id)
|
||||||
|
return count, outimage
|
||||||
|
|
||||||
|
def match(self, image):
|
||||||
|
"""Get a list of coordinates matching the morphological operation on
|
||||||
|
an image.
|
||||||
|
|
||||||
|
Returns a list of tuples of (x,y) coordinates
|
||||||
|
of all matching pixels."""
|
||||||
|
if self.lut is None:
|
||||||
|
raise Exception('No operator loaded')
|
||||||
|
|
||||||
|
return _imagingmorph.match(bytes(self.lut), image.im.id)
|
||||||
|
|
||||||
|
def get_on_pixels(self, image):
|
||||||
|
"""Get a list of all turned on pixels in a binary image
|
||||||
|
|
||||||
|
Returns a list of tuples of (x,y) coordinates
|
||||||
|
of all matching pixels."""
|
||||||
|
|
||||||
|
return _imagingmorph.get_on_pixels(image.im.id)
|
||||||
|
|
||||||
|
def load_lut(self, filename):
|
||||||
|
"""Load an operator from an mrl file"""
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
self.lut = bytearray(f.read())
|
||||||
|
|
||||||
|
if len(self.lut) != 8192:
|
||||||
|
self.lut = None
|
||||||
|
raise Exception('Wrong size operator file!')
|
||||||
|
|
||||||
|
def save_lut(self, filename):
|
||||||
|
"""Save an operator to an mrl file"""
|
||||||
|
if self.lut is None:
|
||||||
|
raise Exception('No operator loaded')
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
f.write(self.lut)
|
||||||
|
|
||||||
|
def set_lut(self, lut):
|
||||||
|
"""Set the lut from an external source"""
|
||||||
|
self.lut = lut
|
||||||
|
|
||||||
|
# End of file
|
|
@ -94,7 +94,7 @@ def autocontrast(image, cutoff=0, ignore=None):
|
||||||
cut = cut - h[lo]
|
cut = cut - h[lo]
|
||||||
h[lo] = 0
|
h[lo] = 0
|
||||||
else:
|
else:
|
||||||
h[lo] = h[lo] - cut
|
h[lo] -= cut
|
||||||
cut = 0
|
cut = 0
|
||||||
if cut <= 0:
|
if cut <= 0:
|
||||||
break
|
break
|
||||||
|
@ -105,7 +105,7 @@ def autocontrast(image, cutoff=0, ignore=None):
|
||||||
cut = cut - h[hi]
|
cut = cut - h[hi]
|
||||||
h[hi] = 0
|
h[hi] = 0
|
||||||
else:
|
else:
|
||||||
h[hi] = h[hi] - cut
|
h[hi] -= cut
|
||||||
cut = 0
|
cut = 0
|
||||||
if cut <= 0:
|
if cut <= 0:
|
||||||
break
|
break
|
||||||
|
@ -392,7 +392,7 @@ def solarize(image, threshold=128):
|
||||||
"""
|
"""
|
||||||
Invert all pixel values above a threshold.
|
Invert all pixel values above a threshold.
|
||||||
|
|
||||||
:param image: The image to posterize.
|
:param image: The image to solarize.
|
||||||
:param threshold: All pixels above this greyscale level are inverted.
|
:param threshold: All pixels above this greyscale level are inverted.
|
||||||
:return: An image.
|
:return: An image.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import array
|
import array
|
||||||
from PIL import Image, ImageColor
|
import warnings
|
||||||
|
from PIL import ImageColor
|
||||||
|
|
||||||
|
|
||||||
class ImagePalette:
|
class ImagePalette:
|
||||||
|
@ -101,11 +102,15 @@ class ImagePalette:
|
||||||
fp.write("# Mode: %s\n" % self.mode)
|
fp.write("# Mode: %s\n" % self.mode)
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
fp.write("%d" % i)
|
fp.write("%d" % i)
|
||||||
for j in range(i, len(self.palette), 256):
|
for j in range(i*len(self.mode), (i+1)*len(self.mode)):
|
||||||
|
try:
|
||||||
fp.write(" %d" % self.palette[j])
|
fp.write(" %d" % self.palette[j])
|
||||||
|
except IndexError:
|
||||||
|
fp.write(" 0")
|
||||||
fp.write("\n")
|
fp.write("\n")
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Internal
|
# Internal
|
||||||
|
|
||||||
|
@ -116,10 +121,31 @@ def raw(rawmode, data):
|
||||||
palette.dirty = 1
|
palette.dirty = 1
|
||||||
return palette
|
return palette
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Factories
|
# Factories
|
||||||
|
|
||||||
def _make_linear_lut(black, white):
|
def _make_linear_lut(black, white):
|
||||||
|
warnings.warn(
|
||||||
|
'_make_linear_lut() is deprecated. '
|
||||||
|
'Please call make_linear_lut() instead.',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
|
return make_linear_lut(black, white)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_gamma_lut(exp):
|
||||||
|
warnings.warn(
|
||||||
|
'_make_gamma_lut() is deprecated. '
|
||||||
|
'Please call make_gamma_lut() instead.',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
|
return make_gamma_lut(exp)
|
||||||
|
|
||||||
|
|
||||||
|
def make_linear_lut(black, white):
|
||||||
lut = []
|
lut = []
|
||||||
if black == 0:
|
if black == 0:
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
|
@ -128,20 +154,20 @@ def _make_linear_lut(black, white):
|
||||||
raise NotImplementedError # FIXME
|
raise NotImplementedError # FIXME
|
||||||
return lut
|
return lut
|
||||||
|
|
||||||
def _make_gamma_lut(exp, mode="RGB"):
|
|
||||||
|
def make_gamma_lut(exp):
|
||||||
lut = []
|
lut = []
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
|
lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
|
||||||
return lut
|
return lut
|
||||||
|
|
||||||
def new(mode, data):
|
|
||||||
return Image.core.new_palette(mode, data)
|
|
||||||
|
|
||||||
def negative(mode="RGB"):
|
def negative(mode="RGB"):
|
||||||
palette = list(range(256))
|
palette = list(range(256))
|
||||||
palette.reverse()
|
palette.reverse()
|
||||||
return ImagePalette(mode, palette * len(mode))
|
return ImagePalette(mode, palette * len(mode))
|
||||||
|
|
||||||
|
|
||||||
def random(mode="RGB"):
|
def random(mode="RGB"):
|
||||||
from random import randint
|
from random import randint
|
||||||
palette = []
|
palette = []
|
||||||
|
@ -149,16 +175,19 @@ def random(mode="RGB"):
|
||||||
palette.append(randint(0, 255))
|
palette.append(randint(0, 255))
|
||||||
return ImagePalette(mode, palette)
|
return ImagePalette(mode, palette)
|
||||||
|
|
||||||
|
|
||||||
def sepia(white="#fff0c0"):
|
def sepia(white="#fff0c0"):
|
||||||
r, g, b = ImageColor.getrgb(white)
|
r, g, b = ImageColor.getrgb(white)
|
||||||
r = _make_linear_lut(0, r)
|
r = make_linear_lut(0, r)
|
||||||
g = _make_linear_lut(0, g)
|
g = make_linear_lut(0, g)
|
||||||
b = _make_linear_lut(0, b)
|
b = make_linear_lut(0, b)
|
||||||
return ImagePalette("RGB", r + g + b)
|
return ImagePalette("RGB", r + g + b)
|
||||||
|
|
||||||
|
|
||||||
def wedge(mode="RGB"):
|
def wedge(mode="RGB"):
|
||||||
return ImagePalette(mode, list(range(256)) * len(mode))
|
return ImagePalette(mode, list(range(256)) * len(mode))
|
||||||
|
|
||||||
|
|
||||||
def load(filename):
|
def load(filename):
|
||||||
|
|
||||||
# FIXME: supports GIMP gradients only
|
# FIXME: supports GIMP gradients only
|
||||||
|
|
|
@ -17,7 +17,7 @@ from __future__ import print_function
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
||||||
if(sys.version_info >= (3, 3)):
|
if sys.version_info >= (3, 3):
|
||||||
from shlex import quote
|
from shlex import quote
|
||||||
else:
|
else:
|
||||||
from pipes import quote
|
from pipes import quote
|
||||||
|
@ -160,7 +160,7 @@ else:
|
||||||
# imagemagick's display command instead.
|
# imagemagick's display command instead.
|
||||||
command = executable = "xv"
|
command = executable = "xv"
|
||||||
if title:
|
if title:
|
||||||
command = command + " -name %s" % quote(title)
|
command += " -name %s" % quote(title)
|
||||||
return command, executable
|
return command, executable
|
||||||
|
|
||||||
if which("xv"):
|
if which("xv"):
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
import operator, math
|
import operator, math
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
|
@ -81,7 +80,7 @@ class Stat:
|
||||||
for i in range(0, len(self.h), 256):
|
for i in range(0, len(self.h), 256):
|
||||||
sum = 0.0
|
sum = 0.0
|
||||||
for j in range(256):
|
for j in range(256):
|
||||||
sum = sum + j * self.h[i+j]
|
sum += j * self.h[i + j]
|
||||||
v.append(sum)
|
v.append(sum)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
@ -92,7 +91,7 @@ class Stat:
|
||||||
for i in range(0, len(self.h), 256):
|
for i in range(0, len(self.h), 256):
|
||||||
sum2 = 0.0
|
sum2 = 0.0
|
||||||
for j in range(256):
|
for j in range(256):
|
||||||
sum2 = sum2 + (j ** 2) * float(self.h[i+j])
|
sum2 += (j ** 2) * float(self.h[i + j])
|
||||||
v.append(sum2)
|
v.append(sum2)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@ __version__ = "0.3"
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile, _binary
|
||||||
import os, tempfile
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16be
|
i16 = _binary.i16be
|
||||||
|
@ -35,17 +36,20 @@ COMPRESSION = {
|
||||||
|
|
||||||
PAD = o8(0) * 4
|
PAD = o8(0) * 4
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Helpers
|
# Helpers
|
||||||
|
|
||||||
def i(c):
|
def i(c):
|
||||||
return i32((PAD + c)[-4:])
|
return i32((PAD + c)[-4:])
|
||||||
|
|
||||||
|
|
||||||
def dump(c):
|
def dump(c):
|
||||||
for i in c:
|
for i in c:
|
||||||
print("%02x" % i8(i), end=' ')
|
print("%02x" % i8(i), end=' ')
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
||||||
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
||||||
|
@ -84,28 +88,6 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
return tag, size
|
return tag, size
|
||||||
|
|
||||||
def _is_raw(self, offset, size):
|
|
||||||
#
|
|
||||||
# check if the file can be mapped
|
|
||||||
|
|
||||||
# DISABLED: the following only slows things down...
|
|
||||||
return 0
|
|
||||||
|
|
||||||
self.fp.seek(offset)
|
|
||||||
t, sz = self.field()
|
|
||||||
if sz != size[0]:
|
|
||||||
return 0
|
|
||||||
y = 1
|
|
||||||
while True:
|
|
||||||
self.fp.seek(sz, 1)
|
|
||||||
t, s = self.field()
|
|
||||||
if t != (8, 10):
|
|
||||||
break
|
|
||||||
if s != sz:
|
|
||||||
return 0
|
|
||||||
y = y + 1
|
|
||||||
return y == size[1]
|
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
|
||||||
# load descriptive fields
|
# load descriptive fields
|
||||||
|
@ -153,10 +135,6 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# tile
|
# tile
|
||||||
if tag == (8, 10):
|
if tag == (8, 10):
|
||||||
if compression == "raw" and self._is_raw(offset, self.size):
|
|
||||||
self.tile = [(compression, (offset, size + 5, -1),
|
|
||||||
(0, 0, self.size[0], self.size[1]))]
|
|
||||||
else:
|
|
||||||
self.tile = [("iptc", (compression, offset),
|
self.tile = [("iptc", (compression, offset),
|
||||||
(0, 0, self.size[0], self.size[1]))]
|
(0, 0, self.size[0], self.size[1]))]
|
||||||
|
|
||||||
|
@ -187,7 +165,7 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
o.write(s)
|
o.write(s)
|
||||||
size = size - len(s)
|
size -= len(s)
|
||||||
o.close()
|
o.close()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -200,14 +178,17 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
im.load()
|
im.load()
|
||||||
self.im = im.im
|
self.im = im.im
|
||||||
finally:
|
finally:
|
||||||
try: os.unlink(outfile)
|
try:
|
||||||
except: pass
|
os.unlink(outfile)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
Image.register_open("IPTC", IptcImageFile)
|
Image.register_open("IPTC", IptcImageFile)
|
||||||
|
|
||||||
Image.register_extension("IPTC", ".iim")
|
Image.register_extension("IPTC", ".iim")
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get IPTC information from TIFF, JPEG, or IPTC file.
|
# Get IPTC information from TIFF, JPEG, or IPTC file.
|
||||||
#
|
#
|
||||||
|
@ -230,31 +211,31 @@ def getiptcinfo(im):
|
||||||
# extract the IPTC/NAA resource
|
# extract the IPTC/NAA resource
|
||||||
try:
|
try:
|
||||||
app = im.app["APP13"]
|
app = im.app["APP13"]
|
||||||
if app[:14] == "Photoshop 3.0\x00":
|
if app[:14] == b"Photoshop 3.0\x00":
|
||||||
app = app[14:]
|
app = app[14:]
|
||||||
# parse the image resource block
|
# parse the image resource block
|
||||||
offset = 0
|
offset = 0
|
||||||
while app[offset:offset+4] == "8BIM":
|
while app[offset:offset+4] == b"8BIM":
|
||||||
offset = offset + 4
|
offset += 4
|
||||||
# resource code
|
# resource code
|
||||||
code = JpegImagePlugin.i16(app, offset)
|
code = JpegImagePlugin.i16(app, offset)
|
||||||
offset = offset + 2
|
offset += 2
|
||||||
# resource name (usually empty)
|
# resource name (usually empty)
|
||||||
name_len = i8(app[offset])
|
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
|
offset = 1 + offset + name_len
|
||||||
if offset & 1:
|
if offset & 1:
|
||||||
offset = offset + 1
|
offset += 1
|
||||||
# resource data block
|
# resource data block
|
||||||
size = JpegImagePlugin.i32(app, offset)
|
size = JpegImagePlugin.i32(app, offset)
|
||||||
offset = offset + 4
|
offset += 4
|
||||||
if code == 0x0404:
|
if code == 0x0404:
|
||||||
# 0x0404 contains IPTC/NAA data
|
# 0x0404 contains IPTC/NAA data
|
||||||
data = app[offset:offset+size]
|
data = app[offset:offset+size]
|
||||||
break
|
break
|
||||||
offset = offset + size
|
offset = offset + size
|
||||||
if offset & 1:
|
if offset & 1:
|
||||||
offset = offset + 1
|
offset += 1
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,12 @@
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile
|
||||||
import struct
|
import struct
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
|
||||||
def _parse_codestream(fp):
|
def _parse_codestream(fp):
|
||||||
"""Parse the JPEG 2000 codestream to extract the size and component
|
"""Parse the JPEG 2000 codestream to extract the size and component
|
||||||
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
||||||
|
@ -39,18 +40,22 @@ def _parse_codestream(fp):
|
||||||
|
|
||||||
size = (xsiz - xosiz, ysiz - yosiz)
|
size = (xsiz - xosiz, ysiz - yosiz)
|
||||||
if csiz == 1:
|
if csiz == 1:
|
||||||
|
if (yrsiz[0] & 0x7f) > 8:
|
||||||
|
mode = 'I;16'
|
||||||
|
else:
|
||||||
mode = 'L'
|
mode = 'L'
|
||||||
elif csiz == 2:
|
elif csiz == 2:
|
||||||
mode = 'LA'
|
mode = 'LA'
|
||||||
elif csiz == 3:
|
elif csiz == 3:
|
||||||
mode = 'RGB'
|
mode = 'RGB'
|
||||||
elif csiz == 4:
|
elif csiz == 4:
|
||||||
mode == 'RGBA'
|
mode = 'RGBA'
|
||||||
else:
|
else:
|
||||||
mode = None
|
mode = None
|
||||||
|
|
||||||
return (size, mode)
|
return (size, mode)
|
||||||
|
|
||||||
|
|
||||||
def _parse_jp2_header(fp):
|
def _parse_jp2_header(fp):
|
||||||
"""Parse the JP2 header box to extract size, component count and
|
"""Parse the JP2 header box to extract size, component count and
|
||||||
color space information, returning a PIL (size, mode) tuple."""
|
color space information, returning a PIL (size, mode) tuple."""
|
||||||
|
@ -65,6 +70,9 @@ def _parse_jp2_header(fp):
|
||||||
else:
|
else:
|
||||||
hlen = 8
|
hlen = 8
|
||||||
|
|
||||||
|
if lbox < hlen:
|
||||||
|
raise SyntaxError('Invalid JP2 header length')
|
||||||
|
|
||||||
if tbox == b'jp2h':
|
if tbox == b'jp2h':
|
||||||
header = fp.read(lbox - hlen)
|
header = fp.read(lbox - hlen)
|
||||||
break
|
break
|
||||||
|
@ -76,6 +84,7 @@ def _parse_jp2_header(fp):
|
||||||
|
|
||||||
size = None
|
size = None
|
||||||
mode = None
|
mode = None
|
||||||
|
bpc = None
|
||||||
|
|
||||||
hio = io.BytesIO(header)
|
hio = io.BytesIO(header)
|
||||||
while True:
|
while True:
|
||||||
|
@ -93,7 +102,9 @@ def _parse_jp2_header(fp):
|
||||||
= struct.unpack('>IIHBBBB', content)
|
= struct.unpack('>IIHBBBB', content)
|
||||||
size = (width, height)
|
size = (width, height)
|
||||||
if unkc:
|
if unkc:
|
||||||
if nc == 1:
|
if nc == 1 and (bpc & 0x7f) > 8:
|
||||||
|
mode = 'I;16'
|
||||||
|
elif nc == 1:
|
||||||
mode = 'L'
|
mode = 'L'
|
||||||
elif nc == 2:
|
elif nc == 2:
|
||||||
mode = 'LA'
|
mode = 'LA'
|
||||||
|
@ -107,13 +118,19 @@ def _parse_jp2_header(fp):
|
||||||
if meth == 1:
|
if meth == 1:
|
||||||
cs = struct.unpack('>I', content[3:7])[0]
|
cs = struct.unpack('>I', content[3:7])[0]
|
||||||
if cs == 16: # sRGB
|
if cs == 16: # sRGB
|
||||||
if nc == 3:
|
if nc == 1 and (bpc & 0x7f) > 8:
|
||||||
|
mode = 'I;16'
|
||||||
|
elif nc == 1:
|
||||||
|
mode = 'L'
|
||||||
|
elif nc == 3:
|
||||||
mode = 'RGB'
|
mode = 'RGB'
|
||||||
elif nc == 4:
|
elif nc == 4:
|
||||||
mode = 'RGBA'
|
mode = 'RGBA'
|
||||||
break
|
break
|
||||||
elif cs == 17: # grayscale
|
elif cs == 17: # grayscale
|
||||||
if nc == 1:
|
if nc == 1 and (bpc & 0x7f) > 8:
|
||||||
|
mode = 'I;16'
|
||||||
|
elif nc == 1:
|
||||||
mode = 'L'
|
mode = 'L'
|
||||||
elif nc == 2:
|
elif nc == 2:
|
||||||
mode = 'LA'
|
mode = 'LA'
|
||||||
|
@ -122,7 +139,7 @@ def _parse_jp2_header(fp):
|
||||||
if nc == 3:
|
if nc == 3:
|
||||||
mode = 'RGB'
|
mode = 'RGB'
|
||||||
elif nc == 4:
|
elif nc == 4:
|
||||||
mode == 'RGBA'
|
mode = 'RGBA'
|
||||||
break
|
break
|
||||||
|
|
||||||
return (size, mode)
|
return (size, mode)
|
||||||
|
@ -130,6 +147,7 @@ def _parse_jp2_header(fp):
|
||||||
##
|
##
|
||||||
# Image plugin for JPEG2000 images.
|
# Image plugin for JPEG2000 images.
|
||||||
|
|
||||||
|
|
||||||
class Jpeg2KImageFile(ImageFile.ImageFile):
|
class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
format = "JPEG2000"
|
format = "JPEG2000"
|
||||||
format_description = "JPEG 2000 (ISO 15444)"
|
format_description = "JPEG 2000 (ISO 15444)"
|
||||||
|
@ -155,15 +173,23 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
self.layers = 0
|
self.layers = 0
|
||||||
|
|
||||||
fd = -1
|
fd = -1
|
||||||
|
length = -1
|
||||||
|
|
||||||
if hasattr(self.fp, "fileno"):
|
|
||||||
try:
|
try:
|
||||||
fd = self.fp.fileno()
|
fd = self.fp.fileno()
|
||||||
|
length = os.fstat(fd).st_size
|
||||||
except:
|
except:
|
||||||
fd = -1
|
fd = -1
|
||||||
|
try:
|
||||||
|
pos = self.fp.tell()
|
||||||
|
self.fp.seek(0, 2)
|
||||||
|
length = self.fp.tell()
|
||||||
|
self.fp.seek(pos, 0)
|
||||||
|
except:
|
||||||
|
length = -1
|
||||||
|
|
||||||
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
|
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
|
||||||
(self.codec, self.reduce, self.layers, fd))]
|
(self.codec, self.reduce, self.layers, fd, length))]
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
if self.reduce:
|
if self.reduce:
|
||||||
|
@ -175,15 +201,17 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
if self.tile:
|
if self.tile:
|
||||||
# Update the reduce and layers settings
|
# Update the reduce and layers settings
|
||||||
t = self.tile[0]
|
t = self.tile[0]
|
||||||
t3 = (t[3][0], self.reduce, self.layers, t[3][3])
|
t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4])
|
||||||
self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
|
self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
|
||||||
|
|
||||||
ImageFile.ImageFile.load(self)
|
ImageFile.ImageFile.load(self)
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return (prefix[:4] == b'\xff\x4f\xff\x51'
|
return (prefix[:4] == b'\xff\x4f\xff\x51'
|
||||||
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Save support
|
# Save support
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,9 @@ __version__ = "0.6"
|
||||||
|
|
||||||
import array
|
import array
|
||||||
import struct
|
import struct
|
||||||
from PIL import Image, ImageFile, _binary
|
import io
|
||||||
|
from struct import unpack
|
||||||
|
from PIL import Image, ImageFile, TiffImagePlugin, _binary
|
||||||
from PIL.JpegPresets import presets
|
from PIL.JpegPresets import presets
|
||||||
from PIL._util import isStringType
|
from PIL._util import isStringType
|
||||||
|
|
||||||
|
@ -110,6 +112,11 @@ def APP(self, marker):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.info["adobe_transform"] = adobe_transform
|
self.info["adobe_transform"] = adobe_transform
|
||||||
|
elif marker == 0xFFE2 and s[:4] == b"MPF\0":
|
||||||
|
# extract MPO information
|
||||||
|
self.info["mp"] = s[4:]
|
||||||
|
# offset is current location minus buffer size plus constant header size
|
||||||
|
self.info["mpoffset"] = self.fp.tell() - n + 4
|
||||||
|
|
||||||
|
|
||||||
def COM(self, marker):
|
def COM(self, marker):
|
||||||
|
@ -354,12 +361,13 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# ALTERNATIVE: handle JPEGs via the IJG command line utilities
|
# ALTERNATIVE: handle JPEGs via the IJG command line utilities
|
||||||
|
|
||||||
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
f, path = tempfile.mkstemp()
|
f, path = tempfile.mkstemp()
|
||||||
os.close(f)
|
os.close(f)
|
||||||
if os.path.exists(self.filename):
|
if os.path.exists(self.filename):
|
||||||
os.system("djpeg '%s' >'%s'" % (self.filename, path))
|
subprocess.check_call(["djpeg", "-outfile", path, self.filename])
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid Filename")
|
raise ValueError("Invalid Filename")
|
||||||
|
|
||||||
|
@ -379,18 +387,22 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
return _getexif(self)
|
return _getexif(self)
|
||||||
|
|
||||||
|
def _getmp(self):
|
||||||
|
return _getmp(self)
|
||||||
|
|
||||||
|
|
||||||
|
def _fixup(value):
|
||||||
|
# Helper function for _getexif() and _getmp()
|
||||||
|
if len(value) == 1:
|
||||||
|
return value[0]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
# Extract EXIF information. This method is highly experimental,
|
# Extract EXIF information. This method is highly experimental,
|
||||||
# and is likely to be replaced with something better in a future
|
# and is likely to be replaced with something better in a future
|
||||||
# version.
|
# version.
|
||||||
from PIL import TiffImagePlugin
|
|
||||||
import io
|
|
||||||
|
|
||||||
def fixup(value):
|
|
||||||
if len(value) == 1:
|
|
||||||
return value[0]
|
|
||||||
return value
|
|
||||||
# The EXIF record consists of a TIFF file embedded in a JPEG
|
# The EXIF record consists of a TIFF file embedded in a JPEG
|
||||||
# application marker (!).
|
# application marker (!).
|
||||||
try:
|
try:
|
||||||
|
@ -404,7 +416,7 @@ def _getexif(self):
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
for key, value in info.items():
|
||||||
exif[key] = fixup(value)
|
exif[key] = _fixup(value)
|
||||||
# get exif extension
|
# get exif extension
|
||||||
try:
|
try:
|
||||||
file.seek(exif[0x8769])
|
file.seek(exif[0x8769])
|
||||||
|
@ -414,7 +426,7 @@ def _getexif(self):
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
for key, value in info.items():
|
||||||
exif[key] = fixup(value)
|
exif[key] = _fixup(value)
|
||||||
# get gpsinfo extension
|
# get gpsinfo extension
|
||||||
try:
|
try:
|
||||||
file.seek(exif[0x8825])
|
file.seek(exif[0x8825])
|
||||||
|
@ -425,9 +437,77 @@ def _getexif(self):
|
||||||
info.load(file)
|
info.load(file)
|
||||||
exif[0x8825] = gps = {}
|
exif[0x8825] = gps = {}
|
||||||
for key, value in info.items():
|
for key, value in info.items():
|
||||||
gps[key] = fixup(value)
|
gps[key] = _fixup(value)
|
||||||
return exif
|
return exif
|
||||||
|
|
||||||
|
|
||||||
|
def _getmp(self):
|
||||||
|
# Extract MP information. This method was inspired by the "highly
|
||||||
|
# experimental" _getexif version that's been in use for years now,
|
||||||
|
# itself based on the ImageFileDirectory class in the TIFF plug-in.
|
||||||
|
|
||||||
|
# The MP record essentially consists of a TIFF file embedded in a JPEG
|
||||||
|
# application marker.
|
||||||
|
try:
|
||||||
|
data = self.info["mp"]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
file = io.BytesIO(data)
|
||||||
|
head = file.read(8)
|
||||||
|
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
||||||
|
mp = {}
|
||||||
|
# process dictionary
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
|
info.load(file)
|
||||||
|
for key, value in info.items():
|
||||||
|
mp[key] = _fixup(value)
|
||||||
|
# it's an error not to have a number of images
|
||||||
|
try:
|
||||||
|
quant = mp[0xB001]
|
||||||
|
except KeyError:
|
||||||
|
raise SyntaxError("malformed MP Index (no number of images)")
|
||||||
|
# get MP entries
|
||||||
|
try:
|
||||||
|
mpentries = []
|
||||||
|
for entrynum in range(0, quant):
|
||||||
|
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
|
||||||
|
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
|
||||||
|
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2')
|
||||||
|
mpentry = dict(zip(labels, unpackedentry))
|
||||||
|
mpentryattr = {
|
||||||
|
'DependentParentImageFlag': bool(mpentry['Attribute'] & (1<<31)),
|
||||||
|
'DependentChildImageFlag': bool(mpentry['Attribute'] & (1<<30)),
|
||||||
|
'RepresentativeImageFlag': bool(mpentry['Attribute'] & (1<<29)),
|
||||||
|
'Reserved': (mpentry['Attribute'] & (3<<27)) >> 27,
|
||||||
|
'ImageDataFormat': (mpentry['Attribute'] & (7<<24)) >> 24,
|
||||||
|
'MPType': mpentry['Attribute'] & 0x00FFFFFF
|
||||||
|
}
|
||||||
|
if mpentryattr['ImageDataFormat'] == 0:
|
||||||
|
mpentryattr['ImageDataFormat'] = 'JPEG'
|
||||||
|
else:
|
||||||
|
raise SyntaxError("unsupported picture format in MPO")
|
||||||
|
mptypemap = {
|
||||||
|
0x000000: 'Undefined',
|
||||||
|
0x010001: 'Large Thumbnail (VGA Equivalent)',
|
||||||
|
0x010002: 'Large Thumbnail (Full HD Equivalent)',
|
||||||
|
0x020001: 'Multi-Frame Image (Panorama)',
|
||||||
|
0x020002: 'Multi-Frame Image: (Disparity)',
|
||||||
|
0x020003: 'Multi-Frame Image: (Multi-Angle)',
|
||||||
|
0x030000: 'Baseline MP Primary Image'
|
||||||
|
}
|
||||||
|
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
|
||||||
|
'Unknown')
|
||||||
|
mpentry['Attribute'] = mpentryattr
|
||||||
|
mpentries.append(mpentry)
|
||||||
|
mp[0xB002] = mpentries
|
||||||
|
except KeyError:
|
||||||
|
raise SyntaxError("malformed MP Index (bad MP Entry)")
|
||||||
|
# Next we should try and parse the individual image unique ID list;
|
||||||
|
# we don't because I've never seen this actually used in a real MPO
|
||||||
|
# file and so can't test it.
|
||||||
|
return mp
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# stuff to save JPEG files
|
# stuff to save JPEG files
|
||||||
|
|
||||||
|
@ -498,7 +578,7 @@ def _save(im, fp, filename):
|
||||||
else:
|
else:
|
||||||
if subsampling in presets:
|
if subsampling in presets:
|
||||||
subsampling = presets[subsampling].get('subsampling', -1)
|
subsampling = presets[subsampling].get('subsampling', -1)
|
||||||
if qtables in presets:
|
if isStringType(qtables) and qtables in presets:
|
||||||
qtables = presets[qtables].get('quantization')
|
qtables = presets[qtables].get('quantization')
|
||||||
|
|
||||||
if subsampling == "4:4:4":
|
if subsampling == "4:4:4":
|
||||||
|
@ -561,8 +641,8 @@ def _save(im, fp, filename):
|
||||||
i = 1
|
i = 1
|
||||||
for marker in markers:
|
for marker in markers:
|
||||||
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
|
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
|
||||||
extra = extra + (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker)
|
extra += b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker
|
||||||
i = i + 1
|
i += 1
|
||||||
|
|
||||||
# get keyword arguments
|
# get keyword arguments
|
||||||
im.encoderconfig = (
|
im.encoderconfig = (
|
||||||
|
@ -602,17 +682,35 @@ def _save(im, fp, filename):
|
||||||
def _save_cjpeg(im, fp, filename):
|
def _save_cjpeg(im, fp, filename):
|
||||||
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
|
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
|
||||||
import os
|
import os
|
||||||
file = im._dump()
|
import subprocess
|
||||||
os.system("cjpeg %s >%s" % (file, filename))
|
tempfile = im._dump()
|
||||||
|
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
||||||
try:
|
try:
|
||||||
os.unlink(file)
|
os.unlink(file)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Factory for making JPEG and MPO instances
|
||||||
|
def jpeg_factory(fp=None, filename=None):
|
||||||
|
im = JpegImageFile(fp, filename)
|
||||||
|
mpheader = im._getmp()
|
||||||
|
try:
|
||||||
|
if mpheader[45057] > 1:
|
||||||
|
# It's actually an MPO
|
||||||
|
from .MpoImagePlugin import MpoImageFile
|
||||||
|
im = MpoImageFile(fp, filename)
|
||||||
|
except (TypeError, IndexError):
|
||||||
|
# It is really a JPEG
|
||||||
|
pass
|
||||||
|
return im
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------q-
|
# -------------------------------------------------------------------q-
|
||||||
# Registry stuff
|
# Registry stuff
|
||||||
|
|
||||||
Image.register_open("JPEG", JpegImageFile, _accept)
|
Image.register_open("JPEG", jpeg_factory, _accept)
|
||||||
Image.register_save("JPEG", _save)
|
Image.register_save("JPEG", _save)
|
||||||
|
|
||||||
Image.register_extension("JPEG", ".jfif")
|
Image.register_extension("JPEG", ".jfif")
|
||||||
|
|
|
@ -38,13 +38,13 @@ class BitStream:
|
||||||
self.bits = 0
|
self.bits = 0
|
||||||
continue
|
continue
|
||||||
self.bitbuffer = (self.bitbuffer << 8) + c
|
self.bitbuffer = (self.bitbuffer << 8) + c
|
||||||
self.bits = self.bits + 8
|
self.bits += 8
|
||||||
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
|
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
|
||||||
|
|
||||||
def skip(self, bits):
|
def skip(self, bits):
|
||||||
while self.bits < bits:
|
while self.bits < bits:
|
||||||
self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1))
|
self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1))
|
||||||
self.bits = self.bits + 8
|
self.bits += 8
|
||||||
self.bits = self.bits - bits
|
self.bits = self.bits - bits
|
||||||
|
|
||||||
def read(self, bits):
|
def read(self, bits):
|
||||||
|
|
87
PIL/MpoImagePlugin.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
#
|
||||||
|
# The Python Imaging Library.
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# MPO file handling
|
||||||
|
#
|
||||||
|
# See "Multi-Picture Format" (CIPA DC-007-Translation 2009, Standard of the
|
||||||
|
# Camera & Imaging Products Association)
|
||||||
|
#
|
||||||
|
# The multi-picture object combines multiple JPEG images (with a modified EXIF
|
||||||
|
# data format) into a single file. While it can theoretically be used much like
|
||||||
|
# a GIF animation, it is commonly used to represent 3D photographs and is (as
|
||||||
|
# of this writing) the most commonly used format by 3D cameras.
|
||||||
|
#
|
||||||
|
# History:
|
||||||
|
# 2014-03-13 Feneric Created
|
||||||
|
#
|
||||||
|
# See the README file for information on usage and redistribution.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
from PIL import Image, JpegImagePlugin
|
||||||
|
|
||||||
|
def _accept(prefix):
|
||||||
|
return JpegImagePlugin._accept(prefix)
|
||||||
|
|
||||||
|
def _save(im, fp, filename):
|
||||||
|
# Note that we can only save the current frame at present
|
||||||
|
return JpegImagePlugin._save(im, fp, filename)
|
||||||
|
|
||||||
|
##
|
||||||
|
# Image plugin for MPO images.
|
||||||
|
|
||||||
|
class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
|
|
||||||
|
format = "MPO"
|
||||||
|
format_description = "MPO (CIPA DC-007)"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
self.fp.seek(0) # prep the fp in order to pass the JPEG test
|
||||||
|
JpegImagePlugin.JpegImageFile._open(self)
|
||||||
|
self.mpinfo = self._getmp()
|
||||||
|
self.__framecount = self.mpinfo[0xB001]
|
||||||
|
self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] \
|
||||||
|
for mpent in self.mpinfo[0xB002]]
|
||||||
|
self.__mpoffsets[0] = 0
|
||||||
|
# Note that the following assertion will only be invalid if something
|
||||||
|
# gets broken within JpegImagePlugin.
|
||||||
|
assert self.__framecount == len(self.__mpoffsets)
|
||||||
|
del self.info['mpoffset'] # no longer needed
|
||||||
|
self.__fp = self.fp # FIXME: hack
|
||||||
|
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
|
||||||
|
self.__frame = 0
|
||||||
|
self.offset = 0
|
||||||
|
# for now we can only handle reading and individual frame extraction
|
||||||
|
self.readonly = 1
|
||||||
|
|
||||||
|
def load_seek(self, pos):
|
||||||
|
self.__fp.seek(pos)
|
||||||
|
|
||||||
|
def seek(self, frame):
|
||||||
|
if frame < 0 or frame >= self.__framecount:
|
||||||
|
raise EOFError("no more images in MPO file")
|
||||||
|
else:
|
||||||
|
self.fp = self.__fp
|
||||||
|
self.offset = self.__mpoffsets[frame]
|
||||||
|
self.tile = [
|
||||||
|
("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
|
||||||
|
]
|
||||||
|
self.__frame = frame
|
||||||
|
|
||||||
|
def tell(self):
|
||||||
|
return self.__frame
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------q-
|
||||||
|
# Registry stuff
|
||||||
|
|
||||||
|
# Note that since MPO shares a factory with JPEG, we do not need to do a
|
||||||
|
# separate registration for it here.
|
||||||
|
#Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
|
||||||
|
Image.register_save("MPO", _save)
|
||||||
|
|
||||||
|
Image.register_extension("MPO", ".mpo")
|
||||||
|
|
||||||
|
Image.register_mime("MPO", "image/mpo")
|
|
@ -7,7 +7,7 @@ This is an improved version of the OleFileIO module from [PIL](http://www.python
|
||||||
|
|
||||||
As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules)
|
As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules)
|
||||||
|
|
||||||
OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-imaging.github.io/), the friendly fork of PIL.
|
OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-pillow.github.io/), the friendly fork of PIL.
|
||||||
|
|
||||||
OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL.
|
OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL.
|
||||||
|
|
||||||
|
|
185
PIL/OleFileIO.py
|
@ -1,28 +1,29 @@
|
||||||
#!/usr/local/bin/python
|
#!/usr/local/bin/python
|
||||||
# -*- coding: latin-1 -*-
|
# -*- coding: latin-1 -*-
|
||||||
"""
|
## OleFileIO_PL:
|
||||||
OleFileIO_PL:
|
## Module to read Microsoft OLE2 files (also called Structured Storage or
|
||||||
Module to read Microsoft OLE2 files (also called Structured Storage or
|
## Microsoft Compound Document File Format), such as Microsoft Office
|
||||||
Microsoft Compound Document File Format), such as Microsoft Office
|
## documents, Image Composer and FlashPix files, Outlook messages, ...
|
||||||
documents, Image Composer and FlashPix files, Outlook messages, ...
|
## This version is compatible with Python 2.6+ and 3.x
|
||||||
This version is compatible with Python 2.6+ and 3.x
|
|
||||||
|
|
||||||
version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info
|
## version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info
|
||||||
|
|
||||||
Project website: http://www.decalage.info/python/olefileio
|
## Project website: http://www.decalage.info/python/olefileio
|
||||||
|
|
||||||
Improved version of the OleFileIO module from PIL library v1.1.6
|
## Improved version of the OleFileIO module from PIL library v1.1.6
|
||||||
See: http://www.pythonware.com/products/pil/index.htm
|
## See: http://www.pythonware.com/products/pil/index.htm
|
||||||
|
|
||||||
The Python Imaging Library (PIL) is
|
## The Python Imaging Library (PIL) is
|
||||||
Copyright (c) 1997-2005 by Secret Labs AB
|
|
||||||
Copyright (c) 1995-2005 by Fredrik Lundh
|
|
||||||
OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
|
|
||||||
|
|
||||||
See source code and LICENSE.txt for information on usage and redistribution.
|
## Copyright (c) 1997-2005 by Secret Labs AB
|
||||||
|
## Copyright (c) 1995-2005 by Fredrik Lundh
|
||||||
|
|
||||||
|
## OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
|
||||||
|
|
||||||
|
## See source code and LICENSE.txt for information on usage and redistribution.
|
||||||
|
|
||||||
|
## WARNING: THIS IS (STILL) WORK IN PROGRESS.
|
||||||
|
|
||||||
WARNING: THIS IS (STILL) WORK IN PROGRESS.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Starting with OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported
|
# Starting with OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported
|
||||||
# This import enables print() as a function rather than a keyword
|
# This import enables print() as a function rather than a keyword
|
||||||
|
@ -370,8 +371,9 @@ for key in list(vars().keys()):
|
||||||
def isOleFile (filename):
|
def isOleFile (filename):
|
||||||
"""
|
"""
|
||||||
Test if file is an OLE container (according to its header).
|
Test if file is an OLE container (according to its header).
|
||||||
filename: file name or path (str, unicode)
|
|
||||||
return: True if OLE, False otherwise.
|
:param filename: file name or path (str, unicode)
|
||||||
|
:returns: True if OLE, False otherwise.
|
||||||
"""
|
"""
|
||||||
f = open(filename, 'rb')
|
f = open(filename, 'rb')
|
||||||
header = f.read(len(MAGIC))
|
header = f.read(len(MAGIC))
|
||||||
|
@ -397,8 +399,8 @@ def i16(c, o = 0):
|
||||||
"""
|
"""
|
||||||
Converts a 2-bytes (16 bits) string to an integer.
|
Converts a 2-bytes (16 bits) string to an integer.
|
||||||
|
|
||||||
c: string containing bytes to convert
|
:param c: string containing bytes to convert
|
||||||
o: offset of bytes to convert in string
|
:param o: offset of bytes to convert in string
|
||||||
"""
|
"""
|
||||||
return i8(c[o]) | (i8(c[o+1])<<8)
|
return i8(c[o]) | (i8(c[o+1])<<8)
|
||||||
|
|
||||||
|
@ -407,8 +409,8 @@ def i32(c, o = 0):
|
||||||
"""
|
"""
|
||||||
Converts a 4-bytes (32 bits) string to an integer.
|
Converts a 4-bytes (32 bits) string to an integer.
|
||||||
|
|
||||||
c: string containing bytes to convert
|
:param c: string containing bytes to convert
|
||||||
o: offset of bytes to convert in string
|
:param o: offset of bytes to convert in string
|
||||||
"""
|
"""
|
||||||
## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24))
|
## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24))
|
||||||
## # [PL]: added int() because "<<" gives long int since Python 2.4
|
## # [PL]: added int() because "<<" gives long int since Python 2.4
|
||||||
|
@ -419,7 +421,8 @@ def i32(c, o = 0):
|
||||||
def _clsid(clsid):
|
def _clsid(clsid):
|
||||||
"""
|
"""
|
||||||
Converts a CLSID to a human-readable string.
|
Converts a CLSID to a human-readable string.
|
||||||
clsid: string of length 16.
|
|
||||||
|
:param clsid: string of length 16.
|
||||||
"""
|
"""
|
||||||
assert len(clsid) == 16
|
assert len(clsid) == 16
|
||||||
# if clsid is only made of null bytes, return an empty string:
|
# if clsid is only made of null bytes, return an empty string:
|
||||||
|
@ -439,8 +442,8 @@ def _unicode(s, errors='replace'):
|
||||||
"""
|
"""
|
||||||
Map unicode string to Latin 1. (Python with Unicode support)
|
Map unicode string to Latin 1. (Python with Unicode support)
|
||||||
|
|
||||||
s: UTF-16LE unicode string to convert to Latin-1
|
:param s: UTF-16LE unicode string to convert to Latin-1
|
||||||
errors: 'replace', 'ignore' or 'strict'.
|
:param errors: 'replace', 'ignore' or 'strict'.
|
||||||
"""
|
"""
|
||||||
#TODO: test if it OleFileIO works with Unicode strings, instead of
|
#TODO: test if it OleFileIO works with Unicode strings, instead of
|
||||||
# converting to Latin-1.
|
# converting to Latin-1.
|
||||||
|
@ -650,14 +653,14 @@ class _OleStream(io.BytesIO):
|
||||||
"""
|
"""
|
||||||
Constructor for _OleStream class.
|
Constructor for _OleStream class.
|
||||||
|
|
||||||
fp : file object, the OLE container or the MiniFAT stream
|
:param fp : file object, the OLE container or the MiniFAT stream
|
||||||
sect : sector index of first sector in the stream
|
:param sect : sector index of first sector in the stream
|
||||||
size : total size of the stream
|
:param size : total size of the stream
|
||||||
offset : offset in bytes for the first FAT or MiniFAT sector
|
:param offset : offset in bytes for the first FAT or MiniFAT sector
|
||||||
sectorsize: size of one sector
|
:param sectorsize: size of one sector
|
||||||
fat : array/list of sector indexes (FAT or MiniFAT)
|
:param fat : array/list of sector indexes (FAT or MiniFAT)
|
||||||
filesize : size of OLE file (for debugging)
|
:param filesize : size of OLE file (for debugging)
|
||||||
return : a BytesIO instance containing the OLE stream
|
:returns : a BytesIO instance containing the OLE stream
|
||||||
"""
|
"""
|
||||||
debug('_OleStream.__init__:')
|
debug('_OleStream.__init__:')
|
||||||
debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s'
|
debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s'
|
||||||
|
@ -793,9 +796,9 @@ class _OleDirectoryEntry:
|
||||||
Constructor for an _OleDirectoryEntry object.
|
Constructor for an _OleDirectoryEntry object.
|
||||||
Parses a 128-bytes entry from the OLE Directory stream.
|
Parses a 128-bytes entry from the OLE Directory stream.
|
||||||
|
|
||||||
entry : string (must be 128 bytes long)
|
:param entry : string (must be 128 bytes long)
|
||||||
sid : index of this directory entry in the OLE file directory
|
:param sid : index of this directory entry in the OLE file directory
|
||||||
olefile: OleFileIO containing this directory entry
|
:param olefile: OleFileIO containing this directory entry
|
||||||
"""
|
"""
|
||||||
self.sid = sid
|
self.sid = sid
|
||||||
# ref to olefile is stored for future use
|
# ref to olefile is stored for future use
|
||||||
|
@ -989,7 +992,7 @@ class _OleDirectoryEntry:
|
||||||
"""
|
"""
|
||||||
Return modification time of a directory entry.
|
Return modification time of a directory entry.
|
||||||
|
|
||||||
return: None if modification time is null, a python datetime object
|
:returns: None if modification time is null, a python datetime object
|
||||||
otherwise (UTC timezone)
|
otherwise (UTC timezone)
|
||||||
|
|
||||||
new in version 0.26
|
new in version 0.26
|
||||||
|
@ -1003,7 +1006,7 @@ class _OleDirectoryEntry:
|
||||||
"""
|
"""
|
||||||
Return creation time of a directory entry.
|
Return creation time of a directory entry.
|
||||||
|
|
||||||
return: None if modification time is null, a python datetime object
|
:returns: None if modification time is null, a python datetime object
|
||||||
otherwise (UTC timezone)
|
otherwise (UTC timezone)
|
||||||
|
|
||||||
new in version 0.26
|
new in version 0.26
|
||||||
|
@ -1020,7 +1023,8 @@ class OleFileIO:
|
||||||
OLE container object
|
OLE container object
|
||||||
|
|
||||||
This class encapsulates the interface to an OLE 2 structured
|
This class encapsulates the interface to an OLE 2 structured
|
||||||
storage file. Use the {@link listdir} and {@link openstream} methods to
|
storage file. Use the :py:meth:`~PIL.OleFileIO.OleFileIO.listdir` and
|
||||||
|
:py:meth:`~PIL.OleFileIO.OleFileIO.openstream` methods to
|
||||||
access the contents of this file.
|
access the contents of this file.
|
||||||
|
|
||||||
Object names are given as a list of strings, one for each subentry
|
Object names are given as a list of strings, one for each subentry
|
||||||
|
@ -1048,8 +1052,8 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Constructor for OleFileIO class.
|
Constructor for OleFileIO class.
|
||||||
|
|
||||||
filename: file to open.
|
:param filename: file to open.
|
||||||
raise_defects: minimal level for defects to be raised as exceptions.
|
:param raise_defects: minimal level for defects to be raised as exceptions.
|
||||||
(use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a
|
(use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a
|
||||||
security-oriented application, see source code for details)
|
security-oriented application, see source code for details)
|
||||||
"""
|
"""
|
||||||
|
@ -1068,13 +1072,13 @@ class OleFileIO:
|
||||||
It may raise an IOError exception according to the minimal level chosen
|
It may raise an IOError exception according to the minimal level chosen
|
||||||
for the OleFileIO object.
|
for the OleFileIO object.
|
||||||
|
|
||||||
defect_level: defect level, possible values are:
|
:param defect_level: defect level, possible values are:
|
||||||
DEFECT_UNSURE : a case which looks weird, but not sure it's a defect
|
DEFECT_UNSURE : a case which looks weird, but not sure it's a defect
|
||||||
DEFECT_POTENTIAL : a potential defect
|
DEFECT_POTENTIAL : a potential defect
|
||||||
DEFECT_INCORRECT : an error according to specifications, but parsing can go on
|
DEFECT_INCORRECT : an error according to specifications, but parsing can go on
|
||||||
DEFECT_FATAL : an error which cannot be ignored, parsing is impossible
|
DEFECT_FATAL : an error which cannot be ignored, parsing is impossible
|
||||||
message: string describing the defect, used with raised exception.
|
:param message: string describing the defect, used with raised exception.
|
||||||
exception_type: exception class to be raised, IOError by default
|
:param exception_type: exception class to be raised, IOError by default
|
||||||
"""
|
"""
|
||||||
# added by [PL]
|
# added by [PL]
|
||||||
if defect_level >= self._raise_defects_level:
|
if defect_level >= self._raise_defects_level:
|
||||||
|
@ -1089,7 +1093,7 @@ class OleFileIO:
|
||||||
Open an OLE2 file.
|
Open an OLE2 file.
|
||||||
Reads the header, FAT and directory.
|
Reads the header, FAT and directory.
|
||||||
|
|
||||||
filename: string-like or file-like object
|
:param filename: string-like or file-like object
|
||||||
"""
|
"""
|
||||||
#[PL] check if filename is a string-like or file-like object:
|
#[PL] check if filename is a string-like or file-like object:
|
||||||
# (it is better to check for a read() method)
|
# (it is better to check for a read() method)
|
||||||
|
@ -1276,8 +1280,8 @@ class OleFileIO:
|
||||||
Checks if a stream has not been already referenced elsewhere.
|
Checks if a stream has not been already referenced elsewhere.
|
||||||
This method should only be called once for each known stream, and only
|
This method should only be called once for each known stream, and only
|
||||||
if stream size is not null.
|
if stream size is not null.
|
||||||
first_sect: index of first sector of the stream in FAT
|
:param first_sect: index of first sector of the stream in FAT
|
||||||
minifat: if True, stream is located in the MiniFAT, else in the FAT
|
:param minifat: if True, stream is located in the MiniFAT, else in the FAT
|
||||||
"""
|
"""
|
||||||
if minifat:
|
if minifat:
|
||||||
debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect)
|
debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect)
|
||||||
|
@ -1371,8 +1375,9 @@ class OleFileIO:
|
||||||
def loadfat_sect(self, sect):
|
def loadfat_sect(self, sect):
|
||||||
"""
|
"""
|
||||||
Adds the indexes of the given sector to the FAT
|
Adds the indexes of the given sector to the FAT
|
||||||
sect: string containing the first FAT sector, or array of long integers
|
|
||||||
return: index of last FAT sector.
|
:param sect: string containing the first FAT sector, or array of long integers
|
||||||
|
:returns: index of last FAT sector.
|
||||||
"""
|
"""
|
||||||
# a FAT sector is an array of ulong integers.
|
# a FAT sector is an array of ulong integers.
|
||||||
if isinstance(sect, array.array):
|
if isinstance(sect, array.array):
|
||||||
|
@ -1505,8 +1510,9 @@ class OleFileIO:
|
||||||
def getsect(self, sect):
|
def getsect(self, sect):
|
||||||
"""
|
"""
|
||||||
Read given sector from file on disk.
|
Read given sector from file on disk.
|
||||||
sect: sector index
|
|
||||||
returns a string containing the sector data.
|
:param sect: sector index
|
||||||
|
:returns: a string containing the sector data.
|
||||||
"""
|
"""
|
||||||
# [PL] this original code was wrong when sectors are 4KB instead of
|
# [PL] this original code was wrong when sectors are 4KB instead of
|
||||||
# 512 bytes:
|
# 512 bytes:
|
||||||
|
@ -1530,7 +1536,8 @@ class OleFileIO:
|
||||||
def loaddirectory(self, sect):
|
def loaddirectory(self, sect):
|
||||||
"""
|
"""
|
||||||
Load the directory.
|
Load the directory.
|
||||||
sect: sector index of directory stream.
|
|
||||||
|
:param sect: sector index of directory stream.
|
||||||
"""
|
"""
|
||||||
# The directory is stored in a standard
|
# The directory is stored in a standard
|
||||||
# substream, independent of its size.
|
# substream, independent of its size.
|
||||||
|
@ -1567,9 +1574,10 @@ class OleFileIO:
|
||||||
Load a directory entry from the directory.
|
Load a directory entry from the directory.
|
||||||
This method should only be called once for each storage/stream when
|
This method should only be called once for each storage/stream when
|
||||||
loading the directory.
|
loading the directory.
|
||||||
sid: index of storage/stream in the directory.
|
|
||||||
return: a _OleDirectoryEntry object
|
:param sid: index of storage/stream in the directory.
|
||||||
raise: IOError if the entry has always been referenced.
|
:returns: a _OleDirectoryEntry object
|
||||||
|
:exception IOError: if the entry has always been referenced.
|
||||||
"""
|
"""
|
||||||
# check if SID is OK:
|
# check if SID is OK:
|
||||||
if sid<0 or sid>=len(self.direntries):
|
if sid<0 or sid>=len(self.direntries):
|
||||||
|
@ -1598,9 +1606,9 @@ class OleFileIO:
|
||||||
Open a stream, either in FAT or MiniFAT according to its size.
|
Open a stream, either in FAT or MiniFAT according to its size.
|
||||||
(openstream helper)
|
(openstream helper)
|
||||||
|
|
||||||
start: index of first sector
|
:param start: index of first sector
|
||||||
size: size of stream (or nothing if size is unknown)
|
:param size: size of stream (or nothing if size is unknown)
|
||||||
force_FAT: if False (default), stream will be opened in FAT or MiniFAT
|
:param force_FAT: if False (default), stream will be opened in FAT or MiniFAT
|
||||||
according to size. If True, it will always be opened in FAT.
|
according to size. If True, it will always be opened in FAT.
|
||||||
"""
|
"""
|
||||||
debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' %
|
debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' %
|
||||||
|
@ -1630,11 +1638,11 @@ class OleFileIO:
|
||||||
def _list(self, files, prefix, node, streams=True, storages=False):
|
def _list(self, files, prefix, node, streams=True, storages=False):
|
||||||
"""
|
"""
|
||||||
(listdir helper)
|
(listdir helper)
|
||||||
files: list of files to fill in
|
:param files: list of files to fill in
|
||||||
prefix: current location in storage tree (list of names)
|
:param prefix: current location in storage tree (list of names)
|
||||||
node: current node (_OleDirectoryEntry object)
|
:param node: current node (_OleDirectoryEntry object)
|
||||||
streams: bool, include streams if True (True by default) - new in v0.26
|
:param streams: bool, include streams if True (True by default) - new in v0.26
|
||||||
storages: bool, include storages if True (False by default) - new in v0.26
|
:param storages: bool, include storages if True (False by default) - new in v0.26
|
||||||
(note: the root storage is never included)
|
(note: the root storage is never included)
|
||||||
"""
|
"""
|
||||||
prefix = prefix + [node.name]
|
prefix = prefix + [node.name]
|
||||||
|
@ -1657,8 +1665,8 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return a list of streams stored in this file
|
Return a list of streams stored in this file
|
||||||
|
|
||||||
streams: bool, include streams if True (True by default) - new in v0.26
|
:param streams: bool, include streams if True (True by default) - new in v0.26
|
||||||
storages: bool, include storages if True (False by default) - new in v0.26
|
:param storages: bool, include storages if True (False by default) - new in v0.26
|
||||||
(note: the root storage is never included)
|
(note: the root storage is never included)
|
||||||
"""
|
"""
|
||||||
files = []
|
files = []
|
||||||
|
@ -1671,12 +1679,13 @@ class OleFileIO:
|
||||||
Returns directory entry of given filename. (openstream helper)
|
Returns directory entry of given filename. (openstream helper)
|
||||||
Note: this method is case-insensitive.
|
Note: this method is case-insensitive.
|
||||||
|
|
||||||
filename: path of stream in storage tree (except root entry), either:
|
:param filename: path of stream in storage tree (except root entry), either:
|
||||||
|
|
||||||
- a string using Unix path syntax, for example:
|
- a string using Unix path syntax, for example:
|
||||||
'storage_1/storage_1.2/stream'
|
'storage_1/storage_1.2/stream'
|
||||||
- a list of storage filenames, path to the desired stream/storage.
|
- a list of storage filenames, path to the desired stream/storage.
|
||||||
Example: ['storage_1', 'storage_1.2', 'stream']
|
Example: ['storage_1', 'storage_1.2', 'stream']
|
||||||
return: sid of requested filename
|
:returns: sid of requested filename
|
||||||
raise IOError if file not found
|
raise IOError if file not found
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -1700,13 +1709,15 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Open a stream as a read-only file object (BytesIO).
|
Open a stream as a read-only file object (BytesIO).
|
||||||
|
|
||||||
filename: path of stream in storage tree (except root entry), either:
|
:param filename: path of stream in storage tree (except root entry), either:
|
||||||
|
|
||||||
- a string using Unix path syntax, for example:
|
- a string using Unix path syntax, for example:
|
||||||
'storage_1/storage_1.2/stream'
|
'storage_1/storage_1.2/stream'
|
||||||
- a list of storage filenames, path to the desired stream/storage.
|
- a list of storage filenames, path to the desired stream/storage.
|
||||||
Example: ['storage_1', 'storage_1.2', 'stream']
|
Example: ['storage_1', 'storage_1.2', 'stream']
|
||||||
return: file object (read-only)
|
|
||||||
raise IOError if filename not found, or if this is not a stream.
|
:returns: file object (read-only)
|
||||||
|
:exception IOError: if filename not found, or if this is not a stream.
|
||||||
"""
|
"""
|
||||||
sid = self._find(filename)
|
sid = self._find(filename)
|
||||||
entry = self.direntries[sid]
|
entry = self.direntries[sid]
|
||||||
|
@ -1720,8 +1731,9 @@ class OleFileIO:
|
||||||
Test if given filename exists as a stream or a storage in the OLE
|
Test if given filename exists as a stream or a storage in the OLE
|
||||||
container, and return its type.
|
container, and return its type.
|
||||||
|
|
||||||
filename: path of stream in storage tree. (see openstream for syntax)
|
:param filename: path of stream in storage tree. (see openstream for syntax)
|
||||||
return: False if object does not exist, its entry type (>0) otherwise:
|
:returns: False if object does not exist, its entry type (>0) otherwise:
|
||||||
|
|
||||||
- STGTY_STREAM: a stream
|
- STGTY_STREAM: a stream
|
||||||
- STGTY_STORAGE: a storage
|
- STGTY_STORAGE: a storage
|
||||||
- STGTY_ROOT: the root entry
|
- STGTY_ROOT: the root entry
|
||||||
|
@ -1738,9 +1750,9 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return modification time of a stream/storage.
|
Return modification time of a stream/storage.
|
||||||
|
|
||||||
filename: path of stream/storage in storage tree. (see openstream for
|
:param filename: path of stream/storage in storage tree. (see openstream for
|
||||||
syntax)
|
syntax)
|
||||||
return: None if modification time is null, a python datetime object
|
:returns: None if modification time is null, a python datetime object
|
||||||
otherwise (UTC timezone)
|
otherwise (UTC timezone)
|
||||||
|
|
||||||
new in version 0.26
|
new in version 0.26
|
||||||
|
@ -1754,9 +1766,9 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return creation time of a stream/storage.
|
Return creation time of a stream/storage.
|
||||||
|
|
||||||
filename: path of stream/storage in storage tree. (see openstream for
|
:param filename: path of stream/storage in storage tree. (see openstream for
|
||||||
syntax)
|
syntax)
|
||||||
return: None if creation time is null, a python datetime object
|
:returns: None if creation time is null, a python datetime object
|
||||||
otherwise (UTC timezone)
|
otherwise (UTC timezone)
|
||||||
|
|
||||||
new in version 0.26
|
new in version 0.26
|
||||||
|
@ -1771,8 +1783,8 @@ class OleFileIO:
|
||||||
Test if given filename exists as a stream or a storage in the OLE
|
Test if given filename exists as a stream or a storage in the OLE
|
||||||
container.
|
container.
|
||||||
|
|
||||||
filename: path of stream in storage tree. (see openstream for syntax)
|
:param filename: path of stream in storage tree. (see openstream for syntax)
|
||||||
return: True if object exist, else False.
|
:returns: True if object exist, else False.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sid = self._find(filename)
|
sid = self._find(filename)
|
||||||
|
@ -1785,9 +1797,10 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return size of a stream in the OLE container, in bytes.
|
Return size of a stream in the OLE container, in bytes.
|
||||||
|
|
||||||
filename: path of stream in storage tree (see openstream for syntax)
|
:param filename: path of stream in storage tree (see openstream for syntax)
|
||||||
return: size in bytes (long integer)
|
:returns: size in bytes (long integer)
|
||||||
raise: IOError if file not found, TypeError if this is not a stream.
|
:exception IOError: if file not found
|
||||||
|
:exception TypeError: if this is not a stream
|
||||||
"""
|
"""
|
||||||
sid = self._find(filename)
|
sid = self._find(filename)
|
||||||
entry = self.direntries[sid]
|
entry = self.direntries[sid]
|
||||||
|
@ -1809,11 +1822,11 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return properties described in substream.
|
Return properties described in substream.
|
||||||
|
|
||||||
filename: path of stream in storage tree (see openstream for syntax)
|
:param filename: path of stream in storage tree (see openstream for syntax)
|
||||||
convert_time: bool, if True timestamps will be converted to Python datetime
|
:param convert_time: bool, if True timestamps will be converted to Python datetime
|
||||||
no_conversion: None or list of int, timestamps not to be converted
|
:param no_conversion: None or list of int, timestamps not to be converted
|
||||||
(for example total editing time is not a real timestamp)
|
(for example total editing time is not a real timestamp)
|
||||||
return: a dictionary of values indexed by id (integer)
|
:returns: a dictionary of values indexed by id (integer)
|
||||||
"""
|
"""
|
||||||
# make sure no_conversion is a list, just to simplify code below:
|
# make sure no_conversion is a list, just to simplify code below:
|
||||||
if no_conversion == None:
|
if no_conversion == None:
|
||||||
|
|
|
@ -73,9 +73,8 @@ class PSDraw:
|
||||||
|
|
||||||
def setink(self, ink):
|
def setink(self, ink):
|
||||||
"""
|
"""
|
||||||
.. warning::
|
.. warning:: This has been in the PIL API for ages but was never implemented.
|
||||||
|
|
||||||
This has been in the PIL API for ages but was never implemented.
|
|
||||||
"""
|
"""
|
||||||
print("*** NOT YET IMPLEMENTED ***")
|
print("*** NOT YET IMPLEMENTED ***")
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ _Palm8BitColormapValues = (
|
||||||
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
|
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
|
||||||
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0))
|
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0))
|
||||||
|
|
||||||
|
|
||||||
# so build a prototype image to be used for palette resampling
|
# so build a prototype image to be used for palette resampling
|
||||||
def build_prototype_image():
|
def build_prototype_image():
|
||||||
image = Image.new("L", (1, len(_Palm8BitColormapValues),))
|
image = Image.new("L", (1, len(_Palm8BitColormapValues),))
|
||||||
|
@ -91,7 +92,8 @@ def build_prototype_image():
|
||||||
|
|
||||||
Palm8BitColormapImage = build_prototype_image()
|
Palm8BitColormapImage = build_prototype_image()
|
||||||
|
|
||||||
# OK, we now have in Palm8BitColormapImage, a "P"-mode image with the right palette
|
# OK, we now have in Palm8BitColormapImage,
|
||||||
|
# a "P"-mode image with the right palette
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -110,6 +112,7 @@ _COMPRESSION_TYPES = {
|
||||||
o8 = _binary.o8
|
o8 = _binary.o8
|
||||||
o16b = _binary.o16be
|
o16b = _binary.o16be
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -127,12 +130,16 @@ def _save(im, fp, filename, check=0):
|
||||||
bpp = 8
|
bpp = 8
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
elif im.mode == "L" and "bpp" in im.encoderinfo and im.encoderinfo["bpp"] in (1, 2, 4):
|
elif (im.mode == "L" and
|
||||||
|
"bpp" in im.encoderinfo and
|
||||||
|
im.encoderinfo["bpp"] in (1, 2, 4)):
|
||||||
|
|
||||||
# this is 8-bit grayscale, so we shift it to get the high-order bits, and invert it because
|
# this is 8-bit grayscale, so we shift it to get the high-order bits,
|
||||||
|
# and invert it because
|
||||||
# Palm does greyscale from white (0) to black (1)
|
# Palm does greyscale from white (0) to black (1)
|
||||||
bpp = im.encoderinfo["bpp"]
|
bpp = im.encoderinfo["bpp"]
|
||||||
im = im.point(lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift))
|
im = im.point(
|
||||||
|
lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift))
|
||||||
# we ignore the palette here
|
# we ignore the palette here
|
||||||
im.mode = "P"
|
im.mode = "P"
|
||||||
rawmode = "P;" + str(bpp)
|
rawmode = "P;" + str(bpp)
|
||||||
|
@ -140,8 +147,9 @@ def _save(im, fp, filename, check=0):
|
||||||
|
|
||||||
elif im.mode == "L" and "bpp" in im.info and im.info["bpp"] in (1, 2, 4):
|
elif im.mode == "L" and "bpp" in im.info and im.info["bpp"] in (1, 2, 4):
|
||||||
|
|
||||||
# here we assume that even though the inherent mode is 8-bit grayscale, only
|
# here we assume that even though the inherent mode is 8-bit grayscale,
|
||||||
# the lower bpp bits are significant. We invert them to match the Palm.
|
# only the lower bpp bits are significant.
|
||||||
|
# We invert them to match the Palm.
|
||||||
bpp = im.info["bpp"]
|
bpp = im.info["bpp"]
|
||||||
im = im.point(lambda x, maxval=(1 << bpp)-1: maxval - (x & maxval))
|
im = im.point(lambda x, maxval=(1 << bpp)-1: maxval - (x & maxval))
|
||||||
# we ignore the palette here
|
# we ignore the palette here
|
||||||
|
@ -172,21 +180,21 @@ def _save(im, fp, filename, check=0):
|
||||||
cols = im.size[0]
|
cols = im.size[0]
|
||||||
rows = im.size[1]
|
rows = im.size[1]
|
||||||
|
|
||||||
rowbytes = ((cols + (16//bpp - 1)) / (16 // bpp)) * 2;
|
rowbytes = int((cols + (16//bpp - 1)) / (16 // bpp)) * 2
|
||||||
transparent_index = 0
|
transparent_index = 0
|
||||||
compression_type = _COMPRESSION_TYPES["none"]
|
compression_type = _COMPRESSION_TYPES["none"]
|
||||||
|
|
||||||
flags = 0;
|
flags = 0
|
||||||
if im.mode == "P" and "custom-colormap" in im.info:
|
if im.mode == "P" and "custom-colormap" in im.info:
|
||||||
flags = flags & _FLAGS["custom-colormap"]
|
flags = flags & _FLAGS["custom-colormap"]
|
||||||
colormapsize = 4 * 256 + 2;
|
colormapsize = 4 * 256 + 2
|
||||||
colormapmode = im.palette.mode
|
colormapmode = im.palette.mode
|
||||||
colormap = im.getdata().getpalette()
|
colormap = im.getdata().getpalette()
|
||||||
else:
|
else:
|
||||||
colormapsize = 0
|
colormapsize = 0
|
||||||
|
|
||||||
if "offset" in im.info:
|
if "offset" in im.info:
|
||||||
offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4;
|
offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4
|
||||||
else:
|
else:
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
|
@ -205,12 +213,19 @@ def _save(im, fp, filename, check=0):
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
fp.write(o8(i))
|
fp.write(o8(i))
|
||||||
if colormapmode == 'RGB':
|
if colormapmode == 'RGB':
|
||||||
fp.write(o8(colormap[3 * i]) + o8(colormap[3 * i + 1]) + o8(colormap[3 * i + 2]))
|
fp.write(
|
||||||
|
o8(colormap[3 * i]) +
|
||||||
|
o8(colormap[3 * i + 1]) +
|
||||||
|
o8(colormap[3 * i + 2]))
|
||||||
elif colormapmode == 'RGBA':
|
elif colormapmode == 'RGBA':
|
||||||
fp.write(o8(colormap[4 * i]) + o8(colormap[4 * i + 1]) + o8(colormap[4 * i + 2]))
|
fp.write(
|
||||||
|
o8(colormap[4 * i]) +
|
||||||
|
o8(colormap[4 * i + 1]) +
|
||||||
|
o8(colormap[4 * i + 2]))
|
||||||
|
|
||||||
# now convert data to raw form
|
# now convert data to raw form
|
||||||
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, rowbytes, 1))])
|
ImageFile._save(
|
||||||
|
im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))])
|
||||||
|
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ def _save(im, fp, filename, check=0):
|
||||||
# bytes per plane
|
# bytes per plane
|
||||||
stride = (im.size[0] * bits + 7) // 8
|
stride = (im.size[0] * bits + 7) // 8
|
||||||
# stride should be even
|
# stride should be even
|
||||||
stride = stride + (stride % 2)
|
stride += stride % 2
|
||||||
# Stride needs to be kept in sync with the PcxEncode.c version.
|
# Stride needs to be kept in sync with the PcxEncode.c version.
|
||||||
# Ideally it should be passed in in the state, but the bytes value
|
# Ideally it should be passed in in the state, but the bytes value
|
||||||
# gets overwritten.
|
# gets overwritten.
|
||||||
|
|
|
@ -108,8 +108,8 @@ def _save(im, fp, filename):
|
||||||
r = i8(palette[i*3])
|
r = i8(palette[i*3])
|
||||||
g = i8(palette[i*3+1])
|
g = i8(palette[i*3+1])
|
||||||
b = i8(palette[i*3+2])
|
b = i8(palette[i*3+2])
|
||||||
colorspace = colorspace + "%02x%02x%02x " % (r, g, b)
|
colorspace += "%02x%02x%02x " % (r, g, b)
|
||||||
colorspace = colorspace + "> ]"
|
colorspace += "> ]"
|
||||||
procset = "/ImageI" # indexed color
|
procset = "/ImageI" # indexed color
|
||||||
elif im.mode == "RGB":
|
elif im.mode == "RGB":
|
||||||
filter = "/DCTDecode"
|
filter = "/DCTDecode"
|
||||||
|
|
|
@ -147,6 +147,17 @@ class ChunkStream:
|
||||||
|
|
||||||
return cids
|
return cids
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Subclass of string to allow iTXt chunks to look like strings while
|
||||||
|
# keeping their extra information
|
||||||
|
|
||||||
|
class iTXt(str):
|
||||||
|
@staticmethod
|
||||||
|
def __new__(cls, text, lang, tkey):
|
||||||
|
self = str.__new__(cls, text)
|
||||||
|
self.lang = lang
|
||||||
|
self.tkey = tkey
|
||||||
|
return self
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# PNG chunk container (for use with save(pnginfo=))
|
# PNG chunk container (for use with save(pnginfo=))
|
||||||
|
@ -159,14 +170,36 @@ class PngInfo:
|
||||||
def add(self, cid, data):
|
def add(self, cid, data):
|
||||||
self.chunks.append((cid, data))
|
self.chunks.append((cid, data))
|
||||||
|
|
||||||
|
def add_itxt(self, key, value, lang="", tkey="", zip=False):
|
||||||
|
if not isinstance(key, bytes):
|
||||||
|
key = key.encode("latin-1", "strict")
|
||||||
|
if not isinstance(value, bytes):
|
||||||
|
value = value.encode("utf-8", "strict")
|
||||||
|
if not isinstance(lang, bytes):
|
||||||
|
lang = lang.encode("utf-8", "strict")
|
||||||
|
if not isinstance(tkey, bytes):
|
||||||
|
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:
|
||||||
|
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)
|
||||||
|
|
||||||
def add_text(self, key, value, zip=0):
|
def add_text(self, key, value, zip=0):
|
||||||
|
if isinstance(value, iTXt):
|
||||||
|
return self.add_itxt(key, value, value.lang, value.tkey, bool(zip))
|
||||||
|
|
||||||
# The tEXt chunk stores latin-1 text
|
# The tEXt chunk stores latin-1 text
|
||||||
|
if not isinstance(value, bytes):
|
||||||
|
try:
|
||||||
|
value = value.encode('latin-1', 'strict')
|
||||||
|
except UnicodeError:
|
||||||
|
return self.add_itxt(key, value, zip=bool(zip))
|
||||||
|
|
||||||
if not isinstance(key, bytes):
|
if not isinstance(key, bytes):
|
||||||
key = key.encode('latin-1', 'strict')
|
key = key.encode('latin-1', 'strict')
|
||||||
|
|
||||||
if not isinstance(value, bytes):
|
|
||||||
value = value.encode('latin-1', 'replace')
|
|
||||||
|
|
||||||
if zip:
|
if zip:
|
||||||
import zlib
|
import zlib
|
||||||
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
|
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
|
||||||
|
@ -329,6 +362,43 @@ class PngStream(ChunkStream):
|
||||||
self.im_info[k] = self.im_text[k] = v
|
self.im_info[k] = self.im_text[k] = v
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
def chunk_iTXt(self, pos, length):
|
||||||
|
|
||||||
|
# international text
|
||||||
|
r = s = ImageFile._safe_read(self.fp, length)
|
||||||
|
try:
|
||||||
|
k, r = r.split(b"\0", 1)
|
||||||
|
except ValueError:
|
||||||
|
return s
|
||||||
|
if len(r) < 2:
|
||||||
|
return s
|
||||||
|
cf, cm, r = i8(r[0]), i8(r[1]), r[2:]
|
||||||
|
try:
|
||||||
|
lang, tk, v = r.split(b"\0", 2)
|
||||||
|
except ValueError:
|
||||||
|
return s
|
||||||
|
if cf != 0:
|
||||||
|
if cm == 0:
|
||||||
|
import zlib
|
||||||
|
try:
|
||||||
|
v = zlib.decompress(v)
|
||||||
|
except zlib.error:
|
||||||
|
return s
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
if bytes is not str:
|
||||||
|
try:
|
||||||
|
k = k.decode("latin-1", "strict")
|
||||||
|
lang = lang.decode("utf-8", "strict")
|
||||||
|
tk = tk.decode("utf-8", "strict")
|
||||||
|
v = v.decode("utf-8", "strict")
|
||||||
|
except UnicodeError:
|
||||||
|
return s
|
||||||
|
|
||||||
|
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# PNG reader
|
# PNG reader
|
||||||
|
|
||||||
|
|
|
@ -235,7 +235,7 @@ def _layerinfo(file):
|
||||||
if t:
|
if t:
|
||||||
tile.extend(t)
|
tile.extend(t)
|
||||||
layers[i] = name, mode, bbox, tile
|
layers[i] = name, mode, bbox, tile
|
||||||
i = i + 1
|
i += 1
|
||||||
|
|
||||||
return layers
|
return layers
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ def _maketile(file, mode, bbox, channels):
|
||||||
for channel in range(channels):
|
for channel in range(channels):
|
||||||
layer = mode[channel]
|
layer = mode[channel]
|
||||||
if mode == "CMYK":
|
if mode == "CMYK":
|
||||||
layer = layer + ";I"
|
layer += ";I"
|
||||||
tile.append(("raw", bbox, offset, layer))
|
tile.append(("raw", bbox, offset, layer))
|
||||||
offset = offset + xsize*ysize
|
offset = offset + xsize*ysize
|
||||||
|
|
||||||
|
@ -272,13 +272,13 @@ def _maketile(file, mode, bbox, channels):
|
||||||
for channel in range(channels):
|
for channel in range(channels):
|
||||||
layer = mode[channel]
|
layer = mode[channel]
|
||||||
if mode == "CMYK":
|
if mode == "CMYK":
|
||||||
layer = layer + ";I"
|
layer += ";I"
|
||||||
tile.append(
|
tile.append(
|
||||||
("packbits", bbox, offset, layer)
|
("packbits", bbox, offset, layer)
|
||||||
)
|
)
|
||||||
for y in range(ysize):
|
for y in range(ysize):
|
||||||
offset = offset + i16(bytecount[i:i+2])
|
offset = offset + i16(bytecount[i:i+2])
|
||||||
i = i + 2
|
i += 2
|
||||||
|
|
||||||
file.seek(offset)
|
file.seek(offset)
|
||||||
|
|
||||||
|
|
|
@ -261,6 +261,7 @@ mode_map = {'1': _PyAccess8,
|
||||||
'PA': _PyAccess32_2,
|
'PA': _PyAccess32_2,
|
||||||
'RGB': _PyAccess32_3,
|
'RGB': _PyAccess32_3,
|
||||||
'LAB': _PyAccess32_3,
|
'LAB': _PyAccess32_3,
|
||||||
|
'HSV': _PyAccess32_3,
|
||||||
'YCbCr': _PyAccess32_3,
|
'YCbCr': _PyAccess32_3,
|
||||||
'RGBA': _PyAccess32_4,
|
'RGBA': _PyAccess32_4,
|
||||||
'RGBa': _PyAccess32_4,
|
'RGBa': _PyAccess32_4,
|
||||||
|
|
|
@ -31,6 +31,7 @@ i32 = _binary.i32be
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i16(prefix) == 474
|
return i16(prefix) == 474
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for SGI images.
|
# Image plugin for SGI images.
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ class SgiImageFile(ImageFile.ImageFile):
|
||||||
# HEAD
|
# HEAD
|
||||||
s = self.fp.read(512)
|
s = self.fp.read(512)
|
||||||
if i16(s) != 474:
|
if i16(s) != 474:
|
||||||
raise SyntaxError("not an SGI image file")
|
raise ValueError("Not an SGI image file")
|
||||||
|
|
||||||
# relevant header entries
|
# relevant header entries
|
||||||
compression = i8(s[2])
|
compression = i8(s[2])
|
||||||
|
@ -60,22 +61,22 @@ class SgiImageFile(ImageFile.ImageFile):
|
||||||
elif layout == (1, 3, 4):
|
elif layout == (1, 3, 4):
|
||||||
self.mode = "RGBA"
|
self.mode = "RGBA"
|
||||||
else:
|
else:
|
||||||
raise SyntaxError("unsupported SGI image mode")
|
raise ValueError("Unsupported SGI image mode")
|
||||||
|
|
||||||
# size
|
# size
|
||||||
self.size = i16(s[6:]), i16(s[8:])
|
self.size = i16(s[6:]), i16(s[8:])
|
||||||
|
|
||||||
|
|
||||||
# decoder info
|
# decoder info
|
||||||
if compression == 0:
|
if compression == 0:
|
||||||
offset = 512
|
offset = 512
|
||||||
pagesize = self.size[0]*self.size[1]*layout[0]
|
pagesize = self.size[0]*self.size[1]*layout[0]
|
||||||
self.tile = []
|
self.tile = []
|
||||||
for layer in self.mode:
|
for layer in self.mode:
|
||||||
self.tile.append(("raw", (0,0)+self.size, offset, (layer,0,-1)))
|
self.tile.append(
|
||||||
|
("raw", (0, 0)+self.size, offset, (layer, 0, -1)))
|
||||||
offset = offset + pagesize
|
offset = offset + pagesize
|
||||||
elif compression == 1:
|
elif compression == 1:
|
||||||
self.tile = [("sgi_rle", (0,0)+self.size, 512, (self.mode, 0, -1))]
|
raise ValueError("SGI RLE encoding not supported")
|
||||||
|
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
@ -85,5 +86,6 @@ Image.register_open("SGI", SgiImageFile, _accept)
|
||||||
Image.register_extension("SGI", ".bw")
|
Image.register_extension("SGI", ".bw")
|
||||||
Image.register_extension("SGI", ".rgb")
|
Image.register_extension("SGI", ".rgb")
|
||||||
Image.register_extension("SGI", ".rgba")
|
Image.register_extension("SGI", ".rgba")
|
||||||
|
Image.register_extension("SGI", ".sgi")
|
||||||
|
|
||||||
Image.register_extension("SGI", ".sgi") # really?
|
# End of file
|
||||||
|
|
|
@ -29,6 +29,7 @@ i32 = _binary.i32be
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i32(prefix) == 0x59a66a95
|
return i32(prefix) == 0x59a66a95
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Sun raster files.
|
# Image plugin for Sun raster files.
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,8 @@ from PIL import _binary
|
||||||
from PIL._util import isStringType
|
from PIL._util import isStringType
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
import array, sys
|
import array
|
||||||
|
import sys
|
||||||
import collections
|
import collections
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
|
@ -58,8 +59,8 @@ import os
|
||||||
READ_LIBTIFF = False
|
READ_LIBTIFF = False
|
||||||
WRITE_LIBTIFF = False
|
WRITE_LIBTIFF = False
|
||||||
|
|
||||||
II = b"II" # little-endian (intel-style)
|
II = b"II" # little-endian (Intel style)
|
||||||
MM = b"MM" # big-endian (motorola-style)
|
MM = b"MM" # big-endian (Motorola style)
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
o8 = _binary.o8
|
o8 = _binary.o8
|
||||||
|
@ -164,7 +165,7 @@ OPEN_INFO = {
|
||||||
(II, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
|
(II, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
|
||||||
(II, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
|
(II, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
|
||||||
(II, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
|
(II, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
|
||||||
(II, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10
|
(II, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
|
||||||
(II, 3, 1, 1, (1,), ()): ("P", "P;1"),
|
(II, 3, 1, 1, (1,), ()): ("P", "P;1"),
|
||||||
(II, 3, 1, 2, (1,), ()): ("P", "P;1R"),
|
(II, 3, 1, 2, (1,), ()): ("P", "P;1R"),
|
||||||
(II, 3, 1, 1, (2,), ()): ("P", "P;2"),
|
(II, 3, 1, 1, (2,), ()): ("P", "P;2"),
|
||||||
|
@ -196,7 +197,7 @@ OPEN_INFO = {
|
||||||
(MM, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
|
(MM, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
|
||||||
(MM, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
|
(MM, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
|
||||||
(MM, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
|
(MM, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
|
||||||
(MM, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10
|
(MM, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
|
||||||
(MM, 3, 1, 1, (1,), ()): ("P", "P;1"),
|
(MM, 3, 1, 1, (1,), ()): ("P", "P;1"),
|
||||||
(MM, 3, 1, 2, (1,), ()): ("P", "P;1R"),
|
(MM, 3, 1, 2, (1,), ()): ("P", "P;1R"),
|
||||||
(MM, 3, 1, 1, (2,), ()): ("P", "P;2"),
|
(MM, 3, 1, 1, (2,), ()): ("P", "P;2"),
|
||||||
|
@ -214,9 +215,11 @@ OPEN_INFO = {
|
||||||
|
|
||||||
PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"]
|
PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"]
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] in PREFIXES
|
return prefix[:4] in PREFIXES
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Wrapper for TIFF IFDs.
|
# Wrapper for TIFF IFDs.
|
||||||
|
|
||||||
|
@ -287,7 +290,9 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
return dict(self.items())
|
return dict(self.items())
|
||||||
|
|
||||||
def named(self):
|
def named(self):
|
||||||
"""Returns the complete tag dictionary, with named tags where posible."""
|
"""
|
||||||
|
Returns the complete tag dictionary, with named tags where posible.
|
||||||
|
"""
|
||||||
from PIL import TiffTags
|
from PIL import TiffTags
|
||||||
result = {}
|
result = {}
|
||||||
for tag_code, value in self.items():
|
for tag_code, value in self.items():
|
||||||
|
@ -295,7 +300,6 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
result[tag_name] = value
|
result[tag_name] = value
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
# dictionary API
|
# dictionary API
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
@ -449,14 +453,17 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
data = ifd[8:8+size]
|
data = ifd[8:8+size]
|
||||||
|
|
||||||
if len(data) != size:
|
if len(data) != size:
|
||||||
warnings.warn("Possibly corrupt EXIF data. Expecting to read %d bytes but only got %d. Skipping tag %s" % (size, len(data), tag))
|
warnings.warn("Possibly corrupt EXIF data. "
|
||||||
|
"Expecting to read %d bytes but only got %d. "
|
||||||
|
"Skipping tag %s" % (size, len(data), tag))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.tagdata[tag] = data
|
self.tagdata[tag] = data
|
||||||
self.tagtype[tag] = typ
|
self.tagtype[tag] = typ
|
||||||
|
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP):
|
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK,
|
||||||
|
ICCPROFILE, XMP):
|
||||||
print("- value: <table: %d bytes>" % size)
|
print("- value: <table: %d bytes>" % size)
|
||||||
else:
|
else:
|
||||||
print("- value:", self[tag])
|
print("- value:", self[tag])
|
||||||
|
@ -541,7 +548,8 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
typname = TiffTags.TYPES.get(typ, "unknown")
|
typname = TiffTags.TYPES.get(typ, "unknown")
|
||||||
print("save: %s (%d)" % (tagname, tag), end=' ')
|
print("save: %s (%d)" % (tagname, tag), end=' ')
|
||||||
print("- type: %s (%d)" % (typname, typ), end=' ')
|
print("- type: %s (%d)" % (typname, typ), end=' ')
|
||||||
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP):
|
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK,
|
||||||
|
ICCPROFILE, XMP):
|
||||||
size = len(data)
|
size = len(data)
|
||||||
print("- value: <table: %d bytes>" % size)
|
print("- value: <table: %d bytes>" % size)
|
||||||
else:
|
else:
|
||||||
|
@ -558,9 +566,9 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
count = count // 2 # adjust for rational data field
|
count = count // 2 # adjust for rational data field
|
||||||
|
|
||||||
append((tag, typ, count, o32(offset), data))
|
append((tag, typ, count, o32(offset), data))
|
||||||
offset = offset + len(data)
|
offset += len(data)
|
||||||
if offset & 1:
|
if offset & 1:
|
||||||
offset = offset + 1 # word padding
|
offset += 1 # word padding
|
||||||
|
|
||||||
# update strip offset data to point beyond auxiliary data
|
# update strip offset data to point beyond auxiliary data
|
||||||
if stripoffsets is not None:
|
if stripoffsets is not None:
|
||||||
|
@ -586,6 +594,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
|
|
||||||
return offset
|
return offset
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for TIFF files.
|
# Image plugin for TIFF files.
|
||||||
|
|
||||||
|
@ -644,7 +653,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.fp.seek(self.__next)
|
self.fp.seek(self.__next)
|
||||||
self.tag.load(self.fp)
|
self.tag.load(self.fp)
|
||||||
self.__next = self.tag.next
|
self.__next = self.tag.next
|
||||||
self.__frame = self.__frame + 1
|
self.__frame += 1
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
def _tell(self):
|
def _tell(self):
|
||||||
|
@ -694,9 +703,11 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
if not len(self.tile) == 1:
|
if not len(self.tile) == 1:
|
||||||
raise IOError("Not exactly one tile")
|
raise IOError("Not exactly one tile")
|
||||||
|
|
||||||
# (self._compression, (extents tuple), 0, (rawmode, self._compression, fp))
|
# (self._compression, (extents tuple),
|
||||||
|
# 0, (rawmode, self._compression, fp))
|
||||||
ignored, extents, ignored_2, args = self.tile[0]
|
ignored, extents, ignored_2, args = self.tile[0]
|
||||||
decoder = Image._getdecoder(self.mode, 'libtiff', args, self.decoderconfig)
|
decoder = Image._getdecoder(self.mode, 'libtiff', args,
|
||||||
|
self.decoderconfig)
|
||||||
try:
|
try:
|
||||||
decoder.setimage(self.im, extents)
|
decoder.setimage(self.im, extents)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -706,12 +717,12 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# We've got a stringio like thing passed in. Yay for all in memory.
|
# We've got a stringio like thing passed in. Yay for all in memory.
|
||||||
# The decoder needs the entire file in one shot, so there's not
|
# The decoder needs the entire file in one shot, so there's not
|
||||||
# a lot we can do here other than give it the entire file.
|
# a lot we can do here other than give it the entire file.
|
||||||
# unless we could do something like get the address of the underlying
|
# unless we could do something like get the address of the
|
||||||
# string for stringio.
|
# underlying string for stringio.
|
||||||
#
|
#
|
||||||
# Rearranging for supporting byteio items, since they have a fileno
|
# Rearranging for supporting byteio items, since they have a fileno
|
||||||
# that returns an IOError if there's no underlying fp. Easier to deal
|
# that returns an IOError if there's no underlying fp. Easier to
|
||||||
# with here by reordering.
|
# dea. with here by reordering.
|
||||||
if Image.DEBUG:
|
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())
|
n, err = decoder.decode(self.fp.getvalue())
|
||||||
|
@ -720,7 +731,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
if Image.DEBUG:
|
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)
|
self.fp.seek(0)
|
||||||
n,err = decoder.decode(b"fpfp") # 4 bytes, otherwise the trace might error out
|
# 4 bytes, otherwise the trace might error out
|
||||||
|
n, err = decoder.decode(b"fpfp")
|
||||||
else:
|
else:
|
||||||
# we have something else.
|
# we have something else.
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
|
@ -728,7 +740,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# UNDONE -- so much for that buffer size thing.
|
# UNDONE -- so much for that buffer size thing.
|
||||||
n, err = decoder.decode(self.fp.read())
|
n, err = decoder.decode(self.fp.read())
|
||||||
|
|
||||||
|
|
||||||
self.tile = []
|
self.tile = []
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
# libtiff closed the fp in a, we need to close self.fp, if possible
|
# libtiff closed the fp in a, we need to close self.fp, if possible
|
||||||
|
@ -825,13 +836,16 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
offsets = self.tag[STRIPOFFSETS]
|
offsets = self.tag[STRIPOFFSETS]
|
||||||
h = getscalar(ROWSPERSTRIP, ysize)
|
h = getscalar(ROWSPERSTRIP, ysize)
|
||||||
w = self.size[0]
|
w = self.size[0]
|
||||||
if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3", "group4",
|
if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3",
|
||||||
"tiff_jpeg", "tiff_adobe_deflate",
|
"group4", "tiff_jpeg",
|
||||||
"tiff_thunderscan", "tiff_deflate",
|
"tiff_adobe_deflate",
|
||||||
"tiff_sgilog", "tiff_sgilog24",
|
"tiff_thunderscan",
|
||||||
|
"tiff_deflate",
|
||||||
|
"tiff_sgilog",
|
||||||
|
"tiff_sgilog24",
|
||||||
"tiff_raw_16"]:
|
"tiff_raw_16"]:
|
||||||
## if Image.DEBUG:
|
# if Image.DEBUG:
|
||||||
## print "Activating g4 compression for whole file"
|
# print "Activating g4 compression for whole file"
|
||||||
|
|
||||||
# Decoder expects entire file as one tile.
|
# Decoder expects entire file as one tile.
|
||||||
# There's a buffer size limit in load (64k)
|
# There's a buffer size limit in load (64k)
|
||||||
|
@ -850,7 +864,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# libtiff closes the file descriptor, so pass in a dup.
|
# libtiff closes the file descriptor, so pass in a dup.
|
||||||
try:
|
try:
|
||||||
fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno())
|
fp = hasattr(self.fp, "fileno") and \
|
||||||
|
os.dup(self.fp.fileno())
|
||||||
except IOError:
|
except IOError:
|
||||||
# io.BytesIO have a fileno, but returns an IOError if
|
# io.BytesIO have a fileno, but returns an IOError if
|
||||||
# it doesn't use a file descriptor.
|
# it doesn't use a file descriptor.
|
||||||
|
@ -859,7 +874,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# libtiff handles the fillmode for us, so 1;IR should
|
# libtiff handles the fillmode for us, so 1;IR should
|
||||||
# actually be 1;I. Including the R double reverses the
|
# actually be 1;I. Including the R double reverses the
|
||||||
# bits, so stripes of the image are reversed. See
|
# bits, so stripes of the image are reversed. See
|
||||||
# https://github.com/python-imaging/Pillow/issues/279
|
# https://github.com/python-pillow/Pillow/issues/279
|
||||||
if fillorder == 2:
|
if fillorder == 2:
|
||||||
key = (
|
key = (
|
||||||
self.tag.prefix, photo, format, 1,
|
self.tag.prefix, photo, format, 1,
|
||||||
|
@ -900,7 +915,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
y = y + h
|
y = y + h
|
||||||
if y >= self.size[1]:
|
if y >= self.size[1]:
|
||||||
x = y = 0
|
x = y = 0
|
||||||
l = l + 1
|
l += 1
|
||||||
a = None
|
a = None
|
||||||
elif TILEOFFSETS in self.tag:
|
elif TILEOFFSETS in self.tag:
|
||||||
# tiled image
|
# tiled image
|
||||||
|
@ -921,7 +936,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
x, y = 0, y + h
|
x, y = 0, y + h
|
||||||
if y >= self.size[1]:
|
if y >= self.size[1]:
|
||||||
x = y = 0
|
x = y = 0
|
||||||
l = l + 1
|
l += 1
|
||||||
a = None
|
a = None
|
||||||
else:
|
else:
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
|
@ -937,10 +952,12 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Write TIFF files
|
# Write TIFF files
|
||||||
|
|
||||||
# little endian is default except for image modes with explict big endian byte-order
|
# little endian is default except for image modes with
|
||||||
|
# explict big endian byte-order
|
||||||
|
|
||||||
SAVE_INFO = {
|
SAVE_INFO = {
|
||||||
# mode => rawmode, byteorder, photometrics, sampleformat, bitspersample, extra
|
# mode => rawmode, byteorder, photometrics,
|
||||||
|
# sampleformat, bitspersample, extra
|
||||||
"1": ("1", II, 1, 1, (1,), None),
|
"1": ("1", II, 1, 1, (1,), None),
|
||||||
"L": ("L", II, 1, 1, (8,), None),
|
"L": ("L", II, 1, 1, (8,), None),
|
||||||
"LA": ("LA", II, 1, 1, (8, 8), 2),
|
"LA": ("LA", II, 1, 1, (8, 8), 2),
|
||||||
|
@ -963,6 +980,7 @@ SAVE_INFO = {
|
||||||
"F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
|
"F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _cvt_res(value):
|
def _cvt_res(value):
|
||||||
# convert value to TIFF rational number -- (numerator, denominator)
|
# convert value to TIFF rational number -- (numerator, denominator)
|
||||||
if isinstance(value, collections.Sequence):
|
if isinstance(value, collections.Sequence):
|
||||||
|
@ -973,6 +991,7 @@ def _cvt_res(value):
|
||||||
value = float(value)
|
value = float(value)
|
||||||
return (int(value * 65536), 65536)
|
return (int(value * 65536), 65536)
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -982,7 +1001,8 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
ifd = ImageFileDirectory(prefix)
|
ifd = ImageFileDirectory(prefix)
|
||||||
|
|
||||||
compression = im.encoderinfo.get('compression',im.info.get('compression','raw'))
|
compression = im.encoderinfo.get('compression', im.info.get('compression',
|
||||||
|
'raw'))
|
||||||
|
|
||||||
libtiff = WRITE_LIBTIFF or compression != 'raw'
|
libtiff = WRITE_LIBTIFF or compression != 'raw'
|
||||||
|
|
||||||
|
@ -1010,7 +1030,6 @@ def _save(im, fp, filename):
|
||||||
except:
|
except:
|
||||||
pass # might not be an IFD, Might not have populated type
|
pass # might not be an IFD, Might not have populated type
|
||||||
|
|
||||||
|
|
||||||
# additions written by Greg Couch, gregc@cgl.ucsf.edu
|
# additions written by Greg Couch, gregc@cgl.ucsf.edu
|
||||||
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
|
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
|
||||||
if hasattr(im, 'tag'):
|
if hasattr(im, 'tag'):
|
||||||
|
@ -1078,7 +1097,8 @@ def _save(im, fp, filename):
|
||||||
ifd[ROWSPERSTRIP] = im.size[1]
|
ifd[ROWSPERSTRIP] = im.size[1]
|
||||||
ifd[STRIPBYTECOUNTS] = stride * im.size[1]
|
ifd[STRIPBYTECOUNTS] = stride * im.size[1]
|
||||||
ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
|
ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
|
||||||
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression,1) # no compression by default
|
# no compression by default:
|
||||||
|
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
|
||||||
|
|
||||||
if libtiff:
|
if libtiff:
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
|
@ -1089,23 +1109,27 @@ def _save(im, fp, filename):
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
_fp = os.dup(fp.fileno())
|
_fp = os.dup(fp.fileno())
|
||||||
|
|
||||||
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes.
|
# ICC Profile crashes.
|
||||||
|
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE]
|
||||||
atts = {}
|
atts = {}
|
||||||
# bits per sample is a single short in the tiff directory, not a list.
|
# bits per sample is a single short in the tiff directory, not a list.
|
||||||
atts[BITSPERSAMPLE] = bits[0]
|
atts[BITSPERSAMPLE] = bits[0]
|
||||||
# Merge the ones that we have with (optional) more bits from
|
# Merge the ones that we have with (optional) more bits from
|
||||||
# the original file, e.g x,y resolution so that we can
|
# the original file, e.g x,y resolution so that we can
|
||||||
# save(load('')) == original file.
|
# save(load('')) == original file.
|
||||||
for k,v in itertools.chain(ifd.items(), getattr(im, 'ifd', {}).items()):
|
for k, v in itertools.chain(ifd.items(),
|
||||||
|
getattr(im, 'ifd', {}).items()):
|
||||||
if k not in atts and k not in blocklist:
|
if k not in atts and k not in blocklist:
|
||||||
if type(v[0]) == tuple and len(v) > 1:
|
if type(v[0]) == tuple and len(v) > 1:
|
||||||
# A tuple of more than one rational tuples
|
# A tuple of more than one rational tuples
|
||||||
# flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL
|
# flatten to floats,
|
||||||
|
# following tiffcp.c->cpTag->TIFF_RATIONAL
|
||||||
atts[k] = [float(elt[0])/float(elt[1]) for elt in v]
|
atts[k] = [float(elt[0])/float(elt[1]) for elt in v]
|
||||||
continue
|
continue
|
||||||
if type(v[0]) == tuple and len(v) == 1:
|
if type(v[0]) == tuple and len(v) == 1:
|
||||||
# A tuple of one rational tuples
|
# A tuple of one rational tuples
|
||||||
# flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL
|
# flatten to floats,
|
||||||
|
# following tiffcp.c->cpTag->TIFF_RATIONAL
|
||||||
atts[k] = float(v[0][0])/float(v[0][1])
|
atts[k] = float(v[0][0])/float(v[0][1])
|
||||||
continue
|
continue
|
||||||
if type(v) == tuple and len(v) > 2:
|
if type(v) == tuple and len(v) > 2:
|
||||||
|
@ -1115,7 +1139,8 @@ def _save(im, fp, filename):
|
||||||
continue
|
continue
|
||||||
if type(v) == tuple and len(v) == 2:
|
if type(v) == tuple and len(v) == 2:
|
||||||
# one rational tuple
|
# one rational tuple
|
||||||
# flatten to float, following tiffcp.c->cpTag->TIFF_RATIONAL
|
# flatten to float,
|
||||||
|
# following tiffcp.c->cpTag->TIFF_RATIONAL
|
||||||
atts[k] = float(v[0])/float(v[1])
|
atts[k] = float(v[0])/float(v[1])
|
||||||
continue
|
continue
|
||||||
if type(v) == tuple and len(v) == 1:
|
if type(v) == tuple and len(v) == 1:
|
||||||
|
@ -1143,7 +1168,8 @@ def _save(im, fp, filename):
|
||||||
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
|
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
|
||||||
e.setimage(im.im, (0, 0)+im.size)
|
e.setimage(im.im, (0, 0)+im.size)
|
||||||
while True:
|
while True:
|
||||||
l, s, d = e.encode(16*1024) # undone, change to self.decodermaxblock
|
# undone, change to self.decodermaxblock:
|
||||||
|
l, s, d = e.encode(16*1024)
|
||||||
if not _fp:
|
if not _fp:
|
||||||
fp.write(d)
|
fp.write(d)
|
||||||
if s:
|
if s:
|
||||||
|
@ -1158,7 +1184,6 @@ def _save(im, fp, filename):
|
||||||
("raw", (0, 0)+im.size, offset, (rawmode, stride, 1))
|
("raw", (0, 0)+im.size, offset, (rawmode, stride, 1))
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
# -- helper for multi-page save --
|
# -- helper for multi-page save --
|
||||||
if "_debug_multipage" in im.encoderinfo:
|
if "_debug_multipage" in im.encoderinfo:
|
||||||
# just to access o32 and o16 (using correct byte order)
|
# just to access o32 and o16 (using correct byte order)
|
||||||
|
|
|
@ -147,6 +147,100 @@ TAGS = {
|
||||||
# ICC Profile
|
# ICC Profile
|
||||||
34675: "ICCProfile",
|
34675: "ICCProfile",
|
||||||
|
|
||||||
|
# Additional Exif Info
|
||||||
|
33434: "ExposureTime",
|
||||||
|
33437: "FNumber",
|
||||||
|
34850: "ExposureProgram",
|
||||||
|
34852: "SpectralSensitivity",
|
||||||
|
34853: "GPSInfoIFD",
|
||||||
|
34855: "ISOSpeedRatings",
|
||||||
|
34856: "OECF",
|
||||||
|
34864: "SensitivityType",
|
||||||
|
34865: "StandardOutputSensitivity",
|
||||||
|
34866: "RecommendedExposureIndex",
|
||||||
|
34867: "ISOSpeed",
|
||||||
|
34868: "ISOSpeedLatitudeyyy",
|
||||||
|
34869: "ISOSpeedLatitudezzz",
|
||||||
|
36864: "ExifVersion",
|
||||||
|
36867: "DateTimeOriginal",
|
||||||
|
36868: "DateTImeDigitized",
|
||||||
|
37121: "ComponentsConfiguration",
|
||||||
|
37122: "CompressedBitsPerPixel",
|
||||||
|
37377: "ShutterSpeedValue",
|
||||||
|
37378: "ApertureValue",
|
||||||
|
37379: "BrightnessValue",
|
||||||
|
37380: "ExposureBiasValue",
|
||||||
|
37381: "MaxApertureValue",
|
||||||
|
37382: "SubjectDistance",
|
||||||
|
37383: "MeteringMode",
|
||||||
|
37384: "LightSource",
|
||||||
|
37385: "Flash",
|
||||||
|
37386: "FocalLength",
|
||||||
|
37396: "SubjectArea",
|
||||||
|
37500: "MakerNote",
|
||||||
|
37510: "UserComment",
|
||||||
|
37520: "SubSec",
|
||||||
|
37521: "SubSecTimeOriginal",
|
||||||
|
37522: "SubsecTimeDigitized",
|
||||||
|
40960: "FlashPixVersion",
|
||||||
|
40961: "ColorSpace",
|
||||||
|
40962: "PixelXDimension",
|
||||||
|
40963: "PixelYDimension",
|
||||||
|
40964: "RelatedSoundFile",
|
||||||
|
40965: "InteroperabilityIFD",
|
||||||
|
41483: "FlashEnergy",
|
||||||
|
41484: "SpatialFrequencyResponse",
|
||||||
|
41486: "FocalPlaneXResolution",
|
||||||
|
41487: "FocalPlaneYResolution",
|
||||||
|
41488: "FocalPlaneResolutionUnit",
|
||||||
|
41492: "SubjectLocation",
|
||||||
|
41493: "ExposureIndex",
|
||||||
|
41495: "SensingMethod",
|
||||||
|
41728: "FileSource",
|
||||||
|
41729: "SceneType",
|
||||||
|
41730: "CFAPattern",
|
||||||
|
41985: "CustomRendered",
|
||||||
|
41986: "ExposureMode",
|
||||||
|
41987: "WhiteBalance",
|
||||||
|
41988: "DigitalZoomRatio",
|
||||||
|
41989: "FocalLengthIn35mmFilm",
|
||||||
|
41990: "SceneCaptureType",
|
||||||
|
41991: "GainControl",
|
||||||
|
41992: "Contrast",
|
||||||
|
41993: "Saturation",
|
||||||
|
41994: "Sharpness",
|
||||||
|
41995: "DeviceSettingDescription",
|
||||||
|
41996: "SubjectDistanceRange",
|
||||||
|
42016: "ImageUniqueID",
|
||||||
|
42032: "CameraOwnerName",
|
||||||
|
42033: "BodySerialNumber",
|
||||||
|
42034: "LensSpecification",
|
||||||
|
42035: "LensMake",
|
||||||
|
42036: "LensModel",
|
||||||
|
42037: "LensSerialNumber",
|
||||||
|
42240: "Gamma",
|
||||||
|
|
||||||
|
# MP Info
|
||||||
|
45056: "MPFVersion",
|
||||||
|
45057: "NumberOfImages",
|
||||||
|
45058: "MPEntry",
|
||||||
|
45059: "ImageUIDList",
|
||||||
|
45060: "TotalFrames",
|
||||||
|
45313: "MPIndividualNum",
|
||||||
|
45569: "PanOrientation",
|
||||||
|
45570: "PanOverlap_H",
|
||||||
|
45571: "PanOverlap_V",
|
||||||
|
45572: "BaseViewpointNum",
|
||||||
|
45573: "ConvergenceAngle",
|
||||||
|
45574: "BaselineLength",
|
||||||
|
45575: "VerticalDivergence",
|
||||||
|
45576: "AxisDistance_X",
|
||||||
|
45577: "AxisDistance_Y",
|
||||||
|
45578: "AxisDistance_Z",
|
||||||
|
45579: "YawAngle",
|
||||||
|
45580: "PitchAngle",
|
||||||
|
45581: "RollAngle",
|
||||||
|
|
||||||
# Adobe DNG
|
# Adobe DNG
|
||||||
50706: "DNGVersion",
|
50706: "DNGVersion",
|
||||||
50707: "DNGBackwardVersion",
|
50707: "DNGBackwardVersion",
|
||||||
|
@ -161,7 +255,7 @@ TAGS = {
|
||||||
50716: "BlackLevelDeltaV",
|
50716: "BlackLevelDeltaV",
|
||||||
50717: "WhiteLevel",
|
50717: "WhiteLevel",
|
||||||
50718: "DefaultScale",
|
50718: "DefaultScale",
|
||||||
50741: "BestQualityScale",
|
50741: "BestQualityScale", # FIXME! Dictionary contains duplicate keys 50741
|
||||||
50719: "DefaultCropOrigin",
|
50719: "DefaultCropOrigin",
|
||||||
50720: "DefaultCropSize",
|
50720: "DefaultCropSize",
|
||||||
50778: "CalibrationIlluminant1",
|
50778: "CalibrationIlluminant1",
|
||||||
|
@ -185,7 +279,7 @@ TAGS = {
|
||||||
50737: "ChromaBlurRadius",
|
50737: "ChromaBlurRadius",
|
||||||
50738: "AntiAliasStrength",
|
50738: "AntiAliasStrength",
|
||||||
50740: "DNGPrivateData",
|
50740: "DNGPrivateData",
|
||||||
50741: "MakerNoteSafety",
|
50741: "MakerNoteSafety", # FIXME! Dictionary contains duplicate keys 50741
|
||||||
|
|
||||||
#ImageJ
|
#ImageJ
|
||||||
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
|
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
|
||||||
|
|
|
@ -14,11 +14,11 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
# NOTE: This format cannot be automatically recognized, so the reader
|
# NOTE: This format cannot be automatically recognized, so the reader
|
||||||
# is not registered for use with Image.open(). To open a WEL file, use
|
# is not registered for use with Image.open(). To open a WAL file, use
|
||||||
# the WalImageFile.open() function instead.
|
# the WalImageFile.open() function instead.
|
||||||
|
|
||||||
# This reader is based on the specification available from:
|
# This reader is based on the specification available from:
|
||||||
# http://www.flipcode.com/tutorials/tut_q2levels.shtml
|
# http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
|
||||||
# and has been tested with a few sample files found using google.
|
# and has been tested with a few sample files found using google.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
|
@ -59,7 +59,7 @@ word = _binary.i16le
|
||||||
def short(c, o=0):
|
def short(c, o=0):
|
||||||
v = word(c, o)
|
v = word(c, o)
|
||||||
if v >= 32768:
|
if v >= 32768:
|
||||||
v = v - 65536
|
v -= 65536
|
||||||
return v
|
return v
|
||||||
|
|
||||||
dword = _binary.i32le
|
dword = _binary.i32le
|
||||||
|
|
|
@ -29,6 +29,7 @@ xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)")
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:9] == b"/* XPM */"
|
return prefix[:9] == b"/* XPM */"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for X11 pixel maps.
|
# Image plugin for X11 pixel maps.
|
||||||
|
|
||||||
|
@ -86,9 +87,9 @@ class XpmImageFile(ImageFile.ImageFile):
|
||||||
elif rgb[0:1] == b"#":
|
elif rgb[0:1] == b"#":
|
||||||
# FIXME: handle colour names (see ImagePalette.py)
|
# FIXME: handle colour names (see ImagePalette.py)
|
||||||
rgb = int(rgb[1:], 16)
|
rgb = int(rgb[1:], 16)
|
||||||
palette[c] = o8((rgb >> 16) & 255) +\
|
palette[c] = (o8((rgb >> 16) & 255) +
|
||||||
o8((rgb >> 8) & 255) +\
|
o8((rgb >> 8) & 255) +
|
||||||
o8(rgb & 255)
|
o8(rgb & 255))
|
||||||
else:
|
else:
|
||||||
# unknown colour
|
# unknown colour
|
||||||
raise ValueError("cannot read this XPM file")
|
raise ValueError("cannot read this XPM file")
|
||||||
|
|
|
@ -12,10 +12,9 @@
|
||||||
# ;-)
|
# ;-)
|
||||||
|
|
||||||
VERSION = '1.1.7' # PIL version
|
VERSION = '1.1.7' # PIL version
|
||||||
PILLOW_VERSION = '2.4.0' # Pillow
|
PILLOW_VERSION = '2.5.3' # Pillow
|
||||||
|
|
||||||
_plugins = ['ArgImagePlugin',
|
_plugins = ['BmpImagePlugin',
|
||||||
'BmpImagePlugin',
|
|
||||||
'BufrStubImagePlugin',
|
'BufrStubImagePlugin',
|
||||||
'CurImagePlugin',
|
'CurImagePlugin',
|
||||||
'DcxImagePlugin',
|
'DcxImagePlugin',
|
||||||
|
@ -37,6 +36,7 @@ _plugins = ['ArgImagePlugin',
|
||||||
'McIdasImagePlugin',
|
'McIdasImagePlugin',
|
||||||
'MicImagePlugin',
|
'MicImagePlugin',
|
||||||
'MpegImagePlugin',
|
'MpegImagePlugin',
|
||||||
|
'MpoImagePlugin',
|
||||||
'MspImagePlugin',
|
'MspImagePlugin',
|
||||||
'PalmImagePlugin',
|
'PalmImagePlugin',
|
||||||
'PcdImagePlugin',
|
'PcdImagePlugin',
|
||||||
|
|
|
@ -3,20 +3,25 @@ import os
|
||||||
if bytes is str:
|
if bytes is str:
|
||||||
def isStringType(t):
|
def isStringType(t):
|
||||||
return isinstance(t, basestring)
|
return isinstance(t, basestring)
|
||||||
|
|
||||||
def isPath(f):
|
def isPath(f):
|
||||||
return isinstance(f, basestring)
|
return isinstance(f, basestring)
|
||||||
else:
|
else:
|
||||||
def isStringType(t):
|
def isStringType(t):
|
||||||
return isinstance(t, str)
|
return isinstance(t, str)
|
||||||
|
|
||||||
def isPath(f):
|
def isPath(f):
|
||||||
return isinstance(f, (bytes, str))
|
return isinstance(f, (bytes, str))
|
||||||
|
|
||||||
|
|
||||||
# Checks if an object is a string, and that it points to a directory.
|
# Checks if an object is a string, and that it points to a directory.
|
||||||
def isDirectory(f):
|
def isDirectory(f):
|
||||||
return isPath(f) and os.path.isdir(f)
|
return isPath(f) and os.path.isdir(f)
|
||||||
|
|
||||||
|
|
||||||
class deferred_error(object):
|
class deferred_error(object):
|
||||||
def __init__(self, ex):
|
def __init__(self, ex):
|
||||||
self.ex = ex
|
self.ex = ex
|
||||||
|
|
||||||
def __getattr__(self, elt):
|
def __getattr__(self, elt):
|
||||||
raise self.ex
|
raise self.ex
|
||||||
|
|
17
PIL/tests.py
|
@ -1,17 +0,0 @@
|
||||||
import unittest
|
|
||||||
|
|
||||||
|
|
||||||
class PillowTests(unittest.TestCase):
|
|
||||||
"""
|
|
||||||
Can we start moving the test suite here?
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_suite_should_move_here(self):
|
|
||||||
"""
|
|
||||||
Great idea!
|
|
||||||
"""
|
|
||||||
assert True is True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
11
README.rst
|
@ -3,10 +3,10 @@ Pillow
|
||||||
|
|
||||||
*Python Imaging Library (Fork)*
|
*Python Imaging Library (Fork)*
|
||||||
|
|
||||||
Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
Pillow is the "friendly" PIL fork by `Alex Clark and Contributors <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>`_.
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/python-imaging/Pillow.svg?branch=master
|
.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
|
||||||
:target: https://travis-ci.org/python-imaging/Pillow
|
:target: https://travis-ci.org/python-pillow/Pillow
|
||||||
:alt: Travis CI build status
|
:alt: Travis CI build status
|
||||||
|
|
||||||
.. image:: https://pypip.in/v/Pillow/badge.png
|
.. image:: https://pypip.in/v/Pillow/badge.png
|
||||||
|
@ -17,7 +17,6 @@ Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Pyt
|
||||||
:target: https://pypi.python.org/pypi/Pillow/
|
:target: https://pypi.python.org/pypi/Pillow/
|
||||||
:alt: Number of PyPI downloads
|
:alt: Number of PyPI downloads
|
||||||
|
|
||||||
.. image:: https://coveralls.io/repos/python-imaging/Pillow/badge.png?branch=master
|
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
|
||||||
:target: https://coveralls.io/r/python-imaging/Pillow?branch=master
|
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
|
||||||
|
|
||||||
The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details, and more.
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
Python SANE module V1.1 (30 Sep. 2004)
|
Python SANE module V1.1 (30 Sep. 2004)
|
||||||
|
================================================================================
|
||||||
|
|
||||||
The SANE module provides an interface to the SANE scanner and frame
|
The SANE module provides an interface to the SANE scanner and frame
|
||||||
grabber interface for Linux. This module was contributed by Andrew
|
grabber interface for Linux. This module was contributed by Andrew
|
||||||
|
@ -9,11 +9,11 @@ word 'SANE' or 'sane' in the subject of your mail, otherwise it might
|
||||||
be classified as spam in the future.
|
be classified as spam in the future.
|
||||||
|
|
||||||
|
|
||||||
To build this module, type (in the Sane directory):
|
To build this module, type (in the Sane directory)::
|
||||||
|
|
||||||
python setup.py build
|
python setup.py build
|
||||||
|
|
||||||
In order to install the module type:
|
In order to install the module type::
|
||||||
|
|
||||||
python setup.py install
|
python setup.py install
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
-------
|
|
||||||
Scripts
|
Scripts
|
||||||
-------
|
=======
|
||||||
|
|
||||||
This directory contains a number of more or less trivial utilities
|
This directory contains a number of more or less trivial utilities
|
||||||
and demo programs.
|
and demo programs.
|
||||||
|
@ -9,50 +8,50 @@ Comments and contributions are welcome.
|
||||||
|
|
||||||
</F>
|
</F>
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
pildriver.py (by Eric S. Raymond)
|
pildriver.py (by Eric S. Raymond)
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
A class implementing an image-processing calculator for scripts.
|
A class implementing an image-processing calculator for scripts.
|
||||||
Parses lists of commnds (or, called interactively, command-line
|
Parses lists of commnds (or, called interactively, command-line
|
||||||
arguments) into image loads, transformations, and saves.
|
arguments) into image loads, transformations, and saves.
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
viewer.py
|
viewer.py
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
A simple image viewer. Can display all file formats handled by
|
A simple image viewer. Can display all file formats handled by
|
||||||
PIL. Transparent images are properly handled.
|
PIL. Transparent images are properly handled.
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
thresholder.py
|
thresholder.py
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
A simple utility that demonstrates how a transparent 1-bit overlay
|
A simple utility that demonstrates how a transparent 1-bit overlay
|
||||||
can be used to show the current thresholding of an 8-bit image.
|
can be used to show the current thresholding of an 8-bit image.
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
enhancer.py
|
enhancer.py
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
Illustrates the ImageEnhance module. Drag the sliders to modify the
|
Illustrates the ImageEnhance module. Drag the sliders to modify the
|
||||||
images. This might be very slow on some platforms, depending on the
|
images. This might be very slow on some platforms, depending on the
|
||||||
Tk version.
|
Tk version.
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
painter.py
|
painter.py
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
Illustrates how a painting program could be based on PIL and Tk.
|
Illustrates how a painting program could be based on PIL and Tk.
|
||||||
Press the left mouse button and drag over the image to remove the
|
Press the left mouse button and drag over the image to remove the
|
||||||
colour. Some clever tricks have been used to get decent performance
|
colour. Some clever tricks have been used to get decent performance
|
||||||
when updating the screen; see the sources for details.
|
when updating the screen; see the sources for details.
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
player.py
|
player.py
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
A simple image sequence player. You can use either a sequence format
|
A simple image sequence player. You can use either a sequence format
|
||||||
like FLI/FLC, GIF, or ARG, or give a number of images which are
|
like FLI/FLC, GIF, or ARG, or give a number of images which are
|
||||||
interpreted as frames in a sequence. All frames must have the same
|
interpreted as frames in a sequence. All frames must have the same
|
||||||
size.
|
size.
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
gifmaker.py
|
gifmaker.py
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
Convert a sequence file to a GIF animation.
|
Convert a sequence file to a GIF animation.
|
||||||
|
|
||||||
|
@ -60,20 +59,20 @@ Note that the GIF encoder provided with this release of PIL writes
|
||||||
uncompressed GIF files only, so the resulting animations are rather
|
uncompressed GIF files only, so the resulting animations are rather
|
||||||
large compared with these created by other tools.
|
large compared with these created by other tools.
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
explode.py
|
explode.py
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
Split a sequence file into individual frames.
|
Split a sequence file into individual frames.
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
image2py.py
|
image2py.py
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
Convert an image to a Python module containing an IMAGE variable.
|
Convert an image to a Python module containing an IMAGE variable.
|
||||||
Note that the module using the module must include JPEG and ZIP
|
Note that the module using the module must include JPEG and ZIP
|
||||||
decoders, unless the -u option is used.
|
decoders, unless the -u option is used.
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
olesummary.py
|
olesummary.py
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
Uses the OleFileIO module to dump the summary information from an OLE
|
Uses the OleFileIO module to dump the summary information from an OLE
|
||||||
structured storage file. This works with most OLE files, including
|
structured storage file. This works with most OLE files, including
|
16
Scripts/createfontdatachunk.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# create font data chunk for embedding
|
||||||
|
font = "Tests/images/courB08"
|
||||||
|
print(" f._load_pilfont_data(")
|
||||||
|
print(" # %s" % os.path.basename(font))
|
||||||
|
print(" BytesIO(base64.decodestring(b'''")
|
||||||
|
base64.encode(open(font + ".pil", "rb"), sys.stdout)
|
||||||
|
print("''')), Image.open(BytesIO(base64.decodestring(b'''")
|
||||||
|
base64.encode(open(font + ".pbm", "rb"), sys.stdout)
|
||||||
|
print("'''))))")
|
||||||
|
|
||||||
|
# End of file
|
7
Scripts/diffcover-install.sh
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
# Fetch the remote master branch before running diff-cover on Travis CI.
|
||||||
|
# https://github.com/edx/diff-cover#troubleshooting
|
||||||
|
git fetch origin master:refs/remotes/origin/master
|
||||||
|
|
||||||
|
# CFLAGS=-O0 means build with no optimisation.
|
||||||
|
# Makes build much quicker for lxml and other dependencies.
|
||||||
|
time CFLAGS=-O0 pip install --use-wheel diff_cover
|
4
Scripts/diffcover-run.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
coverage xml
|
||||||
|
diff-cover coverage.xml
|
||||||
|
diff-quality --violation=pyflakes
|
||||||
|
diff-quality --violation=pep8
|
|
@ -104,7 +104,7 @@ while True:
|
||||||
except EOFError:
|
except EOFError:
|
||||||
break
|
break
|
||||||
|
|
||||||
ix = ix + 1
|
ix += 1
|
||||||
|
|
||||||
if html:
|
if html:
|
||||||
html.write("</body>\n</html>\n")
|
html.write("</body>\n</html>\n")
|
||||||
|
|
|
@ -100,7 +100,7 @@ def makedelta(fp, sequence):
|
||||||
|
|
||||||
previous = im.copy()
|
previous = im.copy()
|
||||||
|
|
||||||
frames = frames + 1
|
frames += 1
|
||||||
|
|
||||||
fp.write(";")
|
fp.write(";")
|
||||||
|
|
||||||
|
|
|
@ -486,7 +486,7 @@ class PILDriver:
|
||||||
print("Stack: " + repr(self.stack))
|
print("Stack: " + repr(self.stack))
|
||||||
top = self.top()
|
top = self.top()
|
||||||
if not isinstance(top, str):
|
if not isinstance(top, str):
|
||||||
continue;
|
continue
|
||||||
funcname = "do_" + top
|
funcname = "do_" + top
|
||||||
if not hasattr(self, funcname):
|
if not hasattr(self, funcname):
|
||||||
continue
|
continue
|
||||||
|
@ -513,9 +513,9 @@ if __name__ == '__main__':
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if sys.version_info[0] >= 3:
|
if sys.version_info[0] >= 3:
|
||||||
line = input('pildriver> ');
|
line = input('pildriver> ')
|
||||||
else:
|
else:
|
||||||
line = raw_input('pildriver> ');
|
line = raw_input('pildriver> ')
|
||||||
except EOFError:
|
except EOFError:
|
||||||
print("\nPILDriver says goodbye.")
|
print("\nPILDriver says goodbye.")
|
||||||
break
|
break
|
||||||
|
|
|
@ -57,7 +57,7 @@ for o, a in opt:
|
||||||
elif o == "-v":
|
elif o == "-v":
|
||||||
verify = 1
|
verify = 1
|
||||||
elif o == "-D":
|
elif o == "-D":
|
||||||
Image.DEBUG = Image.DEBUG + 1
|
Image.DEBUG += 1
|
||||||
|
|
||||||
def globfix(files):
|
def globfix(files):
|
||||||
# expand wildcards where necessary
|
# expand wildcards where necessary
|
||||||
|
|
|
@ -18,25 +18,6 @@ import sys
|
||||||
Image.DEBUG = 0
|
Image.DEBUG = 0
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# experimental: support ARG animation scripts
|
|
||||||
|
|
||||||
import ArgImagePlugin
|
|
||||||
|
|
||||||
def applet_hook(animation, images):
|
|
||||||
app = animation(animation_display, images)
|
|
||||||
app.run()
|
|
||||||
|
|
||||||
ArgImagePlugin.APPLET_HOOK = applet_hook
|
|
||||||
|
|
||||||
class AppletDisplay:
|
|
||||||
def __init__(self, ui):
|
|
||||||
self.__ui = ui
|
|
||||||
def paste(self, im, bbox):
|
|
||||||
self.__ui.image.paste(im, bbox)
|
|
||||||
def update(self):
|
|
||||||
self.__ui.update_idletasks()
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# an image animation player
|
# an image animation player
|
||||||
|
|
||||||
|
@ -56,10 +37,6 @@ class UI(Label):
|
||||||
else:
|
else:
|
||||||
self.image = ImageTk.PhotoImage(im)
|
self.image = ImageTk.PhotoImage(im)
|
||||||
|
|
||||||
# APPLET SUPPORT (very crude, and not 100% safe)
|
|
||||||
global animation_display
|
|
||||||
animation_display = AppletDisplay(self)
|
|
||||||
|
|
||||||
Label.__init__(self, master, image=self.image, bg="black", bd=0)
|
Label.__init__(self, master, image=self.image, bg="black", bd=0)
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
10
Tests/32bit_segfault_check.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
if sys.maxsize < 2**32:
|
||||||
|
im = Image.new('L', (999999, 999999), 0)
|
||||||
|
|
||||||
|
|
45
Tests/README.rst
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
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
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Install::
|
||||||
|
|
||||||
|
pip install coverage nose
|
||||||
|
|
||||||
|
|
||||||
|
Execution
|
||||||
|
---------
|
||||||
|
|
||||||
|
**If Pillow has been built in-place**
|
||||||
|
|
||||||
|
To run an individual test::
|
||||||
|
|
||||||
|
python Tests/test_image.py
|
||||||
|
|
||||||
|
Run all the tests from the root of the Pillow source distribution::
|
||||||
|
|
||||||
|
nosetests -vx Tests/test_*.py
|
||||||
|
|
||||||
|
Or with coverage::
|
||||||
|
|
||||||
|
coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
|
||||||
|
coverage report
|
||||||
|
coverage html
|
||||||
|
open htmlcov/index.html
|
||||||
|
|
||||||
|
**If Pillow has been installed**
|
||||||
|
|
||||||
|
To run an individual test::
|
||||||
|
|
||||||
|
./test-installed.py Tests/test_image.py
|
||||||
|
|
||||||
|
Run all the tests from the root of the Pillow source distribution::
|
||||||
|
|
||||||
|
./test-installed.py
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
Minimalistic PIL test framework.
|
|
||||||
|
|
||||||
Test scripts are named "test_xxx" and are supposed to output "ok". That's it. To run the tests::
|
|
||||||
|
|
||||||
python setup.py develop
|
|
||||||
|
|
||||||
Run the tests from the root of the Pillow source distribution:
|
|
||||||
|
|
||||||
python selftest.py
|
|
||||||
python Tests/run.py --installed
|
|
||||||
|
|
||||||
To run an individual test:
|
|
||||||
|
|
||||||
python Tests/test_image.py
|
|
|
@ -1,44 +1,51 @@
|
||||||
from tester import *
|
from helper import *
|
||||||
|
|
||||||
# not running this test by default. No DOS against travis.
|
# Not running this test by default. No DOS against Travis CI.
|
||||||
|
|
||||||
from PIL import PyAccess
|
from PIL import PyAccess
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
def iterate_get(size, access):
|
def iterate_get(size, access):
|
||||||
(w, h) = size
|
(w, h) = size
|
||||||
for x in range(w):
|
for x in range(w):
|
||||||
for y in range(h):
|
for y in range(h):
|
||||||
access[(x, y)]
|
access[(x, y)]
|
||||||
|
|
||||||
|
|
||||||
def iterate_set(size, access):
|
def iterate_set(size, access):
|
||||||
(w, h) = size
|
(w, h) = size
|
||||||
for x in range(w):
|
for x in range(w):
|
||||||
for y in range(h):
|
for y in range(h):
|
||||||
access[(x, y)] = (x % 256, y % 256, 0)
|
access[(x, y)] = (x % 256, y % 256, 0)
|
||||||
|
|
||||||
|
|
||||||
def timer(func, label, *args):
|
def timer(func, label, *args):
|
||||||
iterations = 5000
|
iterations = 5000
|
||||||
starttime = time.time()
|
starttime = time.time()
|
||||||
for x in range(iterations):
|
for x in range(iterations):
|
||||||
func(*args)
|
func(*args)
|
||||||
if time.time()-starttime > 10:
|
if time.time()-starttime > 10:
|
||||||
print ("%s: breaking at %s iterations, %.6f per iteration"%(label, x+1, (time.time()-starttime)/(x+1.0)))
|
print("%s: breaking at %s iterations, %.6f per iteration" % (
|
||||||
|
label, x+1, (time.time()-starttime)/(x+1.0)))
|
||||||
break
|
break
|
||||||
if x == iterations-1:
|
if x == iterations-1:
|
||||||
endtime = time.time()
|
endtime = time.time()
|
||||||
print ("%s: %.4f s %.6f per iteration" %(label, endtime-starttime, (endtime-starttime)/(x+1.0)))
|
print("%s: %.4f s %.6f per iteration" % (
|
||||||
|
label, endtime-starttime, (endtime-starttime)/(x+1.0)))
|
||||||
|
|
||||||
def test_direct():
|
|
||||||
|
class BenchCffiAccess(PillowTestCase):
|
||||||
|
|
||||||
|
def test_direct(self):
|
||||||
im = lena()
|
im = lena()
|
||||||
im.load()
|
im.load()
|
||||||
# im = Image.new( "RGB", (2000, 2000), (1, 3, 2))
|
# im = Image.new( "RGB", (2000, 2000), (1, 3, 2))
|
||||||
caccess = im.im.pixel_access(False)
|
caccess = im.im.pixel_access(False)
|
||||||
access = PyAccess.new(im, False)
|
access = PyAccess.new(im, False)
|
||||||
|
|
||||||
assert_equal(caccess[(0,0)], access[(0,0)])
|
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_get, 'PyAccess - get', im.size, access)
|
||||||
|
@ -47,5 +54,7 @@ def test_direct():
|
||||||
timer(iterate_set, 'C-api - set', im.size, caccess)
|
timer(iterate_set, 'C-api - set', im.size, caccess)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0, ".")
|
sys.path.insert(0, ".")
|
||||||
|
|
||||||
import tester
|
import helper
|
||||||
import timeit
|
import timeit
|
||||||
|
|
||||||
|
|
||||||
def bench(mode):
|
def bench(mode):
|
||||||
im = tester.lena(mode)
|
im = helper.lena(mode)
|
||||||
get = im.im.getpixel
|
get = im.im.getpixel
|
||||||
xy = 50, 50 # position shouldn't really matter
|
xy = 50, 50 # position shouldn't really matter
|
||||||
t0 = timeit.default_timer()
|
t0 = timeit.default_timer()
|
||||||
|
|
10
Tests/check_icns_dos.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Tests potential DOS of IcnsImagePlugin with 0 length block.
|
||||||
|
# Run from anywhere that PIL is importable.
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
if bytes is str:
|
||||||
|
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00')))
|
||||||
|
else:
|
||||||
|
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00', 'latin-1')))
|
11
Tests/check_j2k_dos.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Tests potential DOS of Jpeg2kImagePlugin with 0 length block.
|
||||||
|
# Run from anywhere that PIL is importable.
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
if bytes is str:
|
||||||
|
Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang')))
|
||||||
|
else:
|
||||||
|
Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang', 'latin-1')))
|
||||||
|
|
|
@ -1,222 +0,0 @@
|
||||||
# PyCMSTests.py
|
|
||||||
# Examples of how to use pyCMS, as well as tests to verify it works properly
|
|
||||||
# By Kevin Cazabon (kevin@cazabon.com)
|
|
||||||
|
|
||||||
# Imports
|
|
||||||
import os
|
|
||||||
from PIL import Image
|
|
||||||
from PIL import ImageCms
|
|
||||||
|
|
||||||
# import PyCMSError separately so we can catch it
|
|
||||||
PyCMSError = ImageCms.PyCMSError
|
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
# Configuration:
|
|
||||||
#######################################################################
|
|
||||||
# set this to the image you want to test with
|
|
||||||
IMAGE = "c:\\temp\\test.tif"
|
|
||||||
|
|
||||||
# set this to where you want to save the output images
|
|
||||||
OUTPUTDIR = "c:\\temp\\"
|
|
||||||
|
|
||||||
# set these to two different ICC profiles, one for input, one for output
|
|
||||||
# set the corresponding mode to the proper PIL mode for that profile
|
|
||||||
INPUT_PROFILE = "c:\\temp\\profiles\\sRGB.icm"
|
|
||||||
INMODE = "RGB"
|
|
||||||
|
|
||||||
OUTPUT_PROFILE = "c:\\temp\\profiles\\genericRGB.icm"
|
|
||||||
OUTMODE = "RGB"
|
|
||||||
|
|
||||||
PROOF_PROFILE = "c:\\temp\\profiles\\monitor.icm"
|
|
||||||
|
|
||||||
# set to True to show() images, False to save them into OUTPUT_DIRECTORY
|
|
||||||
SHOW = False
|
|
||||||
|
|
||||||
# Tests you can enable/disable
|
|
||||||
TEST_error_catching = True
|
|
||||||
TEST_profileToProfile = True
|
|
||||||
TEST_profileToProfile_inPlace = True
|
|
||||||
TEST_buildTransform = True
|
|
||||||
TEST_buildTransformFromOpenProfiles = True
|
|
||||||
TEST_buildProofTransform = True
|
|
||||||
TEST_getProfileInfo = True
|
|
||||||
TEST_misc = False
|
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
# helper functions
|
|
||||||
#######################################################################
|
|
||||||
def outputImage(im, funcName = None):
|
|
||||||
# save or display the image, depending on value of SHOW_IMAGES
|
|
||||||
if SHOW:
|
|
||||||
im.show()
|
|
||||||
else:
|
|
||||||
im.save(os.path.join(OUTPUTDIR, "%s.tif" %funcName))
|
|
||||||
|
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
# The tests themselves
|
|
||||||
#######################################################################
|
|
||||||
|
|
||||||
if TEST_error_catching:
|
|
||||||
im = Image.open(IMAGE)
|
|
||||||
try:
|
|
||||||
#neither of these proifles exists (unless you make them), so we should
|
|
||||||
# get an error
|
|
||||||
imOut = ImageCms.profileToProfile(im, "missingProfile.icm", "cmyk.icm")
|
|
||||||
|
|
||||||
except PyCMSError as reason:
|
|
||||||
print("We caught a PyCMSError: %s\n\n" %reason)
|
|
||||||
|
|
||||||
print("error catching test completed successfully (if you see the message \
|
|
||||||
above that we caught the error).")
|
|
||||||
|
|
||||||
if TEST_profileToProfile:
|
|
||||||
# open the image file using the standard PIL function Image.open()
|
|
||||||
im = Image.open(IMAGE)
|
|
||||||
|
|
||||||
# send the image, input/output profiles, and rendering intent to
|
|
||||||
# ImageCms.profileToProfile()
|
|
||||||
imOut = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \
|
|
||||||
outputMode = OUTMODE)
|
|
||||||
|
|
||||||
# now that the image is converted, save or display it
|
|
||||||
outputImage(imOut, "profileToProfile")
|
|
||||||
|
|
||||||
print("profileToProfile test completed successfully.")
|
|
||||||
|
|
||||||
if TEST_profileToProfile_inPlace:
|
|
||||||
# we'll do the same test as profileToProfile, but modify im in place
|
|
||||||
# instead of getting a new image returned to us
|
|
||||||
im = Image.open(IMAGE)
|
|
||||||
|
|
||||||
# send the image to ImageCms.profileToProfile(), specifying inPlace = True
|
|
||||||
result = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \
|
|
||||||
outputMode = OUTMODE, inPlace = True)
|
|
||||||
|
|
||||||
# now that the image is converted, save or display it
|
|
||||||
if result is None:
|
|
||||||
# this is the normal result when modifying in-place
|
|
||||||
outputImage(im, "profileToProfile_inPlace")
|
|
||||||
else:
|
|
||||||
# something failed...
|
|
||||||
print("profileToProfile in-place failed: %s" %result)
|
|
||||||
|
|
||||||
print("profileToProfile in-place test completed successfully.")
|
|
||||||
|
|
||||||
if TEST_buildTransform:
|
|
||||||
# make a transform using the input and output profile path strings
|
|
||||||
transform = ImageCms.buildTransform(INPUT_PROFILE, OUTPUT_PROFILE, INMODE, \
|
|
||||||
OUTMODE)
|
|
||||||
|
|
||||||
# now, use the trnsform to convert a couple images
|
|
||||||
im = Image.open(IMAGE)
|
|
||||||
|
|
||||||
# transform im normally
|
|
||||||
im2 = ImageCms.applyTransform(im, transform)
|
|
||||||
outputImage(im2, "buildTransform")
|
|
||||||
|
|
||||||
# then transform it again using the same transform, this time in-place.
|
|
||||||
result = ImageCms.applyTransform(im, transform, inPlace = True)
|
|
||||||
outputImage(im, "buildTransform_inPlace")
|
|
||||||
|
|
||||||
print("buildTransform test completed successfully.")
|
|
||||||
|
|
||||||
# and, to clean up a bit, delete the transform
|
|
||||||
# this should call the C destructor for the transform structure.
|
|
||||||
# Python should also do this automatically when it goes out of scope.
|
|
||||||
del(transform)
|
|
||||||
|
|
||||||
if TEST_buildTransformFromOpenProfiles:
|
|
||||||
# we'll actually test a couple profile open/creation functions here too
|
|
||||||
|
|
||||||
# first, get a handle to an input profile, in this case we'll create
|
|
||||||
# an sRGB profile on the fly:
|
|
||||||
inputProfile = ImageCms.createProfile("sRGB")
|
|
||||||
|
|
||||||
# then, get a handle to the output profile
|
|
||||||
outputProfile = ImageCms.getOpenProfile(OUTPUT_PROFILE)
|
|
||||||
|
|
||||||
# make a transform from these
|
|
||||||
transform = ImageCms.buildTransformFromOpenProfiles(inputProfile, \
|
|
||||||
outputProfile, INMODE, OUTMODE)
|
|
||||||
|
|
||||||
# now, use the trnsform to convert a couple images
|
|
||||||
im = Image.open(IMAGE)
|
|
||||||
|
|
||||||
# transform im normally
|
|
||||||
im2 = ImageCms.applyTransform(im, transform)
|
|
||||||
outputImage(im2, "buildTransformFromOpenProfiles")
|
|
||||||
|
|
||||||
# then do it again using the same transform, this time in-place.
|
|
||||||
result = ImageCms.applyTransform(im, transform, inPlace = True)
|
|
||||||
outputImage(im, "buildTransformFromOpenProfiles_inPlace")
|
|
||||||
|
|
||||||
print("buildTransformFromOpenProfiles test completed successfully.")
|
|
||||||
|
|
||||||
# and, to clean up a bit, delete the transform
|
|
||||||
# this should call the C destructor for the each item.
|
|
||||||
# Python should also do this automatically when it goes out of scope.
|
|
||||||
del(inputProfile)
|
|
||||||
del(outputProfile)
|
|
||||||
del(transform)
|
|
||||||
|
|
||||||
if TEST_buildProofTransform:
|
|
||||||
# make a transform using the input and output and proof profile path
|
|
||||||
# strings
|
|
||||||
# images converted with this transform will simulate the appearance
|
|
||||||
# of the output device while actually being displayed/proofed on the
|
|
||||||
# proof device. This usually means a monitor, but can also mean
|
|
||||||
# other proof-printers like dye-sub, etc.
|
|
||||||
transform = ImageCms.buildProofTransform(INPUT_PROFILE, OUTPUT_PROFILE, \
|
|
||||||
PROOF_PROFILE, INMODE, OUTMODE)
|
|
||||||
|
|
||||||
# now, use the trnsform to convert a couple images
|
|
||||||
im = Image.open(IMAGE)
|
|
||||||
|
|
||||||
# transform im normally
|
|
||||||
im2 = ImageCms.applyTransform(im, transform)
|
|
||||||
outputImage(im2, "buildProofTransform")
|
|
||||||
|
|
||||||
# then transform it again using the same transform, this time in-place.
|
|
||||||
result = ImageCms.applyTransform(im, transform, inPlace = True)
|
|
||||||
outputImage(im, "buildProofTransform_inPlace")
|
|
||||||
|
|
||||||
print("buildProofTransform test completed successfully.")
|
|
||||||
|
|
||||||
# and, to clean up a bit, delete the transform
|
|
||||||
# this should call the C destructor for the transform structure.
|
|
||||||
# Python should also do this automatically when it goes out of scope.
|
|
||||||
del(transform)
|
|
||||||
|
|
||||||
if TEST_getProfileInfo:
|
|
||||||
# get a profile handle
|
|
||||||
profile = ImageCms.getOpenProfile(INPUT_PROFILE)
|
|
||||||
|
|
||||||
# lets print some info about our input profile:
|
|
||||||
print("Profile name (retrieved from profile string path name): %s" %ImageCms.getProfileName(INPUT_PROFILE))
|
|
||||||
|
|
||||||
# or, you could do the same thing using a profile handle as the arg
|
|
||||||
print("Profile name (retrieved from profile handle): %s" %ImageCms.getProfileName(profile))
|
|
||||||
|
|
||||||
# now lets get the embedded "info" tag contents
|
|
||||||
# once again, you can use a path to a profile, or a profile handle
|
|
||||||
print("Profile info (retrieved from profile handle): %s" %ImageCms.getProfileInfo(profile))
|
|
||||||
|
|
||||||
# and what's the default intent of this profile?
|
|
||||||
print("The default intent is (this will be an integer): %d" %(ImageCms.getDefaultIntent(profile)))
|
|
||||||
|
|
||||||
# Hmmmm... but does this profile support INTENT_ABSOLUTE_COLORIMETRIC?
|
|
||||||
print("Does it support INTENT_ABSOLUTE_COLORIMETRIC?: (1 is yes, -1 is no): %s" \
|
|
||||||
%ImageCms.isIntentSupported(profile, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, \
|
|
||||||
ImageCms.DIRECTION_INPUT))
|
|
||||||
|
|
||||||
print("getProfileInfo test completed successfully.")
|
|
||||||
|
|
||||||
if TEST_misc:
|
|
||||||
# test the versions, about, and copyright functions
|
|
||||||
print("Versions: %s" %str(ImageCms.versions()))
|
|
||||||
print("About:\n\n%s" %ImageCms.about())
|
|
||||||
print("Copyright:\n\n%s" %ImageCms.copyright())
|
|
||||||
|
|
||||||
print("misc test completed successfully.")
|
|
||||||
|
|
234
Tests/helper.py
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
"""
|
||||||
|
Helper functions.
|
||||||
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
if sys.version_info[:2] <= (2, 6):
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class PillowTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
unittest.TestCase.__init__(self, *args, **kwargs)
|
||||||
|
# holds last result object passed to run method:
|
||||||
|
self.currentResult = None
|
||||||
|
|
||||||
|
# Nicer output for --verbose
|
||||||
|
def __str__(self):
|
||||||
|
return self.__class__.__name__ + "." + self._testMethodName
|
||||||
|
|
||||||
|
def run(self, result=None):
|
||||||
|
self.currentResult = result # remember result for use later
|
||||||
|
unittest.TestCase.run(self, result) # call superclass run method
|
||||||
|
|
||||||
|
def delete_tempfile(self, path):
|
||||||
|
try:
|
||||||
|
ok = self.currentResult.wasSuccessful()
|
||||||
|
except AttributeError: # for nosetests
|
||||||
|
proxy = self.currentResult
|
||||||
|
ok = (len(proxy.errors) + len(proxy.failures) == 0)
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
# only clean out tempfiles if test passed
|
||||||
|
try:
|
||||||
|
os.remove(path)
|
||||||
|
except OSError:
|
||||||
|
pass # report?
|
||||||
|
else:
|
||||||
|
print("=== orphaned temp file: %s" % path)
|
||||||
|
|
||||||
|
def assert_almost_equal(self, a, b, msg=None, eps=1e-6):
|
||||||
|
self.assertLess(
|
||||||
|
abs(a-b), eps,
|
||||||
|
msg or "got %r, expected %r" % (a, b))
|
||||||
|
|
||||||
|
def assert_deep_equal(self, a, b, msg=None):
|
||||||
|
try:
|
||||||
|
self.assertEqual(
|
||||||
|
len(a), len(b),
|
||||||
|
msg or "got length %s, expected %s" % (len(a), len(b)))
|
||||||
|
self.assertTrue(
|
||||||
|
all([x == y for x, y in zip(a, b)]),
|
||||||
|
msg or "got %s, expected %s" % (a, b))
|
||||||
|
except:
|
||||||
|
self.assertEqual(a, b, msg)
|
||||||
|
|
||||||
|
def assert_image(self, im, mode, size, msg=None):
|
||||||
|
if mode is not None:
|
||||||
|
self.assertEqual(
|
||||||
|
im.mode, mode,
|
||||||
|
msg or "got mode %r, expected %r" % (im.mode, mode))
|
||||||
|
|
||||||
|
if size is not None:
|
||||||
|
self.assertEqual(
|
||||||
|
im.size, size,
|
||||||
|
msg or "got size %r, expected %r" % (im.size, size))
|
||||||
|
|
||||||
|
def assert_image_equal(self, a, b, msg=None):
|
||||||
|
self.assertEqual(
|
||||||
|
a.mode, b.mode,
|
||||||
|
msg or "got mode %r, expected %r" % (a.mode, b.mode))
|
||||||
|
self.assertEqual(
|
||||||
|
a.size, b.size,
|
||||||
|
msg or "got size %r, expected %r" % (a.size, b.size))
|
||||||
|
if a.tobytes() != b.tobytes():
|
||||||
|
self.fail(msg or "got different content")
|
||||||
|
|
||||||
|
def assert_image_similar(self, a, b, epsilon, msg=None):
|
||||||
|
epsilon = float(epsilon)
|
||||||
|
self.assertEqual(
|
||||||
|
a.mode, b.mode,
|
||||||
|
msg or "got mode %r, expected %r" % (a.mode, b.mode))
|
||||||
|
self.assertEqual(
|
||||||
|
a.size, b.size,
|
||||||
|
msg or "got size %r, expected %r" % (a.size, b.size))
|
||||||
|
|
||||||
|
diff = 0
|
||||||
|
try:
|
||||||
|
ord(b'0')
|
||||||
|
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
|
||||||
|
diff += abs(ord(abyte)-ord(bbyte))
|
||||||
|
except:
|
||||||
|
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
|
||||||
|
diff += abs(abyte-bbyte)
|
||||||
|
ave_diff = float(diff)/(a.size[0]*a.size[1])
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
epsilon, ave_diff,
|
||||||
|
(msg or '') + " average pixel value difference %.4f > epsilon %.4f" % (
|
||||||
|
ave_diff, epsilon))
|
||||||
|
|
||||||
|
def assert_warning(self, warn_class, func):
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
result = None
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
|
||||||
|
# Hopefully trigger a warning.
|
||||||
|
result = func()
|
||||||
|
|
||||||
|
# Verify some things.
|
||||||
|
self.assertGreaterEqual(len(w), 1)
|
||||||
|
found = False
|
||||||
|
for v in w:
|
||||||
|
if issubclass(v.category, warn_class):
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
self.assertTrue(found)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def skipKnownBadTest(self, msg=None, platform=None,
|
||||||
|
travis=None, interpreter=None):
|
||||||
|
# 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))
|
||||||
|
return
|
||||||
|
|
||||||
|
skip = True
|
||||||
|
if platform is not None:
|
||||||
|
skip = sys.platform.startswith(platform)
|
||||||
|
if travis is not None:
|
||||||
|
skip = skip and (travis == bool(os.environ.get('TRAVIS', False)))
|
||||||
|
if interpreter is not None:
|
||||||
|
skip = skip and (interpreter == 'pypy' and hasattr(sys, 'pypy_version_info'))
|
||||||
|
if skip:
|
||||||
|
self.skipTest(msg or "Known Bad Test")
|
||||||
|
|
||||||
|
def tempfile(self, template):
|
||||||
|
assert template[:5] in ("temp.", "temp_")
|
||||||
|
(fd, path) = tempfile.mkstemp(template[4:], template[:4])
|
||||||
|
os.close(fd)
|
||||||
|
|
||||||
|
self.addCleanup(self.delete_tempfile, path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def open_withImagemagick(self, f):
|
||||||
|
if not imagemagick_available():
|
||||||
|
raise IOError()
|
||||||
|
|
||||||
|
outfile = self.tempfile("temp.png")
|
||||||
|
if command_succeeds(['convert', f, outfile]):
|
||||||
|
from PIL import Image
|
||||||
|
return Image.open(outfile)
|
||||||
|
raise IOError()
|
||||||
|
|
||||||
|
|
||||||
|
# helpers
|
||||||
|
|
||||||
|
import sys
|
||||||
|
py3 = (sys.version_info >= (3, 0))
|
||||||
|
|
||||||
|
|
||||||
|
def fromstring(data):
|
||||||
|
from io import BytesIO
|
||||||
|
from PIL import Image
|
||||||
|
return Image.open(BytesIO(data))
|
||||||
|
|
||||||
|
|
||||||
|
def tostring(im, format, **options):
|
||||||
|
from io import BytesIO
|
||||||
|
out = BytesIO()
|
||||||
|
im.save(out, format, **options)
|
||||||
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def lena(mode="RGB", 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
|
||||||
|
# (for fast, isolated, repeatable tests).
|
||||||
|
# im = cache.get(mode)
|
||||||
|
if im is None:
|
||||||
|
if mode == "RGB":
|
||||||
|
im = Image.open("Tests/images/lena.ppm")
|
||||||
|
elif mode == "F":
|
||||||
|
im = lena("L").convert(mode)
|
||||||
|
elif mode[:4] == "I;16":
|
||||||
|
im = lena("I").convert(mode)
|
||||||
|
else:
|
||||||
|
im = lena("RGB").convert(mode)
|
||||||
|
# cache[mode] = im
|
||||||
|
return im
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
subprocess.Popen(cmd, stdout=f, stderr=subprocess.STDOUT).wait()
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def djpeg_available():
|
||||||
|
return command_succeeds(['djpeg', '--help'])
|
||||||
|
|
||||||
|
|
||||||
|
def cjpeg_available():
|
||||||
|
return command_succeeds(['cjpeg', '--help'])
|
||||||
|
|
||||||
|
|
||||||
|
def netpbm_available():
|
||||||
|
return (command_succeeds(["ppmquant", "--help"]) and
|
||||||
|
command_succeeds(["ppmtogif", "--help"]))
|
||||||
|
|
||||||
|
|
||||||
|
def imagemagick_available():
|
||||||
|
return command_succeeds(['convert', '-version'])
|
||||||
|
|
||||||
|
# End of file
|
BIN
Tests/images/16bit.cropped.j2k
Normal file
BIN
Tests/images/16bit.cropped.jp2
Normal file
BIN
Tests/images/corner.lut
Normal file
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
Tests/images/deerstalker.cur
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
Tests/images/default_font.png
Normal file
After Width: | Height: | Size: 586 B |
BIN
Tests/images/dilation4.lut
Normal file
BIN
Tests/images/dilation8.lut
Normal file
BIN
Tests/images/dispose_bgnd.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/dispose_none.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/dispose_prev.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/edge.lut
Normal file
BIN
Tests/images/erosion4.lut
Normal file
BIN
Tests/images/erosion8.lut
Normal file
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
BIN
Tests/images/frozenpond.mpo
Normal file
After Width: | Height: | Size: 162 KiB |
BIN
Tests/images/imagedraw/line_horizontal_slope1px_w2px.png
Normal file
After Width: | Height: | Size: 147 B |
BIN
Tests/images/imagedraw/line_horizontal_w101px.png
Normal file
After Width: | Height: | Size: 368 B |
BIN
Tests/images/imagedraw/line_horizontal_w2px_inverted.png
Normal file
After Width: | Height: | Size: 143 B |
BIN
Tests/images/imagedraw/line_horizontal_w2px_normal.png
Normal file
After Width: | Height: | Size: 141 B |