merge a year of master into winbuild

This commit is contained in:
wiredfool 2015-06-10 12:49:07 -07:00
commit fd55099ffc
369 changed files with 8635 additions and 6573 deletions

2
.landscape.yaml Normal file
View File

@ -0,0 +1,2 @@
strictness: medium
test-warnings: yes

View File

@ -3,25 +3,26 @@ language: python
notifications: notifications:
irc: "chat.freenode.net#pil" irc: "chat.freenode.net#pil"
env: MAX_CONCURRENCY=4 # Run slow PyPy* first, to give them a headstart and reduce waiting time.
# Run latest 3.x and 2.x next, to get quick compatibility results.
# Then run the remainder.
python: python:
- "pypy" - "pypy"
- "pypy3" - "pypy3"
- 2.6 - 3.4
- 2.7 - 2.7
- 2.6
- "2.7_with_system_site_packages" # For PyQt4 - "2.7_with_system_site_packages" # For PyQt4
- 3.2 - 3.2
- 3.3 - 3.3
- 3.4
install: install:
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick lcov" - "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
- "pip install cffi" - "travis_retry pip install cffi"
- "pip install coveralls nose coveralls-merge" - "travis_retry pip install coverage nose"
- "gem install coveralls-lcov" - "travis_retry pip install pyroma"
- travis_retry pip install pyroma
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
# webp # webp
- pushd depends && ./install_webp.sh && popd - pushd depends && ./install_webp.sh && popd
@ -39,24 +40,72 @@ script:
after_success: after_success:
# gather the coverage data # gather the coverage data
- travis_retry sudo apt-get -qq install lcov
- lcov --capture --directory . -b . --output-file coverage.info - lcov --capture --directory . -b . --output-file coverage.info
# filter to remove system headers # filter to remove system headers
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info - lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
# convert to json # convert to json
- travis_retry gem install coveralls-lcov
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json - coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
- coverage report - coverage report
- travis_retry pip install coveralls-merge
- coveralls-merge coverage.c.json - coveralls-merge coverage.c.json
- travis_retry pip install pep8 pyflakes
- pip install pep8 pyflakes
- pep8 --statistics --count PIL/*.py - pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py - pep8 --statistics --count Tests/*.py
- pyflakes *.py | tee >(wc -l)
- pyflakes PIL/*.py | tee >(wc -l) - pyflakes PIL/*.py | tee >(wc -l)
- pyflakes Tests/*.py | tee >(wc -l) - pyflakes Tests/*.py | tee >(wc -l)
# Coverage and quality reports on just the latest diff. # Coverage and quality reports on just the latest diff.
# (Installation is very slow on Py3, so just do it for Py2.) # (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-install.sh; fi
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi
# after_all
- |
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
python travis_after_all.py
export $(cat .to_export_back)
if [ "$BUILD_LEADER" = "YES" ]; then
if [ "$BUILD_AGGREGATE_STATUS" = "others_succeeded" ]; then
echo "All jobs succeded! Triggering OS X build..."
# Trigger an OS X build at the pillow-wheels repo
./build_children.sh
else
echo "Some jobs failed"
fi
fi
fi
after_failure:
- |
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
python travis_after_all.py
export $(cat .to_export_back)
if [ "$BUILD_LEADER" = "YES" ]; then
if [ "$BUILD_AGGREGATE_STATUS" = "others_failed" ]; then
echo "All jobs failed"
else
echo "Some jobs failed"
fi
fi
fi
after_script:
- |
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS
fi
matrix:
fast_finish: true
env:
global:
# travis encrypt AUTH_TOKEN=
secure: "Vzm7aG1Qv0SDQcqiPzZMedNLn5ZmpL7IzF0DYnqcD+/l+zmKU22SnJBcX0uVXumo+r7eZfpsShpqfcdsZvMlvmQnwz+Y6AGKQru9tCKZbTMnuRjWKKXekC+tr8Xt9CKvRVtte5PyXW31paxUI3/e+fQGBwoFjEEC+6EpEOjeRfE="

View File

@ -1,10 +1,283 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
2.6.0 (unreleased) 2.9.0 (Unreleased)
------------------ ------------------
- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport) - Provide n_frames attribute to multi-frame formats #1261
[anntzer, radarhere]
- Add duration and loop set to GifImagePlugin #1172, #1269
[radarhere]
- Ico files are little endian #1232
[wiredfool]
- Upgrade olefile from 0.30 to 0.42b #1226
[radarhere, decalage2]
- Setting transparency value to 0 when the tRNS contains only null byte(s) #1239
[juztin]
- Separated out feature checking from selftest #1233
[radarhere]
- Style/health fixes
[radarhere]
- Update WebP from 0.4.1 to 0.4.3 #1235
[radarhere]
- Release GIL during image load (decode) #1224
[lkesteloot]
- Added icns save #1185
[radarhere]
- Fix putdata memory leak #1196
[benoit-pierre]
- Keep user-specified ordering of icon sizes #1193
[karimbahgat]
- Tiff: allow writing floating point tag values #1113
[bpedersen2]
2.8.2 (2015-06-06)
------------------
- Bug fix: Fixed Tiff handling of bad EXIF data
[radarhere]
2.8.1 (2015-04-02)
------------------
- Bug fix: Catch struct.error on invalid JPEG, fixes #1163
[wiredfool, hugovk]
2.8.0 (2015-04-01)
------------------
- Fix 32-bit BMP loading (RGBA or RGBX)
[artscoop]
- Fix UnboundLocalError in ImageFile #1131
[davarisg]
- Re-enable test image caching
[hugovk, homm]
- Fix: Cannot identify EPS images, fixes #1104
[hugovk]
- Configure setuptools to run nosetests, fixes #729
[aclark4life]
- Style/health fixes
[radarhere, hugovk]
- Add support for HTTP response objects to Image.open()
[mfitzp]
- Improve reference docs for PIL.ImageDraw.Draw.pieslice() #1145
[audreyr]
- Added copy method font_variant() and accessible properties to truetype() #1123
[radarhere]
- Fix ImagingEffectNoise #1128
[hugovk]
- Remove unreachable code
[hugovk]
- Let Python do the endian stuff + tests #1121
[amoibos, radarhere]
- Fix webp decode memory leak #1114
[benoit-pierre]
- Fast path for opaque pixels in RGBa unpacker #1088
[bgilbert]
- Enable basic support for 'RGBa' raw encoding/decoding #1096
[immerrr]
- Fix pickling L mode images with no palette, #1095
[hugovk]
- iPython display hook #1091
[wiredfool]
- Adjust buffer size when quality=keep, fixes #148 (again)
[wiredfool]
- Fix for corrupted bitmaps embedded in truetype fonts. #1072
[jackyyf, wiredfool]
2.7.0 (2015-01-01)
------------------
- Split Sane into a separate repo: https://github.com/python-pillow/Sane
[hugovk]
- Look for OSX and Linux fonts in common places. #1054
[charleslaw]
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
[wiredfool]
- Use underscores, not spaces, in TIFF tag kwargs. #1044, #1058
[anntzer, hugovk]
- Update PSDraw for Python3, add tests. #1055
[hugovk]
- Use Bicubic filtering by default for thumbnails. Don't use Jpeg Draft mode for thumbnails. #1029
[homm]
- Fix MSVC compiler error: Use Py_ssize_t instead of ssize_t #1051
[cgohlke]
- Fix compiler error: MSVC needs variables defined at the start of the block #1048
[cgohlke]
- The GIF Palette optimization algorithm is only applicable to mode='P' or 'L' #993
[moriyoshi]
- Use PySide as an alternative to PyQt4/5.
[holg]
- Replace affine-based im.resize implementation with convolution-based im.stretch #997
[homm]
- Replace Gaussian Blur implementation with iterated fast box blur. #961 Note: Radius parameter is interpreted differently than before.
[homm]
- Better docs explaining import _imaging failure #1016, build #1017, mode #1018, PyAccess, PixelAccess objects #1019 Image.quantize #1020 and Image.save #1021
[wiredfool]
- Fix for saving TIFF image into an io.BytesIO buffer #1011
[mfergie]
- Fix antialias compilation on debug versions of Python #1010
[wiredfool]
- Fix for Image.putdata segfault #1009
[wiredfool]
- Ico save, additional tests #1007
[exherb]
- Use PyQt4 if it has already been imported, otherwise prefer PyQt5. #1003
[AurelienBallier]
- Speedup resample implementation up to 2.5 times. #977
[homm]
- Speed up rotation by using cache aware loops, added transpose to rotations. #994
[homm]
- Fix Bicubic interpolation #970
[homm]
- Support for 4-bit greyscale TIFF images #980
[hugovk]
- Updated manifest #957
[wiredfool]
- Fix PyPy 2.4 regression #952
[wiredfool]
- Webp Metadata Skip Test comments #954
[wiredfool]
- Fixes for things rpmlint complains about #942
[manisandro]
2.6.2 (2015-01-01)
------------------
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
[wiredfool]
- Fix Regression in PyPy 2.4 in streamio #958
[wiredfool]
2.6.1 (2014-10-11)
------------------
- Fix SciPy regression in Image.resize #945
[wiredfool]
- Fix manifest to include all test files.
[aclark4life]
2.6.0 (2014-10-01)
------------------
- Relax precision of ImageDraw tests for x86, GimpGradient for PPC
[wiredfool]
2.6.0-rc1 (2014-09-29)
----------------------
- Use redistributable image for testing #884
[hugovk]
- Use redistributable ICC profiles for testing, skip if not available #923
[wiredfool]
- Additional documentation for JPEG info and save options #890
[wiredfool]
- Fix JPEG Encoding memory leak when exif or qtables were specified
[wiredfool]
- Image.tobytes() and Image.tostring() documentation update #916 #917
[mgedmin]
- On Windows, do not execute convert.exe without specifying path #912
[cgohlke]
- Fix msvc build error #911
[cgohlke]
- Fix for handling P + transparency -> RGBA conversions #904
[wiredfool]
- Retain alpha in ImageEnhance operations #909
[wiredfool]
- Jpeg2k Decode/encode memory leak fix #898
[joshware, wiredfool]
- EpsFilePlugin Speed improvements #886
[wiredfool, karstenw]
- Don't resize if already the right size #892
[radarhere]
- Fix for reading multipage TIFFs #885
[kostrom, wiredfool]
- Correctly handle saving gray and CMYK JPEGs with quality=keep #857
[etienned]
- Correct duplicate Tiff Metadata and Exif tag values
[hugovk]
- Windows fixes #871
[wiredfool]
- Fix TGA files with image ID field #856
[megabuz]
- Fixed wrong P-mode of small, unoptimized L-mode GIF #843
[uvNikita]
- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin
[Andrew Drake] [Andrew Drake]
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin - Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin
@ -49,7 +322,7 @@ Changelog (Pillow)
- Added docs for ExifTags - Added docs for ExifTags
[Wintermute3] [Wintermute3]
- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util - More tests for CurImagePlugin, DcxImagePlugin, Effects.c, GimpGradientFile, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
[hugovk] [hugovk]
- Fix return value of FreeTypeFont.textsize() does not include font offsets - Fix return value of FreeTypeFont.textsize() does not include font offsets
@ -109,7 +382,7 @@ Changelog (Pillow)
[wirefool] [wirefool]
- Top level flake8 fixes #741 - Top level flake8 fixes #741
[aclark] [aclark4life]
- Remove obsolete Animated Raster Graphics (ARG) support - Remove obsolete Animated Raster Graphics (ARG) support
[hugovk] [hugovk]
@ -238,7 +511,7 @@ Changelog (Pillow)
[larsmans] [larsmans]
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538 - Avoid conflicting _expand functions in PIL & MINGW, fixes #538
[aclark] [aclark4life]
- Merge from Philippe Lagadecs OleFileIO_PL fork - Merge from Philippe Lagadecs OleFileIO_PL fork
[vadmium] [vadmium]
@ -653,13 +926,13 @@ Changelog (Pillow)
[blueyed] [blueyed]
- Package cleanup and additional documentation - Package cleanup and additional documentation
[aclark] [aclark4life]
1.7.4 (2011-07-21) 1.7.4 (2011-07-21)
------------------ ------------------
- Fix brown bag release - Fix brown bag release
[aclark] [aclark4life]
1.7.3 (2011-07-20) 1.7.3 (2011-07-20)
------------------ ------------------
@ -671,19 +944,19 @@ Changelog (Pillow)
------------------ ------------------
- Bug fix: Python 2.4 compat - Bug fix: Python 2.4 compat
[aclark] [aclark4life]
1.7.1 (2011-05-31) 1.7.1 (2011-05-31)
------------------ ------------------
- More multi-arch support - More multi-arch support
[SteveM, regebro, barry, aclark] [SteveM, regebro, barry, aclark4life]
1.7.0 (2011-05-27) 1.7.0 (2011-05-27)
------------------ ------------------
- Add support for multi-arch library directory /usr/lib/x86_64-linux-gnu - Add support for multi-arch library directory /usr/lib/x86_64-linux-gnu
[aclark] [aclark4life]
1.6 (12/01/2010) 1.6 (12/01/2010)
---------------- ----------------
@ -692,28 +965,28 @@ Changelog (Pillow)
[elro] [elro]
- Doc fixes - Doc fixes
[aclark] [aclark4life]
1.5 (11/28/2010) 1.5 (11/28/2010)
---------------- ----------------
- Module and package fixes - Module and package fixes
[aclark] [aclark4life]
1.4 (11/28/2010) 1.4 (11/28/2010)
---------------- ----------------
- Doc fixes - Doc fixes
[aclark] [aclark4life]
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] [aclark4life]
- Doc fixes - Doc fixes
[aclark] [aclark4life]
1.2 (08/02/2010) 1.2 (08/02/2010)
---------------- ----------------
@ -722,26 +995,29 @@ Changelog (Pillow)
[jezdez] [jezdez]
- Doc fixes - Doc fixes
[aclark] [aclark4life]
1.1 (07/31/2010) 1.1 (07/31/2010)
---------------- ----------------
- Removed setuptools_hg requirement - Removed setuptools_hg requirement
[aclark] [aclark4life]
- Doc fixes - Doc fixes
[aclark] [aclark4life]
1.0 (07/30/2010) 1.0 (07/30/2010)
---------------- ----------------
- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required. - Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required.
- Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_ - Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
[aclark] [aclark4life]
.. Note:: What follows is the original PIL 1.1.7 CHANGES .. Note:: What follows is the original PIL 1.1.7 CHANGES
0.2b5 - 1.1.7 (1995-2010)
-------------------------
:: ::
-*- coding: utf-8 -*- -*- coding: utf-8 -*-
@ -1601,7 +1877,7 @@ Changelog (Pillow)
(1.1.2c1 and 1.1.2 final released) (1.1.2c1 and 1.1.2 final released)
+ Adapted to Python 2.1. Among other things, all uses of the + Adapted to Python 2.1. Among other things, all uses of the
"regex" module has been repleased with "re". "regex" module have been replaced with "re".
+ Fixed attribute error when reading large PNG files (this bug + Fixed attribute error when reading large PNG files (this bug
was introduced in maintenance code released after the 1.1.1 was introduced in maintenance code released after the 1.1.1
@ -2225,7 +2501,7 @@ Changelog (Pillow)
the default value is 75. the default value is 75.
JPEG smooth smooth dithered images. value JPEG smooth smooth dithered images. value
is strengh (1-100). default is is strength (1-100). default is
off (0). off (0).
PNG optimize minimize output file at the PNG optimize minimize output file at the

View File

@ -1,25 +1,29 @@
# Contributing # Contributing to Pillow
## Fixes, Features and Changes Bug fixes, feature additions, tests, documentation and more can be contributed via [issues](https://github.com/python-pillow/Pillow/issues) and/or [pull requests](https://github.com/python-pillow/Pillow/issues). All contributions are welcome.
Send a pull request. We'll generally want documentation and [tests](Tests/README.rst) for new features. Tests or documentation on their own are also welcomed. Feel free to ask questions as an [issue](https://github.com/python-pillow/Pillow/issues/new) or on IRC (irc.freenode.net, #pil) ## Bug fixes, feature additions, etc.
- Fork the repo Please send a pull request to the master branch. Please include [documentation](http://pillow.readthedocs.org) and [tests](Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new) or irc://irc.freenode.net#pil
- 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: - Fork the Pillow repository.
- Try to keep any code commits clean and separate from reformatting commits. - Create a branch from master.
- All new code is going to need tests. - Develop bug fixes, features, tests, etc.
- Try to follow PEP8. - Run the test suite on both Python 2.x and 3.x. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
- Create a pull request to pull the changes from your branch to the Pillow master.
## Bugs ### Guidelines
When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. The best reproductions are self-contained scripts that pull in as few dependencies as possible. An entire Django stack is harder to handle. - Separate code commits from reformatting commits.
- Provide tests for any newly added code.
- Follow PEP8.
## Reporting Issues
When reporting issues, please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. The best reproductions are self-contained scripts with minimal dependencies.
### Provide details
Let us know:
- What did you do? - What did you do?
- What did you expect to happen? - What did you expect to happen?
- What actually happened? - What actually happened?

View File

@ -1,40 +1,22 @@
include *.c include *.c
include *.h include *.h
include *.md include *.md
include *.py include *.py
include *.sh
include *.rst include *.rst
include *.txt include *.txt
include *.yaml
include .coveragerc include .coveragerc
include .gitattributes include .gitattributes
include .travis.yml include .travis.yml
include LICENSE
include Makefile include Makefile
include tox.ini include tox.ini
recursive-include Images *.bdf
recursive-include Images *.fli
recursive-include Images *.gif
recursive-include Images *.icns
recursive-include Images *.ico
recursive-include Images *.jpg
recursive-include Images *.pbm
recursive-include Images *.pil
recursive-include Images *.png
recursive-include Images *.ppm
recursive-include Images *.psd
recursive-include Images *.tar
recursive-include Images *.webp
recursive-include Images *.xpm
recursive-include PIL *.md recursive-include PIL *.md
recursive-include Sane *.c
recursive-include Sane *.py
recursive-include Sane *.rst
recursive-include Sane *.txt
recursive-include Sane CHANGES
recursive-include Sane README
recursive-include Scripts *.py recursive-include Scripts *.py
recursive-include Scripts *.rst recursive-include Scripts *.rst
recursive-include Scripts *.sh recursive-include Scripts *.sh
recursive-include Scripts README recursive-include Scripts README.rst
recursive-include Tests *.bdf recursive-include Tests *.bdf
recursive-include Tests *.bin recursive-include Tests *.bin
recursive-include Tests *.bmp recursive-include Tests *.bmp
@ -44,10 +26,11 @@ recursive-include Tests *.dcx
recursive-include Tests *.doc recursive-include Tests *.doc
recursive-include Tests *.eps recursive-include Tests *.eps
recursive-include Tests *.fli recursive-include Tests *.fli
recursive-include Tests *.ggr
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 *.icc
recursive-include Tests *.icns recursive-include Tests *.icns
recursive-include Tests *.ico recursive-include Tests *.ico
recursive-include Tests *.j2k recursive-include Tests *.j2k
@ -70,15 +53,16 @@ recursive-include Tests *.rst
recursive-include Tests *.sgi recursive-include Tests *.sgi
recursive-include Tests *.spider recursive-include Tests *.spider
recursive-include Tests *.tar recursive-include Tests *.tar
recursive-include Tests *.tga
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 *.webp
recursive-include Tests *.xpm recursive-include Tests *.xpm
recursive-include Tests *.msp
recursive-include Tk *.c recursive-include Tk *.c
recursive-include Tk *.rst recursive-include Tk *.rst
recursive-include Tk *.txt
recursive-include depends *.rst recursive-include depends *.rst
recursive-include depends *.sh recursive-include depends *.sh
recursive-include docs *.bat recursive-include docs *.bat

101
Makefile
View File

@ -1,28 +1,5 @@
.PHONY: pre clean install test inplace coverage test-dep help docs livedocs # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " clean remove build products"
@echo " install make and install"
@echo " test run tests on installed pillow"
@echo " inplace make inplace extension"
@echo " coverage run coverage test (in progress)"
@echo " docs make html docs"
@echo " docserver run an http server on the docs directory"
@echo " test-dep install coveraget and test dependencies"
pre:
virtualenv .
bin/pip install -r requirements.txt
bin/python setup.py develop
bin/python selftest.py
bin/nosetests Tests/test_*.py
bin/python setup.py install
bin/python test-installed.py
check-manifest
pyroma .
viewdoc
clean: clean:
python setup.py clean python setup.py clean
@ -30,32 +7,70 @@ clean:
rm -r build || true rm -r build || true
find . -name __pycache__ | xargs rm -r || 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: coverage:
# requires nose-cov
coverage erase coverage erase
coverage run --parallel-mode --include=PIL/* selftest.py coverage run --parallel-mode --include=PIL/* selftest.py
nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py
# doesn't combine properly before report, # Doesn't combine properly before report, writing report instead of displaying invalid report.
# writing report instead of displaying invalid report
rm -r htmlcov || true rm -r htmlcov || true
coverage combine coverage combine
coverage report coverage report
test-dep: doc:
pip install coveralls nose nose-cov pep8 pyflakes
docs:
$(MAKE) -C docs html $(MAKE) -C docs html
docserver: docserve:
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null& cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
help:
@echo "Welcome to Pillow development. Please use \`make <target>' where <target> is one of"
@echo " clean remove build products"
@echo " coverage run coverage test (in progress)"
@echo " doc make html docs"
@echo " docserve run an http server on the docs directory"
@echo " html to make standalone HTML files"
@echo " inplace make inplace extension"
@echo " install make and install"
@echo " install-req install documentation and test dependencies"
@echo " release-test run code and package tests before release"
@echo " test run tests on installed pillow"
@echo " upload build and upload sdists to PyPI"
@echo " upload-test build and upload sdists to test.pythonpackages.com"
inplace: clean
python setup.py build_ext --inplace
install:
python setup.py install
python selftest.py --installed
install-req:
pip install -r requirements.txt
release-test:
$(MAKE) install-req
python setup.py develop
python selftest.py
nosetests Tests/test_*.py
python setup.py install
python test-installed.py
check-manifest
pyroma .
viewdoc
sdist:
python setup.py sdist --format=gztar,zip
test:
python test-installed.py
# https://docs.python.org/2/distutils/packageindex.html#the-pypirc-file
upload-test:
# [test]
# username:
# password:
# repository = http://test.pythonpackages.com
python setup.py sdist --format=gztar,zip upload -r test
upload:
python setup.py sdist --format=gztar,zip upload

View File

@ -26,12 +26,12 @@ from PIL import FontFile
# -------------------------------------------------------------------- # --------------------------------------------------------------------
bdf_slant = { bdf_slant = {
"R": "Roman", "R": "Roman",
"I": "Italic", "I": "Italic",
"O": "Oblique", "O": "Oblique",
"RI": "Reverse Italic", "RI": "Reverse Italic",
"RO": "Reverse Oblique", "RO": "Reverse Oblique",
"OT": "Other" "OT": "Other"
} }
bdf_spacing = { bdf_spacing = {
@ -40,8 +40,8 @@ bdf_spacing = {
"C": "Cell" "C": "Cell"
} }
def bdf_char(f):
def bdf_char(f):
# skip to STARTCHAR # skip to STARTCHAR
while True: while True:
s = f.readline() s = f.readline()
@ -69,8 +69,8 @@ def bdf_char(f):
bitmap.append(s[:-1]) bitmap.append(s[:-1])
bitmap = b"".join(bitmap) bitmap = b"".join(bitmap)
[x, y, l, d] = [int(s) for s in props["BBX"].split()] [x, y, l, d] = [int(p) for p in props["BBX"].split()]
[dx, dy] = [int(s) for s in props["DWIDTH"].split()] [dx, dy] = [int(p) for p in props["DWIDTH"].split()]
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y) bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
@ -82,6 +82,7 @@ def bdf_char(f):
return id, int(props["ENCODING"]), bbox, im return id, int(props["ENCODING"]), bbox, im
## ##
# Font file plugin for the X11 BDF format. # Font file plugin for the X11 BDF format.
@ -113,10 +114,10 @@ class BdfFontFile(FontFile.FontFile):
font[4] = bdf_slant[font[4].upper()] font[4] = bdf_slant[font[4].upper()]
font[11] = bdf_spacing[font[11].upper()] font[11] = bdf_spacing[font[11].upper()]
ascent = int(props["FONT_ASCENT"]) # ascent = int(props["FONT_ASCENT"])
descent = int(props["FONT_DESCENT"]) # descent = int(props["FONT_DESCENT"])
fontname = ";".join(font[1:]) # fontname = ";".join(font[1:])
# print "#", fontname # print "#", fontname
# for i in comments: # for i in comments:

View File

@ -30,6 +30,7 @@ __version__ = "0.7"
from PIL import Image, ImageFile, ImagePalette, _binary from PIL import Image, ImageFile, ImagePalette, _binary
import math import math
i8 = _binary.i8 i8 = _binary.i8
i16 = _binary.i16le i16 = _binary.i16le
i32 = _binary.i32le i32 = _binary.i32le
@ -48,136 +49,154 @@ BIT2MODE = {
8: ("P", "P"), 8: ("P", "P"),
16: ("RGB", "BGR;15"), 16: ("RGB", "BGR;15"),
24: ("RGB", "BGR"), 24: ("RGB", "BGR"),
32: ("RGB", "BGRX") 32: ("RGB", "BGRX"),
} }
def _accept(prefix): def _accept(prefix):
return prefix[:2] == b"BM" return prefix[:2] == b"BM"
##
# ==============================================================================
# Image plugin for the Windows BMP format. # Image plugin for the Windows BMP format.
# ==============================================================================
class BmpImageFile(ImageFile.ImageFile): class BmpImageFile(ImageFile.ImageFile):
""" Image plugin for the Windows Bitmap format (BMP) """
format = "BMP" # -------------------------------------------------------------- Description
format_description = "Windows Bitmap" format_description = "Windows Bitmap"
format = "BMP"
# --------------------------------------------------- BMP Compression values
COMPRESSIONS = {'RAW': 0, 'RLE8': 1, 'RLE4': 2, 'BITFIELDS': 3, 'JPEG': 4, 'PNG': 5}
RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5
def _bitmap(self, header = 0, offset = 0): def _bitmap(self, header=0, offset=0):
""" Read relevant info about the BMP """
read, seek = self.fp.read, self.fp.seek
if header: if header:
self.fp.seek(header) seek(header)
file_info = dict()
read = self.fp.read file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size)
file_info['direction'] = -1
# CORE/INFO # --------------------- If requested, read header at a specific position
s = read(4) header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size
s = s + ImageFile._safe_read(self.fp, i32(s)-4) # --------------------------------------------------- IBM OS/2 Bitmap v1
# ------ This format has different offsets because of width/height types
if len(s) == 12: if file_info['header_size'] == 12:
file_info['width'] = i16(header_data[0:2])
# OS/2 1.0 CORE file_info['height'] = i16(header_data[2:4])
bits = i16(s[10:]) file_info['planes'] = i16(header_data[4:6])
self.size = i16(s[4:]), i16(s[6:]) file_info['bits'] = i16(header_data[6:8])
compression = 0 file_info['compression'] = self.RAW
lutsize = 3 file_info['palette_padding'] = 3
colors = 0 # ---------------------------------------------- Windows Bitmap v2 to v5
direction = -1 elif file_info['header_size'] in (40, 64, 108, 124): # v3, OS/2 v2, v4, v5
if file_info['header_size'] >= 40: # v3 and OS/2
elif len(s) in [40, 64, 108, 124]: file_info['y_flip'] = i8(header_data[7]) == 0xff
file_info['direction'] = 1 if file_info['y_flip'] else -1
# WIN 3.1 or OS/2 2.0 INFO file_info['width'] = i32(header_data[0:4])
bits = i16(s[14:]) file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8])
self.size = i32(s[4:]), i32(s[8:]) file_info['planes'] = i16(header_data[8:10])
compression = i32(s[16:]) file_info['bits'] = i16(header_data[10:12])
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter file_info['compression'] = i32(header_data[12:16])
lutsize = 4 file_info['data_size'] = i32(header_data[16:20]) # byte size of pixel data
colors = i32(s[32:]) file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))
direction = -1 file_info['colors'] = i32(header_data[28:32])
if i8(s[11]) == 0xff: file_info['palette_padding'] = 4
# upside-down storage self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), file_info['pixels_per_meter']))
self.size = self.size[0], 2**32 - self.size[1] if file_info['compression'] == self.BITFIELDS:
direction = 0 if len(header_data) >= 52:
for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm)) file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
else:
for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']:
file_info[mask] = i32(read(4))
file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'])
file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask'])
else: else:
raise IOError("Unsupported BMP header type (%d)" % len(s)) raise IOError("Unsupported BMP header type (%d)" % file_info['header_size'])
# ------------------ Special case : header is reported 40, which
if (self.size[0]*self.size[1]) > 2**31: # ---------------------- is shorter than real size for bpp >= 16
# Prevent DOS for > 2gb images self.size = file_info['width'], file_info['height']
# -------- If color count was not found in the header, compute from bits
file_info['colors'] = file_info['colors'] if file_info.get('colors', 0) else (1 << file_info['bits'])
# -------------------------------- Check abnormal values for DOS attacks
if file_info['width'] * file_info['height'] > 2**31:
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size) raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
# ----------------------- Check bit depth for unusual unsupported values
if not colors: self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None))
colors = 1 << bits if self.mode is None:
raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits'])
# MODE # ----------------- Process BMP with Bitfields compression (not palette)
try: if file_info['compression'] == self.BITFIELDS:
self.mode, rawmode = BIT2MODE[bits] SUPPORTED = {
except KeyError: 32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
raise IOError("Unsupported BMP pixel depth (%d)" % bits) 24: [(0xff0000, 0xff00, 0xff)],
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]}
if compression == 3: MASK_MODES = {
# BI_BITFIELDS compression (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
mask = i32(read(4)), i32(read(4)), i32(read(4)) (24, (0xff0000, 0xff00, 0xff)): "BGR",
if bits == 32 and mask == (0xff0000, 0x00ff00, 0x0000ff): (16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"}
rawmode = "BGRX" if file_info['bits'] in SUPPORTED:
elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f): if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
rawmode = "BGR;16" raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])]
elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f): self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
rawmode = "BGR;15" elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]:
else: raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])]
# print bits, map(hex, mask)
raise IOError("Unsupported BMP bitfields layout")
elif compression != 0:
raise IOError("Unsupported BMP compression (%d)" % compression)
# LUT
if self.mode == "P":
palette = []
greyscale = 1
if colors == 2:
indices = (0, 255)
elif colors > 2**16 or colors <=0: #We're reading a i32.
raise IOError("Unsupported BMP Palette size (%d)" % colors)
else:
indices = list(range(colors))
for i in indices:
rgb = read(lutsize)[:3]
if rgb != o8(i)*3:
greyscale = 0
palette.append(rgb)
if greyscale:
if colors == 2:
self.mode = rawmode = "1"
else: else:
self.mode = rawmode = "L" raise IOError("Unsupported BMP bitfields layout")
else: else:
self.mode = "P" raise IOError("Unsupported BMP bitfields layout")
self.palette = ImagePalette.raw( elif file_info['compression'] == self.RAW:
"BGR", b"".join(palette) if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset
) raw_mode, self.mode = "BGRA", "RGBA"
else:
raise IOError("Unsupported BMP compression (%d)" % file_info['compression'])
# ---------------- Once the header is processed, process the palette/LUT
if self.mode == "P": # Paletted for 1, 4 and 8 bit images
# ----------------------------------------------------- 1-bit images
if not (0 < file_info['colors'] <= 65536):
raise IOError("Unsupported BMP Palette size (%d)" % file_info['colors'])
else:
padding = file_info['palette_padding']
palette = read(padding * file_info['colors'])
greyscale = True
indices = (0, 255) if file_info['colors'] == 2 else list(range(file_info['colors']))
# ------------------ Check if greyscale and ignore palette if so
for ind, val in enumerate(indices):
rgb = palette[ind*padding:ind*padding + 3]
if rgb != o8(val) * 3:
greyscale = False
# -------- If all colors are grey, white or black, ditch palette
if greyscale:
self.mode = "1" if file_info['colors'] == 2 else "L"
raw_mode = self.mode
else:
self.mode = "P"
self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", palette)
if not offset: # ----------------------------- Finally set the tile data for the plugin
offset = self.fp.tell() self.info['compression'] = file_info['compression']
self.tile = [('raw', (0, 0, file_info['width'], file_info['height']), offset or self.fp.tell(),
self.tile = [("raw", (raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction'])
(0, 0) + self.size, )]
offset,
(rawmode, ((self.size[0]*bits+31)>>3)&(~3), direction))]
self.info["compression"] = compression
def _open(self): def _open(self):
""" Open file, check magic number and read header """
# HEAD # read 14 bytes: magic number, filesize, reserved, header final offset
s = self.fp.read(14) head_data = self.fp.read(14)
if s[:2] != b"BM": # choke if the file does not have the required magic bytes
if head_data[0:2] != b"BM":
raise SyntaxError("Not a BMP file") raise SyntaxError("Not a BMP file")
offset = i32(s[10:]) # read the start position of the BMP image data (u32)
offset = i32(head_data[10:14])
# load bitmap information (offset=raster info)
self._bitmap(offset=offset) self._bitmap(offset=offset)
# ==============================================================================
# Image plugin for the DIB format (BMP alias)
# ==============================================================================
class DibImageFile(BmpImageFile): class DibImageFile(BmpImageFile):
format = "DIB" format = "DIB"
@ -195,10 +214,11 @@ SAVE = {
"L": ("L", 8, 256), "L": ("L", 8, 256),
"P": ("P", 8, 256), "P": ("P", 8, 256),
"RGB": ("BGR", 24, 0), "RGB": ("BGR", 24, 0),
"RGBA": ("BGRA", 32, 0),
} }
def _save(im, fp, filename, check=0):
def _save(im, fp, filename, check=0):
try: try:
rawmode, bits, colors = SAVE[im.mode] rawmode, bits, colors = SAVE[im.mode]
except KeyError: except KeyError:
@ -214,10 +234,10 @@ def _save(im, fp, filename, check=0):
# 1 meter == 39.3701 inches # 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701), dpi)) 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
image = stride * im.size[1] image = stride * im.size[1]
# bitmap header # bitmap header
fp.write(b"BM" + # file type (magic) fp.write(b"BM" + # file type (magic)
@ -248,7 +268,8 @@ def _save(im, fp, filename, check=0):
elif im.mode == "P": elif im.mode == "P":
fp.write(im.im.getpalette("RGB", "BGRX")) fp.write(im.im.getpalette("RGB", "BGRX"))
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, stride, -1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0,
(rawmode, stride, -1))])
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -13,6 +13,7 @@ from PIL import Image, ImageFile
_handler = None _handler = None
## ##
# Install application-specific BUFR image handler. # Install application-specific BUFR image handler.
# #
@ -22,12 +23,14 @@ def register_handler(handler):
global _handler global _handler
_handler = handler _handler = handler
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC" return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
class BufrStubImageFile(ImageFile.StubImageFile): class BufrStubImageFile(ImageFile.StubImageFile):
format = "BUFR" format = "BUFR"
@ -53,6 +56,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
def _load(self): def _load(self):
return _handler return _handler
def _save(im, fp, filename): def _save(im, fp, filename):
if _handler is None or not hasattr("_handler", "save"): if _handler is None or not hasattr("_handler", "save"):
raise IOError("BUFR save handler not installed") raise IOError("BUFR save handler not installed")

View File

@ -18,7 +18,8 @@
# A file object that provides read access to a part of an existing # A file object that provides read access to a part of an existing
# file (for example a TAR file). # file (for example a TAR file).
class ContainerIO:
class ContainerIO(object):
## ##
# Create file object. # Create file object.
@ -48,7 +49,7 @@ class ContainerIO:
# for current offset, and 2 for end of region. You cannot move # for current offset, and 2 for end of region. You cannot move
# the pointer outside the defined region. # the pointer outside the defined region.
def seek(self, offset, mode = 0): def seek(self, offset, mode=0):
if mode == 1: if mode == 1:
self.pos = self.pos + offset self.pos = self.pos + offset
elif mode == 2: elif mode == 2:
@ -75,12 +76,12 @@ class ContainerIO:
# read until end of region. # read until end of region.
# @return An 8-bit string. # @return An 8-bit string.
def read(self, n = 0): def read(self, n=0):
if n: if n:
n = min(n, self.length - self.pos) n = min(n, self.length - self.pos)
else: else:
n = self.length - self.pos n = self.length - self.pos
if not n: # EOF if not n: # EOF
return "" return ""
self.pos = self.pos + n self.pos = self.pos + n
return self.fh.read(n) return self.fh.read(n)

View File

@ -62,6 +62,10 @@ class DcxImageFile(PcxImageFile):
self.__fp = self.fp self.__fp = self.fp
self.seek(0) self.seek(0)
@property
def n_frames(self):
return len(self._offset)
def seek(self, frame): def seek(self, frame):
if frame >= len(self._offset): if frame >= len(self._offset):
raise EOFError("attempt to seek outside DCX directory") raise EOFError("attempt to seek outside DCX directory")

View File

@ -11,7 +11,8 @@
# 1996-08-23 fl Handle files from Macintosh (0.3) # 1996-08-23 fl Handle files from Macintosh (0.3)
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4) # 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5) # 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution resizing # 2014-05-07 e Handling of EPS with binary preview and fixed resolution
# resizing
# #
# Copyright (c) 1997-2003 by Secret Labs AB. # Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2003 by Fredrik Lundh # Copyright (c) 1995-2003 by Fredrik Lundh
@ -51,13 +52,14 @@ if sys.platform.startswith('win'):
else: else:
gs_windows_binary = False gs_windows_binary = False
def has_ghostscript(): def has_ghostscript():
if gs_windows_binary: if gs_windows_binary:
return True return True
if not sys.platform.startswith('win'): if not sys.platform.startswith('win'):
import subprocess import subprocess
try: try:
gs = subprocess.Popen(['gs','--version'], stdout=subprocess.PIPE) gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
gs.stdout.read() gs.stdout.read()
return True return True
except OSError: except OSError:
@ -73,52 +75,62 @@ def Ghostscript(tile, size, fp, scale=1):
decoder, tile, offset, data = tile[0] decoder, tile, offset, data = tile[0]
length, bbox = data length, bbox = data
#Hack to support hi-res rendering # Hack to support hi-res rendering
scale = int(scale) or 1 scale = int(scale) or 1
orig_size = size # orig_size = size
orig_bbox = bbox # orig_bbox = bbox
size = (size[0] * scale, size[1] * scale) size = (size[0] * scale, size[1] * scale)
# resolution is dependend on bbox and size # resolution is dependent on bbox and size
res = ( float((72.0 * size[0]) / (bbox[2]-bbox[0])), float((72.0 * size[1]) / (bbox[3]-bbox[1])) ) res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
#print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res) float((72.0 * size[1]) / (bbox[3]-bbox[1])))
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
import tempfile, os, subprocess import os
import subprocess
import tempfile
out_fd, outfile = tempfile.mkstemp() out_fd, outfile = tempfile.mkstemp()
os.close(out_fd) os.close(out_fd)
in_fd, infile = tempfile.mkstemp()
os.close(in_fd)
# ignore length and offset! infile_temp = None
# ghostscript can read it if hasattr(fp, 'name') and os.path.exists(fp.name):
# copy whole file to read in ghostscript infile = fp.name
with open(infile, 'wb') as f: else:
# fetch length of fp in_fd, infile_temp = tempfile.mkstemp()
fp.seek(0, 2) os.close(in_fd)
fsize = fp.tell() infile = infile_temp
# ensure start position
# go back # ignore length and offset!
fp.seek(0) # ghostscript can read it
lengthfile = fsize # copy whole file to read in ghostscript
while lengthfile > 0: with open(infile_temp, 'wb') as f:
s = fp.read(min(lengthfile, 100*1024)) # fetch length of fp
if not s: fp.seek(0, 2)
break fsize = fp.tell()
length -= len(s) # ensure start position
f.write(s) # go back
fp.seek(0)
lengthfile = fsize
while lengthfile > 0:
s = fp.read(min(lengthfile, 100*1024))
if not s:
break
lengthfile -= len(s)
f.write(s)
# Build ghostscript command # Build ghostscript command
command = ["gs", command = ["gs",
"-q", # quiet mode "-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels) "-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch) "-r%fx%f" % res, # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode "-dNOPAUSE -dSAFER", # don't pause between pages,
"-sDEVICE=ppmraw", # ppm driver # safe mode
"-sOutputFile=%s" % outfile, # output file "-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]), "-c", "%d %d translate" % (-bbox[0], -bbox[1]),
# adjust for image origin # adjust for image origin
"-f", infile, # input file "-f", infile, # input file
] ]
if gs_windows_binary is not None: if gs_windows_binary is not None:
if not gs_windows_binary: if not gs_windows_binary:
@ -127,7 +139,8 @@ def Ghostscript(tile, size, fp, scale=1):
# push data through ghostscript # push data through ghostscript
try: try:
gs = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) gs = subprocess.Popen(command, stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
gs.stdin.close() gs.stdin.close()
status = gs.wait() status = gs.wait()
if status: if status:
@ -136,48 +149,41 @@ def Ghostscript(tile, size, fp, scale=1):
finally: finally:
try: try:
os.unlink(outfile) os.unlink(outfile)
os.unlink(infile) if infile_temp:
except: pass os.unlink(infile_temp)
except:
pass
return im return im
class PSFile: class PSFile(object):
"""Wrapper that treats either CR or LF as end of line.""" """
Wrapper for bytesio object that treats either CR or LF as end of line.
"""
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
self.char = None self.char = None
def __getattr__(self, id):
v = getattr(self.fp, id)
setattr(self, id, v)
return v
def seek(self, offset, whence=0): def seek(self, offset, whence=0):
self.char = None self.char = None
self.fp.seek(offset, whence) self.fp.seek(offset, whence)
def read(self, count):
return self.fp.read(count).decode('latin-1')
def readbinary(self, count):
return self.fp.read(count)
def tell(self):
pos = self.fp.tell()
if self.char:
pos -= 1
return pos
def readline(self): def readline(self):
s = b"" s = self.char or b""
if self.char: self.char = None
c = self.char
self.char = None c = self.fp.read(1)
else:
c = self.fp.read(1)
while c not in b"\r\n": while c not in b"\r\n":
s = s + c s = s + c
c = self.fp.read(1) c = self.fp.read(1)
if c == b"\r":
self.char = self.fp.read(1) self.char = self.fp.read(1)
if self.char == b"\n": # line endings can be 1 or 2 of \r \n, in either order
self.char = None if self.char in b"\r\n":
return s.decode('latin-1') + "\n" self.char = None
return s.decode('latin-1')
def _accept(prefix): def _accept(prefix):
@ -187,38 +193,30 @@ def _accept(prefix):
# Image plugin for Encapsulated Postscript. This plugin supports only # Image plugin for Encapsulated Postscript. This plugin supports only
# a few variants of this format. # a few variants of this format.
class EpsImageFile(ImageFile.ImageFile): class EpsImageFile(ImageFile.ImageFile):
"""EPS File Parser for the Python Imaging Library""" """EPS File Parser for the Python Imaging Library"""
format = "EPS" format = "EPS"
format_description = "Encapsulated Postscript" format_description = "Encapsulated Postscript"
mode_map = {1: "L", 2: "LAB", 3: "RGB"}
def _open(self): def _open(self):
(length, offset) = self._find_offset(self.fp)
fp = PSFile(self.fp) # Rewrap the open file pointer in something that will
# convert line endings and decode to latin-1.
# FIX for: Some EPS file not handled correctly / issue #302 try:
# EPS can contain binary data if bytes is str:
# or start directly with latin coding # Python2, no encoding conversion necessary
# read header in both ways to handle both fp = open(self.fp.name, "Ur")
# file types else:
# more info see http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf # Python3, can use bare open command.
fp = open(self.fp.name, "Ur", encoding='latin-1')
# for HEAD without binary preview except:
s = fp.read(4) # Expect this for bytesio/stringio
# for HEAD with binary preview fp = PSFile(self.fp)
fp.seek(0)
sb = fp.readbinary(160)
if s[:4] == "%!PS":
fp.seek(0, 2)
length = fp.tell()
offset = 0
elif i32(sb[0:4]) == 0xC6D3D0C5:
offset = i32(sb[4:8])
length = i32(sb[8:12])
else:
raise SyntaxError("not an EPS file")
# go to offset - start of "%!PS" # go to offset - start of "%!PS"
fp.seek(offset) fp.seek(offset)
@ -226,23 +224,17 @@ class EpsImageFile(ImageFile.ImageFile):
box = None box = None
self.mode = "RGB" self.mode = "RGB"
self.size = 1, 1 # FIXME: huh? self.size = 1, 1 # FIXME: huh?
# #
# Load EPS header # Load EPS header
s = fp.readline() s = fp.readline().strip('\r\n')
while s: while s:
if len(s) > 255: if len(s) > 255:
raise SyntaxError("not an EPS file") raise SyntaxError("not an EPS file")
if s[-2:] == '\r\n':
s = s[:-2]
elif s[-1:] == '\n':
s = s[:-1]
try: try:
m = split.match(s) m = split.match(s)
except re.error as v: except re.error as v:
@ -256,17 +248,15 @@ class EpsImageFile(ImageFile.ImageFile):
# Note: The DSC spec says that BoundingBox # Note: The DSC spec says that BoundingBox
# fields should be integers, but some drivers # fields should be integers, but some drivers
# put floating point values there anyway. # put floating point values there anyway.
box = [int(float(s)) for s in v.split()] box = [int(float(i)) for i in v.split()]
self.size = box[2] - box[0], box[3] - box[1] self.size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0,0) + self.size, offset, self.tile = [("eps", (0, 0) + self.size, offset,
(length, box))] (length, box))]
except: except:
pass pass
else: else:
m = field.match(s) m = field.match(s)
if m: if m:
k = m.group(1) k = m.group(1)
@ -276,84 +266,69 @@ class EpsImageFile(ImageFile.ImageFile):
self.info[k[:8]] = k[9:] self.info[k[:8]] = k[9:]
else: else:
self.info[k] = "" self.info[k] = ""
elif s[0:1] == '%': elif s[0] == '%':
# handle non-DSC Postscript comments that some # handle non-DSC Postscript comments that some
# tools mistakenly put in the Comments section # tools mistakenly put in the Comments section
pass pass
else: else:
raise IOError("bad EPS header") raise IOError("bad EPS header")
s = fp.readline() s = fp.readline().strip('\r\n')
if s[:1] != "%": if s[:1] != "%":
break break
# #
# Scan for an "ImageData" descriptor # Scan for an "ImageData" descriptor
while s[0] == "%": while s[:1] == "%":
if len(s) > 255: if len(s) > 255:
raise SyntaxError("not an EPS file") raise SyntaxError("not an EPS file")
if s[-2:] == '\r\n':
s = s[:-2]
elif s[-1:] == '\n':
s = s[:-1]
if s[:11] == "%ImageData:": if s[:11] == "%ImageData:":
# Encoded bitmapped image.
x, y, bi, mo = s[11:].split(None, 7)[:4]
[x, y, bi, mo, z3, z4, en, id] =\ if int(bi) != 8:
s[11:].split(None, 7)
x = int(x); y = int(y)
bi = int(bi)
mo = int(mo)
en = int(en)
if en == 1:
decoder = "eps_binary"
elif en == 2:
decoder = "eps_hex"
else:
break break
if bi != 8: try:
break self.mode = self.mode_map[int(mo)]
if mo == 1: except:
self.mode = "L"
elif mo == 2:
self.mode = "LAB"
elif mo == 3:
self.mode = "RGB"
else:
break break
if id[:1] == id[-1:] == '"': self.size = int(x), int(y)
id = id[1:-1] return
# Scan forward to the actual image data s = fp.readline().strip('\r\n')
while True:
s = fp.readline()
if not s:
break
if s[:len(id)] == id:
self.size = x, y
self.tile2 = [(decoder,
(0, 0, x, y),
fp.tell(),
0)]
return
s = fp.readline()
if not s: if not s:
break break
if not box: if not box:
raise IOError("cannot determine EPS bounding box") raise IOError("cannot determine EPS bounding box")
def _find_offset(self, fp):
s = fp.read(160)
if s[:4] == b"%!PS":
# for HEAD without binary preview
fp.seek(0, 2)
length = fp.tell()
offset = 0
elif i32(s[0:4]) == 0xC6D3D0C5:
# FIX for: Some EPS file not handled correctly / issue #302
# EPS can contain binary data
# or start directly with latin coding
# more info see:
# http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
offset = i32(s[4:8])
length = i32(s[8:12])
else:
raise SyntaxError("not an EPS file")
return (length, offset)
def load(self, scale=1): def load(self, scale=1):
# Load EPS via Ghostscript # Load EPS via Ghostscript
if not self.tile: if not self.tile:
@ -363,11 +338,12 @@ class EpsImageFile(ImageFile.ImageFile):
self.size = self.im.size self.size = self.im.size
self.tile = [] self.tile = []
def load_seek(self,*args,**kwargs): def load_seek(self, *args, **kwargs):
# we can't incrementally load, so force ImageFile.parser to # we can't incrementally load, so force ImageFile.parser to
# use our custom load method by defining this method. # use our custom load method by defining this method.
pass pass
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -389,11 +365,13 @@ def _save(im, fp, filename, eps=1):
else: else:
raise ValueError("image mode is not supported") raise ValueError("image mode is not supported")
class NoCloseStream: class NoCloseStream(object):
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.fp, name) return getattr(self.fp, name)
def close(self): def close(self):
pass pass
@ -407,7 +385,7 @@ def _save(im, fp, filename, eps=1):
# write EPS header # write EPS header
fp.write("%!PS-Adobe-3.0 EPSF-3.0\n") fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
fp.write("%%Creator: PIL 0.1 EpsEncode\n") fp.write("%%Creator: PIL 0.1 EpsEncode\n")
#fp.write("%%CreationDate: %s"...) # fp.write("%%CreationDate: %s"...)
fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size) fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
fp.write("%%Pages: 1\n") fp.write("%%Pages: 1\n")
fp.write("%%EndComments\n") fp.write("%%EndComments\n")
@ -421,13 +399,13 @@ def _save(im, fp, filename, eps=1):
fp.write("10 dict begin\n") fp.write("10 dict begin\n")
fp.write("/buf %d string def\n" % (im.size[0] * operator[1])) fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
fp.write("%d %d scale\n" % im.size) fp.write("%d %d scale\n" % im.size)
fp.write("%d %d 8\n" % im.size) # <= bits fp.write("%d %d 8\n" % im.size) # <= bits
fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
fp.write("{ currentfile buf readhexstring pop } bind\n") fp.write("{ currentfile buf readhexstring pop } bind\n")
fp.write(operator[2] + "\n") fp.write(operator[2] + "\n")
fp.flush() fp.flush()
ImageFile._save(im, base_fp, [("eps", (0,0)+im.size, 0, None)]) ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
fp.write("\n%%%%EndBinary\n") fp.write("\n%%%%EndBinary\n")
fp.write("grestore end\n") fp.write("grestore end\n")

View File

@ -67,8 +67,8 @@ TAGS = {
0x0213: "YCbCrPositioning", 0x0213: "YCbCrPositioning",
0x0214: "ReferenceBlackWhite", 0x0214: "ReferenceBlackWhite",
0x1000: "RelatedImageFileFormat", 0x1000: "RelatedImageFileFormat",
0x1001: "RelatedImageLength", # FIXME / Dictionary contains duplicate keys 0x1001: "RelatedImageWidth",
0x1001: "RelatedImageWidth", # FIXME \ Dictionary contains duplicate keys 0x1002: "RelatedImageLength",
0x828d: "CFARepeatPatternDim", 0x828d: "CFARepeatPatternDim",
0x828e: "CFAPattern", 0x828e: "CFAPattern",
0x828f: "BatteryLevel", 0x828f: "BatteryLevel",

View File

@ -18,6 +18,7 @@ _handler = None
# #
# @param handler Handler object. # @param handler Handler object.
def register_handler(handler): def register_handler(handler):
global _handler global _handler
_handler = handler _handler = handler
@ -25,9 +26,11 @@ def register_handler(handler):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[:6] == b"SIMPLE" return prefix[:6] == b"SIMPLE"
class FITSStubImageFile(ImageFile.StubImageFile): class FITSStubImageFile(ImageFile.StubImageFile):
format = "FITS" format = "FITS"

View File

@ -25,12 +25,14 @@ i16 = _binary.i16le
i32 = _binary.i32le i32 = _binary.i32le
o8 = _binary.o8 o8 = _binary.o8
# #
# decoder # decoder
def _accept(prefix): def _accept(prefix):
return i16(prefix[4:6]) in [0xAF11, 0xAF12] return i16(prefix[4:6]) in [0xAF11, 0xAF12]
## ##
# Image plugin for the FLI/FLC animation format. Use the <b>seek</b> # Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
# method to load individual frames. # method to load individual frames.
@ -47,7 +49,7 @@ class FliImageFile(ImageFile.ImageFile):
magic = i16(s[4:6]) magic = i16(s[4:6])
if not (magic in [0xAF11, 0xAF12] and if not (magic in [0xAF11, 0xAF12] and
i16(s[14:16]) in [0, 3] and # flags i16(s[14:16]) in [0, 3] and # flags
s[20:22] == b"\x00\x00"): # reserved s[20:22] == b"\x00\x00"): # reserved
raise SyntaxError("not an FLI/FLC file") raise SyntaxError("not an FLI/FLC file")
# image characteristics # image characteristics
@ -61,7 +63,7 @@ class FliImageFile(ImageFile.ImageFile):
self.info["duration"] = duration self.info["duration"] = duration
# look for palette # look for palette
palette = [(a,a,a) for a in range(256)] palette = [(a, a, a) for a in range(256)]
s = self.fp.read(16) s = self.fp.read(16)
@ -80,13 +82,14 @@ class FliImageFile(ImageFile.ImageFile):
elif i16(s[4:6]) == 4: elif i16(s[4:6]) == 4:
self._palette(palette, 0) self._palette(palette, 0)
palette = [o8(r)+o8(g)+o8(b) for (r,g,b) in palette] palette = [o8(r)+o8(g)+o8(b) for (r, g, b) in palette]
self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.palette = ImagePalette.raw("RGB", b"".join(palette))
# set things up to decode first frame # set things up to decode first frame
self.frame = -1 self.__frame = -1
self.__fp = self.fp self.__fp = self.fp
self.__rewind = self.fp.tell()
self._n_frames = None
self.seek(0) self.seek(0)
def _palette(self, palette, shift): def _palette(self, palette, shift):
@ -107,11 +110,35 @@ class FliImageFile(ImageFile.ImageFile):
palette[i] = (r, g, b) palette[i] = (r, g, b)
i += 1 i += 1
def seek(self, frame): @property
def n_frames(self):
if self._n_frames is None:
current = self.tell()
try:
while True:
self.seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current)
return self._n_frames
if frame != self.frame + 1: def seek(self, frame):
if frame == self.__frame:
return
if frame < self.__frame:
self._seek(0)
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
def _seek(self, frame):
if frame == 0:
self.__frame = -1
self.__fp.seek(self.__rewind)
self.__offset = 128
if frame != self.__frame + 1:
raise ValueError("cannot seek to frame %d" % frame) raise ValueError("cannot seek to frame %d" % frame)
self.frame = frame self.__frame = frame
# move to next frame # move to next frame
self.fp = self.__fp self.fp = self.__fp
@ -124,13 +151,12 @@ class FliImageFile(ImageFile.ImageFile):
framesize = i32(s) framesize = i32(s)
self.decodermaxblock = framesize self.decodermaxblock = framesize
self.tile = [("fli", (0,0)+self.size, self.__offset, None)] self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
self.__offset = self.__offset + framesize self.__offset += framesize
def tell(self): def tell(self):
return self.__frame
return self.frame
# #
# registry # registry

View File

@ -17,11 +17,6 @@
import os import os
from PIL import Image, _binary from PIL import Image, _binary
try:
import zlib
except ImportError:
zlib = None
WIDTH = 800 WIDTH = 800
@ -36,7 +31,7 @@ def puti16(fp, values):
## ##
# Base class for raster font file handlers. # Base class for raster font file handlers.
class FontFile: class FontFile(object):
bitmap = None bitmap = None
@ -83,7 +78,8 @@ class FontFile:
glyph = self[i] glyph = self[i]
if glyph: if glyph:
d, dst, src, im = glyph d, dst, src, im = glyph
xx, yy = src[2] - src[0], src[3] - src[1] xx = src[2] - src[0]
# yy = src[3] - src[1]
x0, y0 = x, y x0, y0 = x, y
x = x + xx x = x + xx
if x > WIDTH: if x > WIDTH:

View File

@ -20,7 +20,7 @@ __version__ = "0.1"
from PIL import Image, ImageFile from PIL import Image, ImageFile
from PIL.OleFileIO import * from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
# we map from colour field tuples to (mode, rawmode) descriptors # we map from colour field tuples to (mode, rawmode) descriptors
@ -34,16 +34,18 @@ MODES = {
(0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"), (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
(0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"), (0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"),
# standard RGB (NIFRGB) # standard RGB (NIFRGB)
(0x00030000, 0x00030001, 0x00030002): ("RGB","RGB"), (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
(0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA","RGBA"), (0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA", "RGBA"),
} }
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _accept(prefix): def _accept(prefix):
return prefix[:8] == MAGIC return prefix[:8] == MAGIC
## ##
# Image plugin for the FlashPix images. # Image plugin for the FlashPix images.
@ -67,7 +69,7 @@ class FpxImageFile(ImageFile.ImageFile):
self._open_index(1) self._open_index(1)
def _open_index(self, index = 1): def _open_index(self, index=1):
# #
# get the Image Contents Property Set # get the Image Contents Property Set
@ -95,7 +97,7 @@ class FpxImageFile(ImageFile.ImageFile):
id = self.maxid << 16 id = self.maxid << 16
s = prop[0x2000002|id] s = prop[0x2000002 | id]
colors = [] colors = []
for i in range(i32(s, 4)): for i in range(i32(s, 4)):
@ -107,7 +109,7 @@ class FpxImageFile(ImageFile.ImageFile):
# load JPEG tables, if any # load JPEG tables, if any
self.jpeg = {} self.jpeg = {}
for i in range(256): for i in range(256):
id = 0x3000001|(i << 16) id = 0x3000001 | (i << 16)
if id in prop: if id in prop:
self.jpeg[i] = prop[id] self.jpeg[i] = prop[id]
@ -115,7 +117,7 @@ class FpxImageFile(ImageFile.ImageFile):
self._open_subimage(1, self.maxid) self._open_subimage(1, self.maxid)
def _open_subimage(self, index = 1, subimage = 0): def _open_subimage(self, index=1, subimage=0):
# #
# setup tile descriptors for a given subimage # setup tile descriptors for a given subimage
@ -128,15 +130,15 @@ class FpxImageFile(ImageFile.ImageFile):
fp = self.ole.openstream(stream) fp = self.ole.openstream(stream)
# skip prefix # skip prefix
p = fp.read(28) fp.read(28)
# header stream # header stream
s = fp.read(36) s = fp.read(36)
size = i32(s, 4), i32(s, 8) size = i32(s, 4), i32(s, 8)
tilecount = i32(s, 12) # tilecount = i32(s, 12)
tilesize = i32(s, 16), i32(s, 20) tilesize = i32(s, 16), i32(s, 20)
channels = i32(s, 24) # channels = i32(s, 24)
offset = i32(s, 28) offset = i32(s, 28)
length = i32(s, 32) length = i32(s, 32)
@ -159,14 +161,14 @@ class FpxImageFile(ImageFile.ImageFile):
compression = i32(s, i+8) compression = i32(s, i+8)
if compression == 0: if compression == 0:
self.tile.append(("raw", (x,y,x+xtile,y+ytile), self.tile.append(("raw", (x, y, x+xtile, y+ytile),
i32(s, i) + 28, (self.rawmode))) i32(s, i) + 28, (self.rawmode)))
elif compression == 1: elif compression == 1:
# FIXME: the fill decoder is not implemented # FIXME: the fill decoder is not implemented
self.tile.append(("fill", (x,y,x+xtile,y+ytile), self.tile.append(("fill", (x, y, x+xtile, y+ytile),
i32(s, i) + 28, (self.rawmode, s[12:16]))) i32(s, i) + 28, (self.rawmode, s[12:16])))
elif compression == 2: elif compression == 2:
@ -182,14 +184,14 @@ class FpxImageFile(ImageFile.ImageFile):
# this problem : # this problem :
jpegmode, rawmode = "YCbCrK", "CMYK" jpegmode, rawmode = "YCbCrK", "CMYK"
else: else:
jpegmode = None # let the decoder decide jpegmode = None # let the decoder decide
else: else:
# The image is stored as defined by rawmode # The image is stored as defined by rawmode
jpegmode = rawmode jpegmode = rawmode
self.tile.append(("jpeg", (x,y,x+xtile,y+ytile), self.tile.append(("jpeg", (x, y, x+xtile, y+ytile),
i32(s, i) + 28, (rawmode, jpegmode))) i32(s, i) + 28, (rawmode, jpegmode)))
# FIXME: jpeg tables are tile dependent; the prefix # FIXME: jpeg tables are tile dependent; the prefix
# data must be placed in the tile descriptor itself! # data must be placed in the tile descriptor itself!
@ -204,7 +206,7 @@ class FpxImageFile(ImageFile.ImageFile):
if x >= xsize: if x >= xsize:
x, y = 0, y + ytile x, y = 0, y + ytile
if y >= ysize: if y >= ysize:
break # isn't really required break # isn't really required
self.stream = stream self.stream = stream
self.fp = None self.fp = None
@ -212,7 +214,8 @@ class FpxImageFile(ImageFile.ImageFile):
def load(self): def load(self):
if not self.fp: if not self.fp:
self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"]) self.fp = self.ole.openstream(self.stream[:2] +
["Subimage 0000 Data"])
ImageFile.ImageFile.load(self) ImageFile.ImageFile.load(self)

View File

@ -17,9 +17,11 @@ from PIL import Image, ImageFile, _binary
i32 = _binary.i32be i32 = _binary.i32be
def _accept(prefix): def _accept(prefix):
return i32(prefix) >= 20 and i32(prefix[4:8]) == 1 return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
## ##
# Image plugin for the GIMP brush format. # Image plugin for the GIMP brush format.
@ -37,8 +39,8 @@ class GbrImageFile(ImageFile.ImageFile):
width = i32(self.fp.read(4)) width = i32(self.fp.read(4))
height = i32(self.fp.read(4)) height = i32(self.fp.read(4))
bytes = i32(self.fp.read(4)) color_depth = i32(self.fp.read(4))
if width <= 0 or height <= 0 or bytes != 1: if width <= 0 or height <= 0 or color_depth != 1:
raise SyntaxError("not a GIMP brush") raise SyntaxError("not a GIMP brush")
comment = self.fp.read(header_size - 20)[:-1] comment = self.fp.read(header_size - 20)[:-1]

View File

@ -36,6 +36,7 @@ except ImportError:
i16 = _binary.i16be i16 = _binary.i16be
## ##
# Image plugin for the GD uncompressed format. Note that this format # Image plugin for the GD uncompressed format. Note that this format
# is not supported by the standard <b>Image.open</b> function. To use # is not supported by the standard <b>Image.open</b> function. To use
@ -52,7 +53,7 @@ class GdImageFile(ImageFile.ImageFile):
# Header # Header
s = self.fp.read(775) s = self.fp.read(775)
self.mode = "L" # FIXME: "P" self.mode = "L" # FIXME: "P"
self.size = i16(s[0:2]), i16(s[2:4]) self.size = i16(s[0:2]), i16(s[2:4])
# transparency index # transparency index
@ -62,7 +63,8 @@ class GdImageFile(ImageFile.ImageFile):
self.palette = ImagePalette.raw("RGB", s[7:]) self.palette = ImagePalette.raw("RGB", s[7:])
self.tile = [("raw", (0,0)+self.size, 775, ("L", 0, -1))] self.tile = [("raw", (0, 0)+self.size, 775, ("L", 0, -1))]
## ##
# Load texture from a GD image file. # Load texture from a GD image file.
@ -73,7 +75,7 @@ class GdImageFile(ImageFile.ImageFile):
# @return An image instance. # @return An image instance.
# @exception IOError If the image could not be read. # @exception IOError If the image could not be read.
def open(fp, mode = "r"): def open(fp, mode="r"):
if mode != "r": if mode != "r":
raise ValueError("bad mode") raise ValueError("bad mode")

View File

@ -24,13 +24,11 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFile, ImagePalette, _binary
__version__ = "0.9" __version__ = "0.9"
from PIL import Image, ImageFile, ImagePalette, _binary
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Helpers # Helpers
@ -46,6 +44,7 @@ o16 = _binary.o16le
def _accept(prefix): def _accept(prefix):
return prefix[:6] in [b"GIF87a", b"GIF89a"] return prefix[:6] in [b"GIF87a", b"GIF89a"]
## ##
# Image plugin for GIF images. This plugin supports both GIF87 and # Image plugin for GIF images. This plugin supports both GIF87 and
# GIF89 images. # GIF89 images.
@ -79,24 +78,45 @@ class GifImageFile(ImageFile.ImageFile):
# get global palette # get global palette
self.info["background"] = i8(s[11]) self.info["background"] = i8(s[11])
# check if palette contains colour indices # check if palette contains colour indices
p = self.fp.read(3<<bits) p = self.fp.read(3 << bits)
for i in range(0, len(p), 3): for i in range(0, len(p), 3):
if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])): if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])):
p = ImagePalette.raw("RGB", p) p = ImagePalette.raw("RGB", p)
self.global_palette = self.palette = p self.global_palette = self.palette = p
break break
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell() self.__rewind = self.fp.tell()
self.seek(0) # get ready to read first frame self._n_frames = None
self._seek(0) # get ready to read first frame
@property
def n_frames(self):
if self._n_frames is None:
current = self.tell()
try:
while True:
self.seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current)
return self._n_frames
def seek(self, frame): def seek(self, frame):
if frame == self.__frame:
return
if frame < self.__frame:
self._seek(0)
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
def _seek(self, frame):
if frame == 0: if frame == 0:
# 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.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._prev_im = None
@ -185,7 +205,7 @@ class GifImageFile(ImageFile.ImageFile):
if flags & 128: if flags & 128:
bits = (flags & 7) + 1 bits = (flags & 7) + 1
self.palette =\ self.palette =\
ImagePalette.raw("RGB", self.fp.read(3<<bits)) ImagePalette.raw("RGB", self.fp.read(3 << bits))
# image data # image data
bits = i8(self.fp.read(1)) bits = i8(self.fp.read(1))
@ -219,7 +239,6 @@ class GifImageFile(ImageFile.ImageFile):
except (AttributeError, KeyError): except (AttributeError, KeyError):
pass 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")
@ -240,7 +259,8 @@ class GifImageFile(ImageFile.ImageFile):
# we do this by pasting the updated area onto the previous # we do this by pasting the updated area onto the previous
# frame which we then use as the current image content # frame which we then use as the current image content
updated = self.im.crop(self.dispose_extent) updated = self.im.crop(self.dispose_extent)
self._prev_im.paste(updated, self.dispose_extent, updated.convert('RGBA')) self._prev_im.paste(updated, self.dispose_extent,
updated.convert('RGBA'))
self.im = self._prev_im self.im = self._prev_im
self._prev_im = self.im.copy() self._prev_im = self.im.copy()
@ -258,6 +278,7 @@ RAWMODE = {
"P": "P", "P": "P",
} }
def _save(im, fp, filename): def _save(im, fp, filename):
if _imaging_gif: if _imaging_gif:
@ -266,10 +287,10 @@ def _save(im, fp, filename):
_imaging_gif.save(im, fp, filename) _imaging_gif.save(im, fp, filename)
return return
except IOError: except IOError:
pass # write uncompressed file pass # write uncompressed file
if im.mode in RAWMODE: if im.mode in RAWMODE:
imOut = im im_out = im
else: 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...)
@ -277,9 +298,9 @@ def _save(im, fp, filename):
palette_size = 256 palette_size = 256
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) im_out = im.convert("P", palette=1, colors=palette_size)
else: else:
imOut = im.convert("L") im_out = im.convert("L")
# header # header
try: try:
@ -288,12 +309,33 @@ def _save(im, fp, filename):
palette = None palette = None
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo) header, used_palette_colors = getheader(im_out, palette, im.encoderinfo)
for s in header: for s in header:
fp.write(s) fp.write(s)
flags = 0 flags = 0
if get_interlace(im):
flags = flags | 64
# local image header
get_local_header(fp, im, (0, 0), flags)
im_out.encoderconfig = (8, get_interlace(im))
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
RAWMODE[im_out.mode])])
fp.write(b"\0") # end of image data
fp.write(b";") # end of file
try:
fp.flush()
except:
pass
def get_interlace(im):
try: try:
interlace = im.encoderinfo["interlace"] interlace = im.encoderinfo["interlace"]
except KeyError: except KeyError:
@ -303,9 +345,11 @@ def _save(im, fp, filename):
if min(im.size) < 16: if min(im.size) < 16:
interlace = 0 interlace = 0
if interlace: return interlace
flags = flags | 64
def get_local_header(fp, im, offset, flags):
transparent_color_exists = False
try: try:
transparency = im.encoderinfo["transparency"] transparency = im.encoderinfo["transparency"]
except KeyError: except KeyError:
@ -313,45 +357,55 @@ def _save(im, fp, filename):
else: else:
transparency = int(transparency) transparency = int(transparency)
# optimize the block away if transparent color is not used # optimize the block away if transparent color is not used
transparentColorExists = True transparent_color_exists = True
# adjust the transparency index after optimize
if usedPaletteColors is not None and len(usedPaletteColors) < 256:
for i in range(len(usedPaletteColors)):
if usedPaletteColors[i] == transparency:
transparency = i
transparentColorExists = True
break
else:
transparentColorExists = False
# transparency extension block if _get_optimize(im, im.encoderinfo):
if transparentColorExists: used_palette_colors = _get_used_palette_colors(im)
fp.write(b"!" +
o8(249) + # extension intro
o8(4) + # length
o8(1) + # transparency info present
o16(0) + # duration
o8(transparency) # transparency index
+ o8(0))
# local image header # adjust the transparency index after optimize
if len(used_palette_colors) < 256:
for i in range(len(used_palette_colors)):
if used_palette_colors[i] == transparency:
transparency = i
transparent_color_exists = True
break
else:
transparent_color_exists = False
if "duration" in im.encoderinfo:
duration = int(im.encoderinfo["duration"] / 10)
else:
duration = 0
if transparent_color_exists or duration != 0:
transparency_flag = 1 if transparent_color_exists else 0
if not transparent_color_exists:
transparency = 0
fp.write(b"!" +
o8(249) + # extension intro
o8(4) + # length
o8(transparency_flag) + # transparency info present
o16(duration) + # duration
o8(transparency) + # transparency index
o8(0))
if "loop" in im.encoderinfo:
number_of_loops = im.encoderinfo["loop"]
fp.write(b"!" +
o8(255) + # extension intro
o8(11) +
b"NETSCAPE2.0" +
o8(3) +
o8(1) +
o16(number_of_loops) + # number of loops
o8(0))
fp.write(b"," + fp.write(b"," +
o16(0) + o16(0) + # bounding box o16(offset[0]) + # offset
o16(im.size[0]) + # size o16(offset[1]) +
o16(im.size[0]) + # size
o16(im.size[1]) + o16(im.size[1]) +
o8(flags) + # flags o8(flags) + # flags
o8(8)) # bits o8(8)) # bits
imOut.encoderconfig = (8, interlace)
ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, RAWMODE[imOut.mode])])
fp.write(b"\0") # end of image data
fp.write(b";") # end of file
try:
fp.flush()
except: pass
def _save_netpbm(im, fp, filename): def _save_netpbm(im, fp, filename):
@ -380,7 +434,8 @@ def _save_netpbm(im, fp, filename):
stderr = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile()
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr) quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
stderr = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile()
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=stderr) togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
stderr=stderr)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits # Allow ppmquant to receive SIGPIPE if ppmtogif exits
quant_proc.stdout.close() quant_proc.stdout.close()
@ -402,11 +457,26 @@ def _save_netpbm(im, fp, filename):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# GIF utilities # GIF utilities
def _get_optimize(im, info):
return im.mode in ("P", "L") and info and info.get("optimize", 0)
def _get_used_palette_colors(im):
used_palette_colors = []
# check which colors are used
i = 0
for count in im.histogram():
if count:
used_palette_colors.append(i)
i += 1
return used_palette_colors
def getheader(im, palette=None, info=None): def getheader(im, palette=None, info=None):
"""Return a list of strings representing a GIF header""" """Return a list of strings representing a GIF header"""
optimize = info and info.get("optimize", 0)
# Header Block # Header Block
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp # http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
header = [ header = [
@ -417,102 +487,94 @@ def getheader(im, palette=None, info=None):
if im.mode == "P": if im.mode == "P":
if palette and isinstance(palette, bytes): if palette and isinstance(palette, bytes):
sourcePalette = palette[:768] source_palette = palette[:768]
else: else:
sourcePalette = im.im.getpalette("RGB")[:768] source_palette = im.im.getpalette("RGB")[:768]
else: # L-mode else: # L-mode
if palette and isinstance(palette, bytes): if palette and isinstance(palette, bytes):
sourcePalette = palette[:768] source_palette = palette[:768]
else: else:
sourcePalette = bytearray([i//3 for i in range(768)]) source_palette = bytearray([i//3 for i in range(768)])
usedPaletteColors = paletteBytes = None used_palette_colors = palette_bytes = None
if optimize: if _get_optimize(im, info):
usedPaletteColors = [] used_palette_colors = _get_used_palette_colors(im)
# check which colors are used
i = 0
for count in im.histogram():
if count:
usedPaletteColors.append(i)
i += 1
# create the new palette if not every color is used # create the new palette if not every color is used
if len(usedPaletteColors) < 256: if len(used_palette_colors) < 256:
paletteBytes = b"" palette_bytes = b""
newPositions = {} new_positions = {}
i = 0 i = 0
# pick only the used colors from the palette # pick only the used colors from the palette
for oldPosition in usedPaletteColors: for oldPosition in used_palette_colors:
paletteBytes += sourcePalette[oldPosition*3:oldPosition*3+3] palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
newPositions[oldPosition] = i new_positions[oldPosition] = i
i += 1 i += 1
# replace the palette color id of all pixel with the new id # replace the palette color id of all pixel with the new id
imageBytes = bytearray(im.tobytes()) image_bytes = bytearray(im.tobytes())
for i in range(len(imageBytes)): for i in range(len(image_bytes)):
imageBytes[i] = newPositions[imageBytes[i]] image_bytes[i] = new_positions[image_bytes[i]]
im.frombytes(bytes(imageBytes)) im.frombytes(bytes(image_bytes))
newPaletteBytes = paletteBytes + (768 - len(paletteBytes)) * b'\x00' new_palette_bytes = (palette_bytes +
im.putpalette(newPaletteBytes) (768 - len(palette_bytes)) * b'\x00')
im.palette = ImagePalette.ImagePalette("RGB", palette = paletteBytes, size = len(paletteBytes)) im.putpalette(new_palette_bytes)
im.palette = ImagePalette.ImagePalette("RGB",
palette=palette_bytes,
size=len(palette_bytes))
if not paletteBytes: if not palette_bytes:
paletteBytes = sourcePalette palette_bytes = source_palette
# Logical Screen Descriptor # Logical Screen Descriptor
# calculate the palette size for the header # calculate the palette size for the header
import math import math
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1 color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
if colorTableSize < 0: colorTableSize = 0 if color_table_size < 0:
color_table_size = 0
# size of global color table + global color table flag # size of global color table + global color table flag
header.append(o8(colorTableSize + 128)) header.append(o8(color_table_size + 128))
# background + reserved/aspect # background + reserved/aspect
header.append(o8(0) + o8(0)) header.append(o8(0) + o8(0))
# end of Logical Screen Descriptor # end of Logical Screen Descriptor
# add the missing amount of bytes # add the missing amount of bytes
# the palette has to be 2<<n in size # the palette has to be 2<<n in size
actualTargetSizeDiff = (2<<colorTableSize) - len(paletteBytes)//3 actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
if actualTargetSizeDiff > 0: if actual_target_size_diff > 0:
paletteBytes += o8(0) * 3 * actualTargetSizeDiff palette_bytes += o8(0) * 3 * actual_target_size_diff
# Header + Logical Screen Descriptor + Global Color Table # Header + Logical Screen Descriptor + Global Color Table
header.append(paletteBytes) header.append(palette_bytes)
return header, usedPaletteColors return header, used_palette_colors
def getdata(im, offset = (0, 0), **params): def getdata(im, offset=(0, 0), **params):
"""Return a list of strings representing this image. """Return a list of strings representing this image.
The first string is a local image header, the rest contains The first string is a local image header, the rest contains
encoded image data.""" encoded image data."""
class collector: class Collector(object):
data = [] data = []
def write(self, data): def write(self, data):
self.data.append(data) self.data.append(data)
im.load() # make sure raster data is available im.load() # make sure raster data is available
fp = collector() fp = Collector()
try: try:
im.encoderinfo = params im.encoderinfo = params
# local image header # local image header
fp.write(b"," + get_local_header(fp, im, offset, 0)
o16(offset[0]) + # offset
o16(offset[1]) +
o16(im.size[0]) + # size
o16(im.size[1]) +
o8(0) + # flags
o8(8)) # bits
ImageFile._save(im, fp, [("gif", (0,0)+im.size, 0, RAWMODE[im.mode])]) ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
fp.write(b"\0") # end of image data fp.write(b"\0") # end of image data
finally: finally:
del im.encoderinfo del im.encoderinfo

View File

@ -24,6 +24,7 @@ from PIL._binary import o8
EPSILON = 1e-10 EPSILON = 1e-10
def linear(middle, pos): def linear(middle, pos):
if pos <= middle: if pos <= middle:
if middle < EPSILON: if middle < EPSILON:
@ -38,25 +39,30 @@ def linear(middle, pos):
else: else:
return 0.5 + 0.5 * pos / middle return 0.5 + 0.5 * pos / middle
def curved(middle, pos): def curved(middle, pos):
return pos ** (log(0.5) / log(max(middle, EPSILON))) return pos ** (log(0.5) / log(max(middle, EPSILON)))
def sine(middle, pos): def sine(middle, pos):
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0 return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
def sphere_increasing(middle, pos): def sphere_increasing(middle, pos):
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2) return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
def sphere_decreasing(middle, pos): def sphere_decreasing(middle, pos):
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2) return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
SEGMENTS = [ linear, curved, sine, sphere_increasing, sphere_decreasing ] SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
class GradientFile:
class GradientFile(object):
gradient = None gradient = None
def getpalette(self, entries = 256): def getpalette(self, entries=256):
palette = [] palette = []
@ -89,6 +95,7 @@ class GradientFile:
return b"".join(palette), "RGBA" return b"".join(palette), "RGBA"
## ##
# File handler for GIMP's gradient format. # File handler for GIMP's gradient format.
@ -99,7 +106,13 @@ class GimpGradientFile(GradientFile):
if fp.readline()[:13] != b"GIMP Gradient": if fp.readline()[:13] != b"GIMP Gradient":
raise SyntaxError("not a GIMP gradient file") raise SyntaxError("not a GIMP gradient file")
count = int(fp.readline()) line = fp.readline()
# GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
if line.startswith(b"Name: "):
line = fp.readline().strip()
count = int(line)
gradient = [] gradient = []
@ -108,13 +121,13 @@ class GimpGradientFile(GradientFile):
s = fp.readline().split() s = fp.readline().split()
w = [float(x) for x in s[:11]] w = [float(x) for x in s[:11]]
x0, x1 = w[0], w[2] x0, x1 = w[0], w[2]
xm = w[1] xm = w[1]
rgb0 = w[3:7] rgb0 = w[3:7]
rgb1 = w[7:11] rgb1 = w[7:11]
segment = SEGMENTS[int(s[11])] segment = SEGMENTS[int(s[11])]
cspace = int(s[12]) cspace = int(s[12])
if cspace != 0: if cspace != 0:
raise IOError("cannot handle HSV colour space") raise IOError("cannot handle HSV colour space")

View File

@ -17,10 +17,11 @@
import re import re
from PIL._binary import o8 from PIL._binary import o8
## ##
# File handler for GIMP's palette format. # File handler for GIMP's palette format.
class GimpPaletteFile: class GimpPaletteFile(object):
rawmode = "RGB" rawmode = "RGB"
@ -56,7 +57,6 @@ class GimpPaletteFile:
self.palette = b"".join(self.palette) self.palette = b"".join(self.palette)
def getpalette(self): def getpalette(self):
return self.palette, self.rawmode return self.palette, self.rawmode

View File

@ -13,6 +13,7 @@ from PIL import Image, ImageFile
_handler = None _handler = None
## ##
# Install application-specific GRIB image handler. # Install application-specific GRIB image handler.
# #
@ -22,12 +23,14 @@ def register_handler(handler):
global _handler global _handler
_handler = handler _handler = handler
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01' return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01'
class GribStubImageFile(ImageFile.StubImageFile): class GribStubImageFile(ImageFile.StubImageFile):
format = "GRIB" format = "GRIB"
@ -53,6 +56,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
def _load(self): def _load(self):
return _handler return _handler
def _save(im, fp, filename): def _save(im, fp, filename):
if _handler is None or not hasattr("_handler", "save"): if _handler is None or not hasattr("_handler", "save"):
raise IOError("GRIB save handler not installed") raise IOError("GRIB save handler not installed")

View File

@ -13,6 +13,7 @@ from PIL import Image, ImageFile
_handler = None _handler = None
## ##
# Install application-specific HDF5 image handler. # Install application-specific HDF5 image handler.
# #
@ -22,12 +23,14 @@ def register_handler(handler):
global _handler global _handler
_handler = handler _handler = handler
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[:8] == b"\x89HDF\r\n\x1a\n" return prefix[:8] == b"\x89HDF\r\n\x1a\n"
class HDF5StubImageFile(ImageFile.StubImageFile): class HDF5StubImageFile(ImageFile.StubImageFile):
format = "HDF5" format = "HDF5"

View File

@ -16,7 +16,12 @@
# #
from PIL import Image, ImageFile, PngImagePlugin, _binary from PIL import Image, ImageFile, PngImagePlugin, _binary
import struct, io import io
import os
import shutil
import struct
import sys
import tempfile
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
if enable_jpeg2k: if enable_jpeg2k:
@ -26,9 +31,11 @@ i8 = _binary.i8
HEADERSIZE = 8 HEADERSIZE = 8
def nextheader(fobj): def nextheader(fobj):
return struct.unpack('>4sI', fobj.read(HEADERSIZE)) return struct.unpack('>4sI', fobj.read(HEADERSIZE))
def read_32t(fobj, start_length, size): def read_32t(fobj, start_length, size):
# The 128x128 icon seems to have an extra header for some reason. # The 128x128 icon seems to have an extra header for some reason.
(start, length) = start_length (start, length) = start_length
@ -38,6 +45,7 @@ def read_32t(fobj, start_length, size):
raise SyntaxError('Unknown signature, expecting 0x00000000') raise SyntaxError('Unknown signature, expecting 0x00000000')
return read_32(fobj, (start + 4, length - 4), size) return read_32(fobj, (start + 4, length - 4), size)
def read_32(fobj, start_length, size): def read_32(fobj, start_length, size):
""" """
Read a 32bit RGB icon resource. Seems to be either uncompressed or Read a 32bit RGB icon resource. Seems to be either uncompressed or
@ -83,9 +91,10 @@ def read_32(fobj, start_length, size):
im.im.putband(band.im, band_ix) im.im.putband(band.im, band_ix)
return {"RGB": im} return {"RGB": im}
def read_mk(fobj, start_length, size): def read_mk(fobj, start_length, size):
# Alpha masks seem to be uncompressed # Alpha masks seem to be uncompressed
(start, length) = start_length start = start_length[0]
fobj.seek(start) fobj.seek(start)
pixel_size = (size[0] * size[2], size[1] * size[2]) pixel_size = (size[0] * size[2], size[1] * size[2])
sizesq = pixel_size[0] * pixel_size[1] sizesq = pixel_size[0] * pixel_size[1]
@ -94,6 +103,7 @@ def read_mk(fobj, start_length, size):
) )
return {"A": band} return {"A": band}
def read_png_or_jpeg2000(fobj, start_length, size): def read_png_or_jpeg2000(fobj, start_length, size):
(start, length) = start_length (start, length) = start_length
fobj.seek(start) fobj.seek(start)
@ -103,10 +113,11 @@ def read_png_or_jpeg2000(fobj, start_length, size):
im = PngImagePlugin.PngImageFile(fobj) im = PngImagePlugin.PngImageFile(fobj)
return {"RGBA": im} return {"RGBA": im}
elif sig[:4] == b'\xff\x4f\xff\x51' \ elif sig[:4] == b'\xff\x4f\xff\x51' \
or sig[:4] == b'\x0d\x0a\x87\x0a' \ or sig[:4] == b'\x0d\x0a\x87\x0a' \
or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
if not enable_jpeg2k: if not enable_jpeg2k:
raise ValueError('Unsupported icon subimage format (rebuild PIL with JPEG 2000 support to fix this)') raise ValueError('Unsupported icon subimage format (rebuild PIL '
'with JPEG 2000 support to fix this)')
# j2k, jpc or j2c # j2k, jpc or j2c
fobj.seek(start) fobj.seek(start)
jp2kstream = fobj.read(length) jp2kstream = fobj.read(length)
@ -118,7 +129,8 @@ def read_png_or_jpeg2000(fobj, start_length, size):
else: else:
raise ValueError('Unsupported icon subimage format') raise ValueError('Unsupported icon subimage format')
class IcnsFile:
class IcnsFile(object):
SIZES = { SIZES = {
(512, 512, 2): [ (512, 512, 2): [
@ -233,12 +245,13 @@ class IcnsFile:
pass pass
return im return im
## ##
# Image plugin for Mac OS icons. # Image plugin for Mac OS icons.
class IcnsImageFile(ImageFile.ImageFile): class IcnsImageFile(ImageFile.ImageFile):
""" """
PIL read-only image support for Mac OS .icns files. PIL image support for Mac OS .icns files.
Chooses the best resolution, but will possibly load Chooses the best resolution, but will possibly load
a different size image if you mutate the size attribute a different size image if you mutate the size attribute
before calling 'load'. before calling 'load'.
@ -284,11 +297,64 @@ class IcnsImageFile(ImageFile.ImageFile):
self.tile = () self.tile = ()
self.load_end() self.load_end()
def _save(im, fp, filename):
"""
Saves the image as a series of PNG files,
that are then converted to a .icns file
using the OS X command line utility 'iconutil'.
OS X only.
"""
try:
fp.flush()
except:
pass
# create the temporary set of pngs
iconset = tempfile.mkdtemp('.iconset')
last_w = None
last_im = None
for w in [16, 32, 128, 256, 512]:
prefix = 'icon_{}x{}'.format(w, w)
if last_w == w:
im_scaled = last_im
else:
im_scaled = im.resize((w, w), Image.LANCZOS)
im_scaled.save(os.path.join(iconset, prefix+'.png'))
im_scaled = im.resize((w*2, w*2), Image.LANCZOS)
im_scaled.save(os.path.join(iconset, prefix+'@2x.png'))
last_im = im_scaled
# iconutil -c icns -o {} {}
from subprocess import Popen, PIPE, CalledProcessError
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
stderr = tempfile.TemporaryFile()
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr)
convert_proc.stdout.close()
retcode = convert_proc.wait()
# remove the temporary files
shutil.rmtree(iconset)
if retcode:
raise CalledProcessError(retcode, convert_cmd)
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns') Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
Image.register_extension("ICNS", '.icns') Image.register_extension("ICNS", '.icns')
if sys.platform == 'darwin':
Image.register_save("ICNS", _save)
Image.register_mime("ICNS", "image/icns")
if __name__ == '__main__': if __name__ == '__main__':
import os, sys
imf = IcnsImageFile(open(sys.argv[1], 'rb')) imf = IcnsImageFile(open(sys.argv[1], 'rb'))
for size in imf.info['sizes']: for size in imf.info['sizes']:
imf.size = size imf.size = size

View File

@ -13,7 +13,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis <casadebender@gmail.com>. # This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
# <casadebender@gmail.com>.
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin # https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
# #
# Icon format references: # Icon format references:
@ -23,6 +24,9 @@
__version__ = "0.1" __version__ = "0.1"
import struct
from io import BytesIO
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
from math import log, ceil from math import log, ceil
@ -35,11 +39,47 @@ i32 = _binary.i32le
_MAGIC = b"\0\0\1\0" _MAGIC = b"\0\0\1\0"
def _save(im, fp, filename):
fp.write(_MAGIC) # (2+2)
sizes = im.encoderinfo.get("sizes",
[(16, 16), (24, 24), (32, 32), (48, 48),
(64, 64), (128, 128), (255, 255)])
width, height = im.size
filter(lambda x: False if (x[0] > width or x[1] > height or
x[0] > 255 or x[1] > 255) else True, sizes)
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
offset = fp.tell() + len(sizes)*16
for size in sizes:
width, height = size
fp.write(struct.pack("B", width)) # bWidth(1)
fp.write(struct.pack("B", height)) # bHeight(1)
fp.write(b"\0") # bColorCount(1)
fp.write(b"\0") # bReserved(1)
fp.write(b"\0\0") # wPlanes(2)
fp.write(struct.pack("<H", 32)) # wBitCount(2)
image_io = BytesIO()
tmp = im.copy()
tmp.thumbnail(size, Image.LANCZOS)
tmp.save(image_io, "png")
image_io.seek(0)
image_bytes = image_io.read()
bytes_len = len(image_bytes)
fp.write(struct.pack("<I", bytes_len)) # dwBytesInRes(4)
fp.write(struct.pack("<I", offset)) # dwImageOffset(4)
current = fp.tell()
fp.seek(offset)
fp.write(image_bytes)
offset = offset + bytes_len
fp.seek(current)
def _accept(prefix): def _accept(prefix):
return prefix[:4] == _MAGIC return prefix[:4] == _MAGIC
class IcoFile: class IcoFile(object):
def __init__(self, buf): def __init__(self, buf):
""" """
Parse image from file-like object containing ico file data Parse image from file-like object containing ico file data
@ -63,7 +103,7 @@ class IcoFile:
icon_header = { icon_header = {
'width': i8(s[0]), 'width': i8(s[0]),
'height': i8(s[1]), 'height': i8(s[1]),
'nb_color': i8(s[2]), # Number of colors in image (0 if >=8bpp) 'nb_color': i8(s[2]), # No. of colors in image (0 if >=8bpp)
'reserved': i8(s[3]), 'reserved': i8(s[3]),
'planes': i16(s[4:]), 'planes': i16(s[4:]),
'bpp': i16(s[6:]), 'bpp': i16(s[6:]),
@ -78,10 +118,14 @@ class IcoFile:
# See Wikipedia notes about color depth. # See Wikipedia notes about color depth.
# We need this just to differ images with equal sizes # We need this just to differ images with equal sizes
icon_header['color_depth'] = (icon_header['bpp'] or (icon_header['nb_color'] != 0 and ceil(log(icon_header['nb_color'],2))) or 256) icon_header['color_depth'] = (icon_header['bpp'] or
(icon_header['nb_color'] != 0 and
ceil(log(icon_header['nb_color'],
2))) or 256)
icon_header['dim'] = (icon_header['width'], icon_header['height']) icon_header['dim'] = (icon_header['width'], icon_header['height'])
icon_header['square'] = icon_header['width'] * icon_header['height'] icon_header['square'] = (icon_header['width'] *
icon_header['height'])
self.entry.append(icon_header) self.entry.append(icon_header)
@ -102,7 +146,7 @@ class IcoFile:
Get an image from the icon Get an image from the icon
""" """
for (i, h) in enumerate(self.entry): for (i, h) in enumerate(self.entry):
if size == h['dim'] and (bpp == False or bpp == h['color_depth']): if size == h['dim'] and (bpp is False or bpp == h['color_depth']):
return self.frame(i) return self.frame(i)
return self.frame(0) return self.frame(0)
@ -127,7 +171,7 @@ class IcoFile:
# change tile dimension to only encompass XOR image # change tile dimension to only encompass XOR image
im.size = (im.size[0], int(im.size[1] / 2)) im.size = (im.size[0], int(im.size[1] / 2))
d, e, o, a = im.tile[0] d, e, o, a = im.tile[0]
im.tile[0] = d, (0,0) + im.size, o, a im.tile[0] = d, (0, 0) + im.size, o, a
# figure out where AND mask image starts # figure out where AND mask image starts
mode = a[0] mode = a[0]
@ -139,8 +183,9 @@ class IcoFile:
if 32 == bpp: if 32 == bpp:
# 32-bit color depth icon image allows semitransparent areas # 32-bit color depth icon image allows semitransparent areas
# PIL's DIB format ignores transparency bits, recover them # PIL's DIB format ignores transparency bits, recover them.
# The DIB is packed in BGRX byte order where X is the alpha channel # The DIB is packed in BGRX byte order where X is the alpha
# channel.
# Back up to start of bmp data # Back up to start of bmp data
self.buf.seek(o) self.buf.seek(o)
@ -162,9 +207,11 @@ class IcoFile:
# bitmap row data is aligned to word boundaries # bitmap row data is aligned to word boundaries
w += 32 - (im.size[0] % 32) w += 32 - (im.size[0] % 32)
# the total mask data is padded row size * height / bits per char # the total mask data is
# padded row size * height / bits per char
and_mask_offset = o + int(im.size[0] * im.size[1] * (bpp / 8.0)) and_mask_offset = o + int(im.size[0] * im.size[1] *
(bpp / 8.0))
total_bytes = int((w * im.size[1]) / 8) total_bytes = int((w * im.size[1]) / 8)
self.buf.seek(and_mask_offset) self.buf.seek(and_mask_offset)
@ -187,6 +234,7 @@ class IcoFile:
return im return im
## ##
# Image plugin for Windows Icon files. # Image plugin for Windows Icon files.
@ -194,15 +242,16 @@ class IcoImageFile(ImageFile.ImageFile):
""" """
PIL read-only image support for Microsoft Windows .ico files. PIL read-only image support for Microsoft Windows .ico files.
By default the largest resolution image in the file will be loaded. This can By default the largest resolution image in the file will be loaded. This
be changed by altering the 'size' attribute before calling 'load'. can be changed by altering the 'size' attribute before calling 'load'.
The info dictionary has a key 'sizes' that is a list of the sizes available The info dictionary has a key 'sizes' that is a list of the sizes available
in the icon file. in the icon file.
Handles classic, XP and Vista icon formats. Handles classic, XP and Vista icon formats.
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis <casadebender@gmail.com>. This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
<casadebender@gmail.com>.
https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
""" """
format = "ICO" format = "ICO"
@ -222,12 +271,13 @@ class IcoImageFile(ImageFile.ImageFile):
self.mode = im.mode self.mode = im.mode
self.size = im.size self.size = im.size
def load_seek(self): def load_seek(self):
# Flage the ImageFile.Parser so that it just does all the decode at the end. # Flag the ImageFile.Parser so that it
# just does all the decode at the end.
pass pass
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
Image.register_open("ICO", IcoImageFile, _accept) Image.register_open("ICO", IcoImageFile, _accept)
Image.register_save("ICO", _save)
Image.register_extension("ICO", ".ico") Image.register_extension("ICO", ".ico")

View File

@ -30,7 +30,7 @@ __version__ = "0.7"
import re import re
from PIL import Image, ImageFile, ImagePalette from PIL import Image, ImageFile, ImagePalette
from PIL._binary import i8, o8 from PIL._binary import i8
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -46,8 +46,8 @@ SCALE = "Scale (x,y)"
SIZE = "Image size (x*y)" SIZE = "Image size (x*y)"
MODE = "Image type" MODE = "Image type"
TAGS = { COMMENT:0, DATE:0, EQUIPMENT:0, FRAMES:0, LUT:0, NAME:0, TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0,
SCALE:0, SIZE:0, MODE:0 } SCALE: 0, SIZE: 0, MODE: 0}
OPEN = { OPEN = {
# ifunc93/p3cfunc formats # ifunc93/p3cfunc formats
@ -94,12 +94,14 @@ for i in range(2, 33):
split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$") split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
def number(s): def number(s):
try: try:
return int(s) return int(s)
except ValueError: except ValueError:
return float(s) return float(s)
## ##
# Image plugin for the IFUNC IM file format. # Image plugin for the IFUNC IM file format.
@ -113,7 +115,7 @@ class ImImageFile(ImageFile.ImageFile):
# Quick rejection: if there's not an LF among the first # Quick rejection: if there's not an LF among the first
# 100 bytes, this is (probably) not a text header. # 100 bytes, this is (probably) not a text header.
if not b"\n" in self.fp.read(100): if b"\n" not in self.fp.read(100):
raise SyntaxError("not an IM file") raise SyntaxError("not an IM file")
self.fp.seek(0) self.fp.seek(0)
@ -155,10 +157,10 @@ class ImImageFile(ImageFile.ImageFile):
if m: if m:
k, v = m.group(1,2) k, v = m.group(1, 2)
# Don't know if this is the correct encoding, but a decent guess # Don't know if this is the correct encoding,
# (I guess) # but a decent guess (I guess)
k = k.decode('latin-1', 'replace') k = k.decode('latin-1', 'replace')
v = v.decode('latin-1', 'replace') v = v.decode('latin-1', 'replace')
@ -186,7 +188,8 @@ class ImImageFile(ImageFile.ImageFile):
else: else:
raise SyntaxError("Syntax error in IM header: " + s.decode('ascii', 'replace')) raise SyntaxError("Syntax error in IM header: " +
s.decode('ascii', 'replace'))
if not n: if not n:
raise SyntaxError("Not an IM file") raise SyntaxError("Not an IM file")
@ -204,8 +207,8 @@ class ImImageFile(ImageFile.ImageFile):
if LUT in self.info: if LUT in self.info:
# convert lookup table to palette or lut attribute # convert lookup table to palette or lut attribute
palette = self.fp.read(768) palette = self.fp.read(768)
greyscale = 1 # greyscale palette greyscale = 1 # greyscale palette
linear = 1 # linear greyscale palette linear = 1 # linear greyscale palette
for i in range(256): for i in range(256):
if palette[i] == palette[i+256] == palette[i+512]: if palette[i] == palette[i+256] == palette[i+512]:
if i8(palette[i]) != i: if i8(palette[i]) != i:
@ -230,7 +233,7 @@ class ImImageFile(ImageFile.ImageFile):
self.__offset = offs = self.fp.tell() self.__offset = offs = self.fp.tell()
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
if self.rawmode[:2] == "F;": if self.rawmode[:2] == "F;":
@ -239,7 +242,7 @@ class ImImageFile(ImageFile.ImageFile):
# use bit decoder (if necessary) # use bit decoder (if necessary)
bits = int(self.rawmode[2:]) bits = int(self.rawmode[2:])
if bits not in [8, 16, 32]: if bits not in [8, 16, 32]:
self.tile = [("bit", (0,0)+self.size, offs, self.tile = [("bit", (0, 0)+self.size, offs,
(bits, 8, 3, 0, -1))] (bits, 8, 3, 0, -1))]
return return
except ValueError: except ValueError:
@ -249,12 +252,17 @@ class ImImageFile(ImageFile.ImageFile):
# Old LabEye/3PC files. Would be very surprised if anyone # Old LabEye/3PC files. Would be very surprised if anyone
# ever stumbled upon such a file ;-) # ever stumbled upon such a file ;-)
size = self.size[0] * self.size[1] size = self.size[0] * self.size[1]
self.tile = [("raw", (0,0)+self.size, offs, ("G", 0, -1)), self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)),
("raw", (0,0)+self.size, offs+size, ("R", 0, -1)), ("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)),
("raw", (0,0)+self.size, offs+2*size, ("B", 0, -1))] ("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))]
else: else:
# LabEye/IFUNC files # LabEye/IFUNC files
self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))] self.tile = [("raw", (0, 0)+self.size, offs,
(self.rawmode, 0, -1))]
@property
def n_frames(self):
return self.info[FRAMES]
def seek(self, frame): def seek(self, frame):
@ -276,7 +284,7 @@ class ImImageFile(ImageFile.ImageFile):
self.fp = self.__fp self.fp = self.__fp
self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))] self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]
def tell(self): def tell(self):
@ -305,10 +313,11 @@ SAVE = {
"YCbCr": ("YCC", "YCbCr;L") "YCbCr": ("YCC", "YCbCr;L")
} }
def _save(im, fp, filename, check=0): def _save(im, fp, filename, check=0):
try: try:
type, rawmode = SAVE[im.mode] image_type, rawmode = SAVE[im.mode]
except KeyError: except KeyError:
raise ValueError("Cannot save %s images as IM" % im.mode) raise ValueError("Cannot save %s images as IM" % im.mode)
@ -320,7 +329,7 @@ def _save(im, fp, filename, check=0):
if check: if check:
return check return check
fp.write(("Image type: %s image\r\n" % type).encode('ascii')) fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
if filename: if filename:
fp.write(("Name: %s\r\n" % filename).encode('ascii')) fp.write(("Name: %s\r\n" % filename).encode('ascii'))
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii')) fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
@ -329,8 +338,8 @@ def _save(im, fp, filename, check=0):
fp.write(b"Lut: 1\r\n") fp.write(b"Lut: 1\r\n")
fp.write(b"\000" * (511-fp.tell()) + b"\032") fp.write(b"\000" * (511-fp.tell()) + b"\032")
if im.mode == "P": if im.mode == "P":
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, -1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))])
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -34,7 +34,8 @@ import warnings
class DecompressionBombWarning(RuntimeWarning): class DecompressionBombWarning(RuntimeWarning):
pass pass
class _imaging_not_installed:
class _imaging_not_installed(object):
# 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")
@ -54,10 +55,11 @@ except ImportError:
pass pass
try: try:
# If the _imaging C module is not present, you can still use # If the _imaging C module is not present, Pillow will not load.
# the "open" function to identify files, but you cannot load # Note that other modules should not refer to _imaging directly;
# them. Note that other modules should not refer to _imaging # import Image and use the Image.core variable instead.
# directly; import Image and use the Image.core variable instead. # Also note that Image.core is not a publicly documented interface,
# and should be considered private and subject to change.
from PIL import _imaging as core from PIL import _imaging as core
if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None): if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
raise ImportError("The _imaging extension was built for another " raise ImportError("The _imaging extension was built for another "
@ -90,6 +92,7 @@ except ImportError as v:
RuntimeWarning RuntimeWarning
) )
# Fail here anyway. Don't let people run with a mostly broken Pillow. # Fail here anyway. Don't let people run with a mostly broken Pillow.
# see docs/porting-pil-to-pillow.rst
raise raise
try: try:
@ -106,6 +109,8 @@ from PIL._util import deferred_error
import os import os
import sys import sys
import io
import struct
# type stuff # type stuff
import collections import collections
@ -149,6 +154,7 @@ FLIP_TOP_BOTTOM = 1
ROTATE_90 = 2 ROTATE_90 = 2
ROTATE_180 = 3 ROTATE_180 = 3
ROTATE_270 = 4 ROTATE_270 = 4
TRANSPOSE = 5
# transforms # transforms
AFFINE = 0 AFFINE = 0
@ -158,11 +164,10 @@ QUAD = 3
MESH = 4 MESH = 4
# resampling filters # resampling filters
NONE = 0 NEAREST = NONE = 0
NEAREST = 0 LANCZOS = ANTIALIAS = 1
ANTIALIAS = 1 # 3-lobed lanczos BILINEAR = LINEAR = 2
LINEAR = BILINEAR = 2 BICUBIC = CUBIC = 3
CUBIC = BICUBIC = 3
# dithers # dithers
NONE = 0 NONE = 0
@ -382,7 +387,7 @@ def init():
for plugin in _plugins: for plugin in _plugins:
try: try:
if DEBUG: if DEBUG:
print ("Importing %s" % plugin) print("Importing %s" % plugin)
__import__("PIL.%s" % plugin, globals(), locals(), []) __import__("PIL.%s" % plugin, globals(), locals(), [])
except ImportError: except ImportError:
if DEBUG: if DEBUG:
@ -438,7 +443,7 @@ def coerce_e(value):
return value if isinstance(value, _E) else _E(value) return value if isinstance(value, _E) else _E(value)
class _E: class _E(object):
def __init__(self, data): def __init__(self, data):
self.data = data self.data = data
@ -473,7 +478,7 @@ def _getscaleoffset(expr):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Implementation wrapper # Implementation wrapper
class Image: class Image(object):
""" """
This class represents an image object. To create This class represents an image object. To create
:py:class:`~PIL.Image.Image` objects, use the appropriate factory :py:class:`~PIL.Image.Image` objects, use the appropriate factory
@ -530,7 +535,7 @@ class Image:
""" """
Closes the file pointer, if possible. Closes the file pointer, if possible.
This operation will destroy the image core and release it's memory. This operation will destroy the image core and release its memory.
The image data will be unusable afterward. The image data will be unusable afterward.
This function is only required to close images that have not This function is only required to close images that have not
@ -541,7 +546,7 @@ class Image:
self.fp.close() self.fp.close()
except Exception as msg: except Exception as msg:
if 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
# deferred error that will better explain that the core image # deferred error that will better explain that the core image
@ -595,6 +600,16 @@ class Image:
id(self) id(self)
) )
def _repr_png_(self):
""" iPython display hook support
:returns: png version of the image as bytes
"""
from io import BytesIO
b = BytesIO()
self.save(b, 'PNG')
return b.getvalue()
def __getattr__(self, name): def __getattr__(self, name):
if name == "__array_interface__": if name == "__array_interface__":
# numpy array interface support # numpy array interface support
@ -622,7 +637,7 @@ class Image:
self.mode = mode self.mode = mode
self.size = size self.size = size
self.im = core.new(mode, size) self.im = core.new(mode, size)
if mode in ("L", "P"): if mode in ("L", "P") and palette:
self.putpalette(palette) self.putpalette(palette)
self.frombytes(data) self.frombytes(data)
@ -664,6 +679,10 @@ class Image:
# Declare tostring as alias to tobytes # Declare tostring as alias to tobytes
def tostring(self, *args, **kw): def tostring(self, *args, **kw):
"""Deprecated alias to tobytes.
.. deprecated:: 2.0
"""
warnings.warn( warnings.warn(
'tostring() is deprecated. Please call tobytes() instead.', 'tostring() is deprecated. Please call tobytes() instead.',
DeprecationWarning, DeprecationWarning,
@ -737,6 +756,7 @@ class Image:
associated with the image. associated with the image.
:returns: An image access object. :returns: An image access object.
:rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
""" """
if self.im and self.palette and self.palette.dirty: if self.im and self.palette and self.palette.dirty:
# realize palette # realize palette
@ -796,7 +816,7 @@ class Image:
use other thresholds, use the :py:meth:`~PIL.Image.Image.point` use other thresholds, use the :py:meth:`~PIL.Image.Image.point`
method. method.
:param mode: The requested mode. :param mode: The requested mode. See: :ref:`concept-modes`.
:param matrix: An optional conversion matrix. If given, this :param matrix: An optional conversion matrix. If given, this
should be 4- or 16-tuple containing floating point values. should be 4- or 16-tuple containing floating point values.
:param dither: Dithering method, used when converting from :param dither: Dithering method, used when converting from
@ -847,8 +867,9 @@ class Image:
t = self.info['transparency'] t = self.info['transparency']
if isinstance(t, bytes): if isinstance(t, bytes):
# Dragons. This can't be represented by a single color # Dragons. This can't be represented by a single color
warnings.warn('Palette images with Transparency expressed ' + warnings.warn('Palette images with Transparency ' +
' in bytes should be converted to RGBA images') ' expressed in bytes should be converted ' +
'to RGBA images')
delete_trns = True delete_trns = True
else: else:
# get the new transparency color. # get the new transparency color.
@ -864,11 +885,20 @@ class Image:
# can't just retrieve the palette number, got to do it # can't just retrieve the palette number, got to do it
# after quantization. # after quantization.
trns_im = trns_im.convert('RGB') trns_im = trns_im.convert('RGB')
trns = trns_im.getpixel((0,0)) trns = trns_im.getpixel((0, 0))
elif self.mode == 'P' and mode == 'RGBA': elif self.mode == 'P' and mode == 'RGBA':
t = self.info['transparency']
delete_trns = True delete_trns = True
if isinstance(t, bytes):
self.im.putpalettealphas(t)
elif isinstance(t, int):
self.im.putpalettealpha(t, 0)
else:
raise ValueError("Transparency for P mode should" +
" be bytes or int")
if mode == "P" and palette == ADAPTIVE: if mode == "P" and palette == ADAPTIVE:
im = self.im.quantize(colors) im = self.im.quantize(colors)
new = self._new(im) new = self._new(im)
@ -920,14 +950,19 @@ class Image:
return new_im return new_im
def quantize(self, colors=256, method=None, kmeans=0, palette=None): def quantize(self, colors=256, method=None, kmeans=0, palette=None):
"""
Convert the image to 'P' mode with the specified number
of colors.
# methods: :param colors: The desired number of colors, <= 256
# 0 = median cut :param method: 0 = median cut
# 1 = maximum coverage 1 = maximum coverage
# 2 = fast octree 2 = fast octree
:param kmeans: Integer
:param palette: Quantize to the :py:class:`PIL.ImagingPalette` palette.
:returns: A new image
# NOTE: this functionality will be moved to the extended """
# quantizer interface in a later version of PIL.
self.load() self.load()
@ -994,8 +1029,6 @@ class Image:
def draft(self, mode, size): def draft(self, mode, size):
""" """
NYI
Configures the image file loader so it returns a version of the Configures the image file loader so it returns a version of the
image that as closely as possible matches the given mode and image that as closely as possible matches the given mode and
size. For example, you can use this method to convert a color size. For example, you can use this method to convert a color
@ -1255,11 +1288,11 @@ class Image:
images (in the latter case, the alpha band is used as mask). images (in the latter case, the alpha band is used as mask).
Where the mask is 255, the given image is copied as is. Where Where the mask is 255, the given image is copied as is. Where
the mask is 0, the current value is preserved. Intermediate the mask is 0, the current value is preserved. Intermediate
values can be used for transparency effects. values will mix the two images together, including their alpha
channels if they have them.
Note that if you paste an "RGBA" image, the alpha band is See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
ignored. You can work around this by using the same image as combine images with respect to their alpha channels.
both source image and mask.
:param im: Source image or pixel value (integer or tuple). :param im: Source image or pixel value (integer or tuple).
:param box: An optional 4-tuple giving the region to paste into. :param box: An optional 4-tuple giving the region to paste into.
@ -1500,36 +1533,30 @@ class Image:
(width, height). (width, height).
:param resample: 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),
environment), :py:attr:`PIL.Image.BICUBIC` (cubic spline :py:attr:`PIL.Image.BICUBIC` (cubic spline interpolation), or
interpolation in a 4x4 environment), or :py:attr:`PIL.Image.LANCZOS` (a high-quality downsampling filter).
:py:attr:`PIL.Image.ANTIALIAS` (a high-quality downsampling filter).
If omitted, or if the image has mode "1" or "P", it is If omitted, or if the image has mode "1" or "P", it is
set :py:attr:`PIL.Image.NEAREST`. set :py:attr:`PIL.Image.NEAREST`.
:returns: An :py:class:`~PIL.Image.Image` object. :returns: An :py:class:`~PIL.Image.Image` object.
""" """
if resample not in (NEAREST, BILINEAR, BICUBIC, ANTIALIAS): if resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS):
raise ValueError("unknown resampling filter") raise ValueError("unknown resampling filter")
self.load() self.load()
size = tuple(size)
if self.size == size:
return self._new(self.im)
if self.mode in ("1", "P"): if self.mode in ("1", "P"):
resample = NEAREST resample = NEAREST
if self.mode == 'RGBA': if self.mode == 'RGBA':
return self.convert('RGBa').resize(size, resample).convert('RGBA') return self.convert('RGBa').resize(size, resample).convert('RGBA')
if resample == ANTIALIAS: return self._new(self.im.resize(size, resample))
# requires stretch support (imToolkit & PIL 1.1.3)
try:
im = self.im.stretch(size, resample)
except AttributeError:
raise ValueError("unsupported resampling filter")
else:
im = self.im.resize(size, resample)
return self._new(im)
def rotate(self, angle, resample=NEAREST, expand=0): def rotate(self, angle, resample=NEAREST, expand=0):
""" """
@ -1600,15 +1627,16 @@ class Image:
Keyword options can be used to provide additional instructions Keyword options can be used to provide additional instructions
to the writer. If a writer doesn't recognise an option, it is to the writer. If a writer doesn't recognise an option, it is
silently ignored. The available options are described later in silently ignored. The available options are described in the
this handbook. :doc:`image format documentation
<../handbook/image-file-formats>` for each writer.
You can use a file object instead of a filename. In this case, You can use a file object instead of a filename. In this case,
you must always specify the format. The file object must you must always specify the format. The file object must
implement the **seek**, **tell**, and **write** implement the ``seek``, ``tell``, and ``write``
methods, and be opened in binary mode. methods, and be opened in binary mode.
:param file: File name or file object. :param fp: File name or file object.
:param format: Optional format override. If omitted, the :param format: Optional format override. If omitted, the
format to use is determined from the filename extension. format to use is determined from the filename extension.
If a file object was used instead of a filename, this If a file object was used instead of a filename, this
@ -1735,7 +1763,7 @@ class Image:
""" """
return 0 return 0
def thumbnail(self, size, resample=ANTIALIAS): def thumbnail(self, size, resample=BICUBIC):
""" """
Make this image into a thumbnail. This method modifies the Make this image into a thumbnail. This method modifies the
image to contain a thumbnail version of itself, no larger than image to contain a thumbnail version of itself, no larger than
@ -1744,12 +1772,7 @@ class Image:
:py:meth:`~PIL.Image.Image.draft` method to configure the file reader :py:meth:`~PIL.Image.Image.draft` method to configure the file reader
(where applicable), and finally resizes the image. (where applicable), and finally resizes the image.
Note that the bilinear and bicubic filters in the current Note that this function modifies the :py:class:`~PIL.Image.Image`
version of PIL are not well-suited for thumbnail generation.
You should use :py:attr:`PIL.Image.ANTIALIAS` unless speed is much more
important than quality.
Also note that this function modifies the :py:class:`~PIL.Image.Image`
object in place. If you need to use the full resolution image as well, object in place. If you need to use the full resolution image as well,
apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
image. image.
@ -1757,10 +1780,9 @@ class Image:
:param size: Requested size. :param size: Requested size.
:param resample: Optional resampling filter. This can be one :param resample: Optional resampling filter. This can be one
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`, of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS` :py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.LANCZOS`.
(best quality). If omitted, it defaults to If omitted, it defaults to :py:attr:`PIL.Image.BICUBIC`.
:py:attr:`PIL.Image.ANTIALIAS`. (was :py:attr:`PIL.Image.NEAREST` (was :py:attr:`PIL.Image.NEAREST` prior to version 2.5.0)
prior to version 2.5.0)
:returns: None :returns: None
""" """
@ -1779,14 +1801,7 @@ class Image:
self.draft(None, size) self.draft(None, size)
self.load() im = self.resize(size, resample)
try:
im = self.resize(size, resample)
except ValueError:
if resample != ANTIALIAS:
raise
im = self.resize(size, NEAREST) # fallback
self.im = im.im self.im = im.im
self.mode = im.mode self.mode = im.mode
@ -1795,7 +1810,7 @@ class Image:
self.readonly = 0 self.readonly = 0
self.pyaccess = None self.pyaccess = None
# FIXME: the different tranform methods need further explanation # FIXME: the different transform methods need further explanation
# instead of bloating the method docs, add a separate chapter. # instead of bloating the method docs, add a separate chapter.
def transform(self, size, method, data=None, resample=NEAREST, fill=1): def transform(self, size, method, data=None, resample=NEAREST, fill=1):
""" """
@ -1902,12 +1917,22 @@ class Image:
:param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`, :param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`,
:py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`, :py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`,
:py:attr:`PIL.Image.ROTATE_180`, or :py:attr:`PIL.Image.ROTATE_270`. :py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270` or
:py:attr:`PIL.Image.TRANSPOSE`.
:returns: Returns a flipped or rotated copy of this image. :returns: Returns a flipped or rotated copy of this image.
""" """
self.load() self.load()
im = self.im.transpose(method) return self._new(self.im.transpose(method))
def effect_spread(self, distance):
"""
Randomly spread pixels in an image.
:param distance: Distance to spread pixels.
"""
self.load()
im = self.im.effect_spread(distance)
return self._new(im) return self._new(im)
@ -1950,12 +1975,12 @@ class _ImageCrop(Image):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Abstract handlers. # Abstract handlers.
class ImagePointHandler: class ImagePointHandler(object):
# used as a mixin by point transforms (for use with im.point) # used as a mixin by point transforms (for use with im.point)
pass pass
class ImageTransformHandler: class ImageTransformHandler(object):
# used as a mixin by geometry transforms (for use with im.transform) # used as a mixin by geometry transforms (for use with im.transform)
pass pass
@ -1976,7 +2001,8 @@ def new(mode, size, color=0):
""" """
Creates a new image with the given mode and size. Creates a new image with the given mode and size.
:param mode: The mode to use for the new image. :param mode: The mode to use for the new image. See:
:ref:`concept-modes`.
:param size: A 2-tuple, containing (width, height) in pixels. :param size: A 2-tuple, containing (width, height) in pixels.
:param color: What color to use for the image. Default is black. :param color: What color to use for the image. Default is black.
If given, this should be a single integer or floating point value If given, this should be a single integer or floating point value
@ -2009,14 +2035,14 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
You can also use any pixel decoder supported by PIL. For more You can also use any pixel decoder supported by PIL. For more
information on available decoders, see the section information on available decoders, see the section
**Writing Your Own File Decoder**. :ref:`Writing Your Own File Decoder <file-decoders>`.
Note that this function decodes pixel data only, not entire images. Note that this function decodes pixel data only, not entire images.
If you have an entire image in a string, wrap it in a If you have an entire image in a string, wrap it in a
:py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load
it. it.
:param mode: The image mode. :param mode: The image mode. See: :ref:`concept-modes`.
:param size: The image size. :param size: The image size.
:param data: A byte buffer containing raw data for the given mode. :param data: A byte buffer containing raw data for the given mode.
:param decoder_name: What decoder to use. :param decoder_name: What decoder to use.
@ -2068,7 +2094,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
issues a warning if you do this; to disable the warning, you should provide issues a warning if you do this; to disable the warning, you should provide
the full set of parameters. See below for details. the full set of parameters. See below for details.
:param mode: The image mode. :param mode: The image mode. See: :ref:`concept-modes`.
:param size: The image size. :param size: The image size.
:param data: A bytes or other buffer object containing raw :param data: A bytes or other buffer object containing raw
data for the given mode. data for the given mode.
@ -2119,7 +2145,8 @@ def fromarray(obj, mode=None):
:param obj: Object with array interface :param obj: Object with array interface
:param mode: Mode to use (will be determined from type if None) :param mode: Mode to use (will be determined from type if None)
:returns: An image memory. See: :ref:`concept-modes`.
:returns: An image object.
.. versionadded:: 1.1.6 .. versionadded:: 1.1.6
""" """
@ -2222,6 +2249,11 @@ def open(fp, mode="r"):
else: else:
filename = "" filename = ""
try:
fp.seek(0)
except (AttributeError, io.UnsupportedOperation):
fp = io.BytesIO(fp.read())
prefix = fp.read(16) prefix = fp.read(16)
preinit() preinit()
@ -2234,7 +2266,7 @@ def open(fp, mode="r"):
im = factory(fp, filename) im = factory(fp, filename)
_decompression_bomb_check(im.size) _decompression_bomb_check(im.size)
return im return im
except (SyntaxError, IndexError, TypeError): except (SyntaxError, IndexError, TypeError, struct.error):
# import traceback # import traceback
# traceback.print_exc() # traceback.print_exc()
pass pass
@ -2249,7 +2281,7 @@ def open(fp, mode="r"):
im = factory(fp, filename) im = factory(fp, filename)
_decompression_bomb_check(im.size) _decompression_bomb_check(im.size)
return im return im
except (SyntaxError, IndexError, TypeError): except (SyntaxError, IndexError, TypeError, struct.error):
# import traceback # import traceback
# traceback.print_exc() # traceback.print_exc()
pass pass
@ -2306,7 +2338,7 @@ def composite(image1, image2, mask):
:param image1: The first image. :param image1: The first image.
:param image2: The second image. Must have the same mode and :param image2: The second image. Must have the same mode and
size as the first image. size as the first image.
:param mask: A mask image. This image can can have mode :param mask: A mask image. This image can have mode
"1", "L", or "RGBA", and must have the same size as the "1", "L", or "RGBA", and must have the same size as the
other two images. other two images.
""" """
@ -2336,7 +2368,8 @@ def merge(mode, bands):
""" """
Merge a set of single band images into a new multiband image. Merge a set of single band images into a new multiband image.
:param mode: The mode to use for the output image. :param mode: The mode to use for the output image. See:
:ref:`concept-modes`.
:param bands: A sequence containing one single-band image for :param bands: A sequence containing one single-band image for
each band in the output image. All bands must have the each band in the output image. All bands must have the
same size. same size.
@ -2419,3 +2452,32 @@ def _show(image, **options):
def _showxv(image, title=None, **options): def _showxv(image, title=None, **options):
from PIL import ImageShow from PIL import ImageShow
ImageShow.show(image, title, **options) ImageShow.show(image, title, **options)
# --------------------------------------------------------------------
# Effects
def effect_mandelbrot(size, extent, quality):
"""
Generate a Mandelbrot set covering the given extent.
:param size: The requested size in pixels, as a 2-tuple:
(width, height).
:param extent: The extent to cover, as a 4-tuple:
(x0, y0, x1, y2).
:param quality: Quality.
"""
return Image()._new(core.effect_mandelbrot(size, extent, quality))
def effect_noise(size, sigma):
"""
Generate Gaussian noise centered around 128.
:param size: The requested size in pixels, as a 2-tuple:
(width, height).
:param sigma: Standard deviation of noise.
"""
return Image()._new(core.effect_noise(size, sigma))
# End of file

View File

@ -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 # Copyright (c) 2013 by Eric Soroos
## See the README file for information on usage and redistribution. See # See the README file for information on usage and redistribution. See
## below for the original description. # below for the original description.
from __future__ import print_function from __future__ import print_function
@ -64,7 +64,7 @@ pyCMS
0.0.2 alpha Jan 6, 2002 0.0.2 alpha Jan 6, 2002
Added try/except statements arount type() checks of Added try/except statements around type() checks of
potential CObjects... Python won't let you use type() potential CObjects... Python won't let you use type()
on them, and raises a TypeError (stupid, if you ask on them, and raises a TypeError (stupid, if you ask
me!) me!)
@ -90,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 import_err from _util import deferred_error
_imagingcms = import_err(ex) _imagingcms = deferred_error(ex)
from PIL._util import isStringType from PIL._util import isStringType
core = _imagingcms core = _imagingcms
@ -123,8 +123,8 @@ FLAGS = {
"NOTCACHE": 64, # Inhibit 1-pixel cache "NOTCACHE": 64, # Inhibit 1-pixel cache
"NOTPRECALC": 256, "NOTPRECALC": 256,
"NULLTRANSFORM": 512, # Don't transform anyway "NULLTRANSFORM": 512, # Don't transform anyway
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy "HIGHRESPRECALC": 1024, # Use more memory to give better accuracy
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces "LOWRESPRECALC": 2048, # Use less memory to minimize resources
"WHITEBLACKCOMPENSATION": 8192, "WHITEBLACKCOMPENSATION": 8192,
"BLACKPOINTCOMPENSATION": 8192, "BLACKPOINTCOMPENSATION": 8192,
"GAMUTCHECK": 4096, # Out of Gamut alarm "GAMUTCHECK": 4096, # Out of Gamut alarm
@ -147,7 +147,7 @@ for flag in FLAGS.values():
## ##
# Profile. # Profile.
class ImageCmsProfile: class ImageCmsProfile(object):
def __init__(self, profile): def __init__(self, profile):
""" """
@ -184,6 +184,7 @@ class ImageCmsProfile:
return core.profile_tobytes(self.profile) return core.profile_tobytes(self.profile)
class ImageCmsTransform(Image.ImagePointHandler): class ImageCmsTransform(Image.ImagePointHandler):
# Transform. This can be used with the procedural API, or with the # Transform. This can be used with the procedural API, or with the
@ -191,7 +192,6 @@ class ImageCmsTransform(Image.ImagePointHandler):
# #
# Will return the output profile in the output.info['icc_profile']. # 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, proof=None, intent=INTENT_PERCEPTUAL, proof=None,
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0): proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
@ -573,7 +573,7 @@ def applyTransform(im, transform, inPlace=0):
This function applies a pre-calculated transform (from This function applies a pre-calculated transform (from
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
to an 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 calculation 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
the return value, set inPlace to TRUE. This can only be done if the return value, set inPlace to TRUE. This can only be done if
@ -858,7 +858,7 @@ def getDefaultIntent(profile):
If an error occurs while trying to obtain the default intent, a If an error occurs while trying to obtain the default intent, a
PyCMSError is raised. PyCMSError is raised.
Use this function to determine the default (and usually best optomized) Use this function to determine the default (and usually best optimized)
rendering intent for this profile. Most profiles support multiple rendering intent for this profile. Most profiles support multiple
rendering intents, but are intended mostly for one type of conversion. rendering intents, but are intended mostly for one type of conversion.
If you wish to use a different intent than returned, use If you wish to use a different intent than returned, use
@ -914,7 +914,7 @@ def isIntentSupported(profile, intent, direction):
see the pyCMS documentation for details on rendering intents and what see the pyCMS documentation for details on rendering intents and what
they do. they do.
:param direction: Integer specifing if the profile is to be used for input, :param direction: Integer specifying if the profile is to be used for input,
output, or proof output, or proof
INPUT = 0 (or use ImageCms.DIRECTION_INPUT) INPUT = 0 (or use ImageCms.DIRECTION_INPUT)

View File

@ -20,6 +20,7 @@
from PIL import Image from PIL import Image
import re import re
def getrgb(color): def getrgb(color):
""" """
Convert a color string to an RGB tuple. If the string cannot be parsed, Convert a color string to an RGB tuple. If the string cannot be parsed,
@ -86,7 +87,8 @@ def getrgb(color):
int(rgb[1] * 255 + 0.5), int(rgb[1] * 255 + 0.5),
int(rgb[2] * 255 + 0.5) int(rgb[2] * 255 + 0.5)
) )
m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$",
color)
if m: if m:
return ( return (
int(m.group(1)), int(m.group(1)),
@ -96,6 +98,7 @@ def getrgb(color):
) )
raise ValueError("unknown color specifier: %r" % color) raise ValueError("unknown color specifier: %r" % color)
def getcolor(color, mode): def getcolor(color, mode):
""" """
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a

View File

@ -40,13 +40,14 @@ try:
except ImportError: except ImportError:
warnings = None warnings = None
## ##
# A simple 2D drawing interface for PIL images. # A simple 2D drawing interface for PIL images.
# <p> # <p>
# Application code should use the <b>Draw</b> factory, instead of # Application code should use the <b>Draw</b> factory, instead of
# directly. # directly.
class ImageDraw: class ImageDraw(object):
## ##
# Create a drawing instance. # Create a drawing instance.
@ -61,7 +62,7 @@ class ImageDraw:
def __init__(self, im, mode=None): def __init__(self, im, mode=None):
im.load() im.load()
if im.readonly: if im.readonly:
im._copy() # make it writable im._copy() # make it writeable
blend = 0 blend = 0
if mode is None: if mode is None:
mode = im.mode mode = im.mode
@ -85,7 +86,7 @@ class ImageDraw:
# FIXME: fix Fill2 to properly support matte for I+F images # FIXME: fix Fill2 to properly support matte for I+F images
self.fontmode = "1" self.fontmode = "1"
else: else:
self.fontmode = "L" # aliasing is okay for other modes self.fontmode = "L" # aliasing is okay for other modes
self.fill = 0 self.fill = 0
self.font = None self.font = None
@ -280,6 +281,7 @@ class ImageDraw:
font = self.getfont() font = self.getfont()
return font.getsize(text) return font.getsize(text)
## ##
# A simple 2D drawing interface for PIL images. # A simple 2D drawing interface for PIL images.
# #
@ -302,6 +304,7 @@ try:
except: except:
Outline = None Outline = None
## ##
# (Experimental) A more advanced 2D drawing interface for PIL images, # (Experimental) A more advanced 2D drawing interface for PIL images,
# based on the WCK interface. # based on the WCK interface.
@ -325,6 +328,7 @@ def getdraw(im=None, hints=None):
im = handler.Draw(im) im = handler.Draw(im)
return im, handler return im, handler
## ##
# (experimental) Fills a bounded region with a given color. # (experimental) Fills a bounded region with a given color.
# #
@ -344,10 +348,10 @@ def floodfill(image, xy, value, border=None):
try: try:
background = pixel[x, y] background = pixel[x, y]
if background == value: if background == value:
return # seed point already has fill color return # seed point already has fill color
pixel[x, y] = value pixel[x, y] = value
except IndexError: except IndexError:
return # seed point outside image return # seed point outside image
edge = [(x, y)] edge = [(x, y)]
if border is None: if border is None:
while edge: while edge:

View File

@ -18,22 +18,26 @@
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
class Pen:
class Pen(object):
def __init__(self, color, width=1, opacity=255): def __init__(self, color, width=1, opacity=255):
self.color = ImageColor.getrgb(color) self.color = ImageColor.getrgb(color)
self.width = width self.width = width
class Brush:
class Brush(object):
def __init__(self, color, opacity=255): def __init__(self, color, opacity=255):
self.color = ImageColor.getrgb(color) self.color = ImageColor.getrgb(color)
class Font:
class Font(object):
def __init__(self, color, file, size=12): def __init__(self, color, file, size=12):
# FIXME: add support for bitmap fonts # FIXME: add support for bitmap fonts
self.color = ImageColor.getrgb(color) self.color = ImageColor.getrgb(color)
self.font = ImageFont.truetype(file, size) self.font = ImageFont.truetype(file, size)
class Draw:
class Draw(object):
def __init__(self, image, size=None, color=None): def __init__(self, image, size=None, color=None):
if not hasattr(image, "im"): if not hasattr(image, "im"):
@ -47,7 +51,8 @@ class Draw:
def render(self, op, xy, pen, brush=None): def render(self, op, xy, pen, brush=None):
# handle color arguments # handle color arguments
outline = fill = None; width = 1 outline = fill = None
width = 1
if isinstance(pen, Pen): if isinstance(pen, Pen):
outline = pen.color outline = pen.color
width = pen.width width = pen.width

View File

@ -21,7 +21,7 @@
from PIL import Image, ImageFilter, ImageStat from PIL import Image, ImageFilter, ImageStat
class _Enhance: class _Enhance(object):
def enhance(self, factor): def enhance(self, factor):
""" """
@ -47,7 +47,11 @@ class Color(_Enhance):
""" """
def __init__(self, image): def __init__(self, image):
self.image = image self.image = image
self.degenerate = image.convert("L").convert(image.mode) self.intermediate_mode = 'L'
if 'A' in image.getbands():
self.intermediate_mode = 'LA'
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
class Contrast(_Enhance): class Contrast(_Enhance):
@ -62,11 +66,14 @@ class Contrast(_Enhance):
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5) mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
self.degenerate = Image.new("L", image.size, mean).convert(image.mode) self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
if 'A' in image.getbands():
self.degenerate.putalpha(image.split()[-1])
class Brightness(_Enhance): class Brightness(_Enhance):
"""Adjust image brightness. """Adjust image brightness.
This class can be used to control the brighntess of an image. An This class can be used to control the brightness of an image. An
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
original image. original image.
""" """
@ -74,6 +81,9 @@ class Brightness(_Enhance):
self.image = image self.image = image
self.degenerate = Image.new(image.mode, image.size, 0) self.degenerate = Image.new(image.mode, image.size, 0)
if 'A' in image.getbands():
self.degenerate.putalpha(image.split()[-1])
class Sharpness(_Enhance): class Sharpness(_Enhance):
"""Adjust image sharpness. """Adjust image sharpness.
@ -85,3 +95,6 @@ class Sharpness(_Enhance):
def __init__(self, image): def __init__(self, image):
self.image = image self.image = image
self.degenerate = image.filter(ImageFilter.SMOOTH) self.degenerate = image.filter(ImageFilter.SMOOTH)
if 'A' in image.getbands():
self.degenerate.putalpha(image.split()[-1])

View File

@ -29,8 +29,10 @@
from PIL import Image from PIL import Image
from PIL._util import isPath from PIL._util import isPath
import traceback, os, sys
import io import io
import os
import sys
import traceback
MAXBLOCK = 65536 MAXBLOCK = 65536
@ -46,6 +48,7 @@ ERRORS = {
-9: "out of memory error" -9: "out of memory error"
} }
def raise_ioerror(error): def raise_ioerror(error):
try: try:
message = Image.core.getcodecstatus(error) message = Image.core.getcodecstatus(error)
@ -55,6 +58,7 @@ def raise_ioerror(error):
message = "decoder error %d" % error message = "decoder error %d" % error
raise IOError(message + " when reading image file") raise IOError(message + " when reading image file")
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Helpers # Helpers
@ -63,6 +67,7 @@ def _tilesort(t):
# sort on offset # sort on offset
return t[2] return t[2]
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# ImageFile base class # ImageFile base class
@ -74,7 +79,7 @@ class ImageFile(Image.Image):
Image.Image.__init__(self) Image.Image.__init__(self)
self.tile = None self.tile = None
self.readonly = 1 # until we know better self.readonly = 1 # until we know better
self.decoderconfig = () self.decoderconfig = ()
self.decodermaxblock = MAXBLOCK self.decodermaxblock = MAXBLOCK
@ -90,19 +95,19 @@ class ImageFile(Image.Image):
try: try:
self._open() self._open()
except IndexError as v: # end of data except IndexError as v: # end of data
if Image.DEBUG > 1: if Image.DEBUG > 1:
traceback.print_exc() traceback.print_exc()
raise SyntaxError(v) raise SyntaxError(v)
except TypeError as v: # end of data (ord) except TypeError as v: # end of data (ord)
if Image.DEBUG > 1: if Image.DEBUG > 1:
traceback.print_exc() traceback.print_exc()
raise SyntaxError(v) raise SyntaxError(v)
except KeyError as v: # unsupported mode except KeyError as v: # unsupported mode
if Image.DEBUG > 1: if Image.DEBUG > 1:
traceback.print_exc() traceback.print_exc()
raise SyntaxError(v) raise SyntaxError(v)
except EOFError as v: # got header but not the first frame except EOFError as v: # got header but not the first frame
if Image.DEBUG > 1: if Image.DEBUG > 1:
traceback.print_exc() traceback.print_exc()
raise SyntaxError(v) raise SyntaxError(v)
@ -191,6 +196,9 @@ class ImageFile(Image.Image):
except AttributeError: except AttributeError:
prefix = b"" prefix = b""
# Buffer length read; assign a default value
t = 0
for d, e, o, a in self.tile: for d, e, o, a in self.tile:
d = Image._getdecoder(self.mode, d, a, self.decoderconfig) d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
seek(o) seek(o)
@ -203,23 +211,25 @@ class ImageFile(Image.Image):
while True: while True:
try: try:
s = read(self.decodermaxblock) s = read(self.decodermaxblock)
except IndexError as ie: # truncated png/gif except IndexError as ie: # truncated png/gif
if LOAD_TRUNCATED_IMAGES: if LOAD_TRUNCATED_IMAGES:
break break
else: else:
raise IndexError(ie) raise IndexError(ie)
if not s and not d.handles_eof: # truncated jpeg if not s and not d.handles_eof: # truncated jpeg
self.tile = [] self.tile = []
# JpegDecode needs to clean things up here either way # JpegDecode needs to clean things up here either way
# If we don't destroy the decompressor, we have a memory leak. # If we don't destroy the decompressor,
# we have a memory leak.
d.cleanup() d.cleanup()
if LOAD_TRUNCATED_IMAGES: if LOAD_TRUNCATED_IMAGES:
break break
else: else:
raise IOError("image file is truncated (%d bytes not processed)" % len(b)) raise IOError("image file is truncated "
"(%d bytes not processed)" % len(b))
b = b + s b = b + s
n, e = d.decode(b) n, e = d.decode(b)
@ -227,11 +237,13 @@ class ImageFile(Image.Image):
break break
b = b[n:] b = b[n:]
t = t + n t = t + n
# Need to cleanup here to prevent leaks in PyPy
d.cleanup()
self.tile = [] self.tile = []
self.readonly = readonly self.readonly = readonly
self.fp = None # might be shared self.fp = None # might be shared
if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0: if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0:
# still raised if decoder fails to return anything # still raised if decoder fails to return anything
@ -299,7 +311,7 @@ class StubImageFile(ImageFile):
) )
class Parser: class Parser(object):
""" """
Incremental image parser. This class implements the standard Incremental image parser. This class implements the standard
feed/close consumer interface. feed/close consumer interface.
@ -310,6 +322,7 @@ class Parser:
image = None image = None
data = None data = None
decoder = None decoder = None
offset = 0
finished = 0 finished = 0
def reset(self): def reset(self):
@ -378,10 +391,10 @@ class Parser:
fp = io.BytesIO(self.data) fp = io.BytesIO(self.data)
im = Image.open(fp) im = Image.open(fp)
finally: finally:
fp.close() # explicitly close the virtual file fp.close() # explicitly close the virtual file
except IOError: except IOError:
# traceback.print_exc() # traceback.print_exc()
pass # not enough data pass # not enough data
else: else:
flag = hasattr(im, "load_seek") or hasattr(im, "load_read") flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
if flag or len(im.tile) != 1: if flag or len(im.tile) != 1:
@ -431,9 +444,10 @@ class Parser:
self.image = Image.open(fp) self.image = Image.open(fp)
finally: finally:
self.image.load() self.image.load()
fp.close() # explicitly close the virtual file fp.close() # explicitly close the virtual file
return self.image return self.image
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _save(im, fp, tile, bufsize=0): def _save(im, fp, tile, bufsize=0):
@ -450,10 +464,10 @@ def _save(im, fp, tile, bufsize=0):
im.encoderconfig = () im.encoderconfig = ()
tile.sort(key=_tilesort) tile.sort(key=_tilesort)
# FIXME: make MAXBLOCK a configuration parameter # FIXME: make MAXBLOCK a configuration parameter
# It would be great if we could have the encoder specifiy what it needs # It would be great if we could have the encoder specify what it needs
# But, it would need at least the image size in most cases. RawEncode is # But, it would need at least the image size in most cases. RawEncode is
# a tricky case. # a tricky case.
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
try: try:
fh = fp.fileno() fh = fp.fileno()
fp.flush() fp.flush()
@ -471,6 +485,7 @@ def _save(im, fp, tile, bufsize=0):
break break
if s < 0: if s < 0:
raise IOError("encoder error %d when writing image file" % s) raise IOError("encoder error %d when writing image file" % s)
e.cleanup()
else: else:
# slight speedup: compress to real file object # slight speedup: compress to real file object
for e, b, o, a in tile: for e, b, o, a in tile:
@ -481,9 +496,11 @@ def _save(im, fp, tile, bufsize=0):
s = e.encode_to_file(fh, bufsize) s = e.encode_to_file(fh, bufsize)
if s < 0: if s < 0:
raise IOError("encoder error %d when writing image file" % s) raise IOError("encoder error %d when writing image file" % s)
e.cleanup()
try: try:
fp.flush() fp.flush()
except: pass except:
pass
def _safe_read(fp, size): def _safe_read(fp, size):

View File

@ -15,7 +15,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from functools import reduce import functools
class Filter(object): class Filter(object):
@ -43,7 +43,7 @@ class Kernel(Filter):
def __init__(self, size, kernel, scale=None, offset=0): def __init__(self, size, kernel, scale=None, offset=0):
if scale is None: if scale is None:
# default scale is sum of kernel # default scale is sum of kernel
scale = reduce(lambda a,b: a+b, kernel) scale = functools.reduce(lambda a, b: a+b, kernel)
if size[0] * size[1] != len(kernel): if size[0] * size[1] != len(kernel):
raise ValueError("not enough coefficients in kernel") raise ValueError("not enough coefficients in kernel")
self.filterargs = size, scale, offset, kernel self.filterargs = size, scale, offset, kernel
@ -162,7 +162,13 @@ class UnsharpMask(Filter):
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
the parameters. the parameters.
:param radius: Blur Radius
:param percent: Unsharp strength, in percent
:param threshold: Threshold controls the minimum brightness change that
will be sharpened
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
""" """
name = "UnsharpMask" name = "UnsharpMask"

View File

@ -25,8 +25,6 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
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 import os
@ -38,7 +36,7 @@ except ImportError:
warnings = None warnings = None
class _imagingft_not_installed: class _imagingft_not_installed(object):
# module placeholder # module placeholder
def __getattr__(self, id): def __getattr__(self, id):
raise ImportError("The _imagingft C module is not installed") raise ImportError("The _imagingft C module is not installed")
@ -64,7 +62,7 @@ except ImportError:
# -------------------------------------------------------------------- # --------------------------------------------------------------------
class ImageFont: class ImageFont(object):
"PIL font wrapper" "PIL font wrapper"
def _load_pilfont(self, filename): def _load_pilfont(self, filename):
@ -120,7 +118,7 @@ class ImageFont:
# 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.
class FreeTypeFont: class FreeTypeFont(object):
"FreeType font wrapper (requires _imagingft service)" "FreeType font wrapper (requires _imagingft service)"
def __init__(self, font=None, size=10, index=0, encoding="", file=None): def __init__(self, font=None, size=10, index=0, encoding="", file=None):
@ -133,6 +131,11 @@ class FreeTypeFont:
DeprecationWarning) DeprecationWarning)
font = file font = file
self.path = font
self.size = size
self.index = index
self.encoding = encoding
if isPath(font): if isPath(font):
self.font = core.getfont(font, size, index, encoding) self.font = core.getfont(font, size, index, encoding)
else: else:
@ -162,6 +165,22 @@ class FreeTypeFont:
self.font.render(text, im.id, mode == "1") self.font.render(text, im.id, mode == "1")
return im, offset return im, offset
def font_variant(self, font=None, size=None, index=None, encoding=None):
"""
Create a copy of this FreeTypeFont object,
using any specified arguments to override the settings.
Parameters are identical to the parameters used to initialize this
object, minus the deprecated 'file' argument.
:return: A FreeTypeFont object.
"""
return FreeTypeFont(font=self.path if font is None else font,
size=self.size if size is None else size,
index=self.index if index is None else index,
encoding=self.encoding if encoding is None else
encoding)
## ##
# Wrapper that creates a transposed font from any existing font # Wrapper that creates a transposed font from any existing font
# object. # object.
@ -172,7 +191,7 @@ class FreeTypeFont:
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270. # Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
class TransposedFont: class TransposedFont(object):
"Wrapper for writing rotated or mirrored text" "Wrapper for writing rotated or mirrored text"
def __init__(self, font, orientation=None): def __init__(self, font, orientation=None):
@ -214,7 +233,7 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
This function requires the _imagingft service. This function requires the _imagingft service.
:param filename: A truetype font file. Under Windows, if the file :param font: A truetype font file. Under Windows, if the file
is not found in this filename, the loader also looks in is not found in this filename, the loader also looks in
Windows :file:`fonts/` directory. Windows :file:`fonts/` directory.
:param size: The requested size, in points. :param size: The requested size, in points.
@ -224,6 +243,7 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert), Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
and "armn" (Apple Roman). See the FreeType documentation and "armn" (Apple Roman). See the FreeType documentation
for more information. for more information.
:param filename: Deprecated. Please use font instead.
:return: A font object. :return: A font object.
:exception IOError: If the file could not be read. :exception IOError: If the file could not be read.
""" """
@ -239,14 +259,44 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
try: try:
return FreeTypeFont(font, size, index, encoding) return FreeTypeFont(font, size, index, encoding)
except IOError: except IOError:
ttf_filename = os.path.basename(font)
dirs = []
if sys.platform == "win32": if sys.platform == "win32":
# check the windows font repository # check the windows font repository
# NOTE: must use uppercase WINDIR, to work around bugs in # NOTE: must use uppercase WINDIR, to work around bugs in
# 1.5.2's os.environ.get() # 1.5.2's os.environ.get()
windir = os.environ.get("WINDIR") windir = os.environ.get("WINDIR")
if windir: if windir:
filename = os.path.join(windir, "fonts", font) dirs.append(os.path.join(windir, "fonts"))
return FreeTypeFont(filename, size, index, encoding) elif sys.platform in ('linux', 'linux2'):
lindirs = os.environ.get("XDG_DATA_DIRS", "")
if not lindirs:
# According to the freedesktop spec, XDG_DATA_DIRS should
# default to /usr/share
lindirs = '/usr/share'
dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
elif sys.platform == 'darwin':
dirs += ['/Library/Fonts', '/System/Library/Fonts',
os.path.expanduser('~/Library/Fonts')]
ext = os.path.splitext(ttf_filename)[1]
first_font_with_a_different_extension = None
for directory in dirs:
for walkroot, walkdir, walkfilenames in os.walk(directory):
for walkfilename in walkfilenames:
if ext and walkfilename == ttf_filename:
fontpath = os.path.join(walkroot, walkfilename)
return FreeTypeFont(fontpath, size, index, encoding)
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
fontpath = os.path.join(walkroot, walkfilename)
if os.path.splitext(fontpath)[1] == '.ttf':
return FreeTypeFont(fontpath, size, index, encoding)
if not ext and first_font_with_a_different_extension is None:
first_font_with_a_different_extension = fontpath
if first_font_with_a_different_extension:
return FreeTypeFont(first_font_with_a_different_extension, size,
index, encoding)
raise raise
@ -259,15 +309,15 @@ def load_path(filename):
:return: A font object. :return: A font object.
:exception IOError: If the file could not be read. :exception IOError: If the file could not be read.
""" """
for dir in sys.path: for directory in sys.path:
if isDirectory(dir): if isDirectory(directory):
if not isinstance(filename, str): if not isinstance(filename, str):
if bytes is str: if bytes is str:
filename = filename.encode("utf-8") filename = filename.encode("utf-8")
else: else:
filename = filename.decode("utf-8") filename = filename.decode("utf-8")
try: try:
return load(os.path.join(dir, filename)) return load(os.path.join(directory, filename))
except IOError: except IOError:
pass pass
raise IOError("cannot find font file") raise IOError("cannot find font file")

View File

@ -17,6 +17,9 @@
from PIL import Image from PIL import Image
import sys
if sys.platform != "win32":
raise ImportError("ImageGrab is Windows only")
try: try:
# built-in driver (1.1.3 and later) # built-in driver (1.1.3 and later)
@ -40,7 +43,7 @@ def grab(bbox=None):
def grabclipboard(): def grabclipboard():
debug = 0 # temporary interface debug = 0 # temporary interface
data = Image.core.grabclipboard(debug) data = Image.core.grabclipboard(debug)
if isinstance(data, bytes): if isinstance(data, bytes):
from PIL import BmpImagePlugin from PIL import BmpImagePlugin

View File

@ -31,7 +31,7 @@ def _isconstant(v):
return isinstance(v, int) or isinstance(v, float) return isinstance(v, int) or isinstance(v, float)
class _Operand: class _Operand(object):
# wraps an image operand, providing standard operators # wraps an image operand, providing standard operators
def __init__(self, im): def __init__(self, im):

View File

@ -16,10 +16,11 @@
# mode descriptor cache # mode descriptor cache
_modes = {} _modes = {}
## ##
# Wrapper for mode strings. # Wrapper for mode strings.
class ModeDescriptor: class ModeDescriptor(object):
def __init__(self, mode, bands, basemode, basetype): def __init__(self, mode, bands, basemode, basetype):
self.mode = mode self.mode = mode
@ -30,6 +31,7 @@ class ModeDescriptor:
def __str__(self): def __str__(self):
return self.mode return self.mode
## ##
# Gets a mode descriptor for the given mode. # Gets a mode descriptor for the given mode.

View File

@ -12,7 +12,7 @@ import re
LUT_SIZE = 1 << 9 LUT_SIZE = 1 << 9
class LutBuilder: class LutBuilder(object):
"""A class for building a MorphLut from a descriptive language """A class for building a MorphLut from a descriptive language
The input patterns is a list of a strings sequences like these:: The input patterns is a list of a strings sequences like these::
@ -176,7 +176,7 @@ class LutBuilder:
return self.lut return self.lut
class MorphOp: class MorphOp(object):
"""A class for binary morphological operators""" """A class for binary morphological operators"""
def __init__(self, def __init__(self,

View File

@ -20,7 +20,8 @@
from PIL import Image from PIL import Image
from PIL._util import isStringType from PIL._util import isStringType
import operator import operator
from functools import reduce import functools
# #
# helpers # helpers
@ -35,12 +36,14 @@ def _border(border):
left = top = right = bottom = border left = top = right = bottom = border
return left, top, right, bottom return left, top, right, bottom
def _color(color, mode): def _color(color, mode):
if isStringType(color): if isStringType(color):
from PIL import ImageColor from PIL import ImageColor
color = ImageColor.getcolor(color, mode) color = ImageColor.getcolor(color, mode)
return color return color
def _lut(image, lut): def _lut(image, lut):
if image.mode == "P": if image.mode == "P":
# FIXME: apply to lookup table, not image data # FIXME: apply to lookup table, not image data
@ -147,7 +150,9 @@ def colorize(image, black, white):
assert image.mode == "L" assert image.mode == "L"
black = _color(black, "RGB") black = _color(black, "RGB")
white = _color(white, "RGB") white = _color(white, "RGB")
red = []; green = []; blue = [] red = []
green = []
blue = []
for i in range(256): for i in range(256):
red.append(black[0]+i*(white[0]-black[0])//255) red.append(black[0]+i*(white[0]-black[0])//255)
green.append(black[1]+i*(white[1]-black[1])//255) green.append(black[1]+i*(white[1]-black[1])//255)
@ -208,7 +213,7 @@ def equalize(image, mask=None):
if len(histo) <= 1: if len(histo) <= 1:
lut.extend(list(range(256))) lut.extend(list(range(256)))
else: else:
step = (reduce(operator.add, histo) - histo[-1]) // 255 step = (functools.reduce(operator.add, histo) - histo[-1]) // 255
if not step: if not step:
lut.extend(list(range(256))) lut.extend(list(range(256)))
else: else:
@ -228,7 +233,6 @@ def expand(image, border=0, fill=0):
:param fill: Pixel fill value (a color value). Default is 0 (black). :param fill: Pixel fill value (a color value). Default is 0 (black).
:return: An image. :return: An image.
""" """
"Add border to image"
left, top, right, bottom = _border(border) left, top, right, bottom = _border(border)
width = left + image.size[0] + right width = left + image.size[0] + right
height = top + image.size[1] + bottom height = top + image.size[1] + bottom
@ -273,7 +277,7 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
centering = [centering[0], centering[1]] centering = [centering[0], centering[1]]
if centering[0] > 1.0 or centering[0] < 0.0: if centering[0] > 1.0 or centering[0] < 0.0:
centering [0] = 0.50 centering[0] = 0.50
if centering[1] > 1.0 or centering[1] < 0.0: if centering[1] > 1.0 or centering[1] < 0.0:
centering[1] = 0.50 centering[1] = 0.50
@ -404,6 +408,7 @@ def solarize(image, threshold=128):
lut.append(255-i) lut.append(255-i)
return _lut(image, lut) return _lut(image, lut)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PIL USM components, from Kevin Cazabon. # PIL USM components, from Kevin Cazabon.
@ -419,6 +424,7 @@ def gaussian_blur(im, radius=None):
gblur = gaussian_blur gblur = gaussian_blur
def unsharp_mask(im, radius=None, percent=None, threshold=None): def unsharp_mask(im, radius=None, percent=None, threshold=None):
""" PIL_usm.usm(im, [radius, percent, threshold])""" """ PIL_usm.usm(im, [radius, percent, threshold])"""
@ -434,3 +440,22 @@ def unsharp_mask(im, radius=None, percent=None, threshold=None):
return im.im.unsharp_mask(radius, percent, threshold) return im.im.unsharp_mask(radius, percent, threshold)
usm = unsharp_mask usm = unsharp_mask
def box_blur(image, radius):
"""
Blur the image by setting each pixel to the average value of the pixels
in a square box extending radius pixels in each direction.
Supports float radius of arbitrary size. Uses an optimized implementation
which runs in linear time relative to the size of the image
for any radius value.
:param image: The image to blur.
:param radius: Size of the box in one direction. Radius 0 does not blur,
returns an identical image. Radius 1 takes 1 pixel
in each direction, i.e. 9 pixels in total.
:return: An image.
"""
image.load()
return image._new(image.im.box_blur(radius))

View File

@ -21,7 +21,7 @@ import warnings
from PIL import ImageColor from PIL import ImageColor
class ImagePalette: class ImagePalette(object):
"Color palette for palette mapped images" "Color palette for palette mapped images"
def __init__(self, mode="RGB", palette=None, size=0): def __init__(self, mode="RGB", palette=None, size=0):
@ -225,8 +225,8 @@ def load(filename):
p = PaletteFile.PaletteFile(fp) p = PaletteFile.PaletteFile(fp)
lut = p.getpalette() lut = p.getpalette()
except (SyntaxError, ValueError): except (SyntaxError, ValueError):
import traceback #import traceback
traceback.print_exc() #traceback.print_exc()
pass pass
if not lut: if not lut:

View File

@ -20,7 +20,7 @@ from PIL import Image
# the Python class below is overridden by the C implementation. # the Python class below is overridden by the C implementation.
class Path: class Path(object):
def __init__(self, xy): def __init__(self, xy):
pass pass

View File

@ -18,20 +18,30 @@
from PIL import Image from PIL import Image
from PIL._util import isPath from PIL._util import isPath
import sys
try: if 'PyQt4.QtGui' not in sys.modules:
from PyQt5.QtGui import QImage, qRgba try:
except: from PyQt5.QtGui import QImage, qRgba
except:
try:
from PyQt4.QtGui import QImage, qRgba
except:
from PySide.QtGui import QImage, qRgba
else: #PyQt4 is used
from PyQt4.QtGui import QImage, qRgba from PyQt4.QtGui import QImage, qRgba
## ##
# (Internal) Turns an RGB color into a Qt compatible color integer. # (Internal) Turns an RGB color into a Qt compatible color integer.
def rgb(r, g, b, a=255): def rgb(r, g, b, a=255):
# use qRgb to pack the colors, and then turn the resulting long # use qRgb to pack the colors, and then turn the resulting long
# into a negative integer with the same bitpattern. # into a negative integer with the same bitpattern.
return (qRgba(r, g, b, a) & 0xffffffff) return (qRgba(r, g, b, a) & 0xffffffff)
## ##
# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage # An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage
# class. # class.

View File

@ -15,7 +15,8 @@
## ##
class Iterator:
class Iterator(object):
""" """
This class implements an iterator object that can be used to loop This class implements an iterator object that can be used to loop
over an image sequence. over an image sequence.
@ -38,4 +39,4 @@ class Iterator:
self.im.seek(ix) self.im.seek(ix)
return self.im return self.im
except EOFError: except EOFError:
raise IndexError # end of sequence raise IndexError # end of sequence

View File

@ -15,7 +15,8 @@
from __future__ import print_function from __future__ import print_function
from PIL import Image from PIL import Image
import os, sys import os
import sys
if sys.version_info >= (3, 3): if sys.version_info >= (3, 3):
from shlex import quote from shlex import quote
@ -24,17 +25,19 @@ else:
_viewers = [] _viewers = []
def register(viewer, order=1): def register(viewer, order=1):
try: try:
if issubclass(viewer, Viewer): if issubclass(viewer, Viewer):
viewer = viewer() viewer = viewer()
except TypeError: except TypeError:
pass # raised if viewer wasn't a class pass # raised if viewer wasn't a class
if order > 0: if order > 0:
_viewers.append(viewer) _viewers.append(viewer)
elif order < 0: elif order < 0:
_viewers.insert(0, viewer) _viewers.insert(0, viewer)
## ##
# Displays a given image. # Displays a given image.
# #
@ -49,10 +52,11 @@ def show(image, title=None, **options):
return 1 return 1
return 0 return 0
## ##
# Base class for viewers. # Base class for viewers.
class Viewer: class Viewer(object):
# main api # main api
@ -102,6 +106,7 @@ if sys.platform == "win32":
class WindowsViewer(Viewer): class WindowsViewer(Viewer):
format = "BMP" format = "BMP"
def get_command(self, file, **options): def get_command(self, file, **options):
return ('start "Pillow" /WAIT "%s" ' return ('start "Pillow" /WAIT "%s" '
'&& ping -n 2 127.0.0.1 >NUL ' '&& ping -n 2 127.0.0.1 >NUL '
@ -113,11 +118,13 @@ elif sys.platform == "darwin":
class MacViewer(Viewer): class MacViewer(Viewer):
format = "BMP" format = "BMP"
def get_command(self, file, **options): def get_command(self, file, **options):
# on darwin open returns immediately resulting in the temp # on darwin open returns immediately resulting in the temp
# file removal while app is opening # file removal while app is opening
command = "open -a /Applications/Preview.app" command = "open -a /Applications/Preview.app"
command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file), quote(file)) command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file),
quote(file))
return command return command
register(MacViewer) register(MacViewer)
@ -140,7 +147,8 @@ else:
class UnixViewer(Viewer): class UnixViewer(Viewer):
def show_file(self, file, **options): def show_file(self, file, **options):
command, executable = self.get_command_ex(file, **options) command, executable = self.get_command_ex(file, **options)
command = "(%s %s; rm -f %s)&" % (command, quote(file), quote(file)) command = "(%s %s; rm -f %s)&" % (command, quote(file),
quote(file))
os.system(command) os.system(command)
return 1 return 1

View File

@ -21,20 +21,21 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import operator, math import math
from functools import reduce import operator
import functools
class Stat: class Stat(object):
def __init__(self, image_or_list, mask = None): def __init__(self, image_or_list, mask=None):
try: try:
if mask: if mask:
self.h = image_or_list.histogram(mask) self.h = image_or_list.histogram(mask)
else: else:
self.h = image_or_list.histogram() self.h = image_or_list.histogram()
except AttributeError: except AttributeError:
self.h = image_or_list # assume it to be a histogram list self.h = image_or_list # assume it to be a histogram list
if not isinstance(self.h, list): if not isinstance(self.h, list):
raise TypeError("first argument must be image or list") raise TypeError("first argument must be image or list")
self.bands = list(range(len(self.h) // 256)) self.bands = list(range(len(self.h) // 256))
@ -58,7 +59,7 @@ class Stat:
if histogram[i]: if histogram[i]:
n = min(n, i) n = min(n, i)
x = max(x, i) x = max(x, i)
return n, x # returns (255, 0) if there's no data in the histogram return n, x # returns (255, 0) if there's no data in the histogram
v = [] v = []
for i in range(0, len(self.h), 256): for i in range(0, len(self.h), 256):
@ -70,7 +71,7 @@ class Stat:
v = [] v = []
for i in range(0, len(self.h), 256): for i in range(0, len(self.h), 256):
v.append(reduce(operator.add, self.h[i:i+256])) v.append(functools.reduce(operator.add, self.h[i:i+256]))
return v return v
def _getsum(self): def _getsum(self):
@ -78,10 +79,10 @@ class Stat:
v = [] v = []
for i in range(0, len(self.h), 256): for i in range(0, len(self.h), 256):
sum = 0.0 layerSum = 0.0
for j in range(256): for j in range(256):
sum += j * self.h[i + j] layerSum += j * self.h[i + j]
v.append(sum) v.append(layerSum)
return v return v
def _getsum2(self): def _getsum2(self):
@ -126,7 +127,6 @@ class Stat:
v.append(math.sqrt(self.sum2[i] / self.count[i])) v.append(math.sqrt(self.sum2[i] / self.count[i]))
return v return v
def _getvar(self): def _getvar(self):
"Get variance for each layer" "Get variance for each layer"
@ -144,4 +144,4 @@ class Stat:
v.append(math.sqrt(self.var[i])) v.append(math.sqrt(self.var[i]))
return v return v
Global = Stat # compatibility Global = Stat # compatibility

View File

@ -40,21 +40,23 @@ from PIL import Image
_pilbitmap_ok = None _pilbitmap_ok = None
def _pilbitmap_check(): def _pilbitmap_check():
global _pilbitmap_ok global _pilbitmap_ok
if _pilbitmap_ok is None: if _pilbitmap_ok is None:
try: try:
im = Image.new("1", (1,1)) im = Image.new("1", (1, 1))
tkinter.BitmapImage(data="PIL:%d" % im.im.id) tkinter.BitmapImage(data="PIL:%d" % im.im.id)
_pilbitmap_ok = 1 _pilbitmap_ok = 1
except tkinter.TclError: except tkinter.TclError:
_pilbitmap_ok = 0 _pilbitmap_ok = 0
return _pilbitmap_ok return _pilbitmap_ok
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PhotoImage # PhotoImage
class PhotoImage: class PhotoImage(object):
""" """
A Tkinter-compatible photo image. This can be used A Tkinter-compatible photo image. This can be used
everywhere Tkinter expects an image object. If the image is an RGBA everywhere Tkinter expects an image object. If the image is an RGBA
@ -95,7 +97,7 @@ class PhotoImage:
try: try:
mode = image.palette.mode mode = image.palette.mode
except AttributeError: except AttributeError:
mode = "RGB" # default mode = "RGB" # default
size = image.size size = image.size
kw["width"], kw["height"] = size kw["width"], kw["height"] = size
else: else:
@ -118,8 +120,7 @@ class PhotoImage:
try: try:
self.__photo.tk.call("image", "delete", name) self.__photo.tk.call("image", "delete", name)
except: except:
pass # ignore internal errors pass # ignore internal errors
def __str__(self): def __str__(self):
""" """
@ -131,7 +132,6 @@ class PhotoImage:
""" """
return str(self.__photo) return str(self.__photo)
def width(self): def width(self):
""" """
Get the width of the image. Get the width of the image.
@ -140,7 +140,6 @@ class PhotoImage:
""" """
return self.__size[0] return self.__size[0]
def height(self): def height(self):
""" """
Get the height of the image. Get the height of the image.
@ -149,7 +148,6 @@ class PhotoImage:
""" """
return self.__size[1] return self.__size[1]
def paste(self, im, box=None): def paste(self, im, box=None):
""" """
Paste a PIL image into the photo image. Note that this can Paste a PIL image into the photo image. Note that this can
@ -170,13 +168,13 @@ class PhotoImage:
block = image block = image
else: else:
block = image.new_block(self.__mode, im.size) block = image.new_block(self.__mode, im.size)
image.convert2(block, image) # convert directly between buffers image.convert2(block, image) # convert directly between buffers
tk = self.__photo.tk tk = self.__photo.tk
try: try:
tk.call("PyImagingPhoto", self.__photo, block.id) tk.call("PyImagingPhoto", self.__photo, block.id)
except tkinter.TclError as v: except tkinter.TclError:
# activate Tkinter hook # activate Tkinter hook
try: try:
from PIL import _imagingtk from PIL import _imagingtk
@ -186,13 +184,13 @@ class PhotoImage:
_imagingtk.tkinit(id(tk), 0) _imagingtk.tkinit(id(tk), 0)
tk.call("PyImagingPhoto", self.__photo, block.id) tk.call("PyImagingPhoto", self.__photo, block.id)
except (ImportError, AttributeError, tkinter.TclError): except (ImportError, AttributeError, tkinter.TclError):
raise # configuration problem; cannot attach to Tkinter raise # configuration problem; cannot attach to Tkinter
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# BitmapImage # BitmapImage
class BitmapImage: class BitmapImage(object):
""" """
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
@ -226,7 +224,7 @@ class BitmapImage:
# fast way (requires the pilbitmap booster patch) # fast way (requires the pilbitmap booster patch)
image.load() image.load()
kw["data"] = "PIL:%d" % image.im.id kw["data"] = "PIL:%d" % image.im.id
self.__im = image # must keep a reference self.__im = image # must keep a reference
else: else:
# slow but safe way # slow but safe way
kw["data"] = image.tobitmap() kw["data"] = image.tobitmap()
@ -238,8 +236,7 @@ class BitmapImage:
try: try:
self.__photo.tk.call("image", "delete", name) self.__photo.tk.call("image", "delete", name)
except: except:
pass # ignore internal errors pass # ignore internal errors
def width(self): def width(self):
""" """
@ -249,7 +246,6 @@ class BitmapImage:
""" """
return self.__size[0] return self.__size[0]
def height(self): def height(self):
""" """
Get the height of the image. Get the height of the image.
@ -258,7 +254,6 @@ class BitmapImage:
""" """
return self.__size[1] return self.__size[1]
def __str__(self): def __str__(self):
""" """
Get the Tkinter bitmap image identifier. This method is automatically Get the Tkinter bitmap image identifier. This method is automatically
@ -274,6 +269,7 @@ def getimage(photo):
"""Copies the contents of a PhotoImage to a PIL image memory.""" """Copies the contents of a PhotoImage to a PIL image memory."""
photo.tk.call("PyImagingPhotoGet", photo) photo.tk.call("PyImagingPhotoGet", photo)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Helper for the Image.show method. # Helper for the Image.show method.
@ -286,7 +282,7 @@ def _show(image, title):
else: else:
self.image = PhotoImage(im, master=master) self.image = PhotoImage(im, master=master)
tkinter.Label.__init__(self, master, image=self.image, tkinter.Label.__init__(self, master, image=self.image,
bg="black", bd=0) bg="black", bd=0)
if not tkinter._default_root: if not tkinter._default_root:
raise IOError("tkinter not initialized") raise IOError("tkinter not initialized")

View File

@ -15,16 +15,20 @@
from PIL import Image from PIL import Image
class Transform(Image.ImageTransformHandler): class Transform(Image.ImageTransformHandler):
def __init__(self, data): def __init__(self, data):
self.data = data self.data = data
def getdata(self): def getdata(self):
return self.method, self.data return self.method, self.data
def transform(self, size, image, **options): def transform(self, size, image, **options):
# can be overridden # can be overridden
method, data = self.getdata() method, data = self.getdata()
return image.transform(size, method, data, **options) return image.transform(size, method, data, **options)
## ##
# Define an affine image transform. # Define an affine image transform.
# <p> # <p>
@ -43,9 +47,11 @@ class Transform(Image.ImageTransformHandler):
# the first two rows from an affine transform matrix. # the first two rows from an affine transform matrix.
# @see Image#Image.transform # @see Image#Image.transform
class AffineTransform(Transform): class AffineTransform(Transform):
method = Image.AFFINE method = Image.AFFINE
## ##
# Define a transform to extract a subregion from an image. # Define a transform to extract a subregion from an image.
# <p> # <p>
@ -68,6 +74,7 @@ class AffineTransform(Transform):
class ExtentTransform(Transform): class ExtentTransform(Transform):
method = Image.EXTENT method = Image.EXTENT
## ##
# Define an quad image transform. # Define an quad image transform.
# <p> # <p>
@ -83,6 +90,7 @@ class ExtentTransform(Transform):
class QuadTransform(Transform): class QuadTransform(Transform):
method = Image.QUAD method = Image.QUAD
## ##
# Define an mesh image transform. A mesh transform consists of one # Define an mesh image transform. A mesh transform consists of one
# or more individual quad transforms. # or more individual quad transforms.

View File

@ -21,30 +21,33 @@ import warnings
from PIL import Image from PIL import Image
class HDC: class HDC(object):
""" """
Wraps a HDC integer. The resulting object can be passed to the Wraps an HDC integer. The resulting object can be passed to the
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
methods. methods.
""" """
def __init__(self, dc): def __init__(self, dc):
self.dc = dc self.dc = dc
def __int__(self): def __int__(self):
return self.dc return self.dc
class HWND:
class HWND(object):
""" """
Wraps a HWND integer. The resulting object can be passed to the Wraps an HWND integer. The resulting object can be passed to the
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
methods, instead of a DC. methods, instead of a DC.
""" """
def __init__(self, wnd): def __init__(self, wnd):
self.wnd = wnd self.wnd = wnd
def __int__(self): def __int__(self):
return self.wnd return self.wnd
class Dib: class Dib(object):
""" """
A Windows bitmap with the given mode and size. The mode can be one of "1", A Windows bitmap with the given mode and size. The mode can be one of "1",
"L", "P", or "RGB". "L", "P", or "RGB".
@ -79,13 +82,12 @@ class Dib:
if image: if image:
self.paste(image) self.paste(image)
def expose(self, handle): def expose(self, handle):
""" """
Copy the bitmap contents to a device context. Copy the bitmap contents to a device context.
:param handle: Device context (HDC), cast to a Python integer, or a HDC :param handle: Device context (HDC), cast to a Python integer, or an
or HWND instance. In PythonWin, you can use the HDC or HWND instance. In PythonWin, you can use the
:py:meth:`CDC.GetHandleAttrib` to get a suitable handle. :py:meth:`CDC.GetHandleAttrib` to get a suitable handle.
""" """
if isinstance(handle, HWND): if isinstance(handle, HWND):
@ -109,7 +111,7 @@ class Dib:
necessary. necessary.
""" """
if not src: if not src:
src = (0,0) + self.size src = (0, 0) + self.size
if isinstance(handle, HWND): if isinstance(handle, HWND):
dc = self.image.getdc(handle) dc = self.image.getdc(handle)
try: try:
@ -120,7 +122,6 @@ class Dib:
result = self.image.draw(handle, dst, src) result = self.image.draw(handle, dst, src)
return result return result
def query_palette(self, handle): def query_palette(self, handle):
""" """
Installs the palette associated with the image in the given device Installs the palette associated with the image in the given device
@ -146,7 +147,6 @@ class Dib:
result = self.image.query_palette(handle) result = self.image.query_palette(handle)
return result return result
def paste(self, im, box=None): def paste(self, im, box=None):
""" """
Paste a PIL image into the bitmap image. Paste a PIL image into the bitmap image.
@ -166,7 +166,6 @@ class Dib:
else: else:
self.image.paste(im.im) self.image.paste(im.im)
def frombytes(self, buffer): def frombytes(self, buffer):
""" """
Load display memory contents from byte data. Load display memory contents from byte data.
@ -176,7 +175,6 @@ class Dib:
""" """
return self.image.frombytes(buffer) return self.image.frombytes(buffer)
def tobytes(self): def tobytes(self):
""" """
Copy display memory contents to bytes object. Copy display memory contents to bytes object.
@ -204,10 +202,11 @@ class Dib:
) )
return self.tobytes() return self.tobytes()
## ##
# Create a Window with the given title size. # Create a Window with the given title size.
class Window: class Window(object):
def __init__(self, title="PIL", width=None, height=None): def __init__(self, title="PIL", width=None, height=None):
self.hwnd = Image.core.createwindow( self.hwnd = Image.core.createwindow(
@ -235,6 +234,7 @@ class Window:
def mainloop(self): def mainloop(self):
Image.core.eventloop() Image.core.eventloop()
## ##
# Create an image window which displays the given image. # Create an image window which displays the given image.

View File

@ -26,6 +26,7 @@ from PIL import Image, ImageFile
field = re.compile(br"([a-z]*) ([^ \r\n]*)") field = re.compile(br"([a-z]*) ([^ \r\n]*)")
## ##
# Image plugin for IM Tools images. # Image plugin for IM Tools images.
@ -39,7 +40,7 @@ class ImtImageFile(ImageFile.ImageFile):
# Quick rejection: if there's not a LF among the first # Quick rejection: if there's not a LF among the first
# 100 bytes, this is (probably) not a text header. # 100 bytes, this is (probably) not a text header.
if not b"\n" in self.fp.read(100): if b"\n" not in self.fp.read(100):
raise SyntaxError("not an IM file") raise SyntaxError("not an IM file")
self.fp.seek(0) self.fp.seek(0)
@ -54,7 +55,7 @@ class ImtImageFile(ImageFile.ImageFile):
if s == b'\x0C': if s == b'\x0C':
# image data begins # image data begins
self.tile = [("raw", (0,0)+self.size, self.tile = [("raw", (0, 0)+self.size,
self.fp.tell(), self.fp.tell(),
(self.mode, 0, 1))] (self.mode, 0, 1))]
@ -68,12 +69,12 @@ class ImtImageFile(ImageFile.ImageFile):
if len(s) == 1 or len(s) > 100: if len(s) == 1 or len(s) > 100:
break break
if s[0] == b"*": if s[0] == b"*":
continue # comment continue # comment
m = field.match(s) m = field.match(s)
if not m: if not m:
break break
k, v = m.group(1,2) k, v = m.group(1, 2)
if k == "width": if k == "width":
xsize = int(v) xsize = int(v)
self.size = xsize, ysize self.size = xsize, ysize

View File

@ -222,7 +222,7 @@ def getiptcinfo(im):
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 += 1 offset += 1
@ -251,7 +251,7 @@ def getiptcinfo(im):
return None # no properties return None # no properties
# create an IptcImagePlugin object without initializing it # create an IptcImagePlugin object without initializing it
class FakeImage: class FakeImage(object):
pass pass
im = FakeImage() im = FakeImage()
im.__class__ = IptcImageFile im.__class__ = IptcImageFile

View File

@ -12,14 +12,13 @@
# #
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
__version__ = "0.1"
from PIL import Image, ImageFile from PIL import Image, ImageFile
import struct import struct
import os import os
import io import io
__version__ = "0.1"
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
@ -208,8 +207,8 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
def _accept(prefix): def _accept(prefix):
return (prefix[:4] == b'\xff\x4f\xff\x51' return (prefix[:4] == b'\xff\x4f\xff\x51' or
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a') prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
# ------------------------------------------------------------ # ------------------------------------------------------------

View File

@ -4,7 +4,7 @@
# #
# JPEG (JFIF) file handling # JPEG (JFIF) file handling
# #
# See "Digital Compression and Coding of Continous-Tone Still Images, # See "Digital Compression and Coding of Continuous-Tone Still Images,
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1) # Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
# #
# History: # History:
@ -115,7 +115,8 @@ def APP(self, marker):
elif marker == 0xFFE2 and s[:4] == b"MPF\0": elif marker == 0xFFE2 and s[:4] == b"MPF\0":
# extract MPO information # extract MPO information
self.info["mp"] = s[4:] self.info["mp"] = s[4:]
# offset is current location minus buffer size plus constant header size # offset is current location minus buffer size
# plus constant header size
self.info["mpoffset"] = self.fp.tell() - n + 4 self.info["mpoffset"] = self.fp.tell() - n + 4
@ -321,7 +322,8 @@ class JpegImageFile(ImageFile.ImageFile):
rawmode = self.mode rawmode = self.mode
if self.mode == "CMYK": if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))] self.tile = [("jpeg", (0, 0) + self.size, 0,
(rawmode, ""))]
# self.__offset = self.fp.tell() # self.__offset = self.fp.tell()
break break
s = self.fp.read(1) s = self.fp.read(1)
@ -353,7 +355,7 @@ class JpegImageFile(ImageFile.ImageFile):
scale = s scale = s
self.tile = [(d, e, o, a)] self.tile = [(d, e, o, a)]
self.decoderconfig = (scale, 1) self.decoderconfig = (scale, 0)
return self return self
@ -452,13 +454,13 @@ def _getmp(self):
data = self.info["mp"] data = self.info["mp"]
except KeyError: except KeyError:
return None return None
file = io.BytesIO(data) file_contents = io.BytesIO(data)
head = file.read(8) head = file_contents.read(8)
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<' endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
mp = {} mp = {}
# process dictionary # process dictionary
info = TiffImagePlugin.ImageFileDirectory(head) info = TiffImagePlugin.ImageFileDirectory(head)
info.load(file) info.load(file_contents)
for key, value in info.items(): for key, value in info.items():
mp[key] = _fixup(value) mp[key] = _fixup(value)
# it's an error not to have a number of images # it's an error not to have a number of images
@ -472,14 +474,18 @@ def _getmp(self):
for entrynum in range(0, quant): for entrynum in range(0, quant):
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16] rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry) unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2') labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
'EntryNo2')
mpentry = dict(zip(labels, unpackedentry)) mpentry = dict(zip(labels, unpackedentry))
mpentryattr = { mpentryattr = {
'DependentParentImageFlag': bool(mpentry['Attribute'] & (1<<31)), 'DependentParentImageFlag': bool(mpentry['Attribute'] &
'DependentChildImageFlag': bool(mpentry['Attribute'] & (1<<30)), (1 << 31)),
'RepresentativeImageFlag': bool(mpentry['Attribute'] & (1<<29)), 'DependentChildImageFlag': bool(mpentry['Attribute'] &
'Reserved': (mpentry['Attribute'] & (3<<27)) >> 27, (1 << 30)),
'ImageDataFormat': (mpentry['Attribute'] & (7<<24)) >> 24, 'RepresentativeImageFlag': bool(mpentry['Attribute'] &
(1 << 29)),
'Reserved': (mpentry['Attribute'] & (3 << 27)) >> 27,
'ImageDataFormat': (mpentry['Attribute'] & (7 << 24)) >> 24,
'MPType': mpentry['Attribute'] & 0x00FFFFFF 'MPType': mpentry['Attribute'] & 0x00FFFFFF
} }
if mpentryattr['ImageDataFormat'] == 0: if mpentryattr['ImageDataFormat'] == 0:
@ -496,7 +502,7 @@ def _getmp(self):
0x030000: 'Baseline MP Primary Image' 0x030000: 'Baseline MP Primary Image'
} }
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'], mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
'Unknown') 'Unknown')
mpentry['Attribute'] = mpentryattr mpentry['Attribute'] = mpentryattr
mpentries.append(mpentry) mpentries.append(mpentry)
mp[0xB002] = mpentries mp[0xB002] = mpentries
@ -530,11 +536,10 @@ zigzag_index = ( 0, 1, 5, 6, 14, 15, 27, 28,
21, 34, 37, 47, 50, 56, 59, 61, 21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63) 35, 36, 48, 49, 57, 58, 62, 63)
samplings = { samplings = {(1, 1, 1, 1, 1, 1): 0,
(1, 1, 1, 1, 1, 1): 0,
(2, 1, 1, 1, 1, 1): 1, (2, 1, 1, 1, 1, 1): 1,
(2, 2, 1, 1, 1, 1): 2, (2, 2, 1, 1, 1, 1): 2,
} }
def convert_dict_qtables(qtables): def convert_dict_qtables(qtables):
@ -545,6 +550,15 @@ def convert_dict_qtables(qtables):
def get_sampling(im): def get_sampling(im):
# There's no subsampling when image have only 1 layer
# (grayscale images) or when they are CMYK (4 layers),
# so set subsampling to default value.
#
# NOTE: currently Pillow can't encode JPEG to YCCK format.
# If YCCK support is added in the future, subsampling code will have
# to be updated (here and in JpegEncode.c) to deal with 4 layers.
if not hasattr(im, 'layers') or im.layers in (1, 4):
return -1
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
return samplings.get(sampling, -1) return samplings.get(sampling, -1)
@ -589,7 +603,8 @@ def _save(im, fp, filename):
subsampling = 2 subsampling = 2
elif subsampling == "keep": elif subsampling == "keep":
if im.format != "JPEG": if im.format != "JPEG":
raise ValueError("Cannot use 'keep' when original image is not a JPEG") raise ValueError(
"Cannot use 'keep' when original image is not a JPEG")
subsampling = get_sampling(im) subsampling = get_sampling(im)
def validate_qtables(qtables): def validate_qtables(qtables):
@ -623,7 +638,8 @@ def _save(im, fp, filename):
if qtables == "keep": if qtables == "keep":
if im.format != "JPEG": if im.format != "JPEG":
raise ValueError("Cannot use 'keep' when original image is not a JPEG") raise ValueError(
"Cannot use 'keep' when original image is not a JPEG")
qtables = getattr(im, "quantization", None) qtables = getattr(im, "quantization", None)
qtables = validate_qtables(qtables) qtables = validate_qtables(qtables)
@ -641,7 +657,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 += 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 += 1 i += 1
# get keyword arguments # get keyword arguments
@ -667,7 +684,8 @@ def _save(im, fp, filename):
# https://github.com/jdriscoll/django-imagekit/issues/50 # https://github.com/jdriscoll/django-imagekit/issues/50
bufsize = 0 bufsize = 0
if "optimize" in info or "progressive" in info or "progression" in info: if "optimize" in info or "progressive" in info or "progression" in info:
if quality >= 95: # keep sets quality to 0, but the actual value may be high.
if quality >= 95 or quality == 0:
bufsize = 2 * im.size[0] * im.size[1] bufsize = 2 * im.size[0] * im.size[1]
else: else:
bufsize = im.size[0] * im.size[1] bufsize = im.size[0] * im.size[1]
@ -686,7 +704,7 @@ def _save_cjpeg(im, fp, filename):
tempfile = im._dump() tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile]) subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
try: try:
os.unlink(file) os.unlink(tempfile)
except: except:
pass pass

View File

@ -48,8 +48,8 @@ You can get the quantization tables of a JPEG with::
im.quantization im.quantization
This will return a dict with a number of arrays. You can pass this dict directly This will return a dict with a number of arrays. You can pass this dict
as the qtables argument when saving a JPEG. directly as the qtables argument when saving a JPEG.
The tables format between im.quantization and quantization in presets differ in The tables format between im.quantization and quantization in presets differ in
3 ways: 3 ways:
@ -67,7 +67,7 @@ Libjpeg ref.: http://www.jpegcameras.com/libjpeg/libjpeg-3.html
""" """
presets = { presets = {
'web_low': {'subsampling': 2, # "4:1:1" 'web_low': {'subsampling': 2, # "4:1:1"
'quantization': [ 'quantization': [
[20, 16, 25, 39, 50, 46, 62, 68, [20, 16, 25, 39, 50, 46, 62, 68,
16, 18, 23, 38, 38, 53, 65, 68, 16, 18, 23, 38, 38, 53, 65, 68,
@ -86,7 +86,7 @@ presets = {
68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68,
68, 68, 68, 68, 68, 68, 68, 68] 68, 68, 68, 68, 68, 68, 68, 68]
]}, ]},
'web_medium': {'subsampling': 2, # "4:1:1" 'web_medium': {'subsampling': 2, # "4:1:1"
'quantization': [ 'quantization': [
[16, 11, 11, 16, 23, 27, 31, 30, [16, 11, 11, 16, 23, 27, 31, 30,
11, 12, 12, 15, 20, 23, 23, 30, 11, 12, 12, 15, 20, 23, 23, 30,
@ -105,7 +105,7 @@ presets = {
38, 35, 46, 53, 64, 64, 64, 64, 38, 35, 46, 53, 64, 64, 64, 64,
48, 43, 53, 64, 64, 64, 64, 64] 48, 43, 53, 64, 64, 64, 64, 64]
]}, ]},
'web_high': {'subsampling': 0, # "4:4:4" 'web_high': {'subsampling': 0, # "4:4:4"
'quantization': [ 'quantization': [
[ 6, 4, 4, 6, 9, 11, 12, 16, [ 6, 4, 4, 6, 9, 11, 12, 16,
4, 5, 5, 6, 8, 10, 12, 12, 4, 5, 5, 6, 8, 10, 12, 12,
@ -124,7 +124,7 @@ presets = {
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31] 31, 31, 31, 31, 31, 31, 31, 31]
]}, ]},
'web_very_high': {'subsampling': 0, # "4:4:4" 'web_very_high': {'subsampling': 0, # "4:4:4"
'quantization': [ 'quantization': [
[ 2, 2, 2, 2, 3, 4, 5, 6, [ 2, 2, 2, 2, 3, 4, 5, 6,
2, 2, 2, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6,
@ -143,7 +143,7 @@ presets = {
15, 12, 12, 12, 12, 12, 12, 12, 15, 12, 12, 12, 12, 12, 12, 12,
15, 12, 12, 12, 12, 12, 12, 12] 15, 12, 12, 12, 12, 12, 12, 12]
]}, ]},
'web_maximum': {'subsampling': 0, # "4:4:4" 'web_maximum': {'subsampling': 0, # "4:4:4"
'quantization': [ 'quantization': [
[ 1, 1, 1, 1, 1, 1, 1, 1, [ 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
@ -162,7 +162,7 @@ presets = {
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3] 3, 3, 3, 3, 3, 3, 3, 3]
]}, ]},
'low': {'subsampling': 2, # "4:1:1" 'low': {'subsampling': 2, # "4:1:1"
'quantization': [ 'quantization': [
[18, 14, 14, 21, 30, 35, 34, 17, [18, 14, 14, 21, 30, 35, 34, 17,
14, 16, 16, 19, 26, 23, 12, 12, 14, 16, 16, 19, 26, 23, 12, 12,
@ -181,7 +181,7 @@ presets = {
17, 12, 12, 12, 12, 12, 12, 12, 17, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12] 17, 12, 12, 12, 12, 12, 12, 12]
]}, ]},
'medium': {'subsampling': 2, # "4:1:1" 'medium': {'subsampling': 2, # "4:1:1"
'quantization': [ 'quantization': [
[12, 8, 8, 12, 17, 21, 24, 17, [12, 8, 8, 12, 17, 21, 24, 17,
8, 9, 9, 11, 15, 19, 12, 12, 8, 9, 9, 11, 15, 19, 12, 12,
@ -200,7 +200,7 @@ presets = {
17, 12, 12, 12, 12, 12, 12, 12, 17, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12] 17, 12, 12, 12, 12, 12, 12, 12]
]}, ]},
'high': {'subsampling': 0, # "4:4:4" 'high': {'subsampling': 0, # "4:4:4"
'quantization': [ 'quantization': [
[ 6, 4, 4, 6, 9, 11, 12, 16, [ 6, 4, 4, 6, 9, 11, 12, 16,
4, 5, 5, 6, 8, 10, 12, 12, 4, 5, 5, 6, 8, 10, 12, 12,
@ -219,7 +219,7 @@ presets = {
17, 12, 12, 12, 12, 12, 12, 12, 17, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12] 17, 12, 12, 12, 12, 12, 12, 12]
]}, ]},
'maximum': {'subsampling': 0, # "4:4:4" 'maximum': {'subsampling': 0, # "4:4:4"
'quantization': [ 'quantization': [
[ 2, 2, 2, 2, 3, 4, 5, 6, [ 2, 2, 2, 2, 3, 4, 5, 6,
2, 2, 2, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6,

View File

@ -21,9 +21,11 @@ __version__ = "0.2"
import struct import struct
from PIL import Image, ImageFile from PIL import Image, ImageFile
def _accept(s): def _accept(s):
return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04" return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04"
## ##
# Image plugin for McIdas area images. # Image plugin for McIdas area images.
@ -47,10 +49,12 @@ class McIdasImageFile(ImageFile.ImageFile):
mode = rawmode = "L" mode = rawmode = "L"
elif w[11] == 2: elif w[11] == 2:
# FIXME: add memory map support # FIXME: add memory map support
mode = "I"; rawmode = "I;16B" mode = "I"
rawmode = "I;16B"
elif w[11] == 4: elif w[11] == 4:
# FIXME: add memory map support # FIXME: add memory map support
mode = "I"; rawmode = "I;32B" mode = "I"
rawmode = "I;32B"
else: else:
raise SyntaxError("unsupported McIdas format") raise SyntaxError("unsupported McIdas format")

View File

@ -21,7 +21,7 @@ __version__ = "0.1"
from PIL import Image, TiffImagePlugin from PIL import Image, TiffImagePlugin
from PIL.OleFileIO import * from PIL.OleFileIO import MAGIC, OleFileIO
# #
@ -31,6 +31,7 @@ from PIL.OleFileIO import *
def _accept(prefix): def _accept(prefix):
return prefix[:8] == MAGIC return prefix[:8] == MAGIC
## ##
# Image plugin for Microsoft's Image Composer file format. # Image plugin for Microsoft's Image Composer file format.
@ -53,9 +54,9 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
# best way to identify MIC files, but what the... ;-) # best way to identify MIC files, but what the... ;-)
self.images = [] self.images = []
for file in self.ole.listdir(): for path in self.ole.listdir():
if file[1:] and file[0][-4:] == ".ACI" and file[1] == "Image": if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image":
self.images.append(file) self.images.append(path)
# if we didn't find any images, this is probably not # if we didn't find any images, this is probably not
# an MIC file. # an MIC file.
@ -70,6 +71,10 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self.seek(0) self.seek(0)
@property
def n_frames(self):
return len(self.images)
def seek(self, frame): def seek(self, frame):
try: try:

View File

@ -18,10 +18,11 @@ __version__ = "0.1"
from PIL import Image, ImageFile from PIL import Image, ImageFile
from PIL._binary import i8 from PIL._binary import i8
# #
# Bitstream parser # Bitstream parser
class BitStream: class BitStream(object):
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
@ -52,6 +53,7 @@ class BitStream:
self.bits = self.bits - bits self.bits = self.bits - bits
return v return v
## ##
# Image plugin for MPEG streams. This plugin can identify a stream, # Image plugin for MPEG streams. This plugin can identify a stream,
# but it cannot read it. # but it cannot read it.

View File

@ -22,13 +22,16 @@ __version__ = "0.1"
from PIL import Image, JpegImagePlugin from PIL import Image, JpegImagePlugin
def _accept(prefix): def _accept(prefix):
return JpegImagePlugin._accept(prefix) return JpegImagePlugin._accept(prefix)
def _save(im, fp, filename): def _save(im, fp, filename):
# Note that we can only save the current frame at present # Note that we can only save the current frame at present
return JpegImagePlugin._save(im, fp, filename) return JpegImagePlugin._save(im, fp, filename)
## ##
# Image plugin for MPO images. # Image plugin for MPO images.
@ -38,19 +41,19 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
format_description = "MPO (CIPA DC-007)" format_description = "MPO (CIPA DC-007)"
def _open(self): def _open(self):
self.fp.seek(0) # prep the fp in order to pass the JPEG test self.fp.seek(0) # prep the fp in order to pass the JPEG test
JpegImagePlugin.JpegImageFile._open(self) JpegImagePlugin.JpegImageFile._open(self)
self.mpinfo = self._getmp() self.mpinfo = self._getmp()
self.__framecount = self.mpinfo[0xB001] self.__framecount = self.mpinfo[0xB001]
self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] \ self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset']
for mpent in self.mpinfo[0xB002]] for mpent in self.mpinfo[0xB002]]
self.__mpoffsets[0] = 0 self.__mpoffsets[0] = 0
# Note that the following assertion will only be invalid if something # Note that the following assertion will only be invalid if something
# gets broken within JpegImagePlugin. # gets broken within JpegImagePlugin.
assert self.__framecount == len(self.__mpoffsets) assert self.__framecount == len(self.__mpoffsets)
del self.info['mpoffset'] # no longer needed del self.info['mpoffset'] # no longer needed
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self.__frame = 0 self.__frame = 0
self.offset = 0 self.offset = 0
# for now we can only handle reading and individual frame extraction # for now we can only handle reading and individual frame extraction
@ -59,6 +62,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
def load_seek(self, pos): def load_seek(self, pos):
self.__fp.seek(pos) self.__fp.seek(pos)
@property
def n_frames(self):
return self.__framecount
def seek(self, frame): def seek(self, frame):
if frame < 0 or frame >= self.__framecount: if frame < 0 or frame >= self.__framecount:
raise EOFError("no more images in MPO file") raise EOFError("no more images in MPO file")
@ -79,7 +86,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
# Note that since MPO shares a factory with JPEG, we do not need to do a # Note that since MPO shares a factory with JPEG, we do not need to do a
# separate registration for it here. # separate registration for it here.
#Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept) # Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
Image.register_save("MPO", _save) Image.register_save("MPO", _save)
Image.register_extension("MPO", ".mpo") Image.register_extension("MPO", ".mpo")

View File

@ -27,9 +27,11 @@ from PIL import Image, ImageFile, _binary
i16 = _binary.i16le i16 = _binary.i16le
def _accept(prefix): def _accept(prefix):
return prefix[:4] in [b"DanM", b"LinS"] return prefix[:4] in [b"DanM", b"LinS"]
## ##
# Image plugin for Windows MSP images. This plugin supports both # Image plugin for Windows MSP images. This plugin supports both
# uncompressed (Windows 1.0). # uncompressed (Windows 1.0).
@ -47,25 +49,26 @@ class MspImageFile(ImageFile.ImageFile):
raise SyntaxError("not an MSP file") raise SyntaxError("not an MSP file")
# Header checksum # Header checksum
sum = 0 checksum = 0
for i in range(0, 32, 2): for i in range(0, 32, 2):
sum = sum ^ i16(s[i:i+2]) checksum = checksum ^ i16(s[i:i+2])
if sum != 0: if checksum != 0:
raise SyntaxError("bad MSP checksum") raise SyntaxError("bad MSP checksum")
self.mode = "1" self.mode = "1"
self.size = i16(s[4:]), i16(s[6:]) self.size = i16(s[4:]), i16(s[6:])
if s[:4] == b"DanM": if s[:4] == b"DanM":
self.tile = [("raw", (0,0)+self.size, 32, ("1", 0, 1))] self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
else: else:
self.tile = [("msp", (0,0)+self.size, 32+2*self.size[1], None)] self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)]
# #
# write MSP files (uncompressed only) # write MSP files (uncompressed only)
o16 = _binary.o16le o16 = _binary.o16le
def _save(im, fp, filename): def _save(im, fp, filename):
if im.mode != "1": if im.mode != "1":
@ -74,23 +77,23 @@ def _save(im, fp, filename):
# create MSP header # create MSP header
header = [0] * 16 header = [0] * 16
header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1 header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1
header[2], header[3] = im.size header[2], header[3] = im.size
header[4], header[5] = 1, 1 header[4], header[5] = 1, 1
header[6], header[7] = 1, 1 header[6], header[7] = 1, 1
header[8], header[9] = im.size header[8], header[9] = im.size
sum = 0 checksum = 0
for h in header: for h in header:
sum = sum ^ h checksum = checksum ^ h
header[12] = sum # FIXME: is this the right field? header[12] = checksum # FIXME: is this the right field?
# header # header
for h in header: for h in header:
fp.write(o16(h)) fp.write(o16(h))
# image body # image body
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 32, ("1", 0, 1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
# #
# registry # registry

1125
PIL/OleFileIO.py Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -15,14 +15,13 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from __future__ import print_function
from PIL import EpsImagePlugin from PIL import EpsImagePlugin
## ##
# Simple Postscript graphics interface. # Simple Postscript graphics interface.
class PSDraw: class PSDraw(object):
""" """
Sets up printing to the given file. If **file** is omitted, Sets up printing to the given file. If **file** is omitted,
:py:attr:`sys.stdout` is assumed. :py:attr:`sys.stdout` is assumed.
@ -34,25 +33,31 @@ class PSDraw:
fp = sys.stdout fp = sys.stdout
self.fp = fp self.fp = fp
def begin_document(self, id = None): def _fp_write(self, to_write):
if bytes is str:
self.fp.write(to_write)
else:
self.fp.write(bytes(to_write, 'UTF-8'))
def begin_document(self, id=None):
"""Set up printing of a document. (Write Postscript DSC header.)""" """Set up printing of a document. (Write Postscript DSC header.)"""
# FIXME: incomplete # FIXME: incomplete
self.fp.write("%!PS-Adobe-3.0\n" self._fp_write("%!PS-Adobe-3.0\n"
"save\n" "save\n"
"/showpage { } def\n" "/showpage { } def\n"
"%%EndComments\n" "%%EndComments\n"
"%%BeginDocument\n") "%%BeginDocument\n")
#self.fp.write(ERROR_PS) # debugging! # self.fp_write(ERROR_PS) # debugging!
self.fp.write(EDROFF_PS) self._fp_write(EDROFF_PS)
self.fp.write(VDI_PS) self._fp_write(VDI_PS)
self.fp.write("%%EndProlog\n") self._fp_write("%%EndProlog\n")
self.isofont = {} self.isofont = {}
def end_document(self): def end_document(self):
"""Ends printing. (Write Postscript DSC footer.)""" """Ends printing. (Write Postscript DSC footer.)"""
self.fp.write("%%EndDocument\n" self._fp_write("%%EndDocument\n"
"restore showpage\n" "restore showpage\n"
"%%End\n") "%%End\n")
if hasattr(self.fp, "flush"): if hasattr(self.fp, "flush"):
self.fp.flush() self.fp.flush()
@ -65,18 +70,11 @@ class PSDraw:
""" """
if font not in self.isofont: if font not in self.isofont:
# reencode font # reencode font
self.fp.write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %\ self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %
(font, font)) (font, font))
self.isofont[font] = 1 self.isofont[font] = 1
# rough # rough
self.fp.write("/F0 %d /PSDraw-%s F\n" % (size, font)) self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font))
def setink(self, ink):
"""
.. warning:: This has been in the PIL API for ages but was never implemented.
"""
print("*** NOT YET IMPLEMENTED ***")
def line(self, xy0, xy1): def line(self, xy0, xy1):
""" """
@ -85,7 +83,7 @@ class PSDraw:
left corner of the page). left corner of the page).
""" """
xy = xy0 + xy1 xy = xy0 + xy1
self.fp.write("%d %d %d %d Vl\n" % xy) self._fp_write("%d %d %d %d Vl\n" % xy)
def rectangle(self, box): def rectangle(self, box):
""" """
@ -100,7 +98,7 @@ class PSDraw:
%d %d M %d %d 0 Vr\n %d %d M %d %d 0 Vr\n
""" """
self.fp.write("%d %d M %d %d 0 Vr\n" % box) self._fp_write("%d %d M %d %d 0 Vr\n" % box)
def text(self, xy, text): def text(self, xy, text):
""" """
@ -110,16 +108,16 @@ class PSDraw:
text = "\\(".join(text.split("(")) text = "\\(".join(text.split("("))
text = "\\)".join(text.split(")")) text = "\\)".join(text.split(")"))
xy = xy + (text,) xy = xy + (text,)
self.fp.write("%d %d M (%s) S\n" % xy) self._fp_write("%d %d M (%s) S\n" % xy)
def image(self, box, im, dpi = None): def image(self, box, im, dpi=None):
"""Draw a PIL image, centered in the given box.""" """Draw a PIL image, centered in the given box."""
# default resolution depends on mode # default resolution depends on mode
if not dpi: if not dpi:
if im.mode == "1": if im.mode == "1":
dpi = 200 # fax dpi = 200 # fax
else: else:
dpi = 100 # greyscale dpi = 100 # greyscale
# image size (on paper) # image size (on paper)
x = float(im.size[0] * 72) / dpi x = float(im.size[0] * 72) / dpi
y = float(im.size[1] * 72) / dpi y = float(im.size[1] * 72) / dpi
@ -127,19 +125,21 @@ class PSDraw:
xmax = float(box[2] - box[0]) xmax = float(box[2] - box[0])
ymax = float(box[3] - box[1]) ymax = float(box[3] - box[1])
if x > xmax: if x > xmax:
y = y * xmax / x; x = xmax y = y * xmax / x
x = xmax
if y > ymax: if y > ymax:
x = x * ymax / y; y = ymax x = x * ymax / y
y = ymax
dx = (xmax - x) / 2 + box[0] dx = (xmax - x) / 2 + box[0]
dy = (ymax - y) / 2 + box[1] dy = (ymax - y) / 2 + box[1]
self.fp.write("gsave\n%f %f translate\n" % (dx, dy)) self._fp_write("gsave\n%f %f translate\n" % (dx, dy))
if (x, y) != im.size: if (x, y) != im.size:
# EpsImagePlugin._save prints the image at (0,0,xsize,ysize) # EpsImagePlugin._save prints the image at (0,0,xsize,ysize)
sx = x / im.size[0] sx = x / im.size[0]
sy = y / im.size[1] sy = y / im.size[1]
self.fp.write("%f %f scale\n" % (sx, sy)) self._fp_write("%f %f scale\n" % (sx, sy))
EpsImagePlugin._save(im, self.fp, None, 0) EpsImagePlugin._save(im, self.fp, None, 0)
self.fp.write("\ngrestore\n") self._fp_write("\ngrestore\n")
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Postscript driver # Postscript driver

View File

@ -15,10 +15,11 @@
from PIL._binary import o8 from PIL._binary import o8
## ##
# File handler for Teragon-style palette files. # File handler for Teragon-style palette files.
class PaletteFile: class PaletteFile(object):
rawmode = "RGB" rawmode = "RGB"
@ -49,7 +50,6 @@ class PaletteFile:
self.palette = b"".join(self.palette) self.palette = b"".join(self.palette)
def getpalette(self): def getpalette(self):
return self.palette, self.rawmode return self.palette, self.rawmode

View File

@ -22,6 +22,7 @@ from PIL import Image, ImageFile, _binary
i8 = _binary.i8 i8 = _binary.i8
## ##
# Image plugin for PhotoCD images. This plugin only reads the 768x512 # Image plugin for PhotoCD images. This plugin only reads the 768x512
# image from the file; higher resolutions are encoded in a proprietary # image from the file; higher resolutions are encoded in a proprietary
@ -43,32 +44,13 @@ class PcdImageFile(ImageFile.ImageFile):
orientation = i8(s[1538]) & 3 orientation = i8(s[1538]) & 3
if orientation == 1: if orientation == 1:
self.tile_post_rotate = 90 # hack self.tile_post_rotate = 90 # hack
elif orientation == 3: elif orientation == 3:
self.tile_post_rotate = -90 self.tile_post_rotate = -90
self.mode = "RGB" self.mode = "RGB"
self.size = 768, 512 # FIXME: not correct for rotated images! self.size = 768, 512 # FIXME: not correct for rotated images!
self.tile = [("pcd", (0,0)+self.size, 96*2048, None)] self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)]
def draft(self, mode, size):
if len(self.tile) != 1:
return
d, e, o, a = self.tile[0]
if size:
scale = max(self.size[0] / size[0], self.size[1] / size[1])
for s, o in [(4,0*2048), (2,0*2048), (1,96*2048)]:
if scale >= s:
break
# e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1]
# self.size = ((self.size[0]+s-1)/s, (self.size[1]+s-1)/s)
self.tile = [(d, e, o, a)]
return self
# #
# registry # registry

View File

@ -23,20 +23,20 @@ from PIL import _binary
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# declarations # declarations
PCF_MAGIC = 0x70636601 # "\x01fcp" PCF_MAGIC = 0x70636601 # "\x01fcp"
PCF_PROPERTIES = (1<<0) PCF_PROPERTIES = (1 << 0)
PCF_ACCELERATORS = (1<<1) PCF_ACCELERATORS = (1 << 1)
PCF_METRICS = (1<<2) PCF_METRICS = (1 << 2)
PCF_BITMAPS = (1<<3) PCF_BITMAPS = (1 << 3)
PCF_INK_METRICS = (1<<4) PCF_INK_METRICS = (1 << 4)
PCF_BDF_ENCODINGS = (1<<5) PCF_BDF_ENCODINGS = (1 << 5)
PCF_SWIDTHS = (1<<6) PCF_SWIDTHS = (1 << 6)
PCF_GLYPH_NAMES = (1<<7) PCF_GLYPH_NAMES = (1 << 7)
PCF_BDF_ACCELERATORS = (1<<8) PCF_BDF_ACCELERATORS = (1 << 8)
BYTES_PER_ROW = [ BYTES_PER_ROW = [
lambda bits: ((bits+7) >> 3), lambda bits: ((bits+7) >> 3),
lambda bits: ((bits+15) >> 3) & ~1, lambda bits: ((bits+15) >> 3) & ~1,
lambda bits: ((bits+31) >> 3) & ~3, lambda bits: ((bits+31) >> 3) & ~3,
lambda bits: ((bits+63) >> 3) & ~7, lambda bits: ((bits+63) >> 3) & ~7,
@ -48,9 +48,11 @@ l32 = _binary.i32le
b16 = _binary.i16be b16 = _binary.i16be
b32 = _binary.i32be b32 = _binary.i32be
def sz(s, o): def sz(s, o):
return s[o:s.index(b"\0", o)] return s[o:s.index(b"\0", o)]
## ##
# Font file plugin for the X11 PCF format. # Font file plugin for the X11 PCF format.
@ -122,7 +124,7 @@ class PcfFontFile(FontFile.FontFile):
for i in range(nprops): for i in range(nprops):
p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4)))) p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
if nprops & 3: if nprops & 3:
fp.seek(4 - (nprops & 3), 1) # pad fp.seek(4 - (nprops & 3), 1) # pad
data = fp.read(i32(fp.read(4))) data = fp.read(i32(fp.read(4)))
@ -202,16 +204,16 @@ class PcfFontFile(FontFile.FontFile):
for i in range(4): for i in range(4):
bitmapSizes.append(i32(fp.read(4))) bitmapSizes.append(i32(fp.read(4)))
byteorder = format & 4 # non-zero => MSB # byteorder = format & 4 # non-zero => MSB
bitorder = format & 8 # non-zero => MSB bitorder = format & 8 # non-zero => MSB
padindex = format & 3 padindex = format & 3
bitmapsize = bitmapSizes[padindex] bitmapsize = bitmapSizes[padindex]
offsets.append(bitmapsize) offsets.append(bitmapsize)
data = fp.read(bitmapsize) data = fp.read(bitmapsize)
pad = BYTES_PER_ROW[padindex] pad = BYTES_PER_ROW[padindex]
mode = "1;R" mode = "1;R"
if bitorder: if bitorder:
mode = "1" mode = "1"
@ -245,6 +247,6 @@ class PcfFontFile(FontFile.FontFile):
try: try:
encoding[i+firstCol] = encodingOffset encoding[i+firstCol] = encodingOffset
except IndexError: except IndexError:
break # only load ISO-8859-1 glyphs break # only load ISO-8859-1 glyphs
return encoding return encoding

View File

@ -25,7 +25,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
__version__ = "0.6" from __future__ import print_function
from PIL import Image, ImageFile, ImagePalette, _binary from PIL import Image, ImageFile, ImagePalette, _binary
@ -33,9 +33,13 @@ i8 = _binary.i8
i16 = _binary.i16le i16 = _binary.i16le
o8 = _binary.o8 o8 = _binary.o8
__version__ = "0.6"
def _accept(prefix): def _accept(prefix):
return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5] return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
## ##
# Image plugin for Paintbrush images. # Image plugin for Paintbrush images.
@ -52,23 +56,22 @@ class PcxImageFile(ImageFile.ImageFile):
raise SyntaxError("not a PCX file") raise SyntaxError("not a PCX file")
# image # image
bbox = i16(s,4), i16(s,6), i16(s,8)+1, i16(s,10)+1 bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]: if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
raise SyntaxError("bad PCX image size") raise SyntaxError("bad PCX image size")
if Image.DEBUG: if Image.DEBUG:
print ("BBox: %s %s %s %s" % bbox) print("BBox: %s %s %s %s" % bbox)
# format # format
version = i8(s[1]) version = i8(s[1])
bits = i8(s[3]) bits = i8(s[3])
planes = i8(s[65]) planes = i8(s[65])
stride = i16(s,66) stride = i16(s, 66)
if Image.DEBUG: if Image.DEBUG:
print ("PCX version %s, bits %s, planes %s, stride %s" % print("PCX version %s, bits %s, planes %s, stride %s" %
(version, bits, planes, stride)) (version, bits, planes, stride))
self.info["dpi"] = i16(s,12), i16(s,14) self.info["dpi"] = i16(s, 12), i16(s, 14)
if bits == 1 and planes == 1: if bits == 1 and planes == 1:
mode = rawmode = "1" mode = rawmode = "1"
@ -105,7 +108,7 @@ class PcxImageFile(ImageFile.ImageFile):
bbox = (0, 0) + self.size bbox = (0, 0) + self.size
if Image.DEBUG: if Image.DEBUG:
print ("size: %sx%s" % self.size) print("size: %sx%s" % self.size)
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))] self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
@ -122,6 +125,7 @@ SAVE = {
o16 = _binary.o16le o16 = _binary.o16le
def _save(im, fp, filename, check=0): def _save(im, fp, filename, check=0):
try: try:
@ -140,9 +144,8 @@ def _save(im, fp, filename, check=0):
# 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.
if Image.DEBUG: if Image.DEBUG:
print ("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % ( print("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
im.size[0], bits, stride)) im.size[0], bits, stride))
# under windows, we could determine the current screen size with # under windows, we could determine the current screen size with
@ -163,13 +166,13 @@ def _save(im, fp, filename, check=0):
assert fp.tell() == 128 assert fp.tell() == 128
ImageFile._save(im, fp, [("pcx", (0,0)+im.size, 0, ImageFile._save(im, fp, [("pcx", (0, 0)+im.size, 0,
(rawmode, bits*planes))]) (rawmode, bits*planes))])
if im.mode == "P": if im.mode == "P":
# colour palette # colour palette
fp.write(o8(12)) fp.write(o8(12))
fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
elif im.mode == "L": elif im.mode == "L":
# greyscale palette # greyscale palette
fp.write(o8(12)) fp.write(o8(12))

View File

@ -63,7 +63,7 @@ def _save(im, fp, filename):
xref = [0]*(5+1) # placeholders xref = [0]*(5+1) # placeholders
class TextWriter: class TextWriter(object):
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp

View File

@ -29,6 +29,7 @@ from PIL import Image, ImageFile, _binary
i16 = _binary.i16le i16 = _binary.i16le
i32 = _binary.i32le i32 = _binary.i32le
## ##
# Image plugin for PIXAR raster images. # Image plugin for PIXAR raster images.
@ -57,7 +58,7 @@ class PixarImageFile(ImageFile.ImageFile):
# FIXME: to be continued... # FIXME: to be continued...
# create tile descriptor (assuming "dumped") # create tile descriptor (assuming "dumped")
self.tile = [("raw", (0,0)+self.size, 1024, (self.mode, 0, 1))] self.tile = [("raw", (0, 0)+self.size, 1024, (self.mode, 0, 1))]
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -52,30 +52,46 @@ _MAGIC = b"\211PNG\r\n\032\n"
_MODES = { _MODES = {
# supported bits/color combinations, and corresponding modes/rawmodes # supported bits/color combinations, and corresponding modes/rawmodes
(1, 0): ("1", "1"), (1, 0): ("1", "1"),
(2, 0): ("L", "L;2"), (2, 0): ("L", "L;2"),
(4, 0): ("L", "L;4"), (4, 0): ("L", "L;4"),
(8, 0): ("L", "L"), (8, 0): ("L", "L"),
(16,0): ("I", "I;16B"), (16, 0): ("I", "I;16B"),
(8, 2): ("RGB", "RGB"), (8, 2): ("RGB", "RGB"),
(16,2): ("RGB", "RGB;16B"), (16, 2): ("RGB", "RGB;16B"),
(1, 3): ("P", "P;1"), (1, 3): ("P", "P;1"),
(2, 3): ("P", "P;2"), (2, 3): ("P", "P;2"),
(4, 3): ("P", "P;4"), (4, 3): ("P", "P;4"),
(8, 3): ("P", "P"), (8, 3): ("P", "P"),
(8, 4): ("LA", "LA"), (8, 4): ("LA", "LA"),
(16,4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
(8, 6): ("RGBA", "RGBA"), (8, 6): ("RGBA", "RGBA"),
(16,6): ("RGBA", "RGBA;16B"), (16, 6): ("RGBA", "RGBA;16B"),
} }
_simple_palette = re.compile(b'^\xff+\x00\xff*$') _simple_palette = re.compile(b'^\xff+\x00\xff*$')
_null_palette = re.compile(b'^\x00*$')
# Maximum decompressed size for a iTXt or zTXt chunk.
# Eliminates decompression bombs where compressed chunks can expand 1000x
MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK
# Set the maximum total text chunk size.
MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK
def _safe_zlib_decompress(s):
dobj = zlib.decompressobj()
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
if dobj.unconsumed_tail:
raise ValueError("Decompressed Data Too Large")
return plaintext
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Support classes. Suitable for PNG and related formats like MNG etc. # Support classes. Suitable for PNG and related formats like MNG etc.
class ChunkStream: class ChunkStream(object):
def __init__(self, fp): def __init__(self, fp):
@ -123,15 +139,15 @@ class ChunkStream:
crc1 = Image.core.crc32(data, Image.core.crc32(cid)) crc1 = Image.core.crc32(data, Image.core.crc32(cid))
crc2 = i16(self.fp.read(2)), i16(self.fp.read(2)) crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
if crc1 != crc2: if crc1 != crc2:
raise SyntaxError("broken PNG file"\ raise SyntaxError("broken PNG file"
"(bad header checksum in %s)" % cid) "(bad header checksum in %s)" % cid)
def crc_skip(self, cid, data): def crc_skip(self, cid, data):
"Read checksum. Used if the C module is not present" "Read checksum. Used if the C module is not present"
self.fp.read(4) self.fp.read(4)
def verify(self, endchunk = b"IEND"): def verify(self, endchunk=b"IEND"):
# Simple approach; just calculate checksum for all remaining # Simple approach; just calculate checksum for all remaining
# blocks. Must be called directly after open. # blocks. Must be called directly after open.
@ -147,30 +163,57 @@ 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): class iTXt(str):
"""
Subclass of string to allow iTXt chunks to look like strings while
keeping their extra information
"""
@staticmethod @staticmethod
def __new__(cls, text, lang, tkey): def __new__(cls, text, lang, tkey):
"""
:param value: value for this key
:param lang: language code
:param tkey: UTF-8 version of the key name
"""
self = str.__new__(cls, text) self = str.__new__(cls, text)
self.lang = lang self.lang = lang
self.tkey = tkey self.tkey = tkey
return self return self
# --------------------------------------------------------------------
# PNG chunk container (for use with save(pnginfo=))
class PngInfo: class PngInfo(object):
"""
PNG chunk container (for use with save(pnginfo=))
"""
def __init__(self): def __init__(self):
self.chunks = [] self.chunks = []
def add(self, cid, data): def add(self, cid, data):
"""Appends an arbitrary chunk. Use with caution.
:param cid: a byte string, 4 bytes long.
:param data: a byte string of the encoded data
"""
self.chunks.append((cid, data)) self.chunks.append((cid, data))
def add_itxt(self, key, value, lang="", tkey="", zip=False): def add_itxt(self, key, value, lang="", tkey="", zip=False):
"""Appends an iTXt chunk.
:param key: latin-1 encodable text key name
:param value: value for this key
:param lang: language code
:param tkey: UTF-8 version of the key name
:param zip: compression flag
"""
if not isinstance(key, bytes): if not isinstance(key, bytes):
key = key.encode("latin-1", "strict") key = key.encode("latin-1", "strict")
if not isinstance(value, bytes): if not isinstance(value, bytes):
@ -181,12 +224,21 @@ class PngInfo:
tkey = tkey.encode("utf-8", "strict") tkey = tkey.encode("utf-8", "strict")
if zip: if zip:
import zlib self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" +
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value)) zlib.compress(value))
else: else:
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value) 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):
"""Appends a text chunk.
:param key: latin-1 encodable text key name
:param value: value for this key, text or an
:py:class:`PIL.PngImagePlugin.iTXt` instance
:param zip: compression flag
"""
if isinstance(value, iTXt): if isinstance(value, iTXt):
return self.add_itxt(key, value, value.lang, value.tkey, bool(zip)) return self.add_itxt(key, value, value.lang, value.tkey, bool(zip))
@ -201,11 +253,11 @@ class PngInfo:
key = key.encode('latin-1', 'strict') key = key.encode('latin-1', 'strict')
if zip: if zip:
import zlib
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value)) self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
else: else:
self.add(b"tEXt", key + b"\0" + value) self.add(b"tEXt", key + b"\0" + value)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG image stream (IHDR/IEND) # PNG image stream (IHDR/IEND)
@ -218,11 +270,19 @@ class PngStream(ChunkStream):
# local copies of Image attributes # local copies of Image attributes
self.im_info = {} self.im_info = {}
self.im_text = {} self.im_text = {}
self.im_size = (0,0) self.im_size = (0, 0)
self.im_mode = None self.im_mode = None
self.im_tile = None self.im_tile = None
self.im_palette = None self.im_palette = None
self.text_memory = 0
def check_text_memory(self, chunklen):
self.text_memory += chunklen
if self.text_memory > MAX_TEXT_MEMORY:
raise ValueError("Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" %
self.text_memory)
def chunk_iCCP(self, pos, length): def chunk_iCCP(self, pos, length):
# ICC profile # ICC profile
@ -238,11 +298,12 @@ class PngStream(ChunkStream):
print("Compression method", i8(s[i])) print("Compression method", i8(s[i]))
comp_method = i8(s[i]) comp_method = i8(s[i])
if comp_method != 0: if comp_method != 0:
raise SyntaxError("Unknown compression method %s in iCCP chunk" % comp_method) raise SyntaxError("Unknown compression method %s in iCCP chunk" %
comp_method)
try: try:
icc_profile = zlib.decompress(s[i+2:]) icc_profile = _safe_zlib_decompress(s[i+2:])
except zlib.error: except zlib.error:
icc_profile = None # FIXME icc_profile = None # FIXME
self.im_info["icc_profile"] = icc_profile self.im_info["icc_profile"] = icc_profile
return s return s
@ -264,7 +325,7 @@ class PngStream(ChunkStream):
def chunk_IDAT(self, pos, length): def chunk_IDAT(self, pos, length):
# image data # image data
self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)] self.im_tile = [("zip", (0, 0)+self.im_size, pos, self.im_rawmode)]
self.im_idat = length self.im_idat = length
raise EOFError raise EOFError
@ -290,6 +351,8 @@ class PngStream(ChunkStream):
i = s.find(b"\0") i = s.find(b"\0")
if i >= 0: if i >= 0:
self.im_info["transparency"] = i self.im_info["transparency"] = i
elif _null_palette.match(s):
self.im_info["transparency"] = 0
else: else:
self.im_info["transparency"] = s self.im_info["transparency"] = s
elif self.im_mode == "L": elif self.im_mode == "L":
@ -311,7 +374,7 @@ class PngStream(ChunkStream):
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
px, py = i32(s), i32(s[4:]) px, py = i32(s), i32(s[4:])
unit = i8(s[8]) unit = i8(s[8])
if unit == 1: # meter if unit == 1: # meter
dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5) dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5)
self.im_info["dpi"] = dpi self.im_info["dpi"] = dpi
elif unit == 0: elif unit == 0:
@ -325,13 +388,17 @@ class PngStream(ChunkStream):
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
k = s; v = b"" # fallback for broken tEXt tags # fallback for broken tEXt tags
k = s
v = b""
if k: if k:
if bytes is not str: if bytes is not str:
k = k.decode('latin-1', 'strict') k = k.decode('latin-1', 'strict')
v = v.decode('latin-1', 'replace') v = v.decode('latin-1', 'replace')
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))
return s return s
def chunk_zTXt(self, pos, length): def chunk_zTXt(self, pos, length):
@ -341,16 +408,17 @@ class PngStream(ChunkStream):
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
k = s; v = b"" k = s
v = b""
if v: if v:
comp_method = i8(v[0]) comp_method = i8(v[0])
else: else:
comp_method = 0 comp_method = 0
if comp_method != 0: if comp_method != 0:
raise SyntaxError("Unknown compression method %s in zTXt chunk" % comp_method) raise SyntaxError("Unknown compression method %s in zTXt chunk" %
import zlib comp_method)
try: try:
v = zlib.decompress(v[1:]) v = _safe_zlib_decompress(v[1:])
except zlib.error: except zlib.error:
v = b"" v = b""
@ -360,6 +428,8 @@ class PngStream(ChunkStream):
v = v.decode('latin-1', 'replace') v = v.decode('latin-1', 'replace')
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))
return s return s
def chunk_iTXt(self, pos, length): def chunk_iTXt(self, pos, length):
@ -379,9 +449,8 @@ class PngStream(ChunkStream):
return s return s
if cf != 0: if cf != 0:
if cm == 0: if cm == 0:
import zlib
try: try:
v = zlib.decompress(v) v = _safe_zlib_decompress(v)
except zlib.error: except zlib.error:
return s return s
else: else:
@ -396,15 +465,18 @@ class PngStream(ChunkStream):
return s return s
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk) self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
self.check_text_memory(len(v))
return s return s
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG reader # PNG reader
def _accept(prefix): def _accept(prefix):
return prefix[:8] == _MAGIC return prefix[:8] == _MAGIC
## ##
# Image plugin for PNG images. # Image plugin for PNG images.
@ -451,7 +523,7 @@ class PngImageFile(ImageFile.ImageFile):
self.mode = self.png.im_mode self.mode = self.png.im_mode
self.size = self.png.im_size self.size = self.png.im_size
self.info = self.png.im_info self.info = self.png.im_info
self.text = self.png.im_text # experimental self.text = self.png.im_text # experimental
self.tile = self.png.im_tile self.tile = self.png.im_tile
if self.png.im_palette: if self.png.im_palette:
@ -460,7 +532,6 @@ class PngImageFile(ImageFile.ImageFile):
self.__idat = length # used by load_read() self.__idat = length # used by load_read()
def verify(self): def verify(self):
"Verify PNG file" "Verify PNG file"
@ -489,7 +560,7 @@ class PngImageFile(ImageFile.ImageFile):
while self.__idat == 0: while self.__idat == 0:
# end of chunk, skip forward to next one # end of chunk, skip forward to next one
self.fp.read(4) # CRC self.fp.read(4) # CRC
cid, pos, length = self.png.read() cid, pos, length = self.png.read()
@ -509,7 +580,6 @@ class PngImageFile(ImageFile.ImageFile):
return self.fp.read(read_bytes) return self.fp.read(read_bytes)
def load_end(self): def load_end(self):
"internal: finished reading image data" "internal: finished reading image data"
@ -526,21 +596,22 @@ o32 = _binary.o32be
_OUTMODES = { _OUTMODES = {
# supported PIL modes, and corresponding rawmodes/bits/color combinations # supported PIL modes, and corresponding rawmodes/bits/color combinations
"1": ("1", b'\x01\x00'), "1": ("1", b'\x01\x00'),
"L;1": ("L;1", b'\x01\x00'), "L;1": ("L;1", b'\x01\x00'),
"L;2": ("L;2", b'\x02\x00'), "L;2": ("L;2", b'\x02\x00'),
"L;4": ("L;4", b'\x04\x00'), "L;4": ("L;4", b'\x04\x00'),
"L": ("L", b'\x08\x00'), "L": ("L", b'\x08\x00'),
"LA": ("LA", b'\x08\x04'), "LA": ("LA", b'\x08\x04'),
"I": ("I;16B", b'\x10\x00'), "I": ("I;16B", b'\x10\x00'),
"P;1": ("P;1", b'\x01\x03'), "P;1": ("P;1", b'\x01\x03'),
"P;2": ("P;2", b'\x02\x03'), "P;2": ("P;2", b'\x02\x03'),
"P;4": ("P;4", b'\x04\x03'), "P;4": ("P;4", b'\x04\x03'),
"P": ("P", b'\x08\x03'), "P": ("P", b'\x08\x03'),
"RGB": ("RGB", b'\x08\x02'), "RGB": ("RGB", b'\x08\x02'),
"RGBA":("RGBA", b'\x08\x06'), "RGBA": ("RGBA", b'\x08\x06'),
} }
def putchunk(fp, cid, *data): def putchunk(fp, cid, *data):
"Write a PNG chunk (including CRC field)" "Write a PNG chunk (including CRC field)"
@ -551,15 +622,18 @@ def putchunk(fp, cid, *data):
hi, lo = Image.core.crc32(data, Image.core.crc32(cid)) hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
fp.write(o16(hi) + o16(lo)) fp.write(o16(hi) + o16(lo))
class _idat:
class _idat(object):
# wrap output from the encoder in IDAT chunks # wrap output from the encoder in IDAT chunks
def __init__(self, fp, chunk): def __init__(self, fp, chunk):
self.fp = fp self.fp = fp
self.chunk = chunk self.chunk = chunk
def write(self, data): def write(self, data):
self.chunk(self.fp, b"IDAT", data) self.chunk(self.fp, b"IDAT", data)
def _save(im, fp, filename, chunk=putchunk, check=0): def _save(im, fp, filename, chunk=putchunk, check=0):
# save an image to disk (called by the save method) # save an image to disk (called by the save method)
@ -597,9 +671,9 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
dictionary = b"" dictionary = b""
im.encoderconfig = ("optimize" in im.encoderinfo, im.encoderconfig = ("optimize" in im.encoderinfo,
im.encoderinfo.get("compress_level", -1), im.encoderinfo.get("compress_level", -1),
im.encoderinfo.get("compress_type", -1), im.encoderinfo.get("compress_type", -1),
dictionary) dictionary)
# get the corresponding PNG mode # get the corresponding PNG mode
try: try:
@ -616,8 +690,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
fp.write(_MAGIC) fp.write(_MAGIC)
chunk(fp, b"IHDR", chunk(fp, b"IHDR",
o32(im.size[0]), o32(im.size[1]), # 0: size o32(im.size[0]), o32(im.size[1]), # 0: size
mode, # 8: depth/type mode, # 8: depth/type
b'\0', # 10: compression b'\0', # 10: compression
b'\0', # 11: filter category b'\0', # 11: filter category
b'\0') # 12: interlace flag b'\0') # 12: interlace flag
@ -629,7 +703,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
palette_bytes += b'\0' palette_bytes += b'\0'
chunk(fp, b"PLTE", palette_bytes) chunk(fp, b"PLTE", palette_bytes)
transparency = im.encoderinfo.get('transparency',im.info.get('transparency', None)) transparency = im.encoderinfo.get('transparency',
im.info.get('transparency', None))
if transparency or transparency == 0: if transparency or transparency == 0:
if im.mode == "P": if im.mode == "P":
@ -658,10 +733,6 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
alpha_bytes = 2**bits alpha_bytes = 2**bits
chunk(fp, b"tRNS", alpha[:alpha_bytes]) chunk(fp, b"tRNS", alpha[:alpha_bytes])
if 0:
# FIXME: to be supported some day
chunk(fp, b"gAMA", o32(int(gamma * 100000.0)))
dpi = im.encoderinfo.get("dpi") dpi = im.encoderinfo.get("dpi")
if dpi: if dpi:
chunk(fp, b"pHYs", chunk(fp, b"pHYs",
@ -686,7 +757,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
data = name + b"\0\0" + zlib.compress(im.info["icc_profile"]) data = name + b"\0\0" + zlib.compress(im.info["icc_profile"])
chunk(fp, b"iCCP", data) chunk(fp, b"iCCP", data)
ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)]) ImageFile._save(im, _idat(fp, chunk),
[("zip", (0, 0)+im.size, 0, rawmode)])
chunk(fp, b"IEND", b"") chunk(fp, b"IEND", b"")
@ -702,10 +774,12 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
def getchunks(im, **params): def getchunks(im, **params):
"""Return a list of PNG chunks representing this image.""" """Return a list of PNG chunks representing this image."""
class collector: class collector(object):
data = [] data = []
def write(self, data): def write(self, data):
pass pass
def append(self, chunk): def append(self, chunk):
self.data.append(chunk) self.data.append(chunk)

View File

@ -27,12 +27,13 @@ from PIL import Image, ImageFile
b_whitespace = string.whitespace b_whitespace = string.whitespace
try: try:
import locale import locale
locale_lang,locale_enc = locale.getlocale() locale_lang, locale_enc = locale.getlocale()
if locale_enc is None: if locale_enc is None:
locale_lang,locale_enc = locale.getdefaultlocale() locale_lang, locale_enc = locale.getdefaultlocale()
b_whitespace = b_whitespace.decode(locale_enc) b_whitespace = b_whitespace.decode(locale_enc)
except: pass except:
b_whitespace = b_whitespace.encode('ascii','ignore') pass
b_whitespace = b_whitespace.encode('ascii', 'ignore')
MODES = { MODES = {
# standard # standard
@ -47,9 +48,11 @@ MODES = {
b"PyCMYK": "CMYK" b"PyCMYK": "CMYK"
} }
def _accept(prefix): def _accept(prefix):
return prefix[0:1] == b"P" and prefix[1] in b"0456y" return prefix[0:1] == b"P" and prefix[1] in b"0456y"
## ##
# Image plugin for PBM, PGM, and PPM images. # Image plugin for PBM, PGM, and PPM images.
@ -58,8 +61,8 @@ class PpmImageFile(ImageFile.ImageFile):
format = "PPM" format = "PPM"
format_description = "Pbmplus image" format_description = "Pbmplus image"
def _token(self, s = b""): def _token(self, s=b""):
while True: # read until next whitespace while True: # read until next whitespace
c = self.fp.read(1) c = self.fp.read(1)
if not c or c in b_whitespace: if not c or c in b_whitespace:
break break
@ -104,12 +107,12 @@ class PpmImageFile(ImageFile.ImageFile):
# maxgrey # maxgrey
if s > 255: if s > 255:
if not mode == 'L': if not mode == 'L':
raise ValueError("Too many colors for band: %s" %s) raise ValueError("Too many colors for band: %s" % s)
if s < 2**16: if s < 2**16:
self.mode = 'I' self.mode = 'I'
rawmode = 'I;16B' rawmode = 'I;16B'
else: else:
self.mode = 'I'; self.mode = 'I'
rawmode = 'I;32B' rawmode = 'I;32B'
self.size = xsize, ysize self.size = xsize, ysize
@ -123,6 +126,7 @@ class PpmImageFile(ImageFile.ImageFile):
# self.mode = self.im.mode # self.mode = self.im.mode
# self.size = self.im.size # self.size = self.im.size
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -152,7 +156,7 @@ def _save(im, fp, filename):
fp.write(b"65535\n") fp.write(b"65535\n")
elif rawmode == "I;32B": elif rawmode == "I;32B":
fp.write(b"2147483648\n") fp.write(b"2147483648\n")
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
# ALTERNATIVE: save via builtin debug function # ALTERNATIVE: save via builtin debug function
# im._dump(filename) # im._dump(filename)

View File

@ -28,8 +28,8 @@ MODES = {
(2, 8): ("P", 1), (2, 8): ("P", 1),
(3, 8): ("RGB", 3), (3, 8): ("RGB", 3),
(4, 8): ("CMYK", 4), (4, 8): ("CMYK", 4),
(7, 8): ("L", 1), # FIXME: multilayer (7, 8): ("L", 1), # FIXME: multilayer
(8, 8): ("L", 1), # duotone (8, 8): ("L", 1), # duotone
(9, 8): ("LAB", 3) (9, 8): ("LAB", 3)
} }
@ -40,12 +40,14 @@ i8 = _binary.i8
i16 = _binary.i16be i16 = _binary.i16be
i32 = _binary.i32be i32 = _binary.i32be
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
# read PSD images # read PSD images
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"8BPS" return prefix[:4] == b"8BPS"
## ##
# Image plugin for Photoshop images. # Image plugin for Photoshop images.
@ -100,12 +102,12 @@ class PsdImageFile(ImageFile.ImageFile):
id = i16(read(2)) id = i16(read(2))
name = read(i8(read(1))) name = read(i8(read(1)))
if not (len(name) & 1): if not (len(name) & 1):
read(1) # padding read(1) # padding
data = read(i32(read(4))) data = read(i32(read(4)))
if (len(data) & 1): if (len(data) & 1):
read(1) # padding read(1) # padding
self.resources.append((id, name, data)) self.resources.append((id, name, data))
if id == 1039: # ICC profile if id == 1039: # ICC profile
self.info["icc_profile"] = data self.info["icc_profile"] = data
# #
@ -130,6 +132,10 @@ class PsdImageFile(ImageFile.ImageFile):
self._fp = self.fp self._fp = self.fp
self.frame = 0 self.frame = 0
@property
def n_frames(self):
return len(self.layers)
def seek(self, layer): def seek(self, layer):
# seek to given layer (1..max) # seek to given layer (1..max)
if layer == self.frame: if layer == self.frame:
@ -159,6 +165,7 @@ class PsdImageFile(ImageFile.ImageFile):
if self.mode == "P": if self.mode == "P":
Image.Image.load(self) Image.Image.load(self)
def _layerinfo(file): def _layerinfo(file):
# read layerinfo block # read layerinfo block
layers = [] layers = []
@ -166,8 +173,10 @@ def _layerinfo(file):
for i in range(abs(i16(read(2)))): for i in range(abs(i16(read(2)))):
# bounding box # bounding box
y0 = i32(read(4)); x0 = i32(read(4)) y0 = i32(read(4))
y1 = i32(read(4)); x1 = i32(read(4)) x0 = i32(read(4))
y1 = i32(read(4))
x1 = i32(read(4))
# image info # image info
info = [] info = []
@ -197,7 +206,7 @@ def _layerinfo(file):
elif mode == ["A", "B", "G", "R"]: elif mode == ["A", "B", "G", "R"]:
mode = "RGBA" mode = "RGBA"
else: else:
mode = None # unknown mode = None # unknown
# skip over blend flags and extra information # skip over blend flags and extra information
filler = read(12) filler = read(12)
@ -207,8 +216,10 @@ def _layerinfo(file):
if size: if size:
length = i32(read(4)) length = i32(read(4))
if length: if length:
mask_y = i32(read(4)); mask_x = i32(read(4)) mask_y = i32(read(4))
mask_h = i32(read(4)) - mask_y; mask_w = i32(read(4)) - mask_x mask_x = i32(read(4))
mask_h = i32(read(4)) - mask_y
mask_w = i32(read(4)) - mask_x
file.seek(length - 16, 1) file.seek(length - 16, 1)
combined += length + 4 combined += length + 4
@ -219,7 +230,8 @@ def _layerinfo(file):
length = i8(read(1)) length = i8(read(1))
if length: if length:
# Don't know the proper encoding, Latin-1 should be a good guess # Don't know the proper encoding,
# Latin-1 should be a good guess
name = read(length).decode('latin-1', 'replace') name = read(length).decode('latin-1', 'replace')
combined += length + 1 combined += length + 1
@ -239,6 +251,7 @@ def _layerinfo(file):
return layers return layers
def _maketile(file, mode, bbox, channels): def _maketile(file, mode, bbox, channels):
tile = None tile = None
@ -283,7 +296,7 @@ def _maketile(file, mode, bbox, channels):
file.seek(offset) file.seek(offset)
if offset & 1: if offset & 1:
read(1) # padding read(1) # padding
return tile return tile

View File

@ -16,7 +16,8 @@
# * Implements the pixel access object following Access. # * Implements the pixel access object following Access.
# * Does not implement the line functions, as they don't appear to be used # * Does not implement the line functions, as they don't appear to be used
# * Taking only the tuple form, which is used from python. # * Taking only the tuple form, which is used from python.
# * Fill.c uses the integer form, but it's still going to use the old Access.c implementation. # * Fill.c uses the integer form, but it's still going to use the old
# Access.c implementation.
# #
from __future__ import print_function from __future__ import print_function
@ -40,7 +41,7 @@ ffi.cdef(defs)
class PyAccess(object): class PyAccess(object):
def __init__(self, img, readonly = False): def __init__(self, img, readonly=False):
vals = dict(img.im.unsafe_ptrs) vals = dict(img.im.unsafe_ptrs)
self.readonly = readonly self.readonly = readonly
self.image8 = ffi.cast('unsigned char **', vals['image8']) self.image8 = ffi.cast('unsigned char **', vals['image8'])
@ -50,10 +51,11 @@ class PyAccess(object):
self.ysize = vals['ysize'] self.ysize = vals['ysize']
if DEBUG: if DEBUG:
print (vals) print(vals)
self._post_init() self._post_init()
def _post_init(): pass def _post_init(self):
pass
def __setitem__(self, xy, color): def __setitem__(self, xy, color):
""" """
@ -64,9 +66,10 @@ class PyAccess(object):
:param xy: The pixel coordinate, given as (x, y). :param xy: The pixel coordinate, given as (x, y).
:param value: The pixel value. :param value: The pixel value.
""" """
if self.readonly: raise ValueError('Attempt to putpixel a read only image') if self.readonly:
(x,y) = self.check_xy(xy) raise ValueError('Attempt to putpixel a read only image')
return self.set_pixel(x,y,color) (x, y) = self.check_xy(xy)
return self.set_pixel(x, y, color)
def __getitem__(self, xy): def __getitem__(self, xy):
""" """
@ -75,34 +78,38 @@ class PyAccess(object):
images images
:param xy: The pixel coordinate, given as (x, y). :param xy: The pixel coordinate, given as (x, y).
:returns: a pixel value for single band images, a tuple of
pixel values for multiband images.
""" """
(x,y) = self.check_xy(xy) (x, y) = self.check_xy(xy)
return self.get_pixel(x,y) return self.get_pixel(x, y)
putpixel = __setitem__ putpixel = __setitem__
getpixel = __getitem__ getpixel = __getitem__
def check_xy(self, xy): def check_xy(self, xy):
(x,y) = xy (x, y) = xy
if not (0 <= x < self.xsize and 0 <= y < self.ysize): if not (0 <= x < self.xsize and 0 <= y < self.ysize):
raise ValueError('pixel location out of range') raise ValueError('pixel location out of range')
return xy return xy
class _PyAccess32_2(PyAccess): class _PyAccess32_2(PyAccess):
""" PA, LA, stored in first and last bytes of a 32 bit word """ """ PA, LA, stored in first and last bytes of a 32 bit word """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return (pixel.r, pixel.a) return (pixel.r, pixel.a)
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
# tuple # tuple
pixel.r = min(color[0],255) pixel.r = min(color[0], 255)
pixel.a = min(color[1],255) pixel.a = min(color[1], 255)
class _PyAccess32_3(PyAccess): class _PyAccess32_3(PyAccess):
""" RGB and friends, stored in the first three bytes of a 32 bit word """ """ RGB and friends, stored in the first three bytes of a 32 bit word """
@ -110,33 +117,34 @@ class _PyAccess32_3(PyAccess):
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return (pixel.r, pixel.g, pixel.b) return (pixel.r, pixel.g, pixel.b)
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
# tuple # tuple
pixel.r = min(color[0],255) pixel.r = min(color[0], 255)
pixel.g = min(color[1],255) pixel.g = min(color[1], 255)
pixel.b = min(color[2],255) pixel.b = min(color[2], 255)
class _PyAccess32_4(PyAccess): class _PyAccess32_4(PyAccess):
""" RGBA etc, all 4 bytes of a 32 bit word """ """ RGBA etc, all 4 bytes of a 32 bit word """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return (pixel.r, pixel.g, pixel.b, pixel.a) return (pixel.r, pixel.g, pixel.b, pixel.a)
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
# tuple # tuple
pixel.r = min(color[0],255) pixel.r = min(color[0], 255)
pixel.g = min(color[1],255) pixel.g = min(color[1], 255)
pixel.b = min(color[2],255) pixel.b = min(color[2], 255)
pixel.a = min(color[3],255) pixel.a = min(color[3], 255)
class _PyAccess8(PyAccess): class _PyAccess8(PyAccess):
@ -144,26 +152,27 @@ class _PyAccess8(PyAccess):
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = self.image8 self.pixels = self.image8
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
try: try:
# integer # integer
self.pixels[y][x] = min(color,255) self.pixels[y][x] = min(color, 255)
except: except:
# tuple # tuple
self.pixels[y][x] = min(color[0],255) self.pixels[y][x] = min(color[0], 255)
class _PyAccessI16_N(PyAccess): class _PyAccessI16_N(PyAccess):
""" I;16 access, native bitendian without conversion """ """ I;16 access, native bitendian without conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('unsigned short **', self.image) self.pixels = ffi.cast('unsigned short **', self.image)
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
try: try:
# integer # integer
self.pixels[y][x] = min(color, 65535) self.pixels[y][x] = min(color, 65535)
@ -171,35 +180,37 @@ class _PyAccessI16_N(PyAccess):
# tuple # tuple
self.pixels[y][x] = min(color[0], 65535) self.pixels[y][x] = min(color[0], 65535)
class _PyAccessI16_L(PyAccess): class _PyAccessI16_L(PyAccess):
""" I;16L access, with conversion """ """ I;16L access, with conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('struct Pixel_I16 **', self.image) self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return pixel.l + pixel.r * 256 return pixel.l + pixel.r * 256
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
try: try:
color = min(color, 65535) color = min(color, 65535)
except: except TypeError:
color = min(color[0], 65535) color = min(color[0], 65535)
pixel.l = color & 0xFF pixel.l = color & 0xFF
pixel.r = color >> 8 pixel.r = color >> 8
class _PyAccessI16_B(PyAccess): class _PyAccessI16_B(PyAccess):
""" I;16B access, with conversion """ """ I;16B access, with conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('struct Pixel_I16 **', self.image) self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return pixel.l *256 + pixel.r return pixel.l * 256 + pixel.r
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
try: try:
color = min(color, 65535) color = min(color, 65535)
@ -209,17 +220,19 @@ class _PyAccessI16_B(PyAccess):
pixel.l = color >> 8 pixel.l = color >> 8
pixel.r = color & 0xFF pixel.r = color & 0xFF
class _PyAccessI32_N(PyAccess): class _PyAccessI32_N(PyAccess):
""" Signed Int32 access, native endian """ """ Signed Int32 access, native endian """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = self.image32 self.pixels = self.image32
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
self.pixels[y][x] = color self.pixels[y][x] = color
class _PyAccessI32_Swap(PyAccess): class _PyAccessI32_Swap(PyAccess):
""" I;32L/B access, with byteswapping conversion """ """ I;32L/B access, with byteswapping conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
@ -228,24 +241,26 @@ class _PyAccessI32_Swap(PyAccess):
def reverse(self, i): def reverse(self, i):
orig = ffi.new('int *', i) orig = ffi.new('int *', i)
chars = ffi.cast('unsigned char *', orig) chars = ffi.cast('unsigned char *', orig)
chars[0],chars[1],chars[2],chars[3] = chars[3], chars[2],chars[1],chars[0] chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], \
chars[1], chars[0]
return ffi.cast('int *', chars)[0] return ffi.cast('int *', chars)[0]
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.reverse(self.pixels[y][x]) return self.reverse(self.pixels[y][x])
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
self.pixels[y][x] = self.reverse(color) self.pixels[y][x] = self.reverse(color)
class _PyAccessF(PyAccess): class _PyAccessF(PyAccess):
""" 32 bit float access """ """ 32 bit float access """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('float **', self.image32) self.pixels = ffi.cast('float **', self.image32)
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
try: try:
# not a tuple # not a tuple
self.pixels[y][x] = color self.pixels[y][x] = color
@ -286,13 +301,15 @@ else:
mode_map['I;32L'] = _PyAccessI32_Swap mode_map['I;32L'] = _PyAccessI32_Swap
mode_map['I;32B'] = _PyAccessI32_N mode_map['I;32B'] = _PyAccessI32_N
def new(img, readonly=False):
def new(img, readonly=False):
access_type = mode_map.get(img.mode, None) access_type = mode_map.get(img.mode, None)
if not access_type: if not access_type:
if DEBUG: print ("PyAccess Not Implemented: %s" % img.mode) if DEBUG:
print("PyAccess Not Implemented: %s" % img.mode)
return None return None
if DEBUG: print ("New PyAccess: %s" % img.mode) if DEBUG:
print("New PyAccess: %s" % img.mode)
return access_type(img, readonly) return access_type(img, readonly)
# End of file

View File

@ -48,7 +48,7 @@ def isInt(f):
return 1 return 1
else: else:
return 0 return 0
except: except ValueError:
return 0 return 0
iforms = [1, 3, -11, -12, -21, -22] iforms = [1, 3, -11, -12, -21, -22]
@ -127,12 +127,12 @@ class SpiderImageFile(ImageFile.ImageFile):
if self.istack == 0 and self.imgnumber == 0: if self.istack == 0 and self.imgnumber == 0:
# stk=0, img=0: a regular 2D image # stk=0, img=0: a regular 2D image
offset = hdrlen offset = hdrlen
self.nimages = 1 self._nimages = 1
elif self.istack > 0 and self.imgnumber == 0: elif self.istack > 0 and self.imgnumber == 0:
# stk>0, img=0: Opening the stack for the first time # stk>0, img=0: Opening the stack for the first time
self.imgbytes = int(h[12]) * int(h[2]) * 4 self.imgbytes = int(h[12]) * int(h[2]) * 4
self.hdrlen = hdrlen self.hdrlen = hdrlen
self.nimages = int(h[26]) self._nimages = int(h[26])
# Point to the first image in the stack # Point to the first image in the stack
offset = hdrlen * 2 offset = hdrlen * 2
self.imgnumber = 1 self.imgnumber = 1
@ -154,6 +154,10 @@ class SpiderImageFile(ImageFile.ImageFile):
(self.rawmode, 0, 1))] (self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
@property
def n_frames(self):
return self._nimages
# 1st image index is zero (although SPIDER imgnumber starts at 1) # 1st image index is zero (although SPIDER imgnumber starts at 1)
def tell(self): def tell(self):
if self.imgnumber < 1: if self.imgnumber < 1:
@ -164,7 +168,7 @@ class SpiderImageFile(ImageFile.ImageFile):
def seek(self, frame): def seek(self, frame):
if self.istack == 0: if self.istack == 0:
return return
if frame >= self.nimages: if frame >= self._nimages:
raise EOFError("attempt to seek past end of file") raise EOFError("attempt to seek past end of file")
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes) self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
self.fp = self.__fp self.fp = self.__fp
@ -173,11 +177,11 @@ class SpiderImageFile(ImageFile.ImageFile):
# returns a byte image after rescaling to 0..255 # returns a byte image after rescaling to 0..255
def convert2byte(self, depth=255): def convert2byte(self, depth=255):
(min, max) = self.getextrema() (minimum, maximum) = self.getextrema()
m = 1 m = 1
if max != min: if maximum != minimum:
m = depth / (max-min) m = depth / (maximum-minimum)
b = -m * min b = -m * minimum
return self.point(lambda i, m=m, b=b: i * m + b).convert("L") return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
# returns a ImageTk.PhotoImage object, after rescaling to 0..255 # returns a ImageTk.PhotoImage object, after rescaling to 0..255
@ -271,7 +275,7 @@ def _save(im, fp, filename):
def _save_spider(im, fp, filename): def _save_spider(im, fp, filename):
# get the filename extension and register it with Image # get the filename extension and register it with Image
fn, ext = os.path.splitext(filename) ext = os.path.splitext(filename)[1]
Image.register_extension("SPIDER", ext) Image.register_extension("SPIDER", ext)
_save(im, fp, filename) _save(im, fp, filename)

View File

@ -16,6 +16,7 @@
from PIL import ContainerIO from PIL import ContainerIO
## ##
# A file object that provides read access to a given member of a TAR # A file object that provides read access to a given member of a TAR
# file. # file.

View File

@ -42,9 +42,6 @@ MODES = {
} }
def _accept(prefix):
return prefix[0:1] == b"\0"
## ##
# Image plugin for Targa files. # Image plugin for Targa files.
@ -58,7 +55,7 @@ class TgaImageFile(ImageFile.ImageFile):
# process header # process header
s = self.fp.read(18) s = self.fp.read(18)
id = i8(s[0]) idlen = i8(s[0])
colormaptype = i8(s[1]) colormaptype = i8(s[1])
imagetype = i8(s[2]) imagetype = i8(s[2])
@ -70,7 +67,7 @@ class TgaImageFile(ImageFile.ImageFile):
self.size = i16(s[12:]), i16(s[14:]) self.size = i16(s[12:]), i16(s[14:])
# validate header fields # validate header fields
if id != 0 or colormaptype not in (0, 1) or\ if colormaptype not in (0, 1) or\
self.size[0] <= 0 or self.size[1] <= 0 or\ self.size[0] <= 0 or self.size[1] <= 0 or\
depth not in (1, 8, 16, 24, 32): depth not in (1, 8, 16, 24, 32):
raise SyntaxError("not a TGA file") raise SyntaxError("not a TGA file")
@ -79,7 +76,7 @@ class TgaImageFile(ImageFile.ImageFile):
if imagetype in (3, 11): if imagetype in (3, 11):
self.mode = "L" self.mode = "L"
if depth == 1: if depth == 1:
self.mode = "1" # ??? self.mode = "1" # ???
elif imagetype in (1, 9): elif imagetype in (1, 9):
self.mode = "P" self.mode = "P"
elif imagetype in (2, 10): elif imagetype in (2, 10):
@ -103,22 +100,25 @@ class TgaImageFile(ImageFile.ImageFile):
if imagetype & 8: if imagetype & 8:
self.info["compression"] = "tga_rle" self.info["compression"] = "tga_rle"
if idlen:
self.info["id_section"] = self.fp.read(idlen)
if colormaptype: if colormaptype:
# read palette # read palette
start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:]) start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:])
if mapdepth == 16: if mapdepth == 16:
self.palette = ImagePalette.raw("BGR;16", self.palette = ImagePalette.raw(
b"\0"*2*start + self.fp.read(2*size)) "BGR;16", b"\0"*2*start + self.fp.read(2*size))
elif mapdepth == 24: elif mapdepth == 24:
self.palette = ImagePalette.raw("BGR", self.palette = ImagePalette.raw(
b"\0"*3*start + self.fp.read(3*size)) "BGR", b"\0"*3*start + self.fp.read(3*size))
elif mapdepth == 32: elif mapdepth == 32:
self.palette = ImagePalette.raw("BGRA", self.palette = ImagePalette.raw(
b"\0"*4*start + self.fp.read(4*size)) "BGRA", b"\0"*4*start + self.fp.read(4*size))
# setup tile descriptor # setup tile descriptor
try: try:
rawmode = MODES[(imagetype&7, depth)] rawmode = MODES[(imagetype & 7, depth)]
if imagetype & 8: if imagetype & 8:
# compressed # compressed
self.tile = [("tga_rle", (0, 0)+self.size, self.tile = [("tga_rle", (0, 0)+self.size,
@ -127,7 +127,7 @@ class TgaImageFile(ImageFile.ImageFile):
self.tile = [("raw", (0, 0)+self.size, self.tile = [("raw", (0, 0)+self.size,
self.fp.tell(), (rawmode, 0, orientation))] self.fp.tell(), (rawmode, 0, orientation))]
except KeyError: except KeyError:
pass # cannot decode pass # cannot decode
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -145,6 +145,7 @@ SAVE = {
"RGBA": ("BGRA", 32, 0, 2), "RGBA": ("BGRA", 32, 0, 2),
} }
def _save(im, fp, filename, check=0): def _save(im, fp, filename, check=0):
try: try:
@ -185,13 +186,14 @@ def _save(im, fp, filename, check=0):
if colormaptype: if colormaptype:
fp.write(im.im.getpalette("RGB", "BGR")) fp.write(im.im.getpalette("RGB", "BGR"))
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, orientation))]) ImageFile._save(
im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))])
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Registry # Registry
Image.register_open("TGA", TgaImageFile, _accept) Image.register_open("TGA", TgaImageFile)
Image.register_save("TGA", _save) Image.register_save("TGA", _save)
Image.register_extension("TGA", ".tga") Image.register_extension("TGA", ".tga")

View File

@ -54,6 +54,7 @@ import sys
import collections import collections
import itertools import itertools
import os import os
import io
# Set these to true to force use of libtiff for reading or writing. # Set these to true to force use of libtiff for reading or writing.
READ_LIBTIFF = False READ_LIBTIFF = False
@ -149,6 +150,7 @@ OPEN_INFO = {
(II, 0, 1, 2, (8,), ()): ("L", "L;IR"), (II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
(II, 0, 3, 1, (32,), ()): ("F", "F;32F"), (II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
(II, 1, 1, 1, (1,), ()): ("1", "1"), (II, 1, 1, 1, (1,), ()): ("1", "1"),
(II, 1, 1, 1, (4,), ()): ("L", "L;4"),
(II, 1, 1, 2, (1,), ()): ("1", "1;R"), (II, 1, 1, 2, (1,), ()): ("1", "1;R"),
(II, 1, 1, 1, (8,), ()): ("L", "L"), (II, 1, 1, 1, (8,), ()): ("L", "L"),
(II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"), (II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
@ -281,6 +283,7 @@ class ImageFileDirectory(collections.MutableMapping):
self.tagdata = {} self.tagdata = {}
self.tagtype = {} # added 2008-06-05 by Florian Hoech self.tagtype = {} # added 2008-06-05 by Florian Hoech
self.next = None self.next = None
self.offset = None
def __str__(self): def __str__(self):
return str(self.as_dict()) return str(self.as_dict())
@ -291,7 +294,7 @@ class ImageFileDirectory(collections.MutableMapping):
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 possible.
""" """
from PIL import TiffTags from PIL import TiffTags
result = {} result = {}
@ -415,6 +418,7 @@ class ImageFileDirectory(collections.MutableMapping):
# load tag dictionary # load tag dictionary
self.reset() self.reset()
self.offset = fp.tell()
i16 = self.i16 i16 = self.i16
i32 = self.i32 i32 = self.i32
@ -422,6 +426,11 @@ class ImageFileDirectory(collections.MutableMapping):
for i in range(i16(fp.read(2))): for i in range(i16(fp.read(2))):
ifd = fp.read(12) ifd = fp.read(12)
if len(ifd) != 12:
warnings.warn("Possibly corrupt EXIF data. "
"Expecting to read 12 bytes but only got %d."
% (len(ifd)))
continue
tag, typ = i16(ifd), i16(ifd, 2) tag, typ = i16(ifd), i16(ifd, 2)
@ -446,7 +455,11 @@ class ImageFileDirectory(collections.MutableMapping):
# Get and expand tag value # Get and expand tag value
if size > 4: if size > 4:
here = fp.tell() here = fp.tell()
if Image.DEBUG:
print("Tag Location: %s" % here)
fp.seek(i32(ifd, 8)) fp.seek(i32(ifd, 8))
if Image.DEBUG:
print("Data Location: %s" % fp.tell())
data = ImageFile._safe_read(fp, size) data = ImageFile._safe_read(fp, size)
fp.seek(here) fp.seek(here)
else: else:
@ -468,7 +481,14 @@ class ImageFileDirectory(collections.MutableMapping):
else: else:
print("- value:", self[tag]) print("- value:", self[tag])
self.next = i32(fp.read(4)) ifd = fp.read(4)
if len(ifd) != 4:
warnings.warn("Possibly corrupt EXIF data. "
"Expecting to read 4 bytes but only got %d."
% (len(ifd)))
return
self.next = i32(ifd)
# save primitives # save primitives
@ -498,7 +518,7 @@ class ImageFileDirectory(collections.MutableMapping):
typ = self.tagtype[tag] typ = self.tagtype[tag]
if Image.DEBUG: if Image.DEBUG:
print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) print("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
if typ == 1: if typ == 1:
# byte data # byte data
@ -509,6 +529,15 @@ class ImageFileDirectory(collections.MutableMapping):
elif typ == 7: elif typ == 7:
# untyped data # untyped data
data = value = b"".join(value) data = value = b"".join(value)
elif typ in (11, 12):
# float value
tmap = {11: 'f', 12: 'd'}
if not isinstance(value, tuple):
value = (value,)
a = array.array(tmap[typ], value)
if self.prefix != native_prefix:
a.byteswap()
data = a.tostring()
elif isStringType(value[0]): elif isStringType(value[0]):
# string data # string data
if isinstance(value, tuple): if isinstance(value, tuple):
@ -619,45 +648,63 @@ class TiffImageFile(ImageFile.ImageFile):
self.__first = self.__next = self.ifd.i32(ifh, 4) self.__first = self.__next = self.ifd.i32(ifh, 4)
self.__frame = -1 self.__frame = -1
self.__fp = self.fp self.__fp = self.fp
self._frame_pos = []
self._n_frames = None
if Image.DEBUG: if Image.DEBUG:
print ("*** TiffImageFile._open ***") print("*** TiffImageFile._open ***")
print ("- __first:", self.__first) print("- __first:", self.__first)
print ("- ifh: ", ifh) print("- ifh: ", ifh)
# and load the first frame # and load the first frame
self._seek(0) self._seek(0)
@property
def n_frames(self):
if self._n_frames is None:
current = self.tell()
try:
while True:
self._seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current)
return self._n_frames
def seek(self, frame): def seek(self, frame):
"Select a given frame as current image" "Select a given frame as current image"
self._seek(max(frame, 0)) # Questionable backwards compatibility.
if frame < 0: # Create a new core image object on second and
frame = 0 # subsequent frames in the image. Image may be
self._seek(frame) # different size/mode.
Image._decompression_bomb_check(self.size)
def tell(self): self.im = Image.core.new(self.mode, self.size)
"Return the current frame number"
return self._tell()
def _seek(self, frame): def _seek(self, frame):
self.fp = self.__fp self.fp = self.__fp
if frame < self.__frame: while len(self._frame_pos) <= frame:
# rewind file
self.__frame = -1
self.__next = self.__first
while self.__frame < frame:
if not self.__next: if not self.__next:
raise EOFError("no more images in TIFF file") raise EOFError("no more images in TIFF file")
if Image.DEBUG:
print("Seeking to frame %s, on frame %s, __next %s, location: %s" %
(frame, self.__frame, self.__next, self.fp.tell()))
# reset python3 buffered io handle in case fp
# was passed to libtiff, invalidating the buffer
self.fp.tell()
self.fp.seek(self.__next) self.fp.seek(self.__next)
self._frame_pos.append(self.__next)
if Image.DEBUG:
print("Loading tags, location: %s" % self.fp.tell())
self.tag.load(self.fp) self.tag.load(self.fp)
self.__next = self.tag.next self.__next = self.tag.next
self.__frame += 1 self.__frame += 1
self.fp.seek(self._frame_pos[frame])
self.tag.load(self.fp)
self.__frame = frame
self._setup() self._setup()
def _tell(self): def tell(self):
"Return the current frame number"
return self.__frame return self.__frame
def _decoder(self, rawmode, layer, tile=None): def _decoder(self, rawmode, layer, tile=None):
@ -705,7 +752,8 @@ class TiffImageFile(ImageFile.ImageFile):
# (self._compression, (extents tuple), # (self._compression, (extents tuple),
# 0, (rawmode, self._compression, fp)) # 0, (rawmode, self._compression, fp))
ignored, extents, ignored_2, args = self.tile[0] extents = self.tile[0][1]
args = self.tile[0][3] + (self.ifd.offset,)
decoder = Image._getdecoder(self.mode, 'libtiff', args, decoder = Image._getdecoder(self.mode, 'libtiff', args,
self.decoderconfig) self.decoderconfig)
try: try:
@ -722,21 +770,21 @@ class TiffImageFile(ImageFile.ImageFile):
# #
# 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 # that returns an IOError if there's no underlying fp. Easier to
# dea. with here by reordering. # deal with here by reordering.
if Image.DEBUG: 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())
elif hasattr(self.fp, "fileno"): elif hasattr(self.fp, "fileno"):
# we've got a actual file on disk, pass in the fp. # we've got a actual file on disk, pass in the fp.
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)
# 4 bytes, otherwise the trace might error out # 4 bytes, otherwise the trace might error out
n, err = decoder.decode(b"fpfp") n, err = decoder.decode(b"fpfp")
else: else:
# we have something else. # we have something else.
if Image.DEBUG: if Image.DEBUG:
print ("don't have fileno or getvalue. just reading") print("don't have fileno or getvalue. just reading")
# UNDONE -- so much for that buffer size thing. # UNDONE -- so much for that buffer size thing.
n, err = decoder.decode(self.fp.read()) n, err = decoder.decode(self.fp.read())
@ -744,7 +792,8 @@ class TiffImageFile(ImageFile.ImageFile):
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
if hasattr(self.fp, 'close'): if hasattr(self.fp, 'close'):
self.fp.close() if not self.__next:
self.fp.close()
self.fp = None # might be shared self.fp = None # might be shared
if err < 0: if err < 0:
@ -866,6 +915,10 @@ class TiffImageFile(ImageFile.ImageFile):
try: try:
fp = hasattr(self.fp, "fileno") and \ fp = hasattr(self.fp, "fileno") and \
os.dup(self.fp.fileno()) os.dup(self.fp.fileno())
# flush the file descriptor, prevents error on pypy 2.4+
# should also eliminate the need for fp.tell for py3
# in _seek
self.fp.flush()
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.
@ -911,7 +964,7 @@ class TiffImageFile(ImageFile.ImageFile):
(0, min(y, ysize), w, min(y+h, ysize)), (0, min(y, ysize), w, min(y+h, ysize)),
offsets[i], a)) offsets[i], a))
if Image.DEBUG: if Image.DEBUG:
print ("tiles: ", self.tile) print("tiles: ", self.tile)
y = y + h y = y + h
if y >= self.size[1]: if y >= self.size[1]:
x = y = 0 x = y = 0
@ -946,14 +999,14 @@ class TiffImageFile(ImageFile.ImageFile):
# fixup palette descriptor # fixup palette descriptor
if self.mode == "P": if self.mode == "P":
palette = [o8(a // 256) for a in self.tag[COLORMAP]] palette = [o8(b // 256) for b in self.tag[COLORMAP]]
self.palette = ImagePalette.raw("RGB;L", b"".join(palette)) self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write TIFF files # Write TIFF files
# little endian is default except for image modes with # little endian is default except for image modes with
# explict big endian byte-order # explicit big endian byte-order
SAVE_INFO = { SAVE_INFO = {
# mode => rawmode, byteorder, photometrics, # mode => rawmode, byteorder, photometrics,
@ -1045,31 +1098,25 @@ def _save(im, fp, filename):
if "icc_profile" in im.info: if "icc_profile" in im.info:
ifd[ICCPROFILE] = im.info["icc_profile"] ifd[ICCPROFILE] = im.info["icc_profile"]
if "description" in im.encoderinfo: for key, name, cvt in [
ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] (IMAGEDESCRIPTION, "description", lambda x: x),
if "resolution" in im.encoderinfo: (X_RESOLUTION, "resolution", _cvt_res),
ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \ (Y_RESOLUTION, "resolution", _cvt_res),
= _cvt_res(im.encoderinfo["resolution"]) (X_RESOLUTION, "x_resolution", _cvt_res),
if "x resolution" in im.encoderinfo: (Y_RESOLUTION, "y_resolution", _cvt_res),
ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"]) (RESOLUTION_UNIT, "resolution_unit",
if "y resolution" in im.encoderinfo: lambda x: {"inch": 2, "cm": 3, "centimeter": 3}.get(x, 1)),
ifd[Y_RESOLUTION] = _cvt_res(im.encoderinfo["y resolution"]) (SOFTWARE, "software", lambda x: x),
if "resolution unit" in im.encoderinfo: (DATE_TIME, "date_time", lambda x: x),
unit = im.encoderinfo["resolution unit"] (ARTIST, "artist", lambda x: x),
if unit == "inch": (COPYRIGHT, "copyright", lambda x: x)]:
ifd[RESOLUTION_UNIT] = 2 name_with_spaces = name.replace("_", " ")
elif unit == "cm" or unit == "centimeter": if "_" in name and name_with_spaces in im.encoderinfo:
ifd[RESOLUTION_UNIT] = 3 warnings.warn("%r is deprecated; use %r instead" %
else: (name_with_spaces, name), DeprecationWarning)
ifd[RESOLUTION_UNIT] = 1 ifd[key] = cvt(im.encoderinfo[name.replace("_", " ")])
if "software" in im.encoderinfo: if name in im.encoderinfo:
ifd[SOFTWARE] = im.encoderinfo["software"] ifd[key] = cvt(im.encoderinfo[name])
if "date time" in im.encoderinfo:
ifd[DATE_TIME] = im.encoderinfo["date time"]
if "artist" in im.encoderinfo:
ifd[ARTIST] = im.encoderinfo["artist"]
if "copyright" in im.encoderinfo:
ifd[COPYRIGHT] = im.encoderinfo["copyright"]
dpi = im.encoderinfo.get("dpi") dpi = im.encoderinfo.get("dpi")
if dpi: if dpi:
@ -1102,12 +1149,15 @@ def _save(im, fp, filename):
if libtiff: if libtiff:
if Image.DEBUG: if Image.DEBUG:
print ("Saving using libtiff encoder") print("Saving using libtiff encoder")
print (ifd.items()) print(ifd.items())
_fp = 0 _fp = 0
if hasattr(fp, "fileno"): if hasattr(fp, "fileno"):
fp.seek(0) try:
_fp = os.dup(fp.fileno()) fp.seek(0)
_fp = os.dup(fp.fileno())
except io.UnsupportedOperation:
pass
# ICC Profile crashes. # ICC Profile crashes.
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE]
@ -1132,8 +1182,11 @@ def _save(im, fp, filename):
# following tiffcp.c->cpTag->TIFF_RATIONAL # 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 or
(len(v) == 2 and v[1] == 0))):
# List of ints? # List of ints?
# Avoid divide by zero in next if-clause
if type(v[0]) in (int, float): if type(v[0]) in (int, float):
atts[k] = list(v) atts[k] = list(v)
continue continue
@ -1154,7 +1207,7 @@ def _save(im, fp, filename):
atts[k] = v atts[k] = v
if Image.DEBUG: if Image.DEBUG:
print (atts) print(atts)
# libtiff always expects the bytes in native order. # libtiff always expects the bytes in native order.
# we're storing image byte order. So, if the rawmode # we're storing image byte order. So, if the rawmode

View File

@ -46,8 +46,8 @@ TAGS = {
(262, 5): "CMYK", (262, 5): "CMYK",
(262, 6): "YCbCr", (262, 6): "YCbCr",
(262, 8): "CieLAB", (262, 8): "CieLAB",
(262, 32803): "CFA", # TIFF/EP, Adobe DNG (262, 32803): "CFA", # TIFF/EP, Adobe DNG
(262, 32892): "LinearRaw", # Adobe DNG (262, 32892): "LinearRaw", # Adobe DNG
263: "Thresholding", 263: "Thresholding",
264: "CellWidth", 264: "CellWidth",
@ -255,7 +255,6 @@ TAGS = {
50716: "BlackLevelDeltaV", 50716: "BlackLevelDeltaV",
50717: "WhiteLevel", 50717: "WhiteLevel",
50718: "DefaultScale", 50718: "DefaultScale",
50741: "BestQualityScale", # FIXME! Dictionary contains duplicate keys 50741
50719: "DefaultCropOrigin", 50719: "DefaultCropOrigin",
50720: "DefaultCropSize", 50720: "DefaultCropSize",
50778: "CalibrationIlluminant1", 50778: "CalibrationIlluminant1",
@ -279,11 +278,12 @@ TAGS = {
50737: "ChromaBlurRadius", 50737: "ChromaBlurRadius",
50738: "AntiAliasStrength", 50738: "AntiAliasStrength",
50740: "DNGPrivateData", 50740: "DNGPrivateData",
50741: "MakerNoteSafety", # FIXME! Dictionary contains duplicate keys 50741 50741: "MakerNoteSafety",
50780: "BestQualityScale",
#ImageJ # ImageJ
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe 50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
50839: "ImageJMetaData", # private tag registered with Adobe 50839: "ImageJMetaData", # private tag registered with Adobe
} }
## ##

View File

@ -1,5 +1,3 @@
# -*- coding: iso-8859-1 -*-
#
# The Python Imaging Library. # The Python Imaging Library.
# $Id$ # $Id$
# #
@ -33,6 +31,7 @@ except ImportError:
i32 = _binary.i32le i32 = _binary.i32le
## ##
# Load texture from a Quake2 WAL texture file. # Load texture from a Quake2 WAL texture file.
# <p> # <p>
@ -75,7 +74,7 @@ def open(filename):
quake2palette = ( quake2palette = (
# default palette taken from piffo 0.93 by Hans Häggström # default palette taken from piffo 0.93 by Hans Häggström
b"\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e" b"\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e"
b"\x1e\x1e\x22\x22\x22\x26\x26\x26\x29\x29\x29\x2c\x2c\x2c\x2f\x2f" b"\x1e\x1e\x22\x22\x22\x26\x26\x26\x29\x29\x29\x2c\x2c\x2c\x2f\x2f"
b"\x2f\x32\x32\x32\x35\x35\x35\x37\x37\x37\x3a\x3a\x3a\x3c\x3c\x3c" b"\x2f\x32\x32\x32\x35\x35\x35\x37\x37\x37\x3a\x3a\x3a\x3c\x3c\x3c"

View File

@ -12,7 +12,7 @@ _VALID_WEBP_MODES = {
_VP8_MODES_BY_IDENTIFIER = { _VP8_MODES_BY_IDENTIFIER = {
b"VP8 ": "RGB", b"VP8 ": "RGB",
b"VP8X": "RGBA", b"VP8X": "RGBA",
b"VP8L": "RGBA", # lossless b"VP8L": "RGBA", # lossless
} }
@ -30,7 +30,8 @@ class WebPImageFile(ImageFile.ImageFile):
format_description = "WebP image" format_description = "WebP image"
def _open(self): def _open(self):
data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(self.fp.read()) data, width, height, self.mode, icc_profile, exif = \
_webp.WebPDecode(self.fp.read())
if icc_profile: if icc_profile:
self.info["icc_profile"] = icc_profile self.info["icc_profile"] = icc_profile

View File

@ -24,6 +24,7 @@ _handler = None
if str != bytes: if str != bytes:
long = int long = int
## ##
# Install application-specific WMF image handler. # Install application-specific WMF image handler.
# #
@ -36,14 +37,14 @@ def register_handler(handler):
if hasattr(Image.core, "drawwmf"): if hasattr(Image.core, "drawwmf"):
# install default handler (windows only) # install default handler (windows only)
class WmfHandler: class WmfHandler(object):
def open(self, im): def open(self, im):
im.mode = "RGB" im.mode = "RGB"
self.bbox = im.info["wmf_bbox"] self.bbox = im.info["wmf_bbox"]
def load(self, im): def load(self, im):
im.fp.seek(0) # rewind im.fp.seek(0) # rewind
return Image.frombytes( return Image.frombytes(
"RGB", im.size, "RGB", im.size,
Image.core.drawwmf(im.fp.read(), im.size, self.bbox), Image.core.drawwmf(im.fp.read(), im.size, self.bbox),
@ -56,6 +57,7 @@ if hasattr(Image.core, "drawwmf"):
word = _binary.i16le 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:
@ -64,6 +66,7 @@ def short(c, o=0):
dword = _binary.i32le dword = _binary.i32le
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Read WMF file # Read WMF file
@ -74,6 +77,7 @@ def _accept(prefix):
prefix[:4] == b"\x01\x00\x00\x00" prefix[:4] == b"\x01\x00\x00\x00"
) )
## ##
# Image plugin for Windows metafiles. # Image plugin for Windows metafiles.
@ -95,8 +99,10 @@ class WmfStubImageFile(ImageFile.StubImageFile):
inch = word(s, 14) inch = word(s, 14)
# get bounding box # get bounding box
x0 = short(s, 6); y0 = short(s, 8) x0 = short(s, 6)
x1 = short(s, 10); y1 = short(s, 12) y0 = short(s, 8)
x1 = short(s, 10)
y1 = short(s, 12)
# normalize size to 72 dots per inch # normalize size to 72 dots per inch
size = (x1 - x0) * 72 // inch, (y1 - y0) * 72 // inch size = (x1 - x0) * 72 // inch, (y1 - y0) * 72 // inch
@ -115,8 +121,10 @@ class WmfStubImageFile(ImageFile.StubImageFile):
# enhanced metafile # enhanced metafile
# get bounding box # get bounding box
x0 = dword(s, 8); y0 = dword(s, 12) x0 = dword(s, 8)
x1 = dword(s, 16); y1 = dword(s, 20) y0 = dword(s, 12)
x1 = dword(s, 16)
y1 = dword(s, 20)
# get frame (in 0.01 millimeter units) # get frame (in 0.01 millimeter units)
frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36) frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36)

View File

@ -30,6 +30,7 @@ for r in range(8):
for b in range(4): for b in range(4):
PALETTE = PALETTE + (o8((r*255)//7)+o8((g*255)//7)+o8((b*255)//3)) PALETTE = PALETTE + (o8((r*255)//7)+o8((g*255)//7)+o8((b*255)//3))
## ##
# Image plugin for XV thumbnail images. # Image plugin for XV thumbnail images.

View File

@ -35,9 +35,11 @@ xbm_head = re.compile(
b"[\\000-\\377]*_bits\\[\\]" b"[\\000-\\377]*_bits\\[\\]"
) )
def _accept(prefix): def _accept(prefix):
return prefix.lstrip()[:7] == b"#define" return prefix.lstrip()[:7] == b"#define"
## ##
# Image plugin for X11 bitmaps. # Image plugin for X11 bitmaps.
@ -81,7 +83,7 @@ def _save(im, fp, filename):
fp.write(b"static char im_bits[] = {\n") fp.write(b"static char im_bits[] = {\n")
ImageFile._save(im, fp, [("xbm", (0,0)+im.size, 0, None)]) ImageFile._save(im, fp, [("xbm", (0, 0)+im.size, 0, None)])
fp.write(b"};\n") fp.write(b"};\n")

View File

@ -12,7 +12,7 @@
# ;-) # ;-)
VERSION = '1.1.7' # PIL version VERSION = '1.1.7' # PIL version
PILLOW_VERSION = '2.5.3' # Pillow PILLOW_VERSION = '2.9.0.dev0' # Pillow
_plugins = ['BmpImagePlugin', _plugins = ['BmpImagePlugin',
'BufrStubImagePlugin', 'BufrStubImagePlugin',

View File

@ -11,21 +11,24 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from struct import unpack, pack
if bytes is str: if bytes is str:
def i8(c): def i8(c):
return ord(c) return ord(c)
def o8(i): def o8(i):
return chr(i&255) return chr(i & 255)
else: else:
def i8(c): def i8(c):
return c if c.__class__ is int else c[0] return c if c.__class__ is int else c[0]
def o8(i): def o8(i):
return bytes((i&255,)) return bytes((i & 255,))
# Input, le = little endian, be = big endian # Input, le = little endian, be = big endian
#TODO: replace with more readable struct.unpack equivalent # TODO: replace with more readable struct.unpack equivalent
def i16le(c, o=0): def i16le(c, o=0):
""" """
Converts a 2-bytes (16 bits) string to an integer. Converts a 2-bytes (16 bits) string to an integer.
@ -33,7 +36,8 @@ def i16le(c, o=0):
c: string containing bytes to convert c: string containing bytes to convert
o: offset of bytes to convert in string o: offset of bytes to convert in string
""" """
return i8(c[o]) | (i8(c[o+1])<<8) return unpack("<H", c[o:o+2])[0]
def i32le(c, o=0): def i32le(c, o=0):
""" """
@ -42,24 +46,31 @@ def i32le(c, o=0):
c: string containing bytes to convert c: string containing bytes to convert
o: offset of bytes to convert in string o: offset of bytes to convert in string
""" """
return i8(c[o]) | (i8(c[o+1])<<8) | (i8(c[o+2])<<16) | (i8(c[o+3])<<24) return unpack("<I", c[o:o+4])[0]
def i16be(c, o=0): def i16be(c, o=0):
return (i8(c[o])<<8) | i8(c[o+1]) return unpack(">H", c[o:o+2])[0]
def i32be(c, o=0): def i32be(c, o=0):
return (i8(c[o])<<24) | (i8(c[o+1])<<16) | (i8(c[o+2])<<8) | i8(c[o+3]) return unpack(">I", c[o:o+4])[0]
# Output, le = little endian, be = big endian # Output, le = little endian, be = big endian
def o16le(i): def o16le(i):
return o8(i) + o8(i>>8) return pack("<H", i)
def o32le(i): def o32le(i):
return o8(i) + o8(i>>8) + o8(i>>16) + o8(i>>24) return pack("<I", i)
def o16be(i): def o16be(i):
return o8(i>>8) + o8(i) return pack(">H", i)
def o32be(i): def o32be(i):
return o8(i>>24) + o8(i>>16) + o8(i>>8) + o8(i) return pack(">I", i)
# End of file

67
PIL/features.py Normal file
View File

@ -0,0 +1,67 @@
from PIL import Image
modules = {
"pil": "PIL._imaging",
"tkinter": "PIL._imagingtk",
"freetype2": "PIL._imagingft",
"littlecms2": "PIL._imagingcms",
"webp": "PIL._webp",
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
}
def check_module(feature):
if feature not in modules:
raise ValueError("Unknown module %s" % feature)
module = modules[feature]
method_to_call = None
if type(module) is tuple:
module, method_to_call = module
try:
imported_module = __import__(module)
except ImportError:
# If a method is being checked, None means that
# rather than the method failing, the module required for the method
# failed to be imported first
return None if method_to_call else False
if method_to_call:
method = getattr(imported_module, method_to_call)
return method() is True
else:
return True
def get_supported_modules():
supported_modules = []
for feature in modules:
if check_module(feature):
supported_modules.append(feature)
return supported_modules
codecs = {
"jpg": "jpeg",
"jpg_2000": "jpeg2k",
"zlib": "zip",
"libtiff": "libtiff"
}
def check_codec(feature):
if feature not in codecs:
raise ValueError("Unknown codec %s" % feature)
codec = codecs[feature]
return codec + "_encoder" in dir(Image.core)
def get_supported_codecs():
supported_codecs = []
for feature in codecs:
if check_codec(feature):
supported_codecs.append(feature)
return supported_codecs

View File

@ -1,22 +1,48 @@
Pillow Pillow
====== ======
*Python Imaging Library (Fork)* Python Imaging Library (Fork)
-----------------------------
Pillow is the "friendly" PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation <http://pillow.readthedocs.org/>`_, `check the changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_ and `find out how to contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_. Pillow is the "friendly PIL fork" by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master ..
image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
:target: https://travis-ci.org/python-pillow/Pillow :target: https://travis-ci.org/python-pillow/Pillow
:alt: Travis CI build status :alt: Travis CI build status (Linux)
.. image:: https://pypip.in/v/Pillow/badge.png ..
image:: https://pypip.in/v/Pillow/badge.png
:target: https://pypi.python.org/pypi/Pillow/ :target: https://pypi.python.org/pypi/Pillow/
:alt: Latest PyPI version :alt: Latest PyPI version
.. image:: https://pypip.in/d/Pillow/badge.png ..
image:: https://pypip.in/d/Pillow/badge.png
:target: https://pypi.python.org/pypi/Pillow/ :target: https://pypi.python.org/pypi/Pillow/
:alt: Number of PyPI downloads :alt: Number of PyPI downloads
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master .. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master :target: https://coveralls.io/r/python-pillow/Pillow?branch=master
:alt: Code coverage
.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.png
:target: https://landscape.io/github/python-pillow/Pillow/master
:alt: Code health
More Information
----------------
- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#02b5---117-1995-2010>`_
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_
- `Issues <https://github.com/python-pillow/Pillow/issues>`_
- `Documentation <http://pillow.readthedocs.org/>`_
- `About <http://pillow.readthedocs.org/about.html>`_
- `Guides <http://pillow.readthedocs.org/guides.html>`_
- `Installation <http://pillow.readthedocs.org/installation.html>`_
- `Reference <http://pillow.readthedocs.org/reference/index.html>`_

105
RELEASING.md Normal file
View File

@ -0,0 +1,105 @@
# Release Checklist
## Main Release
Released quarterly on the first day of January, April, July, October.
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174
* [ ] Develop and prepare release in ``master`` branch.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
```
PIL/__init__.py setup.py _imaging.c
```
* [ ] Update `CHANGES.rst`.
* [ ] Run pre-release check via `make pre`.
* [ ] Create branch and tag for release e.g.:
```
$ git branch 2.9.x
$ git tag 2.9.0
$ git push --all
$ git push --tags
```
* [ ] Create and upload source distributions e.g.:
```
$ make sdistup
```
* [ ] Create and upload [binary distributions](#binary-distributions)
* [ ] Manually hide old versions on PyPI as needed, such that only the latest main release is visible when viewing https://pypi.python.org/pypi/Pillow
## Point Release
Released as needed for security, installation or critical bug fixes.
* [ ] Make necessary changes in ``master`` branch.
* [ ] Update `CHANGES.rst`.
* [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``2.9.x``.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``2.9.x``.
* [ ] Checkout release branch e.g.:
```
git checkout -t remotes/origin/2.9.x
```
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
```
PIL/__init__.py setup.py _imaging.c
```
* [ ] Run pre-release check via `make pre`.
* [ ] Create tag for release e.g.:
```
$ git tag 2.9.1
$ git push --tags
```
* [ ] Create and upload source distributions e.g.:
```
$ make sdistup
```
* [ ] Create and upload [binary distributions](#binary-distributions)
## Embargoed Release
Released as needed privately to individual vendors for critical security-related bug fixes.
* [ ] Prepare patch for all versions that will get a fix. Test against local installations.
* [ ] Commit against master, cherry pick to affected release branches.
* [ ] Run local test matrix on each release & Python version.
* [ ] Privately send to distros.
* [ ] Run pre-release check via `make pre`
* [ ] Amend any commits with the CVE #
* [ ] On release date, tag and push to GitHub.
```
git checkout 2.5.x
git tag 2.5.3
git push origin 2.5.x
git push origin --tags
```
* [ ] Create and upload source distributions e.g.:
```
$ make sdistup
```
* [ ] Create and upload [binary distributions](#binary-distributions)
## Binary Distributions
### Windows
* [ ] Contact @cgohlke for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174.
* [ ] Download and extract tarball from @cgohlke and ``twine upload *``.
### OS X
* [ ] Use the [Pillow OS X Wheel Builder](https://github.com/python-pillow/pillow-wheels):
```
$ git checkout https://github.com/python-pillow/pillow-wheels
$ cd pillow-wheels
$ git submodule init
$ git submodule update
$ cd Pillow
$ git fetch --all
$ git commit -a -m "Pillow -> 2.9.0"
$ git push
```
* [ ] Download distributions from the [Pillow OS X Wheel Builder container](http://cdf58691c5cf45771290-6a3b6a0f5f6ab91aadc447b2a897dd9a.r50.cf2.rackcdn.com/) and ``twine upload *``.
### Linux
## Publicize Release
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328.

View File

@ -1,34 +0,0 @@
from V1.0 to V2.0
_sane.c:
- Values for option constraints are correctly translated to floats
if value type is TYPE_FIXED for SANE_CONSTRAINT_RANGE and
SANE_CONSTRAINT_WORD_LIST
- added constants INFO_INEXACT, INFO_RELOAD_OPTIONS,
INFO_RELOAD_PARAMS (possible return values of set_option())
to module dictionnary.
- removed additional return variable 'i' from SaneDev_get_option(),
because it is only set when SANE_ACTION_SET_VALUE is used.
- scanDev.get_parameters() now returns the scanner mode as 'format',
no more the typical PIL codes. So 'L' became 'gray', 'RGB' is now
'color', 'R' is 'red', 'G' is 'green', 'B' is 'red'. This matches
the way scanDev.mode is set.
This should be the only incompatibility vs. version 1.0.
sane.py
- ScanDev got new method __load_option_dict() called from __init__()
and from __setattr__() if backend reported that the frontend should
reload the options.
- Nice human-readable __repr__() method added for class Option
- if __setattr__ (i.e. set_option) reports that all other options
have to be reloaded due to a change in the backend then they are reloaded.
- due to the change in SaneDev_get_option() only the 'value' is
returned from get_option().
- in __setattr__ integer values are automatically converted to floats
if SANE backend expects SANE_FIXED (i.e. fix-point float)
- The scanner options can now directly be accessed via scanDev[optionName]
instead scanDev.opt[optionName]. (The old way still works).
V1.0:
A.M. Kuchling's original pysane package.

View File

@ -1,22 +0,0 @@
Python SANE module V1.1 (30 Sep. 2004)
================================================================================
The SANE module provides an interface to the SANE scanner and frame
grabber interface for Linux. This module was contributed by Andrew
Kuchling and is extended and currently maintained by Ralph Heinkel
(rheinkel-at-email.de). If you write to me please make sure to have the
word 'SANE' or 'sane' in the subject of your mail, otherwise it might
be classified as spam in the future.
To build this module, type (in the Sane directory)::
python setup.py build
In order to install the module type::
python setup.py install
For some basic documentation please look at the file sanedoc.txt
The two demo_*.py scripts give basic examples on how to use the software.

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +0,0 @@
#!/usr/bin/env python
#
# Shows how to scan a 16 bit grayscale image into a numarray object
#
from __future__ import print_function
# Get the path set up to find PIL modules if not installed yet:
import sys ; sys.path.append('../PIL')
from numarray import *
import sane
import Image
def toImage(arr):
if arr.type().bytes == 1:
# need to swap coordinates btw array and image (with [::-1])
im = Image.frombytes('L', arr.shape[::-1], arr.tostring())
else:
arr_c = arr - arr.min()
arr_c *= (255./arr_c.max())
arr = arr_c.astype(UInt8)
# need to swap coordinates btw array and image (with [::-1])
im = Image.frombytes('L', arr.shape[::-1], arr.tostring())
return im
print('SANE version:', sane.init())
print('Available devices=', sane.get_devices())
s = sane.open(sane.get_devices()[0][0])
# Set scan parameters
s.mode = 'gray'
s.br_x=320. ; s.br_y=240.
print('Device parameters:', s.get_parameters())
s.depth=16
arr16 = s.arr_scan()
toImage(arr16).show()

View File

@ -1,35 +0,0 @@
#!/usr/bin/env python
#
# Shows how to scan a color image into a PIL rgb-image
#
from __future__ import print_function
# Get the path set up to find PIL modules if not installed yet:
import sys ; sys.path.append('../PIL')
import sane
print('SANE version:', sane.init())
print('Available devices=', sane.get_devices())
s = sane.open(sane.get_devices()[0][0])
s.mode = 'color'
s.br_x=320. ; s.br_y=240.
print('Device parameters:', s.get_parameters())
# Initiate the scan
s.start()
# Get an Image object
# (For my B&W QuickCam, this is a grey-scale image. Other scanning devices
# may return a
im=s.snap()
# Write the image out as a GIF file
#im.save('foo.gif')
# The show method() simply saves the image to a temporary file and calls "xv".
im.show()

View File

@ -1,288 +0,0 @@
# sane.py
#
# Python wrapper on top of the _sane module, which is in turn a very
# thin wrapper on top of the SANE library. For a complete understanding
# of SANE, consult the documentation at the SANE home page:
# http://www.mostang.com/sane/ .
__version__ = '2.0'
__author__ = ['Andrew Kuchling', 'Ralph Heinkel']
from PIL import Image
import _sane
from _sane import *
TYPE_STR = { TYPE_BOOL: "TYPE_BOOL", TYPE_INT: "TYPE_INT",
TYPE_FIXED: "TYPE_FIXED", TYPE_STRING: "TYPE_STRING",
TYPE_BUTTON: "TYPE_BUTTON", TYPE_GROUP: "TYPE_GROUP" }
UNIT_STR = { UNIT_NONE: "UNIT_NONE",
UNIT_PIXEL: "UNIT_PIXEL",
UNIT_BIT: "UNIT_BIT",
UNIT_MM: "UNIT_MM",
UNIT_DPI: "UNIT_DPI",
UNIT_PERCENT: "UNIT_PERCENT",
UNIT_MICROSECOND: "UNIT_MICROSECOND" }
class Option:
"""Class representing a SANE option.
Attributes:
index -- number from 0 to n, giving the option number
name -- a string uniquely identifying the option
title -- single-line string containing a title for the option
desc -- a long string describing the option; useful as a help message
type -- type of this option. Possible values: TYPE_BOOL,
TYPE_INT, TYPE_STRING, and so forth.
unit -- units of this option. Possible values: UNIT_NONE,
UNIT_PIXEL, etc.
size -- size of the value in bytes
cap -- capabilities available; CAP_EMULATED, CAP_SOFT_SELECT, etc.
constraint -- constraint on values. Possible values:
None : No constraint
(min,max,step) Integer values, from min to max, stepping by
list of integers or strings: only the listed values are allowed
"""
def __init__(self, args, scanDev):
self.scanDev = scanDev # needed to get current value of this option
self.index, self.name = args[0], args[1]
self.title, self.desc = args[2], args[3]
self.type, self.unit = args[4], args[5]
self.size, self.cap = args[6], args[7]
self.constraint = args[8]
def f(x):
if x=='-': return '_'
else: return x
if not isinstance(self.name, str): self.py_name=str(self.name)
else: self.py_name=''.join(map(f, self.name))
def is_active(self):
return _sane.OPTION_IS_ACTIVE(self.cap)
def is_settable(self):
return _sane.OPTION_IS_SETTABLE(self.cap)
def __repr__(self):
if self.is_settable():
settable = 'yes'
else:
settable = 'no'
if self.is_active():
active = 'yes'
curValue = repr(getattr(self.scanDev, self.py_name))
else:
active = 'no'
curValue = '<not available, inactive option>'
s = """\nName: %s
Cur value: %s
Index: %d
Title: %s
Desc: %s
Type: %s
Unit: %s
Constr: %s
active: %s
settable: %s\n""" % (self.py_name, curValue,
self.index, self.title, self.desc,
TYPE_STR[self.type], UNIT_STR[self.unit],
repr(self.constraint), active, settable)
return s
class _SaneIterator:
""" intended for ADF scans.
"""
def __init__(self, device):
self.device = device
def __iter__(self):
return self
def __del__(self):
self.device.cancel()
def next(self):
try:
self.device.start()
except error as v:
if v == 'Document feeder out of documents':
raise StopIteration
else:
raise
return self.device.snap(1)
class SaneDev:
"""Class representing a SANE device.
Methods:
start() -- initiate a scan, using the current settings
snap() -- snap a picture, returning an Image object
arr_snap() -- snap a picture, returning a numarray object
cancel() -- cancel an in-progress scanning operation
fileno() -- return the file descriptor for the scanner (handy for select)
Also available, but rather low-level:
get_parameters() -- get the current parameter settings of the device
get_options() -- return a list of tuples describing all the options.
Attributes:
optlist -- list of option names
You can also access an option name to retrieve its value, and to
set it. For example, if one option has a .name attribute of
imagemode, and scanner is a SaneDev object, you can do:
print scanner.imagemode
scanner.imagemode = 'Full frame'
scanner.['imagemode'] returns the corresponding Option object.
"""
def __init__(self, devname):
d=self.__dict__
d['sane_signature'] = self._getSaneSignature(devname)
d['scanner_model'] = d['sane_signature'][1:3]
d['dev'] = _sane._open(devname)
self.__load_option_dict()
def _getSaneSignature(self, devname):
devices = get_devices()
if not devices:
raise RuntimeError('no scanner available')
for dev in devices:
if devname == dev[0]:
return dev
raise RuntimeError('no such scan device "%s"' % devname)
def __load_option_dict(self):
d=self.__dict__
d['opt']={}
optlist=d['dev'].get_options()
for t in optlist:
o=Option(t, self)
if o.type!=TYPE_GROUP:
d['opt'][o.py_name]=o
def __setattr__(self, key, value):
dev=self.__dict__['dev']
optdict=self.__dict__['opt']
if key not in optdict:
self.__dict__[key]=value ; return
opt=optdict[key]
if opt.type==TYPE_GROUP:
raise AttributeError("Groups can't be set: "+key)
if not _sane.OPTION_IS_ACTIVE(opt.cap):
raise AttributeError('Inactive option: '+key)
if not _sane.OPTION_IS_SETTABLE(opt.cap):
raise AttributeError("Option can't be set by software: "+key)
if isinstance(value, int) and opt.type == TYPE_FIXED:
# avoid annoying errors of backend if int is given instead float:
value = float(value)
self.last_opt = dev.set_option(opt.index, value)
# do binary AND to find if we have to reload options:
if self.last_opt & INFO_RELOAD_OPTIONS:
self.__load_option_dict()
def __getattr__(self, key):
dev=self.__dict__['dev']
optdict=self.__dict__['opt']
if key=='optlist':
return list(self.opt.keys())
if key=='area':
return (self.tl_x, self.tl_y),(self.br_x, self.br_y)
if key not in optdict:
raise AttributeError('No such attribute: '+key)
opt=optdict[key]
if opt.type==TYPE_BUTTON:
raise AttributeError("Buttons don't have values: "+key)
if opt.type==TYPE_GROUP:
raise AttributeError("Groups don't have values: "+key)
if not _sane.OPTION_IS_ACTIVE(opt.cap):
raise AttributeError('Inactive option: '+key)
value = dev.get_option(opt.index)
return value
def __getitem__(self, key):
return self.opt[key]
def get_parameters(self):
"""Return a 5-tuple holding all the current device settings:
(format, last_frame, (pixels_per_line, lines), depth, bytes_per_line)
- format is one of 'L' (grey), 'RGB', 'R' (red), 'G' (green), 'B' (blue).
- last_frame [bool] indicates if this is the last frame of a multi frame image
- (pixels_per_line, lines) specifies the size of the scanned image (x,y)
- lines denotes the number of scanlines per frame
- depth gives number of pixels per sample
"""
return self.dev.get_parameters()
def get_options(self):
"Return a list of tuples describing all the available options"
return self.dev.get_options()
def start(self):
"Initiate a scanning operation"
return self.dev.start()
def cancel(self):
"Cancel an in-progress scanning operation"
return self.dev.cancel()
def snap(self, no_cancel=0):
"Snap a picture, returning a PIL image object with the results"
(mode, last_frame,
(xsize, ysize), depth, bytes_per_line) = self.get_parameters()
if mode in ['gray', 'red', 'green', 'blue']:
format = 'L'
elif mode == 'color':
format = 'RGB'
else:
raise ValueError('got unknown "mode" from self.get_parameters()')
im=Image.new(format, (xsize,ysize))
self.dev.snap( im.im.id, no_cancel )
return im
def scan(self):
self.start()
return self.snap()
def multi_scan(self):
return _SaneIterator(self)
def arr_snap(self, multipleOf=1):
"""Snap a picture, returning a numarray object with the results.
By default the resulting array has the same number of pixels per
line as specified in self.get_parameters()[2][0]
However sometimes it is necessary to obtain arrays where
the number of pixels per line is e.g. a multiple of 4. This can then
be achieved with the option 'multipleOf=4'. So if the scanner
scanned 34 pixels per line, you will obtain an array with 32 pixels
per line.
"""
(mode, last_frame, (xsize, ysize), depth, bpl) = self.get_parameters()
if not mode in ['gray', 'red', 'green', 'blue']:
raise RuntimeError('arr_snap() only works with monochrome images')
if multipleOf < 1:
raise ValueError('option "multipleOf" must be a positive number')
elif multipleOf > 1:
pixels_per_line = xsize - divmod(xsize, 4)[1]
else:
pixels_per_line = xsize
return self.dev.arr_snap(pixels_per_line)
def arr_scan(self, multipleOf=1):
self.start()
return self.arr_snap(multipleOf=multipleOf)
def fileno(self):
"Return the file descriptor for the scanning device"
return self.dev.fileno()
def close(self):
self.dev.close()
def open(devname):
"Open a device for scanning"
new=SaneDev(devname)
return new

View File

@ -1,294 +0,0 @@
The _sane_ module is an Python interface to the SANE (Scanning is Now
Easy) library, which provides access to various raster scanning
devices such as flatbed scanners and digital cameras. For more
information about SANE, consult the SANE Web site at
http://www.mostang.com/sane/ . Note that this
documentation doesn't duplicate all the information in the SANE
documentation, which you must also consult to get a complete
understanding.
This module has been originally developed by A.M. Kuchling (amk1@erols.com),
now development has been taken over by Ralph Heinkel (rheinkel-at-email.de).
If you write to me please make sure to have the word 'SANE' or 'sane' in
the subject of your mail, otherwise it might be classified as spam in the
future.
The module exports two object types, a bunch of constants, and two
functions.
get_devices()
Return a list of 4-tuples containing the available scanning
devices. Each tuple contains 4 strings: the device name, suitable for
passing to _open()_; the device's vendor; the model; and the type of
device, such as 'virtual device' or 'video camera'.
>>> import sane ; sane.get_devices()
[('epson:libusb:001:004', 'Epson', 'GT-8300', 'flatbed scanner')]
open(devicename)
Open a device, given a string containing its name. SANE
devices have names like 'epson:libusb:001:004'. If the attempt
to open the device fails, a _sane.error_ exception will be raised. If
there are no problems, a SaneDev object will be returned.
As an easy way to open the scanner (if only one is available) just type
>>> sane.open(sane.get_devices()[0][0])
SaneDev objects
===============
The basic process of scanning an image consists of getting a SaneDev
object for the device, setting various parameters, starting the scan,
and then reading the image data. Images are composed of one or more
frames; greyscale and one-pass colour scanners return a single frame
containing all the image data, but 3-pass scanners will usually return
3 frames, one for each of the red, green, blue channels.
Methods:
--------
fileno()
Returns a file descriptor for the scanning device. This
method's existence means that SaneDev objects can be used by the
select module.
get_parameters()
Return a tuple containing information about the current settings of
the device and the current frame: (format, last_frame,
pixels_per_line, lines, depth, bytes_per_line).
mode -- 'gray' for greyscale image, 'color' for RGB image, or
one of 'red', 'green', 'blue' if the image is a single
channel of an RGB image (from PIL's point of view,
this is equivalent to 'L').
last_frame -- A Boolean value, which is true if this is the
last frame of the image, and false otherwise.
pixels_per_line -- Width of the frame.
lines -- Height of the frame.
depth -- Depth of the image, measured in bits. SANE will only
allow using 8, 16, or 24-bit depths.
bytes_per_line -- Bytes required to store a single line of
data, as computed from pixels_per_line and depth.
start()
Start a scan. This function must be called before the
_snap()_ method can be used.
cancel()
Cancel a scan already in progress.
snap(no_cancel=0)
Snap a single frame of data, returning a PIL Image object
containing the data. If no_cancel is false, the Sane library function
sane_cancel is called after the scan. This is reasonable in most cases,
but may cause backends for duplex ADF scanners to drop the backside image,
when snap() is called for the front side image. If no_cancel is true,
cancel() should be called manually, after all scans are finished.
scan()
This is just a shortcut for s.start(); s.snap()
Returns a PIL image
multi_scan()
This method returns an iterator. It is intended to be used for
scanning with an automatic document feeder. The next() method of the
iterator tries to start a scan. If this is successful, it returns a
PIL Image object, like scan(); if the document feeder runs out of
paper, it raises StopIteration, thereby signaling that the sequence
is ran out of items.
arr_snap(multipleOf=1)
same as snap, but the result is a NumArray object. (Not that
num_array must be installed already at compilation time, otherwise
this feature will not be activated).
By default the resulting array has the same number of pixels per
line as specified in self.get_parameters()[2][0]
However sometimes it is necessary to obtain arrays where
the number of pixels per line is e.g. a multiple of 4. This can then
be achieved with the option 'multipleOf=4'. So if the scanner
scanned 34 pixels per line, you will obtain an array with 32 pixels
per line.
Note that this only works with monochrome images (e.g. gray-scales)
arr_scan(multipleOf=1)
This is just a shortcut for s.start(); s.arr_snap(multipleOf=1)
Returns a NumArray object
close()
Closes the object.
Attributes:
-----------
SaneDev objects have a few fixed attributes which are always
available, and a larger collection of attributes which vary depending
on the device. An Epson 1660 photo scanner has attributes like
'mode', 'depth', etc.
Another (pseudo scanner), the _pnm:0_ device, takes a PNM file and
simulates a scanner using the image data; a SaneDev object
representing the _pnm:0_ device therefore has a _filename_ attribute
which can be changed to specify the filename, _contrast_ and
_brightness_ attributes to modify the returned image, and so forth.
The values of the scanner options may be an integer, floating-point
value, or string, depending on the nature of the option.
sane_signature
The tuple for this scandev that is returned by sane.get_devices()
e.g. ('epson:libusb:001:006', 'Epson', 'GT-8300', 'flatbed scanner')
scanner_model
same as sane_signature[1:3], i.e. ('Epson', 'GT-8300') for the case above.
optlist
A list containing the all the options supported by this device.
>>> import sane ; s=sane.open('epson:libusb:001:004') ; s.optlist
['focus_position', 'color_correction', 'sharpness', ...., 'br_x']
A closer look at all options listed in s.optlist can be obtained
through the SaneOption objects.
SaneOption objects
==================
SANE's option handling is its most elaborate subsystem, intended to
allow automatically generating dialog boxes and prompts for user
configuration of the scanning device. The SaneOption object can be
used to get a human-readable name and description for an option, the
units to use, and what the legal values are. No information about the
current value of the option is available; for that, read the
corresponding attribute of a SaneDev object.
This documentation does not explain all the details of SANE's option
handling; consult the SANE documentation for all the details.
A scandevice option is accessed via __getitem__. For example
s['mode'] returns the option descriptor for the mode-option which
controls whether the scanner works in color, grayscale, or b/w mode.
>>> s['mode']
Name: mode
Cur value: Color
Index: 2
Title: Scan mode
Desc: Selects the scan mode (e.g., lineart, monochrome, or color).
Type: TYPE_STRING
Unit: UNIT_NONE
Constr: ['Binary', 'Gray', 'Color']
active: yes
settable: yes
In order to change 'mode' to 'gray', just type:
>>> s.mode = 'gray'
With the attributes and methods of sane-option objects it is possible
to access individual option values:
is_active()
Returns true if the option is active.
is_settable()
Returns true if the option can be set under software control.
Attributes:
cap
An integer containing various flags about the object's
capabilities; whether it's active, whether it's settable, etc. Also
available as the _capability_ attribute.
constraint
The constraint placed on the value of this option. If it's
_None_, there are essentially no constraint of the value. It may also
be a list of integers or strings, in which case the value *must* be
one of the possibilities in the list. Numeric values may have a
3-tuple as the constraint; this 3-tuple contains _(minimum, maximum,
increment)_, and the value must be in the defined range.
desc
A lengthy description of what the option does; it may be shown
to the user for clarification.
index
An integer giving the option's index in the option list.
name
A short name for the option, as it comes from the sane-backend.
py_name
The option's name, as a legal Python identifier. The name
attribute may contain the '-' character, so it will be converted to
'_' for the py_name attribute.
size
For a string-valued option, this is the maximum length allowed.
title
A single-line string that can be used as a title string.
type
A constant giving the type of this option: will be one of the following
constants found in the SANE module:
TYPE_BOOL
TYPE_INT
TYPE_FIXED
TYPE_STRING
TYPE_BUTTON
TYPE_GROUP
unit
For numeric-valued options, this is a constant representing
the unit used for this option. It will be one of the following
constants found in the SANE module:
UNIT_NONE
UNIT_PIXEL
UNIT_BIT
UNIT_MM
UNIT_DPI
UNIT_PERCENT
Example us usage:
=================
>>> import sane
>>> print 'SANE version:', sane.init()
>>> print 'Available devices=', sane.get_devices()
SANE version: (16777230, 1, 0, 14)
>>> s = sane.open(sane.get_devices()[0][0])
>>> print 'Device parameters:', s.get_parameters()
Device parameters: ('L', 1, (424, 585), 1, 53)
>>> print s.resolution
50
## In order to scan a color image into a PIL object:
>>> s.mode = 'color'
>>> s.start()
>>> img = s.snap()
>>> img.show()
## In order to obtain a 16-bit grayscale image at 100DPI in a numarray object
## with bottom-right coordinates set to (160, 120) [in millimeter] :
>>> s.mode = 'gray'
>>> s.br_x=160. ; s.br_y=120.
>>> s.resolution = 100
>>> s.depth=16
>>> s.start()
>>> s.get_parameters()[2] # just check the size
(624, 472)
>>> arr16 = s.arr_snap()
>>> arr16
array([[63957, 64721, 65067, ..., 65535, 65535, 65535],
[63892, 64342, 64236, ..., 65535, 65535, 65535],
[64286, 64248, 64705, ..., 65535, 65535, 65535],
...,
[65518, 65249, 65058, ..., 65535, 65535, 65535],
[64435, 65047, 65081, ..., 65535, 65535, 65535],
[65309, 65438, 65535, ..., 65535, 65535, 65535]], type=UInt16)
>>> arr16.shape # inverse order of coordinates, first y, then x!
(472, 624)

View File

@ -1,24 +0,0 @@
from distutils.core import setup, Extension
PIL_BUILD_DIR = '..'
PIL_IMAGING_DIR = PIL_BUILD_DIR+'/libImaging'
defs = []
try:
import numarray
defs.append(('WITH_NUMARRAY',None))
except ImportError:
pass
sane = Extension('_sane',
include_dirs = [PIL_IMAGING_DIR],
libraries = ['sane'],
library_dirs = [PIL_IMAGING_DIR],
define_macros = defs,
sources = ['_sane.c'])
setup (name = 'pysane',
version = '2.0',
description = 'This is the pysane package',
py_modules = ['sane'],
ext_modules = [sane])

View File

@ -63,20 +63,3 @@ explode.py
-------------------------------------------------------------------- --------------------------------------------------------------------
Split a sequence file into individual frames. Split a sequence file into individual frames.
image2py.py
--------------------------------------------------------------------
Convert an image to a Python module containing an IMAGE variable.
Note that the module using the module must include JPEG and ZIP
decoders, unless the -u option is used.
olesummary.py
--------------------------------------------------------------------
Uses the OleFileIO module to dump the summary information from an OLE
structured storage file. This works with most OLE files, including
Word documents, FlashPix images, etc.
Note that datetime fields currently show the number of seconds since
January 1st, 1601.

View File

@ -1,3 +1,4 @@
from __future__ import print_function
import base64 import base64
import os import os
import sys import sys

View File

@ -8,9 +8,9 @@
# #
try: try:
from tkinter import * from tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
except ImportError: except ImportError:
from Tkinter import * from Tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
from PIL import Image, ImageTk, ImageEnhance from PIL import Image, ImageTk, ImageEnhance
import sys import sys
@ -18,6 +18,7 @@ import sys
# #
# enhancer widget # enhancer widget
class Enhance(Frame): class Enhance(Frame):
def __init__(self, master, image, name, enhancer, lo, hi): def __init__(self, master, image, name, enhancer, lo, hi):
Frame.__init__(self, master) Frame.__init__(self, master)
@ -25,7 +26,7 @@ class Enhance(Frame):
# set up the image # set up the image
self.tkim = ImageTk.PhotoImage(image.mode, image.size) self.tkim = ImageTk.PhotoImage(image.mode, image.size)
self.enhancer = enhancer(image) self.enhancer = enhancer(image)
self.update("1.0") # normalize self.update("1.0") # normalize
# image window # image window
Label(self, image=self.tkim).pack() Label(self, image=self.tkim).pack()

View File

@ -9,11 +9,13 @@
from __future__ import print_function from __future__ import print_function
from PIL import Image from PIL import Image
import os, sys import os
import sys
class Interval:
def __init__(self, interval = "0"): class Interval(object):
def __init__(self, interval="0"):
self.setinterval(interval) self.setinterval(interval)

Some files were not shown because too many files have changed in this diff Show More