Merge remote-tracking branch 'upstream/master'

Conflicts:
	PIL/ImageQt.py
This commit is contained in:
Aurélien Ballier 2014-11-11 18:00:00 +01:00
commit f3284e57a6
552 changed files with 23273 additions and 10708 deletions

14
.coveragerc Normal file
View File

@ -0,0 +1,14 @@
# .coveragerc to control coverage.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma:
pragma: no cover
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
# Don't complain about debug code
if Image.DEBUG:
if DEBUG:

73
.gitignore vendored
View File

@ -1,7 +1,68 @@
*.pyc
*.egg-info
build
dist
.tox
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
docs/_build
# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
# Sphinx documentation
docs/_build/
# Vim cruft
.*.swp
#emacs
*~
\#*#
.#*
#Komodo
*.komodoproject
#OS
.DS_Store

2
.landscape.yaml Normal file
View File

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

View File

@ -1,15 +1,73 @@
language: python
notifications:
irc: "chat.freenode.net#pil"
env: MAX_CONCURRENCY=4
# Run slow PyPy* first, to give them a headstart and reduce waiting time.
# Run latest 3.x and 2.x next, to get quick compatibility results.
# Then run the remainder.
python:
- 2.6
- "pypy"
- "pypy3"
- 3.4
- 2.7
- 2.6
- "2.7_with_system_site_packages" # For PyQt4
- 3.2
- 3.3
install: "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev"
install:
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
- "travis_retry pip install cffi"
- "travis_retry pip install coverage nose"
# Pyroma installation is slow on Py3, so just do it for Py2.
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then travis_retry pip install pyroma; fi
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
# webp
- pushd depends && ./install_webp.sh && popd
# openjpeg
- pushd depends && ./install_openjpeg.sh && popd
script:
- coverage erase
- python setup.py clean
- python setup.py build_ext --inplace
- python selftest.py
- python Tests/run.py
- CFLAGS="-coverage" python setup.py build_ext --inplace
- coverage run --append --include=PIL/* selftest.py
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
after_success:
# gather the coverage data
- travis_retry sudo apt-get -qq install lcov
- lcov --capture --directory . -b . --output-file coverage.info
# filter to remove system headers
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
# convert to json
- travis_retry gem install coveralls-lcov
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
- coverage report
- travis_retry pip install coveralls-merge
- coveralls-merge coverage.c.json
- travis_retry pip install pep8 pyflakes
- pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py
- pyflakes *.py | tee >(wc -l)
- pyflakes PIL/*.py | tee >(wc -l)
- pyflakes Tests/*.py | tee >(wc -l)
# Coverage and quality reports on just the latest diff.
# (Installation is very slow on Py3, so just do it for Py2.)
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi
matrix:
fast_finish: true

View File

@ -1,9 +1,475 @@
Changelog (Pillow)
==================
2.7.0 (unreleased)
------------------
- Speedup stretch 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.1 (2014-10-11)
------------------
- Fix SciPy regression in Image.resize #945
[wiredfool]
- Fix manifest to include all test files.
[aclark]
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]
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin
[Andrew Drake]
- setup.py: Close open file handle before deleting #844
[divergentdave]
- Return Profile with Transformed Images #837
[wiredfool]
- Changed docstring to refer to the correct function #836
[MatMoore]
- Adding coverage support for C code tests #833
[wiredfool]
- PyPy performance improvements #821
[wiredfool]
- Added support for reading MPO files
[Feneric]
- Added support for encoding and decoding iTXt chunks #818
[dolda2000]
- HSV Support #816
[wiredfool]
- Removed unusable ImagePalette.new()
[hugovk]
- Fix Scrambled XPM #808
[wiredfool]
- Doc cleanup
[wiredfool]
- Fix `ImageStat` docs
[akx]
- Added docs for ExifTags
[Wintermute3]
- More tests for CurImagePlugin, DcxImagePlugin, Effects.c, GimpGradientFile, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
[hugovk]
- Fix return value of FreeTypeFont.textsize() does not include font offsets
[tk0miya]
- Fix dispose calculations for animated GIFs #765
[larsjsol]
- Added class checking to Image __eq__ function #775
[radarhere, hugovk]
- Test PalmImagePlugin and method to skip known bad tests #776
[hugovk, wiredfool]
2.5.3 (2014-08-18)
------------------
- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport)
[Andrew Drake]
2.5.2 (2014-08-13)
------------------
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport)
[Andrew Drake]
2.5.1 (2014-07-10)
------------------
- Fixed install issue if Multiprocessing.Pool is not available
[wiredfool]
- 32bit mult overflow fix #782
[wiredfool]
2.5.0 (2014-07-01)
------------------
- Imagedraw rewrite
[terseus, wiredfool]
- Add support for multithreaded test execution
[wiredfool]
- Prevent shell injection #748
[mbrown1413, wiredfool]
- Support for Resolution in BMP files #734
[gcq]
- Fix error in setup.py for Python 3
[matthew-brett]
- Pyroma fix and add Python 3.4 to setup metadata #742
[wirefool]
- Top level flake8 fixes #741
[aclark]
- Remove obsolete Animated Raster Graphics (ARG) support
[hugovk]
- Fix test_imagedraw failures #727
[cgohlke]
- Fix AttributeError: class Image has no attribute 'DEBUG' #726
[cgohlke]
- Fix msvc warning: 'inline' : macro redefinition #725
[cgohlke]
- Cleanup #654
[dvska, hugovk, wiredfool]
- 16-bit monochrome support for JPEG2000
[videan42]
- Fixed ImagePalette.save
[brightpisces]
- Support JPEG qtables
[csinchok]
- Add binary morphology addon
[dov, wiredfool]
- Decompression bomb protection
[hugovk]
- Put images in a single directory
[hugovk]
- Support OpenJpeg 2.1
[al45tair]
- Remove unistd.h #include for all platforms
[wiredfool]
- Use unittest for tests
[hugovk]
- ImageCms fixes
[hugovk]
- Added more ImageDraw tests
[hugovk]
- Added tests for Spider files
[hugovk]
- Use libtiff to write any compressed tiff files
[wiredfool]
- Support for pickling Image objects
[hugovk]
- Fixed resolution handling for EPS thumbnails
[eliempje]
- Fixed rendering of some binary EPS files (Issue #302)
[eliempje]
- Rename variables not to use built-in function names
[hugovk]
- Ignore junk JPEG markers
[hugovk]
- Change default interpolation for Image.thumbnail to Image.ANTIALIAS
[hugovk]
- Add tests and fixes for saving PDFs
[hugovk]
- Remove transparency resource after P->RGBA conversion
[hugovk]
- Clean up preprocessor cruft for Windows
[CounterPillow]
- Adjust Homebrew freetype detection logic
[jacknagel]
- Added Image.close, context manager support.
[wiredfool]
- Added support for 16 bit PGM files.
[wiredfool]
- Updated OleFileIO to version 0.30 from upstream
[hugovk]
- Added support for additional TIFF floating point format
[Hijackal]
- Have the tempfile use a suffix with a dot
[wiredfool]
- Fix variable name used for transparency manipulations
[nijel]
2.4.0 (2014-04-01)
------------------
- Indexed Transparency handled for conversions between L, RGB, and P modes. Fixes #510
[wiredfool]
- Conversions enabled from RGBA->P, Fixes #544
[wiredfool]
- Improved icns support
[al45tair]
- Fix libtiff leaking open files, fixes #580
[wiredfool]
- Fixes for Jpeg encoding in Python 3, fixes #577
[wiredfool]
- Added support for JPEG 2000
[al45tair]
- Add more detailed error messages to Image.py
[larsmans]
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
[aclark]
- Merge from Philippe Lagadecs OleFileIO_PL fork
[vadmium]
- Fix ImageColor.getcolor
[homm]
- Make ICO files work with the ImageFile.Parser interface, fixes #522
[wiredfool]
- Handle 32bit compiled python on 64bit architecture
[choppsv1]
- Fix support for characters >128 using .pcf or .pil fonts in Py3k. Fixes #505
[wiredfool]
- Skip CFFI test earlier if it's not installed
[wiredfool]
- Fixed opening and saving odd sized .pcx files, fixes #523
[wiredfool]
- Fixed palette handling when converting from mode P->RGB->P
[d_schmidt]
- Fixed saving mode P image as a PNG with transparency = palette color 0
[d-schmidt]
- Improve heuristic used when saving progressive and optimized JPEGs with high quality values
[e98cuenc]
- Fixed DOS with invalid palette size or invalid image size in BMP file
[wiredfool]
- Added support for BMP version 4 and 5
[eddwardo, wiredfool]
- Fix segfault in getfont when passed a memory resident font
[wiredfool]
- Fix crash on Saving a PNG when icc-profile is None
[brutasse]
- Cffi+Python implementation of the PixelAccess object
[wiredfool]
- PixelAccess returns unsigned ints for I16 mode
[wiredfool]
- Minor patch on booleans + Travis
[sciunto]
- Look in multiarch paths in GNU platforms
[pinotree]
- Add arch support for pcc64, s390, s390x, armv7l, aarch64
[manisandro]
- Add arch support for ppc
[wiredfool]
- Correctly quote file names for WindowsViewer command
[cgohlke]
- Prefer homebrew freetype over X11 freetype (but still allow both)
[dmckeone]
2.3.2 (2014-08-13)
------------------
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport)
[Andrew Drake]
2.3.1 (2014-03-14)
------------------
- Fix insecure use of tempfile.mktemp (CVE-2014-1932 CVE-2014-1933)
[wiredfool]
2.3.0 (2014-01-01)
------------------
- Stop leaking filename parameter passed to getfont
[jpharvey]
- Report availability of LIBTIFF during setup and selftest
[cgohlke]
- Fix msvc build error C1189: "No Target Architecture"
[cgohlke]
- Fix memory leak in font_getsize
[wiredfool]
- Correctly prioritize include and library paths
[ohanar]
- Image.point fixes for numpy.array and docs
[wiredfool]
- Save the transparency header by default for PNGs
[wiredfool]
- Support for PNG tRNS header when converting from RGB->RGBA
[wiredfool]
- PyQT5 Support
[wiredfool]
- Updates for saving color tiffs w/compression using libtiff
[wiredfool]
- 2gigapix image fixes and redux
[wiredfool]
- Save arbitrary tags in Tiff image files
[wiredfool]
- Quote filenames and title before using on command line
[tmccombs]
- Fixed Viewer.show to return properly
[tmccombs]
- Documentation fixes
[wiredfool]
- Fixed memory leak saving images as webp when webpmux is available
[cezarsa]
- Fix compiling with FreeType 2.5.1
[stromnov]
- Adds directories for NetBSD.
[deepy]
- Support RGBA TIFF with missing ExtraSamples tag
[cgohlke]
@ -46,6 +512,12 @@ Changelog (Pillow)
- Fixes to make Pypy 2.1.0 work on Ubuntu 12.04/64
[wiredfool]
2.2.2 (2013-12-11)
------------------
- Fix #427: compiling with FreeType 2.5.1
[stromnov]
2.2.1 (2013-10-02)
------------------
@ -59,7 +531,7 @@ Changelog (Pillow)
[nikmolnar]
- Fix for encoding of b_whitespace, similar to closed issue #272
[mhogg]
[mhogg]
- Fix #273: Add numpy array interface support for 16 and 32 bit integer modes
[cgohlke]
@ -216,10 +688,14 @@ Changelog (Pillow)
2.0.0 (2013-03-15)
------------------
.. Note:: Special thanks to Christoph Gohlke and Eric Soroos for assisting with a pre-PyCon 2013 release!
- Many other bug fixes and enhancements by many other people.
- Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.)
[fluggo]
- Add PyPy support (experimental, please see: https://github.com/python-imaging/Pillow/issues/67)
- Add PyPy support (experimental, please see: https://github.com/python-pillow/Pillow/issues/67)
- Add WebP support.
[lqs]
@ -239,10 +715,6 @@ Changelog (Pillow)
- Added support for PNG images with transparency palette.
[d-schmidt]
- Many other bug fixes and enhancements by many other people (see commit log and/or docs/CONTRIBUTORS.txt).
- Special thanks to Christoph Gohlke and Eric Soroos for rallying around the effort to get a release out for PyCon 2013.
1.7.8 (2012-11-01)
------------------
@ -315,44 +787,55 @@ Changelog (Pillow)
[elro]
- Doc fixes
[aclark]
1.5 (11/28/2010)
----------------
- Module and package fixes
[aclark]
1.4 (11/28/2010)
----------------
- Doc fixes
[aclark]
1.3 (11/28/2010)
----------------
- Add support for /lib64 and /usr/lib64 library directories on Linux
[aclark]
- Doc fixes
[aclark]
1.2 (08/02/2010)
----------------
- On OS X also check for freetype2 in the X11 path [jezdez]
- Doc fixes [aclark]
- On OS X also check for freetype2 in the X11 path
[jezdez]
- Doc fixes
[aclark]
1.1 (07/31/2010)
----------------
- Removed setuptools_hg requirement
[aclark]
- Doc fixes
[aclark]
1.0 (07/30/2010)
----------------
- Forked PIL based on Hanno Schlichting's re-packaging
(http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz)
- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required.
- Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
[aclark]
- Remove support for importing from the standard namespace
.. Note:: What follows is the original PIL 1.1.7 CHANGES file contents
.. Note:: What follows is the original PIL 1.1.7 CHANGES
::

26
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,26 @@
# Contributing
## Fixes, Features and Changes
Send a pull request to the master branch. We'll generally want documentation and [tests](Tests/README.rst) for new features. Tests or documentation on their own are also welcomed. Feel free to ask questions as an [issue](https://github.com/python-pillow/Pillow/issues/new) or on IRC (irc.freenode.net, #pil)
- Fork the repo
- Make a branch from master
- Add your changes + Tests
- Run the test suite. Try to run on both Python 2.x and 3.x, or you'll get tripped up. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
- Push to your fork, and make a pull request onto master.
A few guidelines:
- Try to keep any code commits clean and separate from reformatting commits.
- All new code is going to need tests.
- Try to follow PEP8.
## Bugs
When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. The best reproductions are self-contained scripts that pull in as few dependencies as possible. An entire Django stack is harder to handle.
Let us know:
- What did you do?
- What did you expect to happen?
- What actually happened?
- What versions of Pillow and Python are you using?

View File

@ -1,36 +0,0 @@
Contributors (Pillow)
=====================
.. Note:: Contributors: please add your name here
- Alex Po <alex-86p __at__ yandex.ru>
- Anton Vlasenko <antares.spica __at__ gmail.com>
- Brian J. Crowell <brian __at__ fluggo.com>
- Bryant Mairs <bwmairs __at__ ucsc.edu>
- Christoph Gohlke <cgohlke __at__ uci.edu>
- Corey Richardson <corey __at__ octayn.net>
- Daniel Hahler <github __at__ thequod.de>
- David Schmidt <david.schmiddi.86 __at__ gmail.com>
- Eliot <saltycrane __at__ gmail.com>
- etienne <etienne.desautels __at__ gmail.com>
- Jannis Leidel <jannis __at__ leidel.info>
- Kyle MacFarlane <kyle __at__ deletethetrees.com>
- Lars Yencken <lars __at__ yencken.org>
- Liu Qishuai <lqs __at__ lqs.me>
- Manuel Ebert <Maebert __at__ UOS.de>
- Marc Abramowitz <marc __at__ marc-abramowitz.com>
- Matti Picus <gitmatti __at__ picus.org.il>
- Mikhail Korobov <kmike84 __at__ gmail.com>
- OCHIAI, Gouji <gjo.ext __at__ gmail.com>
- Oliver Tonnhofer <olt __at__ bogosoft.com>
- Phil Elson <pelson.pub __at__ gmail.com>
- Sandro Mani <manisandro __at__ gmail.com>
- Simon Law <simon.law __at__ ecometrica.com>
- Stéphane Klein <stephane __at__ harobed.org>
- Steve Johnson <steve __at__ steveasleep.com>
- Takeshi KOMIYA <i.tkomiya __at__ gmail.com>
- Tom Gross <tom __at__ toms-projekte.de>
- Tom Payne <twpayne __at__ gmail.com>
- Tyler Garner <garnertb __at__ gmail.com>
- tdesvenain <thomas.desvenain __at__ gmail.com>
- wiredfool <eric-github __at__ soroos.net>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,175 +0,0 @@
/* XPM */
static char * lena_xpm[] = {
"128 128 44 1",
" c None",
". c #CC9966",
"+ c #663333",
"@ c #996666",
"# c #CC6666",
"$ c #CC9999",
"% c #FFCC99",
"& c #996633",
"* c #FF9966",
"= c #663366",
"- c #FFCCCC",
"; c #CCCC99",
"> c #FF9999",
", c #993333",
"' c #333333",
") c #CCCCCC",
"! c #996699",
"~ c #CC6633",
"{ c #666666",
"] c #999999",
"^ c #666699",
"/ c #330000",
"( c #663300",
"_ c #330033",
": c #999966",
"< c #FFFFCC",
"[ c #333366",
"} c #660033",
"| c #CC99CC",
"1 c #9999CC",
"2 c #660000",
"3 c #FFFFFF",
"4 c #CCCC66",
"5 c #FFCC66",
"6 c #666633",
"7 c #993366",
"8 c #FFFF99",
"9 c #993300",
"0 c #333300",
"a c #FF99CC",
"b c #CCCCFF",
"c c #CC9933",
"d c #CC6699",
"e c #000000",
".....*...*.%*..#&@#&@&#&#&#.&#.#.#.#.#.#..#.*.#...#..#.#.#.#..#..#..#..#..#.#.#@#.>..>...*..*...*....%%%%.&&.@.#.@.#..&#.~.#&#$.",
">%*..*.*..*%.*.&#@&#@#&#&#&#*&.#.#.#.#.*#..#&.~.#.#.#.&.#.#.#.#.#.#&.#..#&.@.@&#&#.>.*.*...*..*..*...%%%%%&&~@#&#.@&##.&#.#.@.>@",
"...>...*.*.>.%*&#,&#&#&@&#.&#.#.#.#.~.#.#.~.>.#.#..~.#*.~.&*.#.*&*.#..#&.#.##.#@&#.$.>.>.*..*..*.....%%;%%@&@.#.@.#..&#..&#.#@+(",
".*...*...%*.*.>&&@@#@&##&#.#&#.~.#.#.#.#.##.#&*.~.#..#.#.#.#.#.#.#.#~.#.#.#&.#&@#&#.>.....*..*..*.*...%%%%%&.&@.#&#.#.#.#$.$&+++",
"..>.*.*.*.%.>.~&#&#&#.@&#&#&.#.#.#..#.~.~.*.~>&>.#.#*.#.#.~.~.#.#.~.#.*&*.#.#.#.#&#..>.>*..>..*.......%%<%%$&#.#&$&#.&#.&>@,++2+",
".*.%...%*.*..*.#&@#@&##&#&#&#&#.&.#*.#.#.#.#*.~*#.~.#~.~*#.#*#.*.#.#.#.#&.#.#.#&@.##.*..%*..*..*>.*.....%%%%&&&.#~.#.#.#.$&+++++",
"..>.*.*.%*.*..*#&,&#@&#&#&#.#.~#.~.&>.~.~#*&#~#.~>#.~$~$&.~..#.#~.~.~.#.#.#.#&$#&#&$.>.>..%*.>....*.....%%%-.@#&.#&#.#.$$,++++++",
"..*.*%.*%.*.>..#&@#&##&#&#&#&.#.#.#.~.#.*.#.*.~.~.#.#.~.##&#.~.#.##.#*&*~.#.#&#.@#&>.>%.*>.%..*.>..*.*..%%%%%&&&.#.&#&#.@++++'=+",
".*...*.*.*.*.*.~#@&#@&#&#&.#~.#&*&*&#.~#.~*#~#.#*#.~.#&>..#.#.#...#.#.#.#.#..#.#&#.#..>...*.*..%*..>...>.%<%-%@.&#.#.$@@++++++=+",
".%.*%.%*.%.*.*.&&,&#&#@&#&#.#&.#&*#.~.*.#..#.*~.#.##..#&..&*&#.##.~.#~.#~.#~#.#.@#@.#..>.>..*.*..>.*.*....%%%%.&&#.#&$,++++=+='+",
"*>.*.*..**..*>.#,@#@#&#&##&#&#*&>&*#.#~.#~.#.#.#*#.&#&.#@#.@..#..@.#..#.#~..#.#&#&#.>.>...*.$..*.*...*.*.>%%%-%&&@&$#@++/++'++=+",
"..%*.%*...*.*.*&,&#&@#&#&&#.#.#.#&.~#.#.#.~.#.#..#$#$.$...$&@.@.#.@.##.#.##.##.#.@#.#..*.>.%*.>...>.#.#....%%<-.&#.@&+++=+=+=+=+",
"*.*.*.*.*%*.*.*&#,@#&##&##&~#&*&*#*&*&*&*@.#.~.#$..$.$.-$%$-%$.@.@.&..#*#.#*&#&##&#.#.>...*....*>.*.*.*.~..%%%%%@@.@++++++=+=++=",
"%..%*..*.*.*..*&&&,@#&##&#.#.#.#.#.#.#.#.#.#.#..@>@.$$$;$%$.$-$%$.#@.#..#.##.##.#@&#$.>.>.>.*>.....*.~.*....%<%<.$+++/+=+=++=/++",
"*.*..~.~.%*.*.*# #&,&#&#&#~.#~.~#.#*&*#..@.#.$.$$.$.$;$;$%$%$-.$.&@#.#~.##.#&##.#.#....*...*.>*.#..#.~....<%%-&+++++'=+[++=++",
".%*.*.&*.*%*.*. @#&#&#.#&#.##.~.#.#..>.#.$&#$@.$$:$$;$;$;$;-;-$.@&#$#.#&###&@.#..#.>..>.>..~..*#*.*.~...%%<-@+++=++=++=++=+",
"*.%*&,&*.*.*.* @#&##&#.~&*&>~.#.>@#&.@#.@$.@$.$.$.$;$;%-;.)%-%$&@.##.#.@.##&#.>.*.#.#.*&*.*.*.#.#..*...-%-+++/+{++_++=+}+",
"*.>.~&~..%*.*. &#&#&#&#.##&.#~#&*&##.@.#&$#.@.$.$;$.%).;-;$;$)%$&@&@#.##,@@.#..>.>..>.>.~.*.#.#>.>.#.*.-$+/+=+=+=++=+=+_+",
".*.&#(#.**.%*. @#&#&##.#&*#.#..##.#&#@.$.$@>$$.$.$%]%;$;;-;)%)%--&@&@&#&#&##.#..*.#.#..>..~.*..#.*...#..++='+++=++=+'+++@",
"%.#&~&~..*c*.* #@&#&#&#.#&*&#*#.&#.#.#@##.&.@.$$.$%$$;$%]%)$;$;-<-%@&@$&##@&#.>.>.#.#&#.*.*.*.>*.~.*..>++++=+=++'=+++=++$",
"*.~&,&#.*%.*.*. &#@#&#.#&#.#.#.#&#&#&@.#..@$#.#.@.$.]%;-;-;$;$;-;)--<$@#&,@&##.*.*.>.#&#&$..*.#.*.#.*$.$&++/+++='=+}+=++@$$",
".#@&,&#.*.*%*.*& @&#&#@&#.#.#.~&*.#&#.##&@@.#.@.#.$.$.$.$%$;$)-3))-;;-;-&@@@#@#&#.>.*.#.@(,$#...*#.**.*..@++++!'+}+=+=+@+@@$.",
".&,&#&&>.*..*.#&#@,&#@&#&#&#&#&#.#.##&#&#.#.#&$.#.$.@.$.$$-$;$$%)%-;-;3;)<-)&@&@&,#.~.*>.>#&,+.@**.*.~>.#.>.+++'+++=+=+=++@@@.$.",
"&#&#&,#..*%*.*.,&,@@#@#&##&#.#&#&#.&~#.##&#&#.#$.#.>.#.$.;.$$;-;-)-);)-)<)%3-++@@&@#.*.*..>@,+@@..#*.*~**.>+++_+='++=+=+=++@.$..",
"&,@,&@&>.*.%.>.#&+&#&@&#&#&#&#.#.#.#&#~.#.#.$&#.#..#.$.$.$$.-;--)%)););););-<-&+,@~@*.**>.#&+2+@>*.*#.*.#>&2+++=++=++_+++@@$$...",
"&#&@,#&>.%**.*.&,@,&@#&@@#&#.#&#.~#.#&#&#&#&#.@.#.$.@.@.$.$;-$-;-;-)-;-);-);-)-$+@&#.*..*.>@++++@.#.**.>*$,+/=+=++'=+'+=+@$$.$..",
"@,#&@&#.*..%.>.#&@@,@##&#&##&#.#.#&#&#&#.##.#.$.$.#..#.@$.--;%)$-)%));)<))<)-<)%@+@.#.**.>.#++++#@.>&#.>.@++++=+'+++=+++@$.@..-.",
"@&@,&,&>..**.*.#&,&#&@&@#&#&#.#&>&#&#.#&#.#.#.#.#.>@.@.$%).-$;-;$-;-%)-)%)-;)%)-$+@&.*..*.#@+++++.@...$.#++}=+=+=+=+++++$$$.$...",
",#@,@&#..*...>&#@+&@,@#&#&#.#&#&%#&.#&##.#.#.#.#...#&$.-.-;$;$-;)-))-;-;-;)-;3;3-@@&$.*.*.>&++++/@.-%%$$+++++++=+'+=+++$.$..$.$.",
"@,&#@,&#%.%*.*.&,@,&#&#&#&#&#.#.>&#&#.#&#&#.#.@.#@@..$%$%$.-;$;-;-;-;--)--;)-;);-$(#..*.>.>@++/++--;-<<)/'+[+=+++=++'+@$&$.$%.$.",
"@,@&,&..*.*%..>&,&@@,@#@#&#&#&#%>&#&#.#.#.#.#.#..&@.$%$%]%).-;$;-;$)%$;-%)-;))<)3-@@...*..#@++=@-)%)%)-<{+[++{+=+='}+@:$.$..$.>.",
"@&#@#,&#.%.*.*.&,@,@&&#&@#&#.#&%>&#&#&#&#.#.#.@>&#.$%$.-.$;$;-;$;$;-)--)-;-)%);)))%@..*.*..@++]-]$<);<)-]'='=++++=@++$.$..$..$.$",
"#&,&#&#.**%.%*#&,&&##&#&#&##&&#%.#&#&#&>&#&.#..#&-*$.$.-$-.)$.-$;$-.;-;.-)%;);-;-)).$...*>&&+$)$))--;)%)$+'+=+=++'}'$$$.$.%$....",
"#&@#,~&*...*..*&@,@&&#&##&#.#&.%*&#&#.#.#.#.&#.&.>%.$.$.;-$%$).-$;-$;--);$);-);)-))-$.>..~@.%;;-;));-;3-{+'+++=+=++@@$$..$...$$.",
"@&#&##&*.*5.%*.#,&@,&#&#&##&#&>%#&#~@#&#@.##.@.>..#.$%$-$.).$;$;%$;-.;-%)%$;-;-;);-;%$....%--$3)-;-));-)++=+[+/++/+@$.$$..$%>..>",
"~@#&,&#*.%*.*.*&@,&#&#&#&#&#@&%.#&#&~.#&#.&#.#..>.$.%$.%$.-;$.$$%$.-;$.$;$;$;-;-)$)-%$.$-%-;%;-;-)-;-3%)'+'++=+=++@$$$.%$..$..$.",
"@&###&#.*.5.%.>&,@@&#&#,&#@~&#%..~#&#&#&#$&#..>..#.>$.$.$%$.-;%.-.$%$%)%$;-)%$);%).]%-%%$.-;-);-)%)%;-3-+'=+_++++@$$.$.$..$.>..$",
"~@&#~#&*..%**.*.,(#,&#&##&#&#&%.>.*&#&#.#.#.#.#..#...$.-.$.$..$.%;$.%$.-;$%)$;$$;$;-;$)%-;-$%)-;-;-)-;-]++'+'=+++@.$$.$.>...$.>.",
"#&#&,&#.*..%.*.#@@&@&#&#&##&#&-%..#.#&#&#.#.$..#.$.>.$.$.-;%$%.-..$;.).$%$-.-$;$;$;$;$;-);-;)-;-)%$%)%-{+_=+=+'+@$$..$.%$.>..$.$",
",@&##,~*.5*%*.*&,@,##&,&#&#@&~%.**.~&#&#&#.#.#..>.$.$%$.$.$.$.$.$%$.$%$%$;$:-$-.$%$;-;-;$)-)%)-;-$:$.)-+'+'=+++@@.$.$.$...$.>.>.",
"&#,&#&#.*..%.*.#,&@&#&#&#&#&#&%..*&*#&#&>$.#$.#..$.#.$.-$.-.-..-..$%$.-$;-$$%]$;-;%)%))););$)%)%-.$@;$$++=++=++@$.$.$..$%.$..$.$",
"@&#&#&#..*.*.*.&@,@#&#&,#&#&&#%.*.*.&#.#.#.#&$.#.>.$.$.$.$$.$$$.$$$%$$%$.$;$$.$$;$%$;)%)-);-;-;-.).$$-'+'+='+'@@.$..-.>.$...$...",
"&#@,#~#*.%5%.%*#+,&@,#&#&##&#.%*.*.*~&#.>@#.>..#&$.#$$$$$.$$@@$$$.$$$$:$$-.$;-;-%;)%)$;);-)-;--$;@.@;-++=+=+++@$.$.$..$..>$..$>.",
",&#&#&&*.*.*.*.~@@,&#&#&#&#&,.%%..*..#.#.#.#.>.#.$#..$&@@@@@$$@$-$$]-$$+.{$%-$;-%$;-;-;-)-;--;-.$@:@-$/++_+=++@$.$..$.$.$..$..$.",
"@&#,##~.*.%.%*>&,@@,&#,&#&#&,.*.*.**.&#.#$#$.#.@*@.$>@$$@@@+$)@!]+@@@++{+$;$:-%]%)%$;-);;--;-%$@&+@%-+'+=++++@$$.$.>.%.$.$..$..$",
"&#&&#&#*..5*..*&,@,@&#&#,#&#&&%%%*.*.#.#&#.#.$.#.$#.@$@@@{!@!@!+=$=@'+++@$$-$%-;)-;).)%-%)%$.@&@&$%)+++'+==++.$.$%$.$$.$.%$.$...",
"#&#@~#..*.*%%*.#@+#@#&#&&#&,&~%%5*.~.~&#.$#$&#..#.$#$@$@!@={=!!@[@[@=e+)$;-;);$%;$-;-);$<$&@6+@&]$-(++++=++++$$.$.$%.$..$....$..",
"&#&#&##*.%..*..~#+&#&,###&#&#(%%%...>&@#.#.#.#.@>.@#$@@+@+=+!{=!!=+!++)@-]%$%)%$;)$;$%)%@]&@&@.$.)+++'++=+=+@$.$%.$.$.-.$.$$..$.",
"@#&##&#.**%.%*.#&,@,@&#&,#&#&#.%5....#@>#$&#.$#.$$@$@=!==+=]!_+=@[+{+$$)%$);$;;-$-;---@@@@.:%.-$-++++=+=+++@$.#.$.%.$.$.$.$.$$.$",
"&&#&#~#...5*%*.&,@,@#@#&#&#&&,&%%%%..@.$.,.#@&$@,$@+}@='=/={+=]!_!/!$]%$;).-$;$;)-$;.@{@@$-$..%%+/'++=+'=++@$$.%..$.%..$%$.$.$.$",
"#&#&#&#**.*..**#@,&@,@#@#&##.#&%%%..#$##&#.&$.$$+@+!@=+!={=+={=@@_]$@-;$%$;$;$;$;-.$(+@${@:%.--+++++=+=++++@..$.$%.$.$%$.$.$.$$.",
"#&###&#.%.%*%...,@,#&#&,#&,&#&#&%%%.&..#$#.>@#@@@@=@=+=!+_'+=+='!!{@)$--;$;$;$%;$)%]&@@@$@$$%.(+++++'+_+=+@$.$%..$%.$..$.%$.$..$",
"#&@&#,&**.%.*%*&,@&@,#@&#&#&#&&9%%.>.#@..@.@.@@@@@@=_=+=@_+=@@!+!]@$).;$.-.$;$;-;-%-.@@@@@:-@++++++=@+'+++@@@.$.%$..$..$.$.$.$.$",
"#&##&###..*%.*.~@,+#@&###~#&#.#&.%%.&.,.#&#$@$@+=+@=+=^@[+++=++!@]@)$%).).)$.).-;$;%.+@@@$@.6+/++++++=+=+@$&$%..$.$.-.$..$..$...",
"&#&#&,&*.%.%*%*#&,@&#,@&@&#&#~&#&-%#.#&#&>.@@@}+=+={!==@!+'+'+@+$1$)$%$.$$.-;-%;-%-%$@++=@]$+/+=+{=+=/+++@$$.$.%.%..$.%$.%$..$.$",
"#&@##&#.*%*.....,@,@@&##,#&#&>&#&%%&#&.@.@>@@+!+='+==+![$!+++@@$]$-$;$;$%$.$;$%-;-%%%$++@+$]+++=+++++'+++$@$.$.$.$$%.$.$.$%.$...",
"&#&#&,&#..%.%*>&,@(##@#&#&#&#.#&#->.&#&>&@++!+=+=+=+[=+=@++/@$$$$]$).$..$;$-%-;%-%;%.$@+}{$$+'+=+={+=+++@@$.$..$...$..%.$.$..$.$",
"&#&##&#*.*%*...#&@,@&#@#&##.#~&#..-&#..#$@@++++^+==+^=@!'=/@$$$-$$$-;-.$.$%$%$%-%--%%#@+}@]$@++=++=+++++$@.$.$..$.$.$.$;..;.$...",
"@&#@#,&...%.%*.#&@,@#&#&#&#~$~#.##%#.@.$@++!_=@={+=+=='=+@{@.$;$)&$%$.$.-$%$%-;%)%%%-%@+++@${+={=++=+++@$$.$..:.$.$:$.$.$$.$.$$.",
"&@&#&,#*.*%*%*.&,@@&@#@##&#&#.&#&$@.@.#@+!++@'+=+'=+=+=@'@$:$.$%-:-$.$.%>%$-.-%%-;-%%>@@+/@$++=+={++'++@@$$.$%$.;..$..$..$.$:..$",
"#&#@#&@....%.%>&#(@#@&#&#&#&##*#@&#.#$@@+@_!_=+{=+={==+/+@@$.$;$$$..$%>.$>%$%-;.-%%)%-$+}+@)@++={+=++++$..$;$...$$..$:@.$:$.$$$.",
"@&#&#~##..*%.*.&@@,&#@#@##~#.&#.>++&$.@+=+=@=+=++++='+^+]@-;-..-%$@&&#.>%>-.$%-;-)%%%$#++++-@+@+=+[+++@$@.$..$;..$.$.$.@.$$.@.$$",
"&#@,@,&*.%..%%>~@(@@&#&#&@&##.*.#+2$@@@+!=^!'={+_+=@!@_+@:-.).;-.$.$.,(#.>$%$%%%$;-%.@@++++%@+=+{+=+++@$$.$....$;.$:.$.$.$.$$@.@",
"@&&#&#@#.*%.*..&#@,@#&#&#~#&#&>.$@(@@@+@++=!+=+'@/=/@+'$@)$%$.-&@$$$.$&,&#.>.$;%%$@@&@#@+++]$++=+=+'++.@.$%$.$..$%.$.$.$:$$$:$@$",
"##&#@9&..*%*%%*.,+#&#&#&#&@&>..>.$@++!=^!!/^+^+=1'+=++@:-.;-$%@@@+++++$.#.>.%%-;-$@+++@++++$@@++=+=++@$@.$.$..$..$$.$.$.$.$.@$:$",
"&#&#&#,>...%.*..&,@,&#&&#&#..$..$+@+=@!@^]=@=+'+_!'+0+$.$;-%-+(+,++@@2+#.#*>.%-;@+++++}+++{$@@+++'+++@..$.$.$..$...$.$.$.$$$$$@$",
"&,@,@(&.**%*.%*&,@,@&#,.,&#-.$.@$++^+^_=!@_'+==/^+_++@:.-.-%+@,++@++--(@~.#.>%%$++@@a+++=++$]@=+==+++.@$.$.$.$.$.>$.$.$$.@.:@@.@",
"#&#&#,,...*%.*..@,&##&#&#@+,$$$(+/@=+=!+^1+[+'+='=+'+@$$.%-&@&,&@+@@$-#@#&#..)%6@+@@$7++++@@$@@++=+++$..$.$.$..$..$.$.$.$.@.$:$.",
"@&#@,&@#.%.%*%.~#,&#&@.,+&@--%+++{!'=!=^+!+==+=+=+'+$..$;-&&$$@@&$.--%.$~#*.>%-@.$#$$,@+++!{$@@+=+'+@:$.$.$.$.$.$..$.$.@$:$.@..;",
"#@#&@,&#.*5.%*..&@#&##.+#$$$@++@@'}'={=!!!'/'=+'=+++@-$%-&@...*$#$@&@$-.#.#.$%;$.$.@,+@}++@@|{++++++@$.$.$..$....$.@.$.@.@.;.%%%",
"#&@,@,&#.*%*%.*.,,&#&#$#$$+@=+'+1$!==^={$1+=++=++/+$]$.-&&*#>.$.$.$.-.#.>.#*$-%-$.##@#@++++@$@+=+=+@.@...$.$.$.$.$.$.@.$.$.$%;.%",
",@#&,+,.>.%*%.*.&#&#.@&$@!+!+==@!{^'==!!!!{+='/='+@@$--#&#...>.>.$.$.$%#*#.*$%-.$.$.#$,@+++@$@@++++@$.-$..$.$.$....$.:@&$%%%%%.%",
"@&+@,&+...%.*%.#,#,@##$#$$|={={^{!+_=]={@$!+++=+++@$$%$&@.#*>.>%$%%$%$..>.#.>%--.*>##@@+}+{+|$+++++@....$..$...$.$.@.@.;%.;%.;5%",
"@,&,@+,#.*%*%.*.&,&#.$@$+=+='=!+!{[='==!^/@!'++/+@]$--(,#.~.*..>.>.%$%>.*#*#$%-%$...>##+++++$]+++++$@.$..$.$$.$.@:$...$.-%.%%.;%",
"@,@+,&+...*%*%.~#&,@@#$+!@^=!'!1'_+==+=+++]@+=+++$$$)++&#&>~>.>%%%$%..$.*.#..---..>.#$#+++++-$++++@.$...$...$.@...@.@.%;%%;%%%%%",
"@+@@,+,@>%..%*..,#&#&@@$!{=!==!{=='+[+{=++$@@'/+$&--@+/@~#*~.>.$>%.%>.>.>*#~$%%-.>.#.#@,++++$$++++@$.$..$.$.$.:$.$.:..%%.%;.;%;%",
",@(+,+&#..*%.%*&,@#@@$$+@!]={^@!{+=='_++++$@$@+/@$-)+/@@#.~**.>..>%$%..*#.~.#$-%...>$#++7+++1$++++@.$.%..$.@.$.@.$&$.;%%4%%%%;%%",
"+,@@+,+&>.%*.*..&@&#$$+!@^===!1]|'+=+}{@+@$$+++]@%$+++/&###~#..>...>.>..#*#..%--.>..>@,+++++$$++++$.$.$...$.$.:$.@...%.%.%4;%%;-",
"@@+,+,+..*.%*%*#,,@@@$@+!{={1{=$=/'+=+@+]@+@/'/$@%@'++@@~*~**>.$*>%..$*.~.#*@.-<..$#.@+++!+/$$@++@$....$...$.$.@.@.$%%454%%%;-%)",
",@+@,++#$%*%.%..,@.#@@@=@!==!=^][+='/=@$@.++++$@:$+/+/@+@#*#*.*$..>.>..##*.#..--...>@,}++@+/$$$+(@@$.$..$.$.$.$.$@..%%%%4%;%;%)%",
"@,@++(,....*%*..#,@@@$+@!{=@^+!!{|+={++=+@+/+@;$-@+'++@,#&*#.#.>*>.%>*#.*#.#.$%-.>.@$+++=++++$$/+.$.$.$..$.$:$...@.%%.4%%%;%%;%;",
",@,@,@+.>.%..%*&&&,@@@@!@=_!='!!]1+={}'+!++++$-$]+/+++@@&#*~*>.>.>.>.$*.#&#&#&>.*..#@+=++++++$$+&$..$.$.$.$.@.$.$..;%%%4;%%;%-%%",
"@,@,+&@...%*%*.>#+#@$@+!+=]=!=]!|!'+!@/++/@@@$6-++=+/+#@~#.#*~>*.#*.*.>.*..#.$%$.>$@+}+=@++++-@+@.$.>.$..@..$:@.@..%%4%%%;%;%;;%",
"@+@@+,@.$*.%.%..&+&#@$+{!@=_{=!!{!!'@^++!@$$@@$+'+++++#@$~#*#.#.>.>>.>.#.>.%%-%$..#@++++!+@+($$@#.$.$..$.$.$..$..$.%%%;%;%%;-;-;",
",@,+@+#..%.*%*.##,@@$@!+!{==={={=1@={@/+]$$$@@@+++=@++@&#.~*~**.#...*..>..>-.%>.>$.++=++@+++/$.@...$.$..$..@.$.@..%%.%5%;%;-%<%;",
"++@+,+@.>.%.*%..&@@#@$+@!+!'==!!^!1+@'+@@$-@@]+++@++++#+##.#.#*.*.>.>.*.>..%$%%$.$@/+=+7@@++($@@$.$...$.@.:$.:.@..%%%;%<%%<;%-;%",
"+,@+@,&$..*%.*.#&,@@@!+!+[=@^@[+^={|++.)$-+@$++{+=+!+++&#.~***#.#*.~.#..*$*>%$.$#@+++++=+@,++@:....$.$..$.$.@.$.@.%%%5;%;;%;%;-<",
"++,++@,.>%.%%%.#&@#@$@=+!'===!{=^!!{@)@-%@@@+/+@}++++++@##.#~.**.#&#.~#.#&.#.,&@>$++++++}@++(@@@.$....$...$..@....%%%%%%%%;-<%;%",
"+++@+@&$..*%*%*.@$,@@@@=+={^'|!{!!{+@$&3$&]$++=+++=+=+,+&#~.#*#.*>*~#.~~>&#.>$.$$}+}'+=+@@+@+&$.$..$..$.@.$:$.]@&%%;%4%;%;%<-;%<",
"@++++@&>..%*.%.#&$+$@@'=+_=@=]=!^!={]$$;@$$++'/=+++@+++@@&#~.~.#.#.*#*#.*>%-*#$>@+++++++}@2@+&$.$..$..$:.$..$..$&%%%%%<%<%;%%;%%",
"++7+,+@..>.%%*..#+@@d+@='='=^@[@^^@!$]$$@-+++++++=++=+@,,@~@~#*~.*>.~.#*#.>.>.>@+_+=+++@+@+@2+$..$..$..$.$:@:$@:@;%;8%;%;%-;%;%;",
"=++++@&#.%.*%>..,@$@@+!+=$[@b{!{=^!]@$$$-+@+}{=+++++@++@,@#&#.#.#.#*#*#.#~##$#$,+=++{++@7@}@++.$..$..$.$.$.$.$.@.%%%%;%<%;%)%%%%",
"+++++#@..*%.%*..@,$@@+!'_=='|^@1!+1@@{$-@++=++++++=+{+@++&#&#&>.#*.>..#.>..>.>@@/=+++++@+@+@++.$...$..$.$.$@:$@:@;%<%%<%%<%%;%;%",
"+=++@@@#..*%.%>.@+@@+@@='@[=$^!]!^+1$+]$+++++=+=++++=+++++,#&##.#.#.>*.>.>$%$.++=+[+{++@+#+#++$.$..$.$.$:$.$$$.@&;<%;%;;%-<%%%%%",
"+++++@..>.%*%..#@,@@=+^+=^+^]='1|]=]$@@+=+=++'+++++=+'+++&+,,&#.#....>.>%$.>$#(++=+++++@@+7@@(-.$.$..@.$.$@:@$@].-%;<%<%;%%)%;%%",
"+=}++@@...%.*%*.@+@+++!+'='_!]={!!]!!{!+'=+=+='@+++++=@+++(+(,#&#.#>..%.>%$.$@+++'={'++@+@+@@+.$..$:$.@$.$.$.@:@:%<%;%%<%;%%%%..",
"+++++#@#.*.%*%.#+@$@=@==+='=]|={!^|{|$1@_+'}{@}@_+'+=+++@+#+,+,(#&&#.#>.%$.$.@++=++++++@!@+#++%$&@.@.$.]$$@$]@$&.;%%4%;%%%-$$+++",
"++++++&$>.>..*..@-+$+'@={=+{!]=={!^!]!{)!'=++=+@+=+++=+=++++&#.#.#.>...$*.$%$.@+'='@{=+@+!+@@($.@.@&$@.@.@.$.@]@%%<%%%%;%;.(++++",
"++++}@,&$%*%*$*.-++===+=+=+_{|]=[!$^@^!+]@'=@++=+++=+'+@++,@#@#$#.#.>.*.$..$%$;-@+'++++!@@@+@($.&@.@.@@@.]@@.@&$;%%4%8%%%.@++++@",
"@@+++++#.>.%.%>.+@]++_{='!{+[@^@|]^!^@^|]!++='+@=+'=++=++++@@#.#.#.#.#..>.$.-%----]/'++++7@+@+.$.$&$&@&@@&@@@@@&%;%%%%%;$&(++@@@",
"@!@@+@+&..**%*..@++='+=@_+^@='|^!!{^@1@^$+1+@+=+++++=++=@++,@#.#.#.>..>...%.-.-;-)--+++@+@@+,@.@.$.@.$@.@@&@&$&@%;%;%;%.&+(@,+@,",
"$]@@@++,.$%..%>.$@++_+|{=]+^+={={==!{!]!{={{=++=+=+'=++++@+@&#$##.#.#.~.>.$.%%$%%)%3;@'+++@++@$.@.$.$.@.@@@&@&@&%4%%%%$(+(@&@,@@",
"@$$$@@2@.*.*%*..$@+=+_+=@_@'!{=$={'!^$!]=$_$+'/@=+=++'=++@+,@.#..#.*.>..*.%%$%.;$%;-%-$+{+@++.$.$.@.@.@$&$@&$.@&%%<;%%@(@@@+&@&@",
"{$]$@@+@$%.%*.>.@)/'={=+='=+=+[{==!{!{1@1+)=+=+{+'+=@+++@@++#.##.#.#..#..>...$%%;%-;%<)@++@++$$.$.:$.$.$@.@@&$&%;%%%;.@(@&#@#@,@",
"+@|$$@(,..>%*%..@@+/=+='=+{='=+={[+=!!]|]!+{$+/=+=++=++@@@#(@#&$*.#.*#.*..%.%.%-%;%;-%--{++++$.$.$@.@$@.$#.@.&$%%%;%%@(@@,@.@&&&",
"'@]$$@++...%.%*.$@+=/!+=+=@=+_{==@^{+^!@|{=|)=++{+=+{+}+@@@(,.#.#.*>.*.>.>..-..%$%-;%<;--+@++.@$..:$..$.@.@.#..%%%%%.&,@&@#@,@#@",
"++]$$$+&.*%*%*.$$+'=+[='_'$'='_@^{!!!{!]|'=@-+/!+=++=+++#@#2@.#.#.#.~>.*..>..%$%;%;%-;-%)++++$..$..@$@.@$.@&..%%%<%%$&@&#@@&@&&@",
"''$]$@++$%.%.%*.$++'=+=+=@!'+=+='=+{!'!1!@={|'+@{=+++++@@@&2&#.#*~.*..#.>.%$..%.%$;%%;-<-)++($.@.$...$@.@.@.$%%%%%%.&&,@&##@#@@@",
"++!$$$(+..*%*...@+_@_+[+[@@_{='=+[={!=@^$^@!{+_+++=++++#&#@(#.#..#*#.*.*.>..%$.-.%-;-%)-;3/0@@...@.$@.$@.@..%%%;%%%$&#&#@&@,@@#&",
"''])$@(+..%.%*..@'+'+[+=@[++=+=+='+=]=!]|]!@^++=++@++++@#.#,~.*#*..#*#.#..>.$%.%$;%-%;-<--@+@.$..$...@.@$&@%<%%%%%%&&&#&#@,@&@@@",
"/+@]$$+($.%*%%*.@@==/='=+]='+'='=+^='!{|=]|$@{++=+++},@#.@(&##..~**..#*.>.>.>.$%.-;%)%--;-)(]..@..@$.$.@.@:%;%4%%%$&#&&#.&#@@@+@",
"''!)$]+,..%..*..$++'+=+[+!@_!+_+='!+=@!1$_'[+=]$@=+++@@#.>,&&*#.#.~*#.*#.>.$.%$.-.-;-;-;-<$+&]..$..@.@:@.@.%%%%<%*.&&#&#@,&@,@@@",
"++{$$$@($.*%*%.>@'+=+'=+'='+'=+'=+^'^^@^@1+=+++@+@@++&#@.#(##.#*.*$*.#.*#*.*$..%.%%%-;-;-;-(@.$..@.$:$$.@...%%8%%.#.&&#&@@#@@@,@",
"_/!]$]@+.>%.%*..$++=++{=++=+=+=+{=+^+^]1+^@'/={++++2+#&#@#+&#.*.#.~.**#*.>.*..$..$;%%-<-%)-:$.@.$..$@.@:@...%.%%%.~#&#&@,@@@+@@@",
"+'+$]$+&&.*%*%.>$@+'+=+'={={+=+=+[+^='!^+^@|'++=++++@&#..#&#.#*.*.#*#.*.#*.>.%.%.%.-;%);-<--:@.@&$.$$].$&.....%%%.&#&@,&@,@#@@+@",
"_+{$$]@+$..%.*..$@}'++='+=+='='+!'_$!'={[@^@=++/++++#@##@2&#.#*#.*.*.~>~.*..>..$..%$%-%-)%-<@&@&@&@.@$@.$....4%%.#&,&@,@&@@+@@@+",
"+/=]$$&&&>%*.%..@@+=+'}=+{=++=+_=@!{!={=={!+'_{+=+++#&$*@(#.#.>.*#.#.*.#.#*...%.%.%.;-;-;-;-.@&+(@@@@@@$...&.%%%.@&@,&@@@@@&@@++",
"+'+]$)@+...*%*..@++'=+'='=+[+='+'_+^$|{!]='=++=+/++@#.##2&#.#.*#*.*.*#.**.*.>*.$.$%$%;-%<--<$6@&@&+({(@+&$..%%%..,+&@,@@,@@@@@++",
"+++$$).(..%*%.%.@=+++=++='={+=+==+=={|!^@=+^/=+/+++#@#&,(#.#.*#.*#.*#*.#.#*...*.%..%$%;-;-%--&@@@@@++++(+%%.%%%.@(@+@&@@@+@@@+++",
"{@{]$]$+&..*%*.&$@_'+'='+=+='=++='=+_{$^!{='=/+=++@#$#&2#.#.>.~*.*.*.#*#*.#.>.>..$%.%-%-;3;-%{&@&@@@@++(,%>.%<.&++,@+@@(@@@@@+@+",
"@@@$$;$&@%%.%%.&$+++=+=+='=@=+[/=+='=!_@!{_++[+++@#@~,(,.#.#.*.~*#.**..*.*.*...*..$%.;-;-%-;-$@&@@&@@++(+.%-.>&(+@+@@@@+@@@@@@++",
"+{@]$]$&&.*.*%%&@=+=+'='+='='=@='!+=+^@^+='=+/+++@@#(2,~#.#.#*#.*.~.#*~.#*#.#*.$*.$%$.%-;)%-<-6@@@@6@@+@&>%%$&+(+@+@@+@+@@@@{+@+",
"@@@$$.]&@.%.%...@++'=+=+=+=++={}+!'!'=@1+=++++}+@(,(#~..~#.*.**~*.**.*>.*.~.*.>..>.%.-;%-;-%--+@&@@@@&@&#.>.&,&+@+@@@@+@@&@@++++",
"+@{@)$$&..*%*%..{++=+{='='=^+!@='!+=]={!{='=++++,(#&&#~#.*##.#*.#.#*.*.~.#*.#.**...$%.$;%)%);-:@@@&@@@&@&..,@(@&+@@@@++@@@@@@+++",
"+'@@$)$&@..%%%.#++_+=+=+!+=@[+=@={!_+={!@++++/+,@&.#*.*~#..~.#****.~.#*.*..*.*#.>>.%$%%$%)%----+6@@@&@@.##&~@#@+@@@+@+@@]@@++{++",
"+++{$-)$..%*.%.@++++'+{='='@={=$=+!'^$_+^+_+++@$&#&~&~*.~#*#*.~.~.*.*.#*.#*#.*.*..>..$;%;-;;-;-+@&@@&$&#&&#&#&@@@@+@+@@.@@@+++++",
"++'@)-.$&$.%%%%&+'=+==+=+=={=@={@[@='={!$@+++,@,.&.#*&~~.*&.#*#*#*~.*.*.*..*>.*>.>.%.%$;%)%--;-@&@&@@.@.#&#.#&@@+@+@@@@@@@++++!@",
"++++-))$...>.%.@++_++{={'=@_+=+={=]+_@[/^@+@+@#@#.~.~*.~*#*#*.~.*.*.**.*.#*.*#.*.>.$..$;$%);--)$6@&@&#.@*&#.#@,@+@@+@@$@@@++++@@"};

View File

@ -1,52 +1,78 @@
include *.c
include *.h
include *.md
include *.py
include *.rst
include *.txt
include *.yaml
include .coveragerc
include .gitattributes
include .travis.yml
include Makefile
include tox.ini
recursive-include Images *.bdf
recursive-include Images *.fli
recursive-include Images *.gif
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 Sane *.c
recursive-include Sane *.py
recursive-include Sane *.txt
recursive-include Sane CHANGES
recursive-include Sane README
recursive-include PIL *.md
recursive-include Scripts *.py
recursive-include Scripts README
recursive-include Scripts *.rst
recursive-include Scripts *.sh
recursive-include Scripts README.rst
recursive-include Tests *.bdf
recursive-include Tests *.bin
recursive-include Tests *.icm
recursive-include Tests *.bmp
recursive-include Tests *.bw
recursive-include Tests *.cur
recursive-include Tests *.dcx
recursive-include Tests *.doc
recursive-include Tests *.eps
recursive-include Tests *.fli
recursive-include Tests *.ggr
recursive-include Tests *.gif
recursive-include Tests *.gnuplot
recursive-include Tests *.html
recursive-include Tests *.icc
recursive-include Tests *.icns
recursive-include Tests *.ico
recursive-include Tests *.j2k
recursive-include Tests *.jp2
recursive-include Tests *.jpg
recursive-include Tests *.lut
recursive-include Tests *.mpo
recursive-include Tests *.pbm
recursive-include Tests *.pcf
recursive-include Tests *.pcx
recursive-include Tests *.pgm
recursive-include Tests *.pil
recursive-include Tests *.png
recursive-include Tests *.ppm
recursive-include Tests *.psd
recursive-include Tests *.py
recursive-include Tests *.ras
recursive-include Tests *.rgb
recursive-include Tests *.rst
recursive-include Tests *.sgi
recursive-include Tests *.spider
recursive-include Tests *.tar
recursive-include Tests *.tga
recursive-include Tests *.tif
recursive-include Tests *.tiff
recursive-include Tests *.ttf
recursive-include Tests *.txt
recursive-include Tests *.webp
recursive-include Tests *.xpm
recursive-include Tests *.msp
recursive-include Tk *.c
recursive-include Tk *.txt
recursive-include Tk *.rst
recursive-include depends *.rst
recursive-include depends *.sh
recursive-include docs *.bat
recursive-include docs *.gitignore
recursive-include docs *.html
recursive-include docs *.py
recursive-include docs *.rst
recursive-include docs Makefile
recursive-include docs *.txt
recursive-include docs BUILDME
recursive-include docs COPYING
recursive-include docs Guardfile
recursive-include docs LICENSE
recursive-include docs Makefile
recursive-include libImaging *.c
recursive-include libImaging *.h

View File

@ -1,7 +1,61 @@
.PHONY: pre clean install test inplace coverage test-dep help docs livedocs
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " clean remove build products"
@echo " install make and install"
@echo " test run tests on installed pillow"
@echo " inplace make inplace extension"
@echo " coverage run coverage test (in progress)"
@echo " docs make html docs"
@echo " docserver run an http server on the docs directory"
@echo " test-dep install coveraget and test dependencies"
pre:
virtualenv .
bin/pip install -r requirements.txt
bin/python setup.py develop
bin/python selftest.py
bin/python Tests/run.py
bin/nosetests Tests/test_*.py
bin/python setup.py install
bin/python test-installed.py
check-manifest
pyroma .
viewdoc
clean:
python setup.py clean
rm PIL/*.so || true
rm -r build || true
find . -name __pycache__ | xargs rm -r || true
install:
python setup.py install
python selftest.py --installed
test:
python test-installed.py
inplace: clean
python setup.py build_ext --inplace
coverage:
# requires nose-cov
coverage erase
coverage run --parallel-mode --include=PIL/* selftest.py
nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py
# doesn't combine properly before report,
# writing report instead of displaying invalid report
rm -r htmlcov || true
coverage combine
coverage report
test-dep:
pip install coveralls nose nose-cov pep8 pyflakes
docs:
$(MAKE) -C docs html
docserver:
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&

View File

@ -1,506 +0,0 @@
#
# THIS IS WORK IN PROGRESS
#
# The Python Imaging Library.
# $Id$
#
# ARG animation support code
#
# history:
# 1996-12-30 fl Created
# 1996-01-06 fl Added safe scripting environment
# 1996-01-10 fl Added JHDR, UHDR and sYNC support
# 2005-03-02 fl Removed AAPP and ARUN support
#
# Copyright (c) Secret Labs AB 1997.
# Copyright (c) Fredrik Lundh 1996-97.
#
# See the README file for information on usage and redistribution.
#
from __future__ import print_function
__version__ = "0.4"
from PIL import Image, ImageFile, ImagePalette
from PIL.PngImagePlugin import i8, i16, i32, ChunkStream, _MODES
MAGIC = b"\212ARG\r\n\032\n"
# --------------------------------------------------------------------
# ARG parser
class ArgStream(ChunkStream):
"Parser callbacks for ARG data"
def __init__(self, fp):
ChunkStream.__init__(self, fp)
self.eof = 0
self.im = None
self.palette = None
self.__reset()
def __reset(self):
# reset decoder state (called on init and sync)
self.count = 0
self.id = None
self.action = ("NONE",)
self.images = {}
self.names = {}
def chunk_AHDR(self, offset, bytes):
"AHDR -- animation header"
# assertions
if self.count != 0:
raise SyntaxError("misplaced AHDR chunk")
s = self.fp.read(bytes)
self.size = i32(s), i32(s[4:])
try:
self.mode, self.rawmode = _MODES[(i8(s[8]), i8(s[9]))]
except:
raise SyntaxError("unknown ARG mode")
if Image.DEBUG:
print("AHDR size", self.size)
print("AHDR mode", self.mode, self.rawmode)
return s
def chunk_AFRM(self, offset, bytes):
"AFRM -- next frame follows"
# assertions
if self.count != 0:
raise SyntaxError("misplaced AFRM chunk")
self.show = 1
self.id = 0
self.count = 1
self.repair = None
s = self.fp.read(bytes)
if len(s) >= 2:
self.id = i16(s)
if len(s) >= 4:
self.count = i16(s[2:4])
if len(s) >= 6:
self.repair = i16(s[4:6])
else:
self.repair = None
if Image.DEBUG:
print("AFRM", self.id, self.count)
return s
def chunk_ADEF(self, offset, bytes):
"ADEF -- store image"
# assertions
if self.count != 0:
raise SyntaxError("misplaced ADEF chunk")
self.show = 0
self.id = 0
self.count = 1
self.repair = None
s = self.fp.read(bytes)
if len(s) >= 2:
self.id = i16(s)
if len(s) >= 4:
self.count = i16(s[2:4])
if Image.DEBUG:
print("ADEF", self.id, self.count)
return s
def chunk_NAME(self, offset, bytes):
"NAME -- name the current image"
# assertions
if self.count == 0:
raise SyntaxError("misplaced NAME chunk")
name = self.fp.read(bytes)
self.names[self.id] = name
return name
def chunk_AEND(self, offset, bytes):
"AEND -- end of animation"
if Image.DEBUG:
print("AEND")
self.eof = 1
raise EOFError("end of ARG file")
def __getmodesize(self, s, full=1):
size = i32(s), i32(s[4:])
try:
mode, rawmode = _MODES[(i8(s[8]), i8(s[9]))]
except:
raise SyntaxError("unknown image mode")
if full:
if i8(s[12]):
pass # interlace not yet supported
if i8(s[11]):
raise SyntaxError("unknown filter category")
return size, mode, rawmode
def chunk_PAST(self, offset, bytes):
"PAST -- paste one image into another"
# assertions
if self.count == 0:
raise SyntaxError("misplaced PAST chunk")
if self.repair is not None:
# we must repair the target image before we
# start pasting
# brute force; a better solution would be to
# update only the dirty rectangles in images[id].
# note that if images[id] doesn't exist, it must
# be created
self.images[self.id] = self.images[self.repair].copy()
self.repair = None
s = self.fp.read(bytes)
im = self.images[i16(s)]
x, y = i32(s[2:6]), i32(s[6:10])
bbox = x, y, im.size[0]+x, im.size[1]+y
if im.mode in ["RGBA"]:
# paste with transparency
# FIXME: should handle P+transparency as well
self.images[self.id].paste(im, bbox, im)
else:
# paste without transparency
self.images[self.id].paste(im, bbox)
self.action = ("PAST",)
self.__store()
return s
def chunk_BLNK(self, offset, bytes):
"BLNK -- create blank image"
# assertions
if self.count == 0:
raise SyntaxError("misplaced BLNK chunk")
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s, 0)
# store image (FIXME: handle colour)
self.action = ("BLNK",)
self.im = Image.core.fill(mode, size, 0)
self.__store()
return s
def chunk_IHDR(self, offset, bytes):
"IHDR -- full image follows"
# assertions
if self.count == 0:
raise SyntaxError("misplaced IHDR chunk")
# image header
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s)
# decode and store image
self.action = ("IHDR",)
self.im = Image.core.new(mode, size)
self.decoder = Image.core.zip_decoder(rawmode)
self.decoder.setimage(self.im, (0,0) + size)
self.data = b""
return s
def chunk_DHDR(self, offset, bytes):
"DHDR -- delta image follows"
# assertions
if self.count == 0:
raise SyntaxError("misplaced DHDR chunk")
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s)
# delta header
diff = i8(s[13])
offs = i32(s[14:18]), i32(s[18:22])
bbox = offs + (offs[0]+size[0], offs[1]+size[1])
if Image.DEBUG:
print("DHDR", diff, bbox)
# FIXME: decode and apply image
self.action = ("DHDR", diff, bbox)
# setup decoder
self.im = Image.core.new(mode, size)
self.decoder = Image.core.zip_decoder(rawmode)
self.decoder.setimage(self.im, (0,0) + size)
self.data = b""
return s
def chunk_JHDR(self, offset, bytes):
"JHDR -- JPEG image follows"
# assertions
if self.count == 0:
raise SyntaxError("misplaced JHDR chunk")
# image header
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s, 0)
# decode and store image
self.action = ("JHDR",)
self.im = Image.core.new(mode, size)
self.decoder = Image.core.jpeg_decoder(rawmode)
self.decoder.setimage(self.im, (0,0) + size)
self.data = b""
return s
def chunk_UHDR(self, offset, bytes):
"UHDR -- uncompressed image data follows (EXPERIMENTAL)"
# assertions
if self.count == 0:
raise SyntaxError("misplaced UHDR chunk")
# image header
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s, 0)
# decode and store image
self.action = ("UHDR",)
self.im = Image.core.new(mode, size)
self.decoder = Image.core.raw_decoder(rawmode)
self.decoder.setimage(self.im, (0,0) + size)
self.data = b""
return s
def chunk_IDAT(self, offset, bytes):
"IDAT -- image data block"
# pass compressed chunks through the decoder
s = self.fp.read(bytes)
self.data = self.data + s
n, e = self.decoder.decode(self.data)
if n < 0:
# end of image
if e < 0:
raise IOError("decoder error %d" % e)
else:
self.data = self.data[n:]
return s
def chunk_DEND(self, offset, bytes):
return self.chunk_IEND(offset, bytes)
def chunk_JEND(self, offset, bytes):
return self.chunk_IEND(offset, bytes)
def chunk_UEND(self, offset, bytes):
return self.chunk_IEND(offset, bytes)
def chunk_IEND(self, offset, bytes):
"IEND -- end of image"
# we now have a new image. carry out the operation
# defined by the image header.
# won't need these anymore
del self.decoder
del self.data
self.__store()
return self.fp.read(bytes)
def __store(self):
# apply operation
cid = self.action[0]
if cid in ["BLNK", "IHDR", "JHDR", "UHDR"]:
# store
self.images[self.id] = self.im
elif cid == "DHDR":
# paste
cid, mode, bbox = self.action
im0 = self.images[self.id]
im1 = self.im
if mode == 0:
im1 = im1.chop_add_modulo(im0.crop(bbox))
im0.paste(im1, bbox)
self.count = self.count - 1
if self.count == 0 and self.show:
self.im = self.images[self.id]
raise EOFError # end of this frame
def chunk_PLTE(self, offset, bytes):
"PLTE -- palette data"
s = self.fp.read(bytes)
if self.mode == "P":
self.palette = ImagePalette.raw("RGB", s)
return s
def chunk_sYNC(self, offset, bytes):
"SYNC -- reset decoder"
if self.count != 0:
raise SyntaxError("misplaced sYNC chunk")
s = self.fp.read(bytes)
self.__reset()
return s
# --------------------------------------------------------------------
# ARG reader
def _accept(prefix):
return prefix[:8] == MAGIC
##
# Image plugin for the experimental Animated Raster Graphics format.
class ArgImageFile(ImageFile.ImageFile):
format = "ARG"
format_description = "Animated raster graphics"
def _open(self):
if Image.warnings:
Image.warnings.warn(
"The ArgImagePlugin driver is obsolete, and will be removed "
"from a future release of PIL. If you rely on this module, "
"please contact the PIL authors.",
RuntimeWarning
)
if self.fp.read(8) != MAGIC:
raise SyntaxError("not an ARG file")
self.arg = ArgStream(self.fp)
# read and process the first chunk (AHDR)
cid, offset, bytes = self.arg.read()
if cid != "AHDR":
raise SyntaxError("expected an AHDR chunk")
s = self.arg.call(cid, offset, bytes)
self.arg.crc(cid, s)
# image characteristics
self.mode = self.arg.mode
self.size = self.arg.size
def load(self):
if self.arg.im is None:
self.seek(0)
# image data
self.im = self.arg.im
self.palette = self.arg.palette
# set things up for further processing
Image.Image.load(self)
def seek(self, frame):
if self.arg.eof:
raise EOFError("end of animation")
self.fp = self.arg.fp
while True:
#
# process chunks
cid, offset, bytes = self.arg.read()
if self.arg.eof:
raise EOFError("end of animation")
try:
s = self.arg.call(cid, offset, bytes)
except EOFError:
break
except "glurk": # AttributeError
if Image.DEBUG:
print(cid, bytes, "(unknown)")
s = self.fp.read(bytes)
self.arg.crc(cid, s)
self.fp.read(4) # ship extra CRC
def tell(self):
return 0
def verify(self):
"Verify ARG file"
# back up to first chunk
self.fp.seek(8)
self.arg.verify(self)
self.arg.close()
self.fp = None
#
# --------------------------------------------------------------------
Image.register_open("ARG", ArgImageFile, _accept)
Image.register_extension("ARG", ".arg")
Image.register_mime("ARG", "video/x-arg")

View File

@ -26,12 +26,12 @@ from PIL import FontFile
# --------------------------------------------------------------------
bdf_slant = {
"R": "Roman",
"I": "Italic",
"O": "Oblique",
"RI": "Reverse Italic",
"RO": "Reverse Oblique",
"OT": "Other"
"R": "Roman",
"I": "Italic",
"O": "Oblique",
"RI": "Reverse Italic",
"RO": "Reverse Oblique",
"OT": "Other"
}
bdf_spacing = {
@ -40,8 +40,8 @@ bdf_spacing = {
"C": "Cell"
}
def bdf_char(f):
def bdf_char(f):
# skip to STARTCHAR
while True:
s = f.readline()
@ -82,6 +82,7 @@ def bdf_char(f):
return id, int(props["ENCODING"]), bbox, im
##
# Font file plugin for the X11 BDF format.
@ -113,10 +114,10 @@ class BdfFontFile(FontFile.FontFile):
font[4] = bdf_slant[font[4].upper()]
font[11] = bdf_spacing[font[11].upper()]
ascent = int(props["FONT_ASCENT"])
descent = int(props["FONT_DESCENT"])
# ascent = int(props["FONT_ASCENT"])
# descent = int(props["FONT_DESCENT"])
fontname = ";".join(font[1:])
# fontname = ";".join(font[1:])
# print "#", fontname
# for i in comments:
@ -128,5 +129,5 @@ class BdfFontFile(FontFile.FontFile):
if not c:
break
id, ch, (xy, dst, src), im = c
if ch >= 0 and ch < len(self.glyph):
if 0 <= ch < len(self.glyph):
self.glyph[ch] = xy, dst, src, im

View File

@ -28,6 +28,7 @@ __version__ = "0.7"
from PIL import Image, ImageFile, ImagePalette, _binary
import math
i8 = _binary.i8
i16 = _binary.i16le
@ -50,9 +51,11 @@ BIT2MODE = {
32: ("RGB", "BGRX")
}
def _accept(prefix):
return prefix[:2] == b"BM"
##
# Image plugin for the Windows BMP format.
@ -61,8 +64,7 @@ class BmpImageFile(ImageFile.ImageFile):
format = "BMP"
format_description = "Windows Bitmap"
def _bitmap(self, header = 0, offset = 0):
def _bitmap(self, header=0, offset=0):
if header:
self.fp.seek(header)
@ -82,12 +84,13 @@ class BmpImageFile(ImageFile.ImageFile):
colors = 0
direction = -1
elif len(s) in [40, 64]:
elif len(s) in [40, 64, 108, 124]:
# WIN 3.1 or OS/2 2.0 INFO
bits = i16(s[14:])
self.size = i32(s[4:]), i32(s[8:])
compression = i32(s[16:])
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
lutsize = 4
colors = i32(s[32:])
direction = -1
@ -96,9 +99,16 @@ class BmpImageFile(ImageFile.ImageFile):
self.size = self.size[0], 2**32 - self.size[1]
direction = 0
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701),
pxperm))
else:
raise IOError("Unsupported BMP header type (%d)" % len(s))
if (self.size[0]*self.size[1]) > 2**31:
# Prevent DOS for > 2gb images
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
if not colors:
colors = 1 << bits
@ -129,6 +139,8 @@ class BmpImageFile(ImageFile.ImageFile):
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:
@ -153,7 +165,8 @@ class BmpImageFile(ImageFile.ImageFile):
self.tile = [("raw",
(0, 0) + self.size,
offset,
(rawmode, ((self.size[0]*bits+31)>>3)&(~3), direction))]
(rawmode, ((self.size[0]*bits+31) >> 3) & (~3),
direction))]
self.info["compression"] = compression
@ -187,8 +200,8 @@ SAVE = {
"RGB": ("BGR", 24, 0),
}
def _save(im, fp, filename, check=0):
def _save(im, fp, filename, check=0):
try:
rawmode, bits, colors = SAVE[im.mode]
except KeyError:
@ -197,30 +210,37 @@ def _save(im, fp, filename, check=0):
if check:
return check
stride = ((im.size[0]*bits+7)//8+3)&(~3)
header = 40 # or 64 for OS/2 version 2
info = im.encoderinfo
dpi = info.get("dpi", (96, 96))
# 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
stride = ((im.size[0]*bits+7)//8+3) & (~3)
header = 40 # or 64 for OS/2 version 2
offset = 14 + header + colors * 4
image = stride * im.size[1]
image = stride * im.size[1]
# bitmap header
fp.write(b"BM" + # file type (magic)
o32(offset+image) + # file size
o32(0) + # reserved
o32(offset)) # image data offset
fp.write(b"BM" + # file type (magic)
o32(offset+image) + # file size
o32(0) + # reserved
o32(offset)) # image data offset
# bitmap info header
fp.write(o32(header) + # info header size
o32(im.size[0]) + # width
o32(im.size[1]) + # height
o16(1) + # planes
o16(bits) + # depth
o32(0) + # compression (0=uncompressed)
o32(image) + # size of bitmap
o32(1) + o32(1) + # resolution
o32(colors) + # colors used
o32(colors)) # colors important
fp.write(o32(header) + # info header size
o32(im.size[0]) + # width
o32(im.size[1]) + # height
o16(1) + # planes
o16(bits) + # depth
o32(0) + # compression (0=uncompressed)
o32(image) + # size of bitmap
o32(ppm[0]) + o32(ppm[1]) + # resolution
o32(colors) + # colors used
o32(colors)) # colors important
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
if im.mode == "1":
for i in (0, 255):
@ -231,7 +251,8 @@ def _save(im, fp, filename, check=0):
elif im.mode == "P":
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
##
# Install application-specific BUFR image handler.
#
@ -22,12 +23,14 @@ def register_handler(handler):
global _handler
_handler = handler
# --------------------------------------------------------------------
# Image adapter
def _accept(prefix):
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
class BufrStubImageFile(ImageFile.StubImageFile):
format = "BUFR"
@ -53,6 +56,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
def _load(self):
return _handler
def _save(im, fp, filename):
if _handler is None or not hasattr("_handler", "save"):
raise IOError("BUFR save handler not installed")

View File

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

View File

@ -33,6 +33,7 @@ i32 = _binary.i32le
def _accept(prefix):
return prefix[:4] == b"\0\0\2\0"
##
# Image plugin for Windows Cursor files.
@ -48,7 +49,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
# check magic
s = self.fp.read(6)
if not _accept(s):
raise SyntaxError("not an CUR file")
raise SyntaxError("not a CUR file")
# pick the largest cursor in the file
m = b""
@ -58,14 +59,14 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
m = s
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
m = s
#print "width", i8(s[0])
#print "height", i8(s[1])
#print "colors", i8(s[2])
#print "reserved", i8(s[3])
#print "hotspot x", i16(s[4:])
#print "hotspot y", i16(s[6:])
#print "bytes", i32(s[8:])
#print "offset", i32(s[12:])
# print "width", i8(s[0])
# print "height", i8(s[1])
# print "colors", i8(s[2])
# print "reserved", i8(s[3])
# print "hotspot x", i16(s[4:])
# print "hotspot y", i16(s[6:])
# print "bytes", i32(s[8:])
# print "offset", i32(s[12:])
# load as bitmap
self._bitmap(i32(m[12:]) + offset)
@ -73,7 +74,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
# patch up the bitmap height
self.size = self.size[0], self.size[1]//2
d, e, o, a = self.tile[0]
self.tile[0] = d, (0,0)+self.size, o, a
self.tile[0] = d, (0, 0)+self.size, o, a
return

View File

@ -27,13 +27,15 @@ from PIL import Image, _binary
from PIL.PcxImagePlugin import PcxImageFile
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
i32 = _binary.i32le
def _accept(prefix):
return i32(prefix) == MAGIC
##
# Image plugin for the Intel DCX format.

View File

@ -11,6 +11,8 @@
# 1996-08-23 fl Handle files from Macintosh (0.3)
# 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)
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution
# resizing
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2003 by Fredrik Lundh
@ -50,92 +52,138 @@ if sys.platform.startswith('win'):
else:
gs_windows_binary = False
def Ghostscript(tile, size, fp):
def has_ghostscript():
if gs_windows_binary:
return True
if not sys.platform.startswith('win'):
import subprocess
try:
gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
gs.stdout.read()
return True
except OSError:
# no ghostscript
pass
return False
def Ghostscript(tile, size, fp, scale=1):
"""Render an image using Ghostscript"""
# Unpack decoder tile
decoder, tile, offset, data = tile[0]
length, bbox = data
import tempfile, os
# Hack to support hi-res rendering
scale = int(scale) or 1
# orig_size = size
# orig_bbox = bbox
size = (size[0] * scale, size[1] * scale)
# 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])))
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
file = tempfile.mktemp()
import os
import subprocess
import tempfile
out_fd, outfile = tempfile.mkstemp()
os.close(out_fd)
infile_temp = None
if hasattr(fp, 'name') and os.path.exists(fp.name):
infile = fp.name
else:
in_fd, infile_temp = tempfile.mkstemp()
os.close(in_fd)
infile = infile_temp
# ignore length and offset!
# ghostscript can read it
# copy whole file to read in ghostscript
with open(infile_temp, 'wb') as f:
# fetch length of fp
fp.seek(0, 2)
fsize = fp.tell()
# ensure start position
# 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
command = ["gs",
"-q", # quite mode
"-g%dx%d" % size, # set output geometry (pixels)
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % file,# output file
"- >/dev/null 2>/dev/null"]
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages,
# safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
# adjust for image origin
"-f", infile, # input file
]
if gs_windows_binary is not None:
if gs_windows_binary is False:
if not gs_windows_binary:
raise WindowsError('Unable to locate Ghostscript on paths')
command[0] = gs_windows_binary
command[-1] = '- >nul 2>nul'
command = " ".join(command)
# push data through ghostscript
try:
gs = os.popen(command, "w")
# adjust for image origin
if bbox[0] != 0 or bbox[1] != 0:
gs.write("%d %d translate\n" % (-bbox[0], -bbox[1]))
fp.seek(offset)
while length > 0:
s = fp.read(8192)
if not s:
break
length = length - len(s)
gs.write(s)
status = gs.close()
gs = subprocess.Popen(command, stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
gs.stdin.close()
status = gs.wait()
if status:
raise IOError("gs failed (status %d)" % status)
im = Image.core.open_ppm(file)
im = Image.core.open_ppm(outfile)
finally:
try: os.unlink(file)
except: pass
try:
os.unlink(outfile)
if infile_temp:
os.unlink(infile_temp)
except:
pass
return im
class PSFile:
"""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):
self.fp = fp
self.char = None
def __getattr__(self, id):
v = getattr(self.fp, id)
setattr(self, id, v)
return v
def seek(self, offset, whence=0):
self.char = None
self.fp.seek(offset, whence)
def read(self, count):
return self.fp.read(count).decode('latin-1')
def tell(self):
pos = self.fp.tell()
if self.char:
pos = pos - 1
return pos
def readline(self):
s = b""
if self.char:
c = self.char
self.char = None
else:
c = self.fp.read(1)
s = self.char or b""
self.char = None
c = self.fp.read(1)
while c not in b"\r\n":
s = s + c
c = self.fp.read(1)
if c == b"\r":
self.char = self.fp.read(1)
if self.char == b"\n":
self.char = None
return s.decode('latin-1') + "\n"
self.char = self.fp.read(1)
# line endings can be 1 or 2 of \r \n, in either order
if self.char in b"\r\n":
self.char = None
return s.decode('latin-1')
def _accept(prefix):
@ -145,54 +193,48 @@ def _accept(prefix):
# Image plugin for Encapsulated Postscript. This plugin supports only
# a few variants of this format.
class EpsImageFile(ImageFile.ImageFile):
"""EPS File Parser for the Python Imaging Library"""
format = "EPS"
format_description = "Encapsulated Postscript"
mode_map = {1: "L", 2: "LAB", 3: "RGB"}
def _open(self):
(length, offset) = self._find_offset(self.fp)
# FIXME: should check the first 512 bytes to see if this
# really is necessary (platform-dependent, though...)
fp = PSFile(self.fp)
# HEAD
s = fp.read(512)
if s[:4] == "%!PS":
offset = 0
fp.seek(0, 2)
length = fp.tell()
elif i32(s) == 0xC6D3D0C5:
offset = i32(s[4:])
length = i32(s[8:])
fp.seek(offset)
else:
raise SyntaxError("not an EPS file")
# Rewrap the open file pointer in something that will
# convert line endings and decode to latin-1.
try:
if bytes is str:
# Python2, no encoding conversion necessary
fp = open(self.fp.name, "Ur")
else:
# Python3, can use bare open command.
fp = open(self.fp.name, "Ur", encoding='latin-1')
except:
# Expect this for bytesio/stringio
fp = PSFile(self.fp)
# go to offset - start of "%!PS"
fp.seek(offset)
box = None
self.mode = "RGB"
self.size = 1, 1 # FIXME: huh?
self.size = 1, 1 # FIXME: huh?
#
# Load EPS header
s = fp.readline()
s = fp.readline().strip('\r\n')
while s:
if len(s) > 255:
raise SyntaxError("not an EPS file")
if s[-2:] == '\r\n':
s = s[:-2]
elif s[-1:] == '\n':
s = s[:-1]
try:
m = split.match(s)
except re.error as v:
@ -208,15 +250,13 @@ class EpsImageFile(ImageFile.ImageFile):
# put floating point values there anyway.
box = [int(float(s)) for s in v.split()]
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))]
except:
pass
else:
m = field.match(s)
if m:
k = m.group(1)
@ -226,19 +266,18 @@ class EpsImageFile(ImageFile.ImageFile):
self.info[k[:8]] = k[9:]
else:
self.info[k] = ""
elif s[0:1] == '%':
elif s[0] == '%':
# handle non-DSC Postscript comments that some
# tools mistakenly put in the Comments section
pass
else:
raise IOError("bad EPS header")
s = fp.readline()
s = fp.readline().strip('\r\n')
if s[:1] != "%":
if s[0] != "%":
break
#
# Scan for an "ImageData" descriptor
@ -247,72 +286,64 @@ class EpsImageFile(ImageFile.ImageFile):
if len(s) > 255:
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:":
# Encoded bitmapped image.
[x, y, bi, mo, z3, z4, en, id] = s[11:].split(None, 7)
[x, y, bi, mo, z3, z4, en, id] =\
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:
if int(bi) != 8:
break
if bi != 8:
break
if mo == 1:
self.mode = "L"
elif mo == 2:
self.mode = "LAB"
elif mo == 3:
self.mode = "RGB"
else:
try:
self.mode = self.mode_map[int(mo)]
except:
break
if id[:1] == id[-1:] == '"':
id = id[1:-1]
self.size = int(x), int(y)
return
# Scan forward to the actual image data
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()
s = fp.readline().strip('\r\n')
if not s:
break
if not box:
raise IOError("cannot determine EPS bounding box")
def load(self):
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):
# Load EPS via Ghostscript
if not self.tile:
return
self.im = Ghostscript(self.tile, self.size, self.fp)
self.im = Ghostscript(self.tile, self.size, self.fp, scale)
self.mode = self.im.mode
self.size = self.im.size
self.tile = []
def load_seek(self, *args, **kwargs):
# we can't incrementally load, so force ImageFile.parser to
# use our custom load method by defining this method.
pass
#
# --------------------------------------------------------------------
@ -337,20 +368,24 @@ def _save(im, fp, filename, eps=1):
class NoCloseStream:
def __init__(self, fp):
self.fp = fp
def __getattr__(self, name):
return getattr(self.fp, name)
def close(self):
pass
base_fp = fp
fp = io.TextIOWrapper(NoCloseStream(fp), encoding='latin-1')
fp = NoCloseStream(fp)
if sys.version_info[0] > 2:
fp = io.TextIOWrapper(fp, encoding='latin-1')
if eps:
#
# write EPS header
fp.write("%!PS-Adobe-3.0 EPSF-3.0\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("%%Pages: 1\n")
fp.write("%%EndComments\n")
@ -364,13 +399,13 @@ def _save(im, fp, filename, eps=1):
fp.write("10 dict begin\n")
fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
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("{ currentfile buf readhexstring pop } bind\n")
fp.write(operator[2] + "\n")
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("grestore end\n")

View File

@ -63,15 +63,12 @@ TAGS = {
0x0201: "JpegIFOffset",
0x0202: "JpegIFByteCount",
0x0211: "YCbCrCoefficients",
0x0211: "YCbCrCoefficients",
0x0212: "YCbCrSubSampling",
0x0213: "YCbCrPositioning",
0x0213: "YCbCrPositioning",
0x0214: "ReferenceBlackWhite",
0x0214: "ReferenceBlackWhite",
0x1000: "RelatedImageFileFormat",
0x1001: "RelatedImageLength",
0x1001: "RelatedImageWidth",
0x1002: "RelatedImageLength",
0x828d: "CFARepeatPatternDim",
0x828e: "CFAPattern",
0x828f: "BatteryLevel",

View File

@ -25,12 +25,14 @@ i16 = _binary.i16le
i32 = _binary.i32le
o8 = _binary.o8
#
# decoder
def _accept(prefix):
return i16(prefix[4:6]) in [0xAF11, 0xAF12]
##
# Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
# method to load individual frames.
@ -47,7 +49,7 @@ class FliImageFile(ImageFile.ImageFile):
magic = i16(s[4:6])
if not (magic in [0xAF11, 0xAF12] and
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")
# image characteristics
@ -61,7 +63,7 @@ class FliImageFile(ImageFile.ImageFile):
self.info["duration"] = duration
# 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)
@ -80,7 +82,7 @@ class FliImageFile(ImageFile.ImageFile):
elif i16(s[4:6]) == 4:
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))
# set things up to decode first frame
@ -105,7 +107,7 @@ class FliImageFile(ImageFile.ImageFile):
g = i8(s[n+1]) << shift
b = i8(s[n+2]) << shift
palette[i] = (r, g, b)
i = i + 1
i += 1
def seek(self, frame):
@ -124,7 +126,7 @@ class FliImageFile(ImageFile.ImageFile):
framesize = i32(s)
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

View File

@ -17,8 +17,6 @@
import os
from PIL import Image, _binary
import marshal
try:
import zlib
except ImportError:
@ -26,13 +24,15 @@ except ImportError:
WIDTH = 800
def puti16(fp, values):
# write network order (big-endian) 16-bit sequence
for v in values:
if v < 0:
v = v + 65536
v += 65536
fp.write(_binary.o16be(v))
##
# Base class for raster font file handlers.
@ -63,7 +63,7 @@ class FontFile:
h = max(h, src[3] - src[1])
w = w + (src[2] - src[0])
if w > WIDTH:
lines = lines + 1
lines += 1
w = (src[2] - src[0])
maxwidth = max(maxwidth, w)
@ -95,9 +95,8 @@ class FontFile:
# print chr(i), dst, s
self.metrics[i] = d, dst, s
def save1(self, filename):
"Save font in version 1 format"
def save(self, filename):
"Save font"
self.compile()
@ -107,7 +106,7 @@ class FontFile:
# font metrics
fp = open(os.path.splitext(filename)[0] + ".pil", "wb")
fp.write(b"PILfont\n")
fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!!
fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!!
fp.write(b"DATA\n")
for id in range(256):
m = self.metrics[id]
@ -117,30 +116,4 @@ class FontFile:
puti16(fp, m[0] + m[1] + m[2])
fp.close()
def save2(self, filename):
"Save font in version 2 format"
# THIS IS WORK IN PROGRESS
self.compile()
data = marshal.dumps((self.metrics, self.info))
if zlib:
data = b"z" + zlib.compress(data, 9)
else:
data = b"u" + data
fp = open(os.path.splitext(filename)[0] + ".pil", "wb")
fp.write(b"PILfont2\n" + self.name + "\n" + "DATA\n")
fp.write(data)
self.bitmap.save(fp, "PNG")
fp.close()
save = save1 # for now
# End of file

View File

@ -34,16 +34,18 @@ MODES = {
(0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
(0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"),
# standard RGB (NIFRGB)
(0x00030000, 0x00030001, 0x00030002): ("RGB","RGB"),
(0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA","RGBA"),
(0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
(0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA", "RGBA"),
}
#
# --------------------------------------------------------------------
def _accept(prefix):
return prefix[:8] == MAGIC
##
# Image plugin for the FlashPix images.
@ -67,7 +69,7 @@ class FpxImageFile(ImageFile.ImageFile):
self._open_index(1)
def _open_index(self, index = 1):
def _open_index(self, index=1):
#
# get the Image Contents Property Set
@ -84,7 +86,7 @@ class FpxImageFile(ImageFile.ImageFile):
i = 1
while size > 64:
size = size / 2
i = i + 1
i += 1
self.maxid = i - 1
# mode. instead of using a single field for this, flashpix
@ -95,7 +97,7 @@ class FpxImageFile(ImageFile.ImageFile):
id = self.maxid << 16
s = prop[0x2000002|id]
s = prop[0x2000002 | id]
colors = []
for i in range(i32(s, 4)):
@ -107,7 +109,7 @@ class FpxImageFile(ImageFile.ImageFile):
# load JPEG tables, if any
self.jpeg = {}
for i in range(256):
id = 0x3000001|(i << 16)
id = 0x3000001 | (i << 16)
if id in prop:
self.jpeg[i] = prop[id]
@ -115,7 +117,7 @@ class FpxImageFile(ImageFile.ImageFile):
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
@ -159,14 +161,14 @@ class FpxImageFile(ImageFile.ImageFile):
compression = i32(s, i+8)
if compression == 0:
self.tile.append(("raw", (x,y,x+xtile,y+ytile),
i32(s, i) + 28, (self.rawmode)))
self.tile.append(("raw", (x, y, x+xtile, y+ytile),
i32(s, i) + 28, (self.rawmode)))
elif compression == 1:
# FIXME: the fill decoder is not implemented
self.tile.append(("fill", (x,y,x+xtile,y+ytile),
i32(s, i) + 28, (self.rawmode, s[12:16])))
self.tile.append(("fill", (x, y, x+xtile, y+ytile),
i32(s, i) + 28, (self.rawmode, s[12:16])))
elif compression == 2:
@ -182,14 +184,14 @@ class FpxImageFile(ImageFile.ImageFile):
# this problem :
jpegmode, rawmode = "YCbCrK", "CMYK"
else:
jpegmode = None # let the decoder decide
jpegmode = None # let the decoder decide
else:
# The image is stored as defined by rawmode
jpegmode = rawmode
self.tile.append(("jpeg", (x,y,x+xtile,y+ytile),
i32(s, i) + 28, (rawmode, jpegmode)))
self.tile.append(("jpeg", (x, y, x+xtile, y+ytile),
i32(s, i) + 28, (rawmode, jpegmode)))
# FIXME: jpeg tables are tile dependent; the prefix
# data must be placed in the tile descriptor itself!
@ -204,7 +206,7 @@ class FpxImageFile(ImageFile.ImageFile):
if x >= xsize:
x, y = 0, y + ytile
if y >= ysize:
break # isn't really required
break # isn't really required
self.stream = stream
self.fp = None
@ -212,7 +214,8 @@ class FpxImageFile(ImageFile.ImageFile):
def load(self):
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)

View File

@ -17,9 +17,11 @@ from PIL import Image, ImageFile, _binary
i32 = _binary.i32be
def _accept(prefix):
return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
##
# Image plugin for the GIMP brush format.

View File

@ -36,6 +36,7 @@ except ImportError:
i16 = _binary.i16be
##
# Image plugin for the GD uncompressed format. Note that this format
# is not supported by the standard <b>Image.open</b> function. To use
@ -52,7 +53,7 @@ class GdImageFile(ImageFile.ImageFile):
# Header
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])
# transparency index
@ -62,7 +63,8 @@ class GdImageFile(ImageFile.ImageFile):
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.
@ -73,7 +75,7 @@ class GdImageFile(ImageFile.ImageFile):
# @return An image instance.
# @exception IOError If the image could not be read.
def open(fp, mode = "r"):
def open(fp, mode="r"):
if mode != "r":
raise ValueError("bad mode")

View File

@ -46,6 +46,7 @@ o16 = _binary.o16le
def _accept(prefix):
return prefix[:6] in [b"GIF87a", b"GIF89a"]
##
# Image plugin for GIF images. This plugin supports both GIF87 and
# GIF89 images.
@ -79,16 +80,16 @@ class GifImageFile(ImageFile.ImageFile):
# get global palette
self.info["background"] = i8(s[11])
# 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):
if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])):
p = ImagePalette.raw("RGB", p)
self.global_palette = self.palette = p
break
self.__fp = self.fp # FIXME: hack
self.__fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell()
self.seek(0) # get ready to read first frame
self.seek(0) # get ready to read first frame
def seek(self, frame):
@ -96,8 +97,15 @@ class GifImageFile(ImageFile.ImageFile):
# rewind
self.__offset = 0
self.dispose = None
self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1
self.__frame = -1
self.__fp.seek(self.__rewind)
self._prev_im = None
self.disposal_method = 0
else:
# ensure that the previous frame was loaded
if not self.im:
self.load()
if frame != self.__frame + 1:
raise ValueError("cannot seek to frame %d" % frame)
@ -114,8 +122,7 @@ class GifImageFile(ImageFile.ImageFile):
self.__offset = 0
if self.dispose:
self.im = self.dispose
self.dispose = None
self.im.paste(self.dispose, self.dispose_extent)
from copy import copy
self.palette = copy(self.global_palette)
@ -140,17 +147,16 @@ class GifImageFile(ImageFile.ImageFile):
if flags & 1:
self.info["transparency"] = i8(block[3])
self.info["duration"] = i16(block[1:3]) * 10
try:
# disposal methods
if flags & 8:
# replace with background colour
self.dispose = Image.core.fill("P", self.size,
self.info["background"])
elif flags & 16:
# replace with previous contents
self.dispose = self.im.copy()
except (AttributeError, KeyError):
pass
# disposal method - find the value of bits 4 - 6
dispose_bits = 0b00011100 & flags
dispose_bits = dispose_bits >> 2
if dispose_bits:
# only set the dispose if it is not
# unspecified. I'm not sure if this is
# correct, but it seems to prevent the last
# frame from looking odd for some animations
self.disposal_method = dispose_bits
elif i8(s) == 255:
#
# application extension
@ -172,6 +178,7 @@ class GifImageFile(ImageFile.ImageFile):
# extent
x0, y0 = i16(s[0:]), i16(s[2:])
x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:])
self.dispose_extent = x0, y0, x1, y1
flags = i8(s[8])
interlace = (flags & 64) != 0
@ -179,7 +186,7 @@ class GifImageFile(ImageFile.ImageFile):
if flags & 128:
bits = (flags & 7) + 1
self.palette =\
ImagePalette.raw("RGB", self.fp.read(3<<bits))
ImagePalette.raw("RGB", self.fp.read(3 << bits))
# image data
bits = i8(self.fp.read(1))
@ -194,6 +201,25 @@ class GifImageFile(ImageFile.ImageFile):
pass
# raise IOError, "illegal GIF tag `%x`" % i8(s)
try:
if self.disposal_method < 2:
# do not dispose or none specified
self.dispose = None
elif self.disposal_method == 2:
# replace with background colour
self.dispose = Image.core.fill("P", self.size,
self.info["background"])
else:
# replace with previous contents
if self.im:
self.dispose = self.im.copy()
# only dispose the extent in this frame
if self.dispose:
self.dispose = self.dispose.crop(self.dispose_extent)
except (AttributeError, KeyError):
pass
if not self.tile:
# self.__fp = None
raise EOFError("no more images in GIF file")
@ -205,6 +231,19 @@ class GifImageFile(ImageFile.ImageFile):
def tell(self):
return self.__frame
def load_end(self):
ImageFile.ImageFile.load_end(self)
# if the disposal method is 'do not dispose', transparent
# pixels should show the content of the previous frame
if self._prev_im and self.disposal_method == 1:
# we do this by pasting the updated area onto the previous
# frame which we then use as the current image content
updated = self.im.crop(self.dispose_extent)
self._prev_im.paste(updated, self.dispose_extent,
updated.convert('RGBA'))
self.im = self._prev_im
self._prev_im = self.im.copy()
# --------------------------------------------------------------------
# Write GIF files
@ -220,6 +259,7 @@ RAWMODE = {
"P": "P",
}
def _save(im, fp, filename):
if _imaging_gif:
@ -228,29 +268,27 @@ def _save(im, fp, filename):
_imaging_gif.save(im, fp, filename)
return
except IOError:
pass # write uncompressed file
pass # write uncompressed file
try:
rawmode = RAWMODE[im.mode]
if im.mode in RAWMODE:
imOut = im
except KeyError:
else:
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
# should automatically convert images on save...)
if Image.getmodebase(im.mode) == "RGB":
imOut = im.convert("P")
rawmode = "P"
palette_size = 256
if im.palette:
palette_size = len(im.palette.getdata()[1]) // 3
imOut = im.convert("P", palette=1, colors=palette_size)
else:
imOut = im.convert("L")
rawmode = "L"
# header
try:
palette = im.encoderinfo["palette"]
except KeyError:
palette = None
if im.palette:
# use existing if possible
palette = im.palette.getdata()[1]
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
for s in header:
@ -307,15 +345,17 @@ def _save(im, fp, filename):
o8(8)) # bits
imOut.encoderconfig = (8, interlace)
ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, rawmode)])
ImageFile._save(imOut, fp, [("gif", (0, 0)+im.size, 0,
RAWMODE[imOut.mode])])
fp.write(b"\0") # end of image data
fp.write(b"\0") # end of image data
fp.write(b";") # end of file
fp.write(b";") # end of file
try:
fp.flush()
except: pass
except:
pass
def _save_netpbm(im, fp, filename):
@ -326,13 +366,42 @@ def _save_netpbm(im, fp, filename):
# below for information on how to enable this.
import os
from subprocess import Popen, check_call, PIPE, CalledProcessError
import tempfile
file = im._dump()
if im.mode != "RGB":
os.system("ppmtogif %s >%s" % (file, filename))
with open(filename, 'wb') as f:
stderr = tempfile.TemporaryFile()
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
else:
os.system("ppmquant 256 %s | ppmtogif >%s" % (file, filename))
try: os.unlink(file)
except: pass
with open(filename, 'wb') as f:
# Pipe ppmquant output into ppmtogif
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
quant_cmd = ["ppmquant", "256", file]
togif_cmd = ["ppmtogif"]
stderr = tempfile.TemporaryFile()
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
stderr = tempfile.TemporaryFile()
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
stderr=stderr)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
quant_proc.stdout.close()
retcode = quant_proc.wait()
if retcode:
raise CalledProcessError(retcode, quant_cmd)
retcode = togif_proc.wait()
if retcode:
raise CalledProcessError(retcode, togif_cmd)
try:
os.unlink(file)
except:
pass
# --------------------------------------------------------------------
@ -356,7 +425,7 @@ def getheader(im, palette=None, info=None):
sourcePalette = palette[:768]
else:
sourcePalette = im.im.getpalette("RGB")[:768]
else: # L-mode
else: # L-mode
if palette and isinstance(palette, bytes):
sourcePalette = palette[:768]
else:
@ -391,6 +460,11 @@ def getheader(im, palette=None, info=None):
for i in range(len(imageBytes)):
imageBytes[i] = newPositions[imageBytes[i]]
im.frombytes(bytes(imageBytes))
newPaletteBytes = (paletteBytes +
(768 - len(paletteBytes)) * b'\x00')
im.putpalette(newPaletteBytes)
im.palette = ImagePalette.ImagePalette("RGB", palette=paletteBytes,
size=len(paletteBytes))
if not paletteBytes:
paletteBytes = sourcePalette
@ -399,7 +473,8 @@ def getheader(im, palette=None, info=None):
# calculate the palette size for the header
import math
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1
if colorTableSize < 0: colorTableSize = 0
if colorTableSize < 0:
colorTableSize = 0
# size of global color table + global color table flag
header.append(o8(colorTableSize + 128))
# background + reserved/aspect
@ -408,7 +483,7 @@ def getheader(im, palette=None, info=None):
# add the missing amount of bytes
# the palette has to be 2<<n in size
actualTargetSizeDiff = (2<<colorTableSize) - len(paletteBytes)//3
actualTargetSizeDiff = (2 << colorTableSize) - len(paletteBytes)//3
if actualTargetSizeDiff > 0:
paletteBytes += o8(0) * 3 * actualTargetSizeDiff
@ -417,17 +492,18 @@ def getheader(im, palette=None, info=None):
return header, usedPaletteColors
def getdata(im, offset = (0, 0), **params):
def getdata(im, offset=(0, 0), **params):
"""Return a list of strings representing this image.
The first string is a local image header, the rest contains
encoded image data."""
class collector:
data = []
def write(self, data):
self.data.append(data)
im.load() # make sure raster data is available
im.load() # make sure raster data is available
fp = collector()
@ -443,9 +519,9 @@ def getdata(im, offset = (0, 0), **params):
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:
del im.encoderinfo

View File

@ -24,6 +24,7 @@ from PIL._binary import o8
EPSILON = 1e-10
def linear(middle, pos):
if pos <= middle:
if middle < EPSILON:
@ -38,25 +39,30 @@ def linear(middle, pos):
else:
return 0.5 + 0.5 * pos / middle
def curved(middle, pos):
return pos ** (log(0.5) / log(max(middle, EPSILON)))
def sine(middle, pos):
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
def sphere_increasing(middle, pos):
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
def sphere_decreasing(middle, pos):
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:
gradient = None
def getpalette(self, entries = 256):
def getpalette(self, entries=256):
palette = []
@ -68,7 +74,7 @@ class GradientFile:
x = i / float(entries-1)
while x1 < x:
ix = ix + 1
ix += 1
x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
w = x1 - x0
@ -89,6 +95,7 @@ class GradientFile:
return b"".join(palette), "RGBA"
##
# File handler for GIMP's gradient format.
@ -99,7 +106,13 @@ class GimpGradientFile(GradientFile):
if fp.readline()[:13] != b"GIMP Gradient":
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 = []
@ -108,13 +121,13 @@ class GimpGradientFile(GradientFile):
s = fp.readline().split()
w = [float(x) for x in s[:11]]
x0, x1 = w[0], w[2]
xm = w[1]
rgb0 = w[3:7]
rgb1 = w[7:11]
x0, x1 = w[0], w[2]
xm = w[1]
rgb0 = w[3:7]
rgb1 = w[7:11]
segment = SEGMENTS[int(s[11])]
cspace = int(s[12])
cspace = int(s[12])
if cspace != 0:
raise IOError("cannot handle HSV colour space")

View File

@ -17,6 +17,7 @@
import re
from PIL._binary import o8
##
# File handler for GIMP's palette format.
@ -52,11 +53,10 @@ class GimpPaletteFile:
if 0 <= i <= 255:
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
i = i + 1
i += 1
self.palette = b"".join(self.palette)
def getpalette(self):
return self.palette, self.rawmode

View File

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

View File

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

View File

@ -10,20 +10,28 @@
# Copyright (c) 2004 by Bob Ippolito.
# Copyright (c) 2004 by Secret Labs.
# Copyright (c) 2004 by Fredrik Lundh.
# Copyright (c) 2014 by Alastair Houghton.
#
# See the README file for information on usage and redistribution.
#
from PIL import Image, ImageFile, _binary
from PIL import Image, ImageFile, PngImagePlugin, _binary
import io
import struct
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
if enable_jpeg2k:
from PIL import Jpeg2KImagePlugin
i8 = _binary.i8
HEADERSIZE = 8
def nextheader(fobj):
return struct.unpack('>4sI', fobj.read(HEADERSIZE))
def read_32t(fobj, start_length, size):
# The 128x128 icon seems to have an extra header for some reason.
(start, length) = start_length
@ -33,6 +41,7 @@ def read_32t(fobj, start_length, size):
raise SyntaxError('Unknown signature, expecting 0x00000000')
return read_32(fobj, (start + 4, length - 4), size)
def read_32(fobj, start_length, size):
"""
Read a 32bit RGB icon resource. Seems to be either uncompressed or
@ -40,14 +49,15 @@ def read_32(fobj, start_length, size):
"""
(start, length) = start_length
fobj.seek(start)
sizesq = size[0] * size[1]
pixel_size = (size[0] * size[2], size[1] * size[2])
sizesq = pixel_size[0] * pixel_size[1]
if length == sizesq * 3:
# uncompressed ("RGBRGBGB")
indata = fobj.read(length)
im = Image.frombuffer("RGB", size, indata, "raw", "RGB", 0, 1)
im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1)
else:
# decode image
im = Image.new("RGB", size, None)
im = Image.new("RGB", pixel_size, None)
for band_ix in range(3):
data = []
bytesleft = sizesq
@ -64,7 +74,7 @@ def read_32(fobj, start_length, size):
else:
blocksize = byte + 1
data.append(fobj.read(blocksize))
bytesleft = bytesleft - blocksize
bytesleft -= blocksize
if bytesleft <= 0:
break
if bytesleft != 0:
@ -72,36 +82,93 @@ def read_32(fobj, start_length, size):
"Error reading channel [%r left]" % bytesleft
)
band = Image.frombuffer(
"L", size, b"".join(data), "raw", "L", 0, 1
"L", pixel_size, b"".join(data), "raw", "L", 0, 1
)
im.im.putband(band.im, band_ix)
return {"RGB": im}
def read_mk(fobj, start_length, size):
# Alpha masks seem to be uncompressed
(start, length) = start_length
fobj.seek(start)
pixel_size = (size[0] * size[2], size[1] * size[2])
sizesq = pixel_size[0] * pixel_size[1]
band = Image.frombuffer(
"L", size, fobj.read(size[0]*size[1]), "raw", "L", 0, 1
"L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1
)
return {"A": band}
def read_png_or_jpeg2000(fobj, start_length, size):
(start, length) = start_length
fobj.seek(start)
sig = fobj.read(12)
if sig[:8] == b'\x89PNG\x0d\x0a\x1a\x0a':
fobj.seek(start)
im = PngImagePlugin.PngImageFile(fobj)
return {"RGBA": im}
elif sig[:4] == b'\xff\x4f\xff\x51' \
or sig[:4] == b'\x0d\x0a\x87\x0a' \
or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
if not enable_jpeg2k:
raise ValueError('Unsupported icon subimage format (rebuild PIL '
'with JPEG 2000 support to fix this)')
# j2k, jpc or j2c
fobj.seek(start)
jp2kstream = fobj.read(length)
f = io.BytesIO(jp2kstream)
im = Jpeg2KImagePlugin.Jpeg2KImageFile(f)
if im.mode != 'RGBA':
im = im.convert('RGBA')
return {"RGBA": im}
else:
raise ValueError('Unsupported icon subimage format')
class IcnsFile:
SIZES = {
(128, 128): [
(512, 512, 2): [
(b'ic10', read_png_or_jpeg2000),
],
(512, 512, 1): [
(b'ic09', read_png_or_jpeg2000),
],
(256, 256, 2): [
(b'ic14', read_png_or_jpeg2000),
],
(256, 256, 1): [
(b'ic08', read_png_or_jpeg2000),
],
(128, 128, 2): [
(b'ic13', read_png_or_jpeg2000),
],
(128, 128, 1): [
(b'ic07', read_png_or_jpeg2000),
(b'it32', read_32t),
(b't8mk', read_mk),
],
(48, 48): [
(64, 64, 1): [
(b'icp6', read_png_or_jpeg2000),
],
(32, 32, 2): [
(b'ic12', read_png_or_jpeg2000),
],
(48, 48, 1): [
(b'ih32', read_32),
(b'h8mk', read_mk),
],
(32, 32): [
(32, 32, 1): [
(b'icp5', read_png_or_jpeg2000),
(b'il32', read_32),
(b'l8mk', read_mk),
],
(16, 16): [
(16, 16, 2): [
(b'ic11', read_png_or_jpeg2000),
],
(16, 16, 1): [
(b'icp4', read_png_or_jpeg2000),
(b'is32', read_32),
(b's8mk', read_mk),
],
@ -115,16 +182,18 @@ class IcnsFile:
self.dct = dct = {}
self.fobj = fobj
sig, filesize = nextheader(fobj)
if sig != 'icns':
if sig != b'icns':
raise SyntaxError('not an icns file')
i = HEADERSIZE
while i < filesize:
sig, blocksize = nextheader(fobj)
i = i + HEADERSIZE
blocksize = blocksize - HEADERSIZE
if blocksize <= 0:
raise SyntaxError('invalid block header')
i += HEADERSIZE
blocksize -= HEADERSIZE
dct[sig] = (i, blocksize)
fobj.seek(blocksize, 1)
i = i + blocksize
i += blocksize
def itersizes(self):
sizes = []
@ -157,7 +226,14 @@ class IcnsFile:
def getimage(self, size=None):
if size is None:
size = self.bestsize()
if len(size) == 2:
size = (size[0], size[1], 1)
channels = self.dataforsize(size)
im = channels.get('RGBA', None)
if im:
return im
im = channels.get("RGB").copy()
try:
im.putalpha(channels["A"])
@ -165,6 +241,7 @@ class IcnsFile:
pass
return im
##
# Image plugin for Mac OS icons.
@ -185,18 +262,29 @@ class IcnsImageFile(ImageFile.ImageFile):
def _open(self):
self.icns = IcnsFile(self.fp)
self.mode = 'RGBA'
self.size = self.icns.bestsize()
self.best_size = self.icns.bestsize()
self.size = (self.best_size[0] * self.best_size[2],
self.best_size[1] * self.best_size[2])
self.info['sizes'] = self.icns.itersizes()
# Just use this to see if it's loaded or not yet.
self.tile = ('',)
def load(self):
if len(self.size) == 3:
self.best_size = self.size
self.size = (self.best_size[0] * self.best_size[2],
self.best_size[1] * self.best_size[2])
Image.Image.load(self)
if not self.tile:
return
self.load_prepare()
# This is likely NOT the best way to do it, but whatever.
im = self.icns.getimage(self.size)
im = self.icns.getimage(self.best_size)
# If this is a PNG or JPEG 2000, it won't be loaded yet
im.load()
self.im = im.im
self.mode = im.mode
self.size = im.size
@ -205,12 +293,19 @@ class IcnsImageFile(ImageFile.ImageFile):
self.tile = ()
self.load_end()
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
Image.register_extension("ICNS", '.icns')
if __name__ == '__main__':
import os, sys
import os
import sys
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
for size in imf.info['sizes']:
imf.size = size
imf.load()
im = imf.im
im.save('out-%s-%s-%s.png' % size)
im = Image.open(open(sys.argv[1], "rb"))
im.save("out.png")
os.startfile("out.png")
if sys.platform == 'windows':
os.startfile("out.png")

View File

@ -13,7 +13,8 @@
# 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
#
# Icon format references:
@ -35,6 +36,7 @@ i32 = _binary.i32le
_MAGIC = b"\0\0\1\0"
def _accept(prefix):
return prefix[:4] == _MAGIC
@ -63,7 +65,7 @@ class IcoFile:
icon_header = {
'width': i8(s[0]),
'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]),
'planes': i16(s[4:]),
'bpp': i16(s[6:]),
@ -78,10 +80,14 @@ class IcoFile:
# See Wikipedia notes about color depth.
# 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['square'] = icon_header['width'] * icon_header['height']
icon_header['square'] = (icon_header['width'] *
icon_header['height'])
self.entry.append(icon_header)
@ -102,7 +108,7 @@ class IcoFile:
Get an image from the icon
"""
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(0)
@ -127,7 +133,7 @@ class IcoFile:
# change tile dimension to only encompass XOR image
im.size = (im.size[0], int(im.size[1] / 2))
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
mode = a[0]
@ -139,8 +145,9 @@ class IcoFile:
if 32 == bpp:
# 32-bit color depth icon image allows semitransparent areas
# PIL's DIB format ignores transparency bits, recover them
# The DIB is packed in BGRX byte order where X is the alpha channel
# PIL's DIB format ignores transparency bits, recover them.
# The DIB is packed in BGRX byte order where X is the alpha
# channel.
# Back up to start of bmp data
self.buf.seek(o)
@ -162,9 +169,11 @@ class IcoFile:
# bitmap row data is aligned to word boundaries
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)
self.buf.seek(and_mask_offset)
@ -187,6 +196,7 @@ class IcoFile:
return im
##
# Image plugin for Windows Icon files.
@ -194,15 +204,16 @@ class IcoImageFile(ImageFile.ImageFile):
"""
PIL read-only image support for Microsoft Windows .ico files.
By default the largest resolution image in the file will be loaded. This can
be changed by altering the 'size' attribute before calling 'load'.
By default the largest resolution image in the file will be loaded. This
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
in the icon file.
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
"""
format = "ICO"
@ -222,6 +233,10 @@ class IcoImageFile(ImageFile.ImageFile):
self.mode = im.mode
self.size = im.size
def load_seek(self):
# Flage the ImageFile.Parser so that it
# just does all the decode at the end.
pass
#
# --------------------------------------------------------------------

View File

@ -30,7 +30,7 @@ __version__ = "0.7"
import re
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)"
MODE = "Image type"
TAGS = { COMMENT:0, DATE:0, EQUIPMENT:0, FRAMES:0, LUT:0, NAME:0,
SCALE:0, SIZE:0, MODE:0 }
TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0,
SCALE: 0, SIZE: 0, MODE: 0}
OPEN = {
# ifunc93/p3cfunc formats
@ -94,12 +94,14 @@ for i in range(2, 33):
split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
def number(s):
try:
return int(s)
except ValueError:
return float(s)
##
# 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
# 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")
self.fp.seek(0)
@ -155,10 +157,10 @@ class ImImageFile(ImageFile.ImageFile):
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
# (I guess)
# Don't know if this is the correct encoding,
# but a decent guess (I guess)
k = k.decode('latin-1', 'replace')
v = v.decode('latin-1', 'replace')
@ -182,11 +184,12 @@ class ImImageFile(ImageFile.ImageFile):
self.info[k] = v
if k in TAGS:
n = n + 1
n += 1
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:
raise SyntaxError("Not an IM file")
@ -204,8 +207,8 @@ class ImImageFile(ImageFile.ImageFile):
if LUT in self.info:
# convert lookup table to palette or lut attribute
palette = self.fp.read(768)
greyscale = 1 # greyscale palette
linear = 1 # linear greyscale palette
greyscale = 1 # greyscale palette
linear = 1 # linear greyscale palette
for i in range(256):
if palette[i] == palette[i+256] == palette[i+512]:
if i8(palette[i]) != i:
@ -230,7 +233,7 @@ class ImImageFile(ImageFile.ImageFile):
self.__offset = offs = self.fp.tell()
self.__fp = self.fp # FIXME: hack
self.__fp = self.fp # FIXME: hack
if self.rawmode[:2] == "F;":
@ -239,7 +242,7 @@ class ImImageFile(ImageFile.ImageFile):
# use bit decoder (if necessary)
bits = int(self.rawmode[2:])
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))]
return
except ValueError:
@ -249,12 +252,13 @@ class ImImageFile(ImageFile.ImageFile):
# Old LabEye/3PC files. Would be very surprised if anyone
# ever stumbled upon such a file ;-)
size = self.size[0] * self.size[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+2*size, ("B", 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+2*size, ("B", 0, -1))]
else:
# 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))]
def seek(self, frame):
@ -276,7 +280,7 @@ class ImImageFile(ImageFile.ImageFile):
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):
@ -305,6 +309,7 @@ SAVE = {
"YCbCr": ("YCC", "YCbCr;L")
}
def _save(im, fp, filename, check=0):
try:
@ -329,8 +334,8 @@ def _save(im, fp, filename, check=0):
fp.write(b"Lut: 1\r\n")
fp.write(b"\000" * (511-fp.tell()) + b"\032")
if im.mode == "P":
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, -1))])
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))])
#
# --------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -21,14 +21,6 @@ from PIL import Image
import re
##
# Convert color string to RGB tuple.
#
# @param color A CSS3-style colour string.
# @return An RGB-tuple.
# @exception ValueError If the color string could not be interpreted
# as an RGB value.
def getrgb(color):
"""
Convert a color string to an RGB tuple. If the string cannot be parsed,
@ -37,7 +29,7 @@ def getrgb(color):
.. versionadded:: 1.1.4
:param color: A color string
:return: ``(red, green, blue)``
:return: ``(red, green, blue[, alpha])``
"""
try:
rgb = colormap[color]
@ -95,7 +87,8 @@ def getrgb(color):
int(rgb[1] * 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:
return (
int(m.group(1)),
@ -105,6 +98,7 @@ def getrgb(color):
)
raise ValueError("unknown color specifier: %r" % color)
def getcolor(color, mode):
"""
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
@ -114,20 +108,21 @@ def getcolor(color, mode):
.. versionadded:: 1.1.4
:param color: A color string
:return: ``(red, green, blue)``
:return: ``(graylevel [, alpha]) or (red, green, blue[, alpha])``
"""
# same as getrgb, but converts the result to the given mode
color = getrgb(color)
if mode == "RGB":
return color
if mode == "RGBA":
if len(color) == 3:
color = (color + (255,))
r, g, b, a = color
return r, g, b, a
color, alpha = getrgb(color), 255
if len(color) == 4:
color, alpha = color[0:3], color[3]
if Image.getmodebase(mode) == "L":
r, g, b = color
return (r*299 + g*587 + b*114)//1000
color = (r*299 + g*587 + b*114)//1000
if mode[-1] == 'A':
return (color, alpha)
else:
if mode[-1] == 'A':
return color + (alpha,)
return color
colormap = {

View File

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

View File

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

View File

@ -6,7 +6,7 @@
#
# For a background, see "Image Processing By Interpolation and
# Extrapolation", Paul Haeberli and Douglas Voorhies. Available
# at http://www.sgi.com/grafica/interp/index.html
# at http://www.graficaobscura.com/interp/index.html
#
# History:
# 1996-03-23 fl Created
@ -47,8 +47,11 @@ class Color(_Enhance):
"""
def __init__(self, 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):
"""Adjust image contrast.
@ -62,6 +65,9 @@ class Contrast(_Enhance):
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
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):
"""Adjust image brightness.
@ -74,6 +80,9 @@ class Brightness(_Enhance):
self.image = image
self.degenerate = Image.new(image.mode, image.size, 0)
if 'A' in image.getbands():
self.degenerate.putalpha(image.split()[-1])
class Sharpness(_Enhance):
"""Adjust image sharpness.
@ -85,3 +94,6 @@ class Sharpness(_Enhance):
def __init__(self, image):
self.image = image
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._util import isPath
import traceback, os, sys
import io
import os
import sys
import traceback
MAXBLOCK = 65536
@ -46,6 +48,7 @@ ERRORS = {
-9: "out of memory error"
}
def raise_ioerror(error):
try:
message = Image.core.getcodecstatus(error)
@ -55,6 +58,7 @@ def raise_ioerror(error):
message = "decoder error %d" % error
raise IOError(message + " when reading image file")
#
# --------------------------------------------------------------------
# Helpers
@ -63,6 +67,7 @@ def _tilesort(t):
# sort on offset
return t[2]
#
# --------------------------------------------------------------------
# ImageFile base class
@ -74,7 +79,7 @@ class ImageFile(Image.Image):
Image.Image.__init__(self)
self.tile = None
self.readonly = 1 # until we know better
self.readonly = 1 # until we know better
self.decoderconfig = ()
self.decodermaxblock = MAXBLOCK
@ -90,19 +95,19 @@ class ImageFile(Image.Image):
try:
self._open()
except IndexError as v: # end of data
except IndexError as v: # end of data
if Image.DEBUG > 1:
traceback.print_exc()
raise SyntaxError(v)
except TypeError as v: # end of data (ord)
except TypeError as v: # end of data (ord)
if Image.DEBUG > 1:
traceback.print_exc()
raise SyntaxError(v)
except KeyError as v: # unsupported mode
except KeyError as v: # unsupported mode
if Image.DEBUG > 1:
traceback.print_exc()
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:
traceback.print_exc()
raise SyntaxError(v)
@ -133,11 +138,27 @@ class ImageFile(Image.Image):
return pixel
self.map = None
use_mmap = self.filename and len(self.tile) == 1
# As of pypy 2.1.0, memory mapping was failing here.
use_mmap = use_mmap and not hasattr(sys, 'pypy_version_info')
readonly = 0
if self.filename and len(self.tile) == 1 and not hasattr(sys, 'pypy_version_info'):
# As of pypy 2.1.0, memory mapping was failing here.
# look for read/seek overrides
try:
read = self.load_read
# don't use mmap if there are custom read/seek functions
use_mmap = False
except AttributeError:
read = self.fp.read
try:
seek = self.load_seek
use_mmap = False
except AttributeError:
seek = self.fp.seek
if use_mmap:
# try memory mapping
d, e, o, a = self.tile[0]
if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
@ -165,19 +186,7 @@ class ImageFile(Image.Image):
self.load_prepare()
# look for read/seek overrides
try:
read = self.load_read
except AttributeError:
read = self.fp.read
try:
seek = self.load_seek
except AttributeError:
seek = self.fp.seek
if not self.map:
# sort tiles in file order
self.tile.sort(key=_tilesort)
@ -199,23 +208,25 @@ class ImageFile(Image.Image):
while True:
try:
s = read(self.decodermaxblock)
except IndexError as ie: # truncated png/gif
except IndexError as ie: # truncated png/gif
if LOAD_TRUNCATED_IMAGES:
break
else:
raise IndexError(ie)
if not s: # truncated jpeg
if not s and not d.handles_eof: # truncated jpeg
self.tile = []
# 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()
if LOAD_TRUNCATED_IMAGES:
break
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
n, e = d.decode(b)
@ -223,13 +234,15 @@ class ImageFile(Image.Image):
break
b = b[n:]
t = t + n
# Need to cleanup here to prevent leaks in PyPy
d.cleanup()
self.tile = []
self.readonly = readonly
self.fp = None # might be shared
self.fp = None # might be shared
if (not LOAD_TRUNCATED_IMAGES or t == 0) and not self.map 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
raise_ioerror(e)
@ -374,10 +387,10 @@ class Parser:
fp = io.BytesIO(self.data)
im = Image.open(fp)
finally:
fp.close() # explicitly close the virtual file
fp.close() # explicitly close the virtual file
except IOError:
# traceback.print_exc()
pass # not enough data
pass # not enough data
else:
flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
if flag or len(im.tile) != 1:
@ -427,9 +440,10 @@ class Parser:
self.image = Image.open(fp)
finally:
self.image.load()
fp.close() # explicitly close the virtual file
fp.close() # explicitly close the virtual file
return self.image
# --------------------------------------------------------------------
def _save(im, fp, tile, bufsize=0):
@ -446,10 +460,10 @@ def _save(im, fp, tile, bufsize=0):
im.encoderconfig = ()
tile.sort(key=_tilesort)
# 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
# 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:
fh = fp.fileno()
fp.flush()
@ -467,6 +481,7 @@ def _save(im, fp, tile, bufsize=0):
break
if s < 0:
raise IOError("encoder error %d when writing image file" % s)
e.cleanup()
else:
# slight speedup: compress to real file object
for e, b, o, a in tile:
@ -477,9 +492,11 @@ def _save(im, fp, tile, bufsize=0):
s = e.encode_to_file(fh, bufsize)
if s < 0:
raise IOError("encoder error %d when writing image file" % s)
e.cleanup()
try:
fp.flush()
except: pass
except:
pass
def _safe_read(fp, size):
@ -502,5 +519,5 @@ def _safe_read(fp, size):
if not block:
break
data.append(block)
size = size - len(block)
size -= len(block)
return b"".join(data)

View File

@ -43,7 +43,7 @@ class Kernel(Filter):
def __init__(self, size, kernel, scale=None, offset=0):
if scale is None:
# default scale is sum of kernel
scale = reduce(lambda a,b: a+b, kernel)
scale = reduce(lambda a, b: a+b, kernel)
if size[0] * size[1] != len(kernel):
raise ValueError("not enough coefficients in kernel")
self.filterargs = size, scale, offset, kernel
@ -162,7 +162,8 @@ class UnsharpMask(Filter):
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
the parameters.
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
.. _digital unsharp masking:
https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
"""
name = "UnsharpMask"

View File

@ -29,13 +29,15 @@ from __future__ import print_function
from PIL import Image
from PIL._util import isDirectory, isPath
import os, sys
import os
import sys
try:
import warnings
except ImportError:
warnings = None
class _imagingft_not_installed:
# module placeholder
def __getattr__(self, id):
@ -90,8 +92,8 @@ class ImageFont:
# read PILfont header
if file.readline() != b"PILfont\n":
raise SyntaxError("Not a PILfont file")
d = file.readline().split(b";")
self.info = [] # FIXME: should be a dictionary
file.readline().split(b";")
self.info = [] # FIXME: should be a dictionary
while True:
s = file.readline()
if not s or s == b"DATA\n":
@ -113,6 +115,7 @@ class ImageFont:
self.getsize = self.font.getsize
self.getmask = self.font.getmask
##
# Wrapper for FreeType fonts. Application code should use the
# <b>truetype</b> factory function to create font objects.
@ -124,14 +127,18 @@ class FreeTypeFont:
# FIXME: use service provider instead
if file:
if warnings:
warnings.warn('file parameter deprecated, please use font parameter instead.', DeprecationWarning)
warnings.warn(
'file parameter deprecated, '
'please use font parameter instead.',
DeprecationWarning)
font = file
if isPath(font):
self.font = core.getfont(font, size, index, encoding)
else:
self.font_bytes = font.read()
self.font = core.getfont("", size, index, encoding, self.font_bytes)
self.font = core.getfont(
"", size, index, encoding, self.font_bytes)
def getname(self):
return self.font.family, self.font.style
@ -140,7 +147,8 @@ class FreeTypeFont:
return self.font.ascent, self.font.descent
def getsize(self, text):
return self.font.getsize(text)[0]
size, offset = self.font.getsize(text)
return (size[0] + offset[0], size[1] + offset[1])
def getoffset(self, text):
return self.font.getsize(text)[1]
@ -151,7 +159,7 @@ class FreeTypeFont:
def getmask2(self, text, mode="", fill=Image.core.fill):
size, offset = self.font.getsize(text)
im = fill("L", size, 0)
self.font.render(text, im.id, mode=="1")
self.font.render(text, im.id, mode == "1")
return im, offset
##
@ -163,12 +171,13 @@ class FreeTypeFont:
# be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM,
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
class TransposedFont:
"Wrapper for writing rotated or mirrored text"
def __init__(self, font, orientation=None):
self.font = font
self.orientation = orientation # any 'transpose' argument, or None
self.orientation = orientation # any 'transpose' argument, or None
def getsize(self, text):
w, h = self.font.getsize(text)
@ -221,7 +230,10 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
if filename:
if warnings:
warnings.warn('filename parameter deprecated, please use font parameter instead.', DeprecationWarning)
warnings.warn(
'filename parameter deprecated, '
'please use font parameter instead.',
DeprecationWarning)
font = filename
try:
@ -272,8 +284,8 @@ def load_default():
import base64
f = ImageFont()
f._load_pilfont_data(
# courB08
BytesIO(base64.decodestring(b'''
# courB08
BytesIO(base64.decodestring(b'''
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
@ -392,15 +404,4 @@ w7IkEbzhVQAAAABJRU5ErkJggg==
'''))))
return f
if __name__ == "__main__":
# create font data chunk for embedding
import base64, os, sys
font = "../Images/courB08"
print(" f._load_pilfont_data(")
print(" # %s" % os.path.basename(font))
print(" BytesIO(base64.decodestring(b'''")
base64.encode(open(font + ".pil", "rb"), sys.stdout)
print("''')), Image.open(BytesIO(base64.decodestring(b'''")
base64.encode(open(font + ".pbm", "rb"), sys.stdout)
print("'''))))")
# End of file

View File

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

View File

@ -17,7 +17,6 @@
from PIL import Image
from PIL import _imagingmath
import sys
try:
import builtins
@ -27,9 +26,11 @@ except ImportError:
VERBOSE = 0
def _isconstant(v):
return isinstance(v, int) or isinstance(v, float)
class _Operand:
# wraps an image operand, providing standard operators
@ -69,20 +70,25 @@ class _Operand:
im2 = self.__fixup(im2)
if im1.mode != im2.mode:
# convert both arguments to floating point
if im1.mode != "F": im1 = im1.convert("F")
if im2.mode != "F": im2 = im2.convert("F")
if im1.mode != "F":
im1 = im1.convert("F")
if im2.mode != "F":
im2 = im2.convert("F")
if im1.mode != im2.mode:
raise ValueError("mode mismatch")
if im1.size != im2.size:
# crop both arguments to a common size
size = (min(im1.size[0], im2.size[0]),
min(im1.size[1], im2.size[1]))
if im1.size != size: im1 = im1.crop((0, 0) + size)
if im2.size != size: im2 = im2.crop((0, 0) + size)
if im1.size != size:
im1 = im1.crop((0, 0) + size)
if im2.size != size:
im2 = im2.crop((0, 0) + size)
out = Image.new(mode or im1.mode, size, None)
else:
out = Image.new(mode or im1.mode, im1.size, None)
im1.load(); im2.load()
im1.load()
im2.load()
try:
op = getattr(_imagingmath, op+"_"+im1.mode)
except AttributeError:
@ -102,34 +108,47 @@ class _Operand:
def __abs__(self):
return self.apply("abs", self)
def __pos__(self):
return self
def __neg__(self):
return self.apply("neg", self)
# binary operators
def __add__(self, other):
return self.apply("add", self, other)
def __radd__(self, other):
return self.apply("add", other, self)
def __sub__(self, other):
return self.apply("sub", self, other)
def __rsub__(self, other):
return self.apply("sub", other, self)
def __mul__(self, other):
return self.apply("mul", self, other)
def __rmul__(self, other):
return self.apply("mul", other, self)
def __truediv__(self, other):
return self.apply("div", self, other)
def __rtruediv__(self, other):
return self.apply("div", other, self)
def __mod__(self, other):
return self.apply("mod", self, other)
def __rmod__(self, other):
return self.apply("mod", other, self)
def __pow__(self, other):
return self.apply("pow", self, other)
def __rpow__(self, other):
return self.apply("pow", other, self)
@ -143,54 +162,77 @@ class _Operand:
# bitwise
def __invert__(self):
return self.apply("invert", self)
def __and__(self, other):
return self.apply("and", self, other)
def __rand__(self, other):
return self.apply("and", other, self)
def __or__(self, other):
return self.apply("or", self, other)
def __ror__(self, other):
return self.apply("or", other, self)
def __xor__(self, other):
return self.apply("xor", self, other)
def __rxor__(self, other):
return self.apply("xor", other, self)
def __lshift__(self, other):
return self.apply("lshift", self, other)
def __rshift__(self, other):
return self.apply("rshift", self, other)
# logical
def __eq__(self, other):
return self.apply("eq", self, other)
def __ne__(self, other):
return self.apply("ne", self, other)
def __lt__(self, other):
return self.apply("lt", self, other)
def __le__(self, other):
return self.apply("le", self, other)
def __gt__(self, other):
return self.apply("gt", self, other)
def __ge__(self, other):
return self.apply("ge", self, other)
# conversions
def imagemath_int(self):
return _Operand(self.im.convert("I"))
def imagemath_float(self):
return _Operand(self.im.convert("F"))
# logical
def imagemath_equal(self, other):
return self.apply("eq", self, other, mode="I")
def imagemath_notequal(self, other):
return self.apply("ne", self, other, mode="I")
def imagemath_min(self, other):
return self.apply("min", self, other)
def imagemath_max(self, other):
return self.apply("max", self, other)
def imagemath_convert(self, mode):
return _Operand(self.im.convert(mode))

View File

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

245
PIL/ImageMorph.py Normal file
View File

@ -0,0 +1,245 @@
# A binary morphology add-on for the Python Imaging Library
#
# History:
# 2014-06-04 Initial version.
#
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
from PIL import Image
from PIL import _imagingmorph
import re
LUT_SIZE = 1 << 9
class LutBuilder:
"""A class for building a MorphLut from a descriptive language
The input patterns is a list of a strings sequences like these::
4:(...
.1.
111)->1
(whitespaces including linebreaks are ignored). The option 4
describes a series of symmetry operations (in this case a
4-rotation), the pattern is described by:
- . or X - Ignore
- 1 - Pixel is on
- 0 - Pixel is off
The result of the operation is described after "->" string.
The default is to return the current pixel value, which is
returned if no other match is found.
Operations:
- 4 - 4 way rotation
- N - Negate
- 1 - Dummy op for no other operation (an op must always be given)
- M - Mirroring
Example::
lb = LutBuilder(patterns = ["4:(... .1. 111)->1"])
lut = lb.build_lut()
"""
def __init__(self, patterns=None, op_name=None):
if patterns is not None:
self.patterns = patterns
else:
self.patterns = []
self.lut = None
if op_name is not None:
known_patterns = {
'corner': ['1:(... ... ...)->0',
'4:(00. 01. ...)->1'],
'dilation4': ['4:(... .0. .1.)->1'],
'dilation8': ['4:(... .0. .1.)->1',
'4:(... .0. ..1)->1'],
'erosion4': ['4:(... .1. .0.)->0'],
'erosion8': ['4:(... .1. .0.)->0',
'4:(... .1. ..0)->0'],
'edge': ['1:(... ... ...)->0',
'4:(.0. .1. ...)->1',
'4:(01. .1. ...)->1']
}
if op_name not in known_patterns:
raise Exception('Unknown pattern '+op_name+'!')
self.patterns = known_patterns[op_name]
def add_patterns(self, patterns):
self.patterns += patterns
def build_default_lut(self):
symbols = [0, 1]
m = 1 << 4 # pos of current pixel
self.lut = bytearray([symbols[(i & m) > 0] for i in range(LUT_SIZE)])
def get_lut(self):
return self.lut
def _string_permute(self, pattern, permutation):
"""string_permute takes a pattern and a permutation and returns the
string permuted according to the permutation list.
"""
assert(len(permutation) == 9)
return ''.join([pattern[p] for p in permutation])
def _pattern_permute(self, basic_pattern, options, basic_result):
"""pattern_permute takes a basic pattern and its result and clones
the pattern according to the modifications described in the $options
parameter. It returns a list of all cloned patterns."""
patterns = [(basic_pattern, basic_result)]
# rotations
if '4' in options:
res = patterns[-1][1]
for i in range(4):
patterns.append(
(self._string_permute(patterns[-1][0], [6, 3, 0,
7, 4, 1,
8, 5, 2]), res))
# mirror
if 'M' in options:
n = len(patterns)
for pattern, res in patterns[0:n]:
patterns.append(
(self._string_permute(pattern, [2, 1, 0,
5, 4, 3,
8, 7, 6]), res))
# negate
if 'N' in options:
n = len(patterns)
for pattern, res in patterns[0:n]:
# Swap 0 and 1
pattern = (pattern
.replace('0', 'Z')
.replace('1', '0')
.replace('Z', '1'))
res = '%d' % (1-int(res))
patterns.append((pattern, res))
return patterns
def build_lut(self):
"""Compile all patterns into a morphology lut.
TBD :Build based on (file) morphlut:modify_lut
"""
self.build_default_lut()
patterns = []
# Parse and create symmetries of the patterns strings
for p in self.patterns:
m = re.search(
r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n', ''))
if not m:
raise Exception('Syntax error in pattern "'+p+'"')
options = m.group(1)
pattern = m.group(2)
result = int(m.group(3))
# Get rid of spaces
pattern = pattern.replace(' ', '').replace('\n', '')
patterns += self._pattern_permute(pattern, options, result)
# # Debugging
# for p,r in patterns:
# print p,r
# print '--'
# compile the patterns into regular expressions for speed
for i in range(len(patterns)):
p = patterns[i][0].replace('.', 'X').replace('X', '[01]')
p = re.compile(p)
patterns[i] = (p, patterns[i][1])
# Step through table and find patterns that match.
# Note that all the patterns are searched. The last one
# caught overrides
for i in range(LUT_SIZE):
# Build the bit pattern
bitpattern = bin(i)[2:]
bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1]
for p, r in patterns:
if p.match(bitpattern):
self.lut[i] = [0, 1][r]
return self.lut
class MorphOp:
"""A class for binary morphological operators"""
def __init__(self,
lut=None,
op_name=None,
patterns=None):
"""Create a binary morphological operator"""
self.lut = lut
if op_name is not None:
self.lut = LutBuilder(op_name=op_name).build_lut()
elif patterns is not None:
self.lut = LutBuilder(patterns=patterns).build_lut()
def apply(self, image):
"""Run a single morphological operation on an image
Returns a tuple of the number of changed pixels and the
morphed image"""
if self.lut is None:
raise Exception('No operator loaded')
outimage = Image.new(image.mode, image.size, None)
count = _imagingmorph.apply(
bytes(self.lut), image.im.id, outimage.im.id)
return count, outimage
def match(self, image):
"""Get a list of coordinates matching the morphological operation on
an image.
Returns a list of tuples of (x,y) coordinates
of all matching pixels."""
if self.lut is None:
raise Exception('No operator loaded')
return _imagingmorph.match(bytes(self.lut), image.im.id)
def get_on_pixels(self, image):
"""Get a list of all turned on pixels in a binary image
Returns a list of tuples of (x,y) coordinates
of all matching pixels."""
return _imagingmorph.get_on_pixels(image.im.id)
def load_lut(self, filename):
"""Load an operator from an mrl file"""
with open(filename, 'rb') as f:
self.lut = bytearray(f.read())
if len(self.lut) != 8192:
self.lut = None
raise Exception('Wrong size operator file!')
def save_lut(self, filename):
"""Save an operator to an mrl file"""
if self.lut is None:
raise Exception('No operator loaded')
with open(filename, 'wb') as f:
f.write(self.lut)
def set_lut(self, lut):
"""Set the lut from an external source"""
self.lut = lut
# End of file

View File

@ -22,6 +22,7 @@ from PIL._util import isStringType
import operator
from functools import reduce
#
# helpers
@ -35,12 +36,14 @@ def _border(border):
left = top = right = bottom = border
return left, top, right, bottom
def _color(color, mode):
if isStringType(color):
from PIL import ImageColor
color = ImageColor.getcolor(color, mode)
return color
def _lut(image, lut):
if image.mode == "P":
# FIXME: apply to lookup table, not image data
@ -94,7 +97,7 @@ def autocontrast(image, cutoff=0, ignore=None):
cut = cut - h[lo]
h[lo] = 0
else:
h[lo] = h[lo] - cut
h[lo] -= cut
cut = 0
if cut <= 0:
break
@ -105,7 +108,7 @@ def autocontrast(image, cutoff=0, ignore=None):
cut = cut - h[hi]
h[hi] = 0
else:
h[hi] = h[hi] - cut
h[hi] -= cut
cut = 0
if cut <= 0:
break
@ -147,7 +150,9 @@ def colorize(image, black, white):
assert image.mode == "L"
black = _color(black, "RGB")
white = _color(white, "RGB")
red = []; green = []; blue = []
red = []
green = []
blue = []
for i in range(256):
red.append(black[0]+i*(white[0]-black[0])//255)
green.append(black[1]+i*(white[1]-black[1])//255)
@ -273,7 +278,7 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
centering = [centering[0], centering[1]]
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:
centering[1] = 0.50
@ -392,7 +397,7 @@ def solarize(image, threshold=128):
"""
Invert all pixel values above a threshold.
:param image: The image to posterize.
:param image: The image to solarize.
:param threshold: All pixels above this greyscale level are inverted.
:return: An image.
"""
@ -404,6 +409,7 @@ def solarize(image, threshold=128):
lut.append(255-i)
return _lut(image, lut)
# --------------------------------------------------------------------
# PIL USM components, from Kevin Cazabon.
@ -419,6 +425,7 @@ def gaussian_blur(im, radius=None):
gblur = gaussian_blur
def unsharp_mask(im, radius=None, percent=None, threshold=None):
""" PIL_usm.usm(im, [radius, percent, threshold])"""

View File

@ -17,19 +17,21 @@
#
import array
from PIL import Image, ImageColor
import warnings
from PIL import ImageColor
class ImagePalette:
"Color palette for palette mapped images"
def __init__(self, mode = "RGB", palette = None):
def __init__(self, mode="RGB", palette=None, size=0):
self.mode = mode
self.rawmode = None # if set, palette contains raw data
self.rawmode = None # if set, palette contains raw data
self.palette = palette or list(range(256))*len(self.mode)
self.colors = {}
self.dirty = None
if len(self.mode)*256 != len(self.palette):
if ((size == 0 and len(self.mode)*256 != len(self.palette)) or
(size != 0 and size != len(self.palette))):
raise ValueError("wrong palette size")
def getdata(self):
@ -54,7 +56,7 @@ class ImagePalette:
return self.palette
arr = array.array("B", self.palette)
if hasattr(arr, 'tobytes'):
#py3k has a tobytes, tostring is deprecated.
# py3k has a tobytes, tostring is deprecated.
return arr.tobytes()
return arr.tostring()
@ -100,11 +102,15 @@ class ImagePalette:
fp.write("# Mode: %s\n" % self.mode)
for i in range(256):
fp.write("%d" % i)
for j in range(i, len(self.palette), 256):
fp.write(" %d" % self.palette[j])
for j in range(i*len(self.mode), (i+1)*len(self.mode)):
try:
fp.write(" %d" % self.palette[j])
except IndexError:
fp.write(" 0")
fp.write("\n")
fp.close()
# --------------------------------------------------------------------
# Internal
@ -115,32 +121,53 @@ def raw(rawmode, data):
palette.dirty = 1
return palette
# --------------------------------------------------------------------
# Factories
def _make_linear_lut(black, white):
warnings.warn(
'_make_linear_lut() is deprecated. '
'Please call make_linear_lut() instead.',
DeprecationWarning,
stacklevel=2
)
return make_linear_lut(black, white)
def _make_gamma_lut(exp):
warnings.warn(
'_make_gamma_lut() is deprecated. '
'Please call make_gamma_lut() instead.',
DeprecationWarning,
stacklevel=2
)
return make_gamma_lut(exp)
def make_linear_lut(black, white):
lut = []
if black == 0:
for i in range(256):
lut.append(white*i//255)
else:
raise NotImplementedError # FIXME
raise NotImplementedError # FIXME
return lut
def _make_gamma_lut(exp, mode="RGB"):
def make_gamma_lut(exp):
lut = []
for i in range(256):
lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
return lut
def new(mode, data):
return Image.core.new_palette(mode, data)
def negative(mode="RGB"):
palette = list(range(256))
palette.reverse()
return ImagePalette(mode, palette * len(mode))
def random(mode="RGB"):
from random import randint
palette = []
@ -148,16 +175,19 @@ def random(mode="RGB"):
palette.append(randint(0, 255))
return ImagePalette(mode, palette)
def sepia(white="#fff0c0"):
r, g, b = ImageColor.getrgb(white)
r = _make_linear_lut(0, r)
g = _make_linear_lut(0, g)
b = _make_linear_lut(0, b)
r = make_linear_lut(0, r)
g = make_linear_lut(0, g)
b = make_linear_lut(0, b)
return ImagePalette("RGB", r + g + b)
def wedge(mode="RGB"):
return ImagePalette(mode, list(range(256)) * len(mode))
def load(filename):
# FIXME: supports GIMP gradients only
@ -173,8 +203,8 @@ def load(filename):
p = GimpPaletteFile.GimpPaletteFile(fp)
lut = p.getpalette()
except (SyntaxError, ValueError):
#import traceback
#traceback.print_exc()
# import traceback
# traceback.print_exc()
pass
if not lut:
@ -184,8 +214,8 @@ def load(filename):
p = GimpGradientFile.GimpGradientFile(fp)
lut = p.getpalette()
except (SyntaxError, ValueError):
#import traceback
#traceback.print_exc()
# import traceback
# traceback.print_exc()
pass
if not lut:
@ -202,4 +232,4 @@ def load(filename):
if not lut:
raise IOError("cannot load palette")
return lut # data, rawmode
return lut # data, rawmode

View File

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

View File

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

View File

@ -15,21 +15,29 @@
from __future__ import print_function
from PIL import Image
import os, sys
import os
import sys
if sys.version_info >= (3, 3):
from shlex import quote
else:
from pipes import quote
_viewers = []
def register(viewer, order=1):
try:
if issubclass(viewer, Viewer):
viewer = viewer()
except TypeError:
pass # raised if viewer wasn't a class
pass # raised if viewer wasn't a class
if order > 0:
_viewers.append(viewer)
elif order < 0:
_viewers.insert(0, viewer)
##
# Displays a given image.
#
@ -44,6 +52,7 @@ def show(image, title=None, **options):
return 1
return 0
##
# Base class for viewers.
@ -65,7 +74,7 @@ class Viewer:
if base != image.mode and image.mode != "1":
image = image.convert(base)
self.show_image(image, **options)
return self.show_image(image, **options)
# hook methods
@ -97,9 +106,11 @@ if sys.platform == "win32":
class WindowsViewer(Viewer):
format = "BMP"
def get_command(self, file, **options):
return ("start /wait %s && ping -n 2 127.0.0.1 >NUL "
"&& del /f %s" % (file, file))
return ('start "Pillow" /WAIT "%s" '
'&& ping -n 2 127.0.0.1 >NUL '
'&& del /f "%s"' % (file, file))
register(WindowsViewer)
@ -107,11 +118,13 @@ elif sys.platform == "darwin":
class MacViewer(Viewer):
format = "BMP"
def get_command(self, file, **options):
# on darwin open returns immediately resulting in the temp
# file removal while app is opening
command = "open -a /Applications/Preview.app"
command = "(%s %s; sleep 20; rm -f %s)&" % (command, file, file)
command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file),
quote(file))
return command
register(MacViewer)
@ -134,7 +147,8 @@ else:
class UnixViewer(Viewer):
def show_file(self, file, **options):
command, executable = self.get_command_ex(file, **options)
command = "(%s %s; rm -f %s)&" % (command, file, file)
command = "(%s %s; rm -f %s)&" % (command, quote(file),
quote(file))
os.system(command)
return 1
@ -154,8 +168,7 @@ else:
# imagemagick's display command instead.
command = executable = "xv"
if title:
# FIXME: do full escaping
command = command + " -name \"%s\"" % title
command += " -name %s" % quote(title)
return command, executable
if which("xv"):

View File

@ -21,21 +21,21 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image
import operator, math
import math
import operator
from functools import reduce
class Stat:
def __init__(self, image_or_list, mask = None):
def __init__(self, image_or_list, mask=None):
try:
if mask:
self.h = image_or_list.histogram(mask)
else:
self.h = image_or_list.histogram()
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):
raise TypeError("first argument must be image or list")
self.bands = list(range(len(self.h) // 256))
@ -59,7 +59,7 @@ class Stat:
if histogram[i]:
n = min(n, 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 = []
for i in range(0, len(self.h), 256):
@ -81,7 +81,7 @@ class Stat:
for i in range(0, len(self.h), 256):
sum = 0.0
for j in range(256):
sum = sum + j * self.h[i+j]
sum += j * self.h[i + j]
v.append(sum)
return v
@ -92,7 +92,7 @@ class Stat:
for i in range(0, len(self.h), 256):
sum2 = 0.0
for j in range(256):
sum2 = sum2 + (j ** 2) * float(self.h[i+j])
sum2 += (j ** 2) * float(self.h[i + j])
v.append(sum2)
return v
@ -127,7 +127,6 @@ class Stat:
v.append(math.sqrt(self.sum2[i] / self.count[i]))
return v
def _getvar(self):
"Get variance for each layer"
@ -145,4 +144,4 @@ class Stat:
v.append(math.sqrt(self.var[i]))
return v
Global = Stat # compatibility
Global = Stat # compatibility

View File

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

View File

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

View File

@ -23,23 +23,26 @@ from PIL import Image
class HDC:
"""
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`
methods.
"""
def __init__(self, dc):
self.dc = dc
def __int__(self):
return self.dc
class HWND:
"""
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`
methods, instead of a DC.
"""
def __init__(self, wnd):
self.wnd = wnd
def __int__(self):
return self.wnd
@ -79,13 +82,12 @@ class Dib:
if image:
self.paste(image)
def expose(self, handle):
"""
Copy the bitmap contents to a device context.
:param handle: Device context (HDC), cast to a Python integer, or a HDC
or HWND instance. In PythonWin, you can use the
:param handle: Device context (HDC), cast to a Python integer, or an
HDC or HWND instance. In PythonWin, you can use the
:py:meth:`CDC.GetHandleAttrib` to get a suitable handle.
"""
if isinstance(handle, HWND):
@ -109,7 +111,7 @@ class Dib:
necessary.
"""
if not src:
src = (0,0) + self.size
src = (0, 0) + self.size
if isinstance(handle, HWND):
dc = self.image.getdc(handle)
try:
@ -120,7 +122,6 @@ class Dib:
result = self.image.draw(handle, dst, src)
return result
def query_palette(self, handle):
"""
Installs the palette associated with the image in the given device
@ -146,7 +147,6 @@ class Dib:
result = self.image.query_palette(handle)
return result
def paste(self, im, box=None):
"""
Paste a PIL image into the bitmap image.
@ -166,7 +166,6 @@ class Dib:
else:
self.image.paste(im.im)
def frombytes(self, buffer):
"""
Load display memory contents from byte data.
@ -176,7 +175,6 @@ class Dib:
"""
return self.image.frombytes(buffer)
def tobytes(self):
"""
Copy display memory contents to bytes object.
@ -204,6 +202,7 @@ class Dib:
)
return self.tobytes()
##
# Create a Window with the given title size.
@ -235,6 +234,7 @@ class Window:
def mainloop(self):
Image.core.eventloop()
##
# 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]*)")
##
# 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
# 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")
self.fp.seek(0)
@ -54,7 +55,7 @@ class ImtImageFile(ImageFile.ImageFile):
if s == b'\x0C':
# image data begins
self.tile = [("raw", (0,0)+self.size,
self.tile = [("raw", (0, 0)+self.size,
self.fp.tell(),
(self.mode, 0, 1))]
@ -68,12 +69,12 @@ class ImtImageFile(ImageFile.ImageFile):
if len(s) == 1 or len(s) > 100:
break
if s[0] == b"*":
continue # comment
continue # comment
m = field.match(s)
if not m:
break
k, v = m.group(1,2)
k, v = m.group(1, 2)
if k == "width":
xsize = int(v)
self.size = xsize, ysize

View File

@ -21,7 +21,8 @@ __version__ = "0.3"
from PIL import Image, ImageFile, _binary
import os, tempfile
import os
import tempfile
i8 = _binary.i8
i16 = _binary.i16be
@ -35,17 +36,20 @@ COMPRESSION = {
PAD = o8(0) * 4
#
# Helpers
def i(c):
return i32((PAD + c)[-4:])
def dump(c):
for i in c:
print("%02x" % i8(i), end=' ')
print()
##
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
@ -84,35 +88,13 @@ class IptcImageFile(ImageFile.ImageFile):
return tag, size
def _is_raw(self, offset, size):
#
# check if the file can be mapped
# DISABLED: the following only slows things down...
return 0
self.fp.seek(offset)
t, sz = self.field()
if sz != size[0]:
return 0
y = 1
while True:
self.fp.seek(sz, 1)
t, s = self.field()
if t != (8, 10):
break
if s != sz:
return 0
y = y + 1
return y == size[1]
def _open(self):
# load descriptive fields
while True:
offset = self.fp.tell()
tag, size = self.field()
if not tag or tag == (8,10):
if not tag or tag == (8, 10):
break
if size:
tagdata = self.fp.read(size)
@ -129,10 +111,10 @@ class IptcImageFile(ImageFile.ImageFile):
# print tag, self.info[tag]
# mode
layers = i8(self.info[(3,60)][0])
component = i8(self.info[(3,60)][1])
if (3,65) in self.info:
id = i8(self.info[(3,65)][0])-1
layers = i8(self.info[(3, 60)][0])
component = i8(self.info[(3, 60)][1])
if (3, 65) in self.info:
id = i8(self.info[(3, 65)][0])-1
else:
id = 0
if layers == 1 and not component:
@ -143,22 +125,18 @@ class IptcImageFile(ImageFile.ImageFile):
self.mode = "CMYK"[id]
# size
self.size = self.getint((3,20)), self.getint((3,30))
self.size = self.getint((3, 20)), self.getint((3, 30))
# compression
try:
compression = COMPRESSION[self.getint((3,120))]
compression = COMPRESSION[self.getint((3, 120))]
except KeyError:
raise IOError("Unknown IPTC image compression")
# tile
if tag == (8,10):
if compression == "raw" and self._is_raw(offset, self.size):
self.tile = [(compression, (offset, size + 5, -1),
(0, 0, self.size[0], self.size[1]))]
else:
self.tile = [("iptc", (compression, offset),
(0, 0, self.size[0], self.size[1]))]
if tag == (8, 10):
self.tile = [("iptc", (compression, offset),
(0, 0, self.size[0], self.size[1]))]
def load(self):
@ -172,8 +150,8 @@ class IptcImageFile(ImageFile.ImageFile):
self.fp.seek(offset)
# Copy image data to temporary file
outfile = tempfile.mktemp()
o = open(outfile, "wb")
o_fd, outfile = tempfile.mkstemp(text=False)
o = os.fdopen(o_fd)
if encoding == "raw":
# To simplify access to the extracted file,
# prepend a PPM header
@ -187,7 +165,7 @@ class IptcImageFile(ImageFile.ImageFile):
if not s:
break
o.write(s)
size = size - len(s)
size -= len(s)
o.close()
try:
@ -200,14 +178,17 @@ class IptcImageFile(ImageFile.ImageFile):
im.load()
self.im = im.im
finally:
try: os.unlink(outfile)
except: pass
try:
os.unlink(outfile)
except:
pass
Image.register_open("IPTC", IptcImageFile)
Image.register_extension("IPTC", ".iim")
##
# Get IPTC information from TIFF, JPEG, or IPTC file.
#
@ -230,31 +211,31 @@ def getiptcinfo(im):
# extract the IPTC/NAA resource
try:
app = im.app["APP13"]
if app[:14] == "Photoshop 3.0\x00":
if app[:14] == b"Photoshop 3.0\x00":
app = app[14:]
# parse the image resource block
offset = 0
while app[offset:offset+4] == "8BIM":
offset = offset + 4
while app[offset:offset+4] == b"8BIM":
offset += 4
# resource code
code = JpegImagePlugin.i16(app, offset)
offset = offset + 2
offset += 2
# resource name (usually empty)
name_len = i8(app[offset])
name = app[offset+1:offset+1+name_len]
offset = 1 + offset + name_len
if offset & 1:
offset = offset + 1
offset += 1
# resource data block
size = JpegImagePlugin.i32(app, offset)
offset = offset + 4
offset += 4
if code == 0x0404:
# 0x0404 contains IPTC/NAA data
data = app[offset:offset+size]
break
offset = offset + size
if offset & 1:
offset = offset + 1
offset += 1
except (AttributeError, KeyError):
pass
@ -262,12 +243,12 @@ def getiptcinfo(im):
# get raw data from the IPTC/NAA tag (PhotoShop tags the data
# as 4-byte integers, so we cannot use the get method...)
try:
type, data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
except (AttributeError, KeyError):
pass
if data is None:
return None # no properties
return None # no properties
# create an IptcImagePlugin object without initializing it
class FakeImage:
@ -282,6 +263,6 @@ def getiptcinfo(im):
try:
im._open()
except (IndexError, KeyError):
pass # expected failure
pass # expected failure
return im.info

277
PIL/Jpeg2KImagePlugin.py Normal file
View File

@ -0,0 +1,277 @@
#
# The Python Imaging Library
# $Id$
#
# JPEG2000 file handling
#
# History:
# 2014-03-12 ajh Created
#
# Copyright (c) 2014 Coriolis Systems Limited
# Copyright (c) 2014 Alastair Houghton
#
# See the README file for information on usage and redistribution.
#
__version__ = "0.1"
from PIL import Image, ImageFile
import struct
import os
import io
def _parse_codestream(fp):
"""Parse the JPEG 2000 codestream to extract the size and component
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
hdr = fp.read(2)
lsiz = struct.unpack('>H', hdr)[0]
siz = hdr + fp.read(lsiz - 2)
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
xtosiz, ytosiz, csiz \
= struct.unpack('>HHIIIIIIIIH', siz[:38])
ssiz = [None]*csiz
xrsiz = [None]*csiz
yrsiz = [None]*csiz
for i in range(csiz):
ssiz[i], xrsiz[i], yrsiz[i] \
= struct.unpack('>BBB', siz[36 + 3 * i:39 + 3 * i])
size = (xsiz - xosiz, ysiz - yosiz)
if csiz == 1:
if (yrsiz[0] & 0x7f) > 8:
mode = 'I;16'
else:
mode = 'L'
elif csiz == 2:
mode = 'LA'
elif csiz == 3:
mode = 'RGB'
elif csiz == 4:
mode = 'RGBA'
else:
mode = None
return (size, mode)
def _parse_jp2_header(fp):
"""Parse the JP2 header box to extract size, component count and
color space information, returning a PIL (size, mode) tuple."""
# Find the JP2 header box
header = None
while True:
lbox, tbox = struct.unpack('>I4s', fp.read(8))
if lbox == 1:
lbox = struct.unpack('>Q', fp.read(8))[0]
hlen = 16
else:
hlen = 8
if lbox < hlen:
raise SyntaxError('Invalid JP2 header length')
if tbox == b'jp2h':
header = fp.read(lbox - hlen)
break
else:
fp.seek(lbox - hlen, os.SEEK_CUR)
if header is None:
raise SyntaxError('could not find JP2 header')
size = None
mode = None
bpc = None
hio = io.BytesIO(header)
while True:
lbox, tbox = struct.unpack('>I4s', hio.read(8))
if lbox == 1:
lbox = struct.unpack('>Q', hio.read(8))[0]
hlen = 16
else:
hlen = 8
content = hio.read(lbox - hlen)
if tbox == b'ihdr':
height, width, nc, bpc, c, unkc, ipr \
= struct.unpack('>IIHBBBB', content)
size = (width, height)
if unkc:
if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L'
elif nc == 2:
mode = 'LA'
elif nc == 3:
mode = 'RGB'
elif nc == 4:
mode = 'RGBA'
break
elif tbox == b'colr':
meth, prec, approx = struct.unpack('>BBB', content[:3])
if meth == 1:
cs = struct.unpack('>I', content[3:7])[0]
if cs == 16: # sRGB
if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L'
elif nc == 3:
mode = 'RGB'
elif nc == 4:
mode = 'RGBA'
break
elif cs == 17: # grayscale
if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L'
elif nc == 2:
mode = 'LA'
break
elif cs == 18: # sYCC
if nc == 3:
mode = 'RGB'
elif nc == 4:
mode = 'RGBA'
break
return (size, mode)
##
# Image plugin for JPEG2000 images.
class Jpeg2KImageFile(ImageFile.ImageFile):
format = "JPEG2000"
format_description = "JPEG 2000 (ISO 15444)"
def _open(self):
sig = self.fp.read(4)
if sig == b'\xff\x4f\xff\x51':
self.codec = "j2k"
self.size, self.mode = _parse_codestream(self.fp)
else:
sig = sig + self.fp.read(8)
if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
self.codec = "jp2"
self.size, self.mode = _parse_jp2_header(self.fp)
else:
raise SyntaxError('not a JPEG 2000 file')
if self.size is None or self.mode is None:
raise SyntaxError('unable to determine size/mode')
self.reduce = 0
self.layers = 0
fd = -1
length = -1
try:
fd = self.fp.fileno()
length = os.fstat(fd).st_size
except:
fd = -1
try:
pos = self.fp.tell()
self.fp.seek(0, 2)
length = self.fp.tell()
self.fp.seek(pos, 0)
except:
length = -1
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
(self.codec, self.reduce, self.layers, fd, length))]
def load(self):
if self.reduce:
power = 1 << self.reduce
adjust = power >> 1
self.size = (int((self.size[0] + adjust) / power),
int((self.size[1] + adjust) / power))
if self.tile:
# Update the reduce and layers settings
t = self.tile[0]
t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4])
self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
ImageFile.ImageFile.load(self)
def _accept(prefix):
return (prefix[:4] == b'\xff\x4f\xff\x51'
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
# ------------------------------------------------------------
# Save support
def _save(im, fp, filename):
if filename.endswith('.j2k'):
kind = 'j2k'
else:
kind = 'jp2'
# Get the keyword arguments
info = im.encoderinfo
offset = info.get('offset', None)
tile_offset = info.get('tile_offset', None)
tile_size = info.get('tile_size', None)
quality_mode = info.get('quality_mode', 'rates')
quality_layers = info.get('quality_layers', None)
num_resolutions = info.get('num_resolutions', 0)
cblk_size = info.get('codeblock_size', None)
precinct_size = info.get('precinct_size', None)
irreversible = info.get('irreversible', False)
progression = info.get('progression', 'LRCP')
cinema_mode = info.get('cinema_mode', 'no')
fd = -1
if hasattr(fp, "fileno"):
try:
fd = fp.fileno()
except:
fd = -1
im.encoderconfig = (
offset,
tile_offset,
tile_size,
quality_mode,
quality_layers,
num_resolutions,
cblk_size,
precinct_size,
irreversible,
progression,
cinema_mode,
fd
)
ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
# ------------------------------------------------------------
# Registry stuff
Image.register_open('JPEG2000', Jpeg2KImageFile, _accept)
Image.register_save('JPEG2000', _save)
Image.register_extension('JPEG2000', '.jp2')
Image.register_extension('JPEG2000', '.j2k')
Image.register_extension('JPEG2000', '.jpc')
Image.register_extension('JPEG2000', '.jpf')
Image.register_extension('JPEG2000', '.jpx')
Image.register_extension('JPEG2000', '.j2c')
Image.register_mime('JPEG2000', 'image/jp2')
Image.register_mime('JPEG2000', 'image/jpx')

View File

@ -34,8 +34,11 @@
__version__ = "0.6"
import array, struct
from PIL import Image, ImageFile, _binary
import array
import struct
import io
from struct import unpack
from PIL import Image, ImageFile, TiffImagePlugin, _binary
from PIL.JpegPresets import presets
from PIL._util import isStringType
@ -44,6 +47,7 @@ o8 = _binary.o8
i16 = _binary.i16be
i32 = _binary.i32be
#
# Parser
@ -51,6 +55,7 @@ def Skip(self, marker):
n = i16(self.fp.read(2))-2
ImageFile._safe_read(self.fp, n)
def APP(self, marker):
#
# Application marker. Store these in the APP dictionary.
@ -59,14 +64,14 @@ def APP(self, marker):
n = i16(self.fp.read(2))-2
s = ImageFile._safe_read(self.fp, n)
app = "APP%d" % (marker&15)
app = "APP%d" % (marker & 15)
self.app[app] = s # compatibility
self.app[app] = s # compatibility
self.applist.append((app, s))
if marker == 0xFFE0 and s[:4] == b"JFIF":
# extract JFIF information
self.info["jfif"] = version = i16(s, 5) # version
self.info["jfif"] = version = i16(s, 5) # version
self.info["jfif_version"] = divmod(version, 256)
# extract JFIF properties
try:
@ -81,10 +86,10 @@ def APP(self, marker):
self.info["jfif_density"] = jfif_density
elif marker == 0xFFE1 and s[:5] == b"Exif\0":
# extract Exif information (incomplete)
self.info["exif"] = s # FIXME: value will change
self.info["exif"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
# extract FlashPix information (incomplete)
self.info["flashpix"] = s # FIXME: value will change
self.info["flashpix"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0":
# Since an ICC profile can be larger than the maximum size of
# a JPEG marker (64K), we need provisions to split it into
@ -107,17 +112,24 @@ def APP(self, marker):
pass
else:
self.info["adobe_transform"] = adobe_transform
elif marker == 0xFFE2 and s[:4] == b"MPF\0":
# extract MPO information
self.info["mp"] = s[4:]
# offset is current location minus buffer size
# plus constant header size
self.info["mpoffset"] = self.fp.tell() - n + 4
def COM(self, marker):
#
# Comment marker. Store these in the APP dictionary.
n = i16(self.fp.read(2))-2
s = ImageFile._safe_read(self.fp, n)
self.app["COM"] = s # compatibility
self.app["COM"] = s # compatibility
self.applist.append(("COM", s))
def SOF(self, marker):
#
# Start of frame marker. Defines the size and mode of the
@ -149,21 +161,22 @@ def SOF(self, marker):
if self.icclist:
# fixup icc profile
self.icclist.sort() # sort by sequence number
self.icclist.sort() # sort by sequence number
if i8(self.icclist[0][13]) == len(self.icclist):
profile = []
for p in self.icclist:
profile.append(p[14:])
icc_profile = b"".join(profile)
else:
icc_profile = None # wrong number of fragments
icc_profile = None # wrong number of fragments
self.info["icc_profile"] = icc_profile
self.icclist = None
for i in range(6, len(s), 3):
t = s[i:i+3]
# 4-tuples: id, vsamp, hsamp, qtable
self.layer.append((t[0], i8(t[1])//16, i8(t[1])&15, i8(t[2])))
self.layer.append((t[0], i8(t[1])//16, i8(t[1]) & 15, i8(t[2])))
def DQT(self, marker):
#
@ -181,10 +194,10 @@ def DQT(self, marker):
raise SyntaxError("bad quantization table marker")
v = i8(s[0])
if v//16 == 0:
self.quantization[v&15] = array.array("b", s[1:65])
self.quantization[v & 15] = array.array("b", s[1:65])
s = s[65:]
else:
return # FIXME: add code to read 16-bit tables!
return # FIXME: add code to read 16-bit tables!
# raise SyntaxError, "bad quantization table element size"
@ -261,6 +274,7 @@ MARKER = {
def _accept(prefix):
return prefix[0:1] == b"\377"
##
# Image plugin for JPEG and JFIF images.
@ -284,32 +298,38 @@ class JpegImageFile(ImageFile.ImageFile):
self.huffman_dc = {}
self.huffman_ac = {}
self.quantization = {}
self.app = {} # compatibility
self.app = {} # compatibility
self.applist = []
self.icclist = []
while True:
s = s + self.fp.read(1)
i = i16(s)
i = i8(s)
if i == 0xFF:
s = s + self.fp.read(1)
i = i16(s)
else:
# Skip non-0xFF junk
s = b"\xff"
continue
if i in MARKER:
name, description, handler = MARKER[i]
# print hex(i), name, description
if handler is not None:
handler(self, i)
if i == 0xFFDA: # start of scan
if i == 0xFFDA: # start of scan
rawmode = self.mode
if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0,0) + self.size, 0, (rawmode, ""))]
rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0, 0) + self.size, 0,
(rawmode, ""))]
# self.__offset = self.fp.tell()
break
s = self.fp.read(1)
elif i == 0 or i == 65535:
elif i == 0 or i == 0xFFFF:
# padded marker or junk; move on
s = "\xff"
s = b"\xff"
else:
raise SyntaxError("no marker found")
@ -343,15 +363,23 @@ class JpegImageFile(ImageFile.ImageFile):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities
import tempfile, os
file = tempfile.mktemp()
os.system("djpeg %s >%s" % (self.filename, file))
import subprocess
import tempfile
import os
f, path = tempfile.mkstemp()
os.close(f)
if os.path.exists(self.filename):
subprocess.check_call(["djpeg", "-outfile", path, self.filename])
else:
raise ValueError("Invalid Filename")
try:
self.im = Image.core.open_ppm(file)
self.im = Image.core.open_ppm(path)
finally:
try: os.unlink(file)
except: pass
try:
os.unlink(path)
except:
pass
self.mode = self.im.mode
self.size = self.im.size
@ -361,17 +389,22 @@ class JpegImageFile(ImageFile.ImageFile):
def _getexif(self):
return _getexif(self)
def _getmp(self):
return _getmp(self)
def _fixup(value):
# Helper function for _getexif() and _getmp()
if len(value) == 1:
return value[0]
return value
def _getexif(self):
# Extract EXIF information. This method is highly experimental,
# and is likely to be replaced with something better in a future
# version.
from PIL import TiffImagePlugin
import io
def fixup(value):
if len(value) == 1:
return value[0]
return value
# The EXIF record consists of a TIFF file embedded in a JPEG
# application marker (!).
try:
@ -385,7 +418,7 @@ def _getexif(self):
info = TiffImagePlugin.ImageFileDirectory(head)
info.load(file)
for key, value in info.items():
exif[key] = fixup(value)
exif[key] = _fixup(value)
# get exif extension
try:
file.seek(exif[0x8769])
@ -395,7 +428,7 @@ def _getexif(self):
info = TiffImagePlugin.ImageFileDirectory(head)
info.load(file)
for key, value in info.items():
exif[key] = fixup(value)
exif[key] = _fixup(value)
# get gpsinfo extension
try:
file.seek(exif[0x8825])
@ -406,9 +439,81 @@ def _getexif(self):
info.load(file)
exif[0x8825] = gps = {}
for key, value in info.items():
gps[key] = fixup(value)
gps[key] = _fixup(value)
return exif
def _getmp(self):
# Extract MP information. This method was inspired by the "highly
# experimental" _getexif version that's been in use for years now,
# itself based on the ImageFileDirectory class in the TIFF plug-in.
# The MP record essentially consists of a TIFF file embedded in a JPEG
# application marker.
try:
data = self.info["mp"]
except KeyError:
return None
file = io.BytesIO(data)
head = file.read(8)
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
mp = {}
# process dictionary
info = TiffImagePlugin.ImageFileDirectory(head)
info.load(file)
for key, value in info.items():
mp[key] = _fixup(value)
# it's an error not to have a number of images
try:
quant = mp[0xB001]
except KeyError:
raise SyntaxError("malformed MP Index (no number of images)")
# get MP entries
try:
mpentries = []
for entrynum in range(0, quant):
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
'EntryNo2')
mpentry = dict(zip(labels, unpackedentry))
mpentryattr = {
'DependentParentImageFlag': bool(mpentry['Attribute'] &
(1 << 31)),
'DependentChildImageFlag': bool(mpentry['Attribute'] &
(1 << 30)),
'RepresentativeImageFlag': bool(mpentry['Attribute'] &
(1 << 29)),
'Reserved': (mpentry['Attribute'] & (3 << 27)) >> 27,
'ImageDataFormat': (mpentry['Attribute'] & (7 << 24)) >> 24,
'MPType': mpentry['Attribute'] & 0x00FFFFFF
}
if mpentryattr['ImageDataFormat'] == 0:
mpentryattr['ImageDataFormat'] = 'JPEG'
else:
raise SyntaxError("unsupported picture format in MPO")
mptypemap = {
0x000000: 'Undefined',
0x010001: 'Large Thumbnail (VGA Equivalent)',
0x010002: 'Large Thumbnail (Full HD Equivalent)',
0x020001: 'Multi-Frame Image (Panorama)',
0x020002: 'Multi-Frame Image: (Disparity)',
0x020003: 'Multi-Frame Image: (Multi-Angle)',
0x030000: 'Baseline MP Primary Image'
}
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
'Unknown')
mpentry['Attribute'] = mpentryattr
mpentries.append(mpentry)
mp[0xB002] = mpentries
except KeyError:
raise SyntaxError("malformed MP Index (bad MP Entry)")
# Next we should try and parse the individual image unique ID list;
# we don't because I've never seen this actually used in a real MPO
# file and so can't test it.
return mp
# --------------------------------------------------------------------
# stuff to save JPEG files
@ -418,7 +523,7 @@ RAWMODE = {
"RGB": "RGB",
"RGBA": "RGB",
"RGBX": "RGB",
"CMYK": "CMYK;I", # assume adobe conventions
"CMYK": "CMYK;I", # assume adobe conventions
"YCbCr": "YCbCr",
}
@ -431,22 +536,33 @@ zigzag_index = ( 0, 1, 5, 6, 14, 15, 27, 28,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63)
samplings = {
(1, 1, 1, 1, 1, 1): 0,
samplings = {(1, 1, 1, 1, 1, 1): 0,
(2, 1, 1, 1, 1, 1): 1,
(2, 2, 1, 1, 1, 1): 2,
}
}
def convert_dict_qtables(qtables):
qtables = [qtables[key] for key in xrange(len(qtables)) if qtables.has_key(key)]
qtables = [qtables[key] for key in range(len(qtables)) if key in qtables]
for idx, table in enumerate(qtables):
qtables[idx] = [table[i] for i in zigzag_index]
return qtables
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]
return samplings.get(sampling, -1)
def _save(im, fp, filename):
try:
@ -476,7 +592,7 @@ def _save(im, fp, filename):
else:
if subsampling in presets:
subsampling = presets[subsampling].get('subsampling', -1)
if qtables in presets:
if isStringType(qtables) and qtables in presets:
qtables = presets[qtables].get('quantization')
if subsampling == "4:4:4":
@ -487,7 +603,8 @@ def _save(im, fp, filename):
subsampling = 2
elif subsampling == "keep":
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)
def validate_qtables(qtables):
@ -500,7 +617,7 @@ def _save(im, fp, filename):
except ValueError:
raise ValueError("Invalid quantization table")
else:
qtables = [lines[s:s+64] for s in xrange(0, len(lines), 64)]
qtables = [lines[s:s+64] for s in range(0, len(lines), 64)]
if isinstance(qtables, (tuple, list, dict)):
if isinstance(qtables, dict):
qtables = convert_dict_qtables(qtables)
@ -521,7 +638,8 @@ def _save(im, fp, filename):
if qtables == "keep":
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 = validate_qtables(qtables)
@ -539,8 +657,9 @@ def _save(im, fp, filename):
i = 1
for marker in markers:
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
extra = extra + (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker)
i = i + 1
extra += (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) +
o8(len(markers)) + marker)
i += 1
# get keyword arguments
im.encoderconfig = (
@ -559,33 +678,56 @@ def _save(im, fp, filename):
info.get("exif", b"")
)
# if we optimize, libjpeg needs a buffer big enough to hold the whole image in a shot.
# Guessing on the size, at im.size bytes. (raw pizel size is channels*size, this
# is a value that's been used in a django patch.
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
# in a shot. Guessing on the size, at im.size bytes. (raw pizel size is
# channels*size, this is a value that's been used in a django patch.
# https://github.com/jdriscoll/django-imagekit/issues/50
bufsize=0
bufsize = 0
if "optimize" in info or "progressive" in info or "progression" in info:
bufsize = im.size[0]*im.size[1]
if quality >= 95:
bufsize = 2 * im.size[0] * im.size[1]
else:
bufsize = im.size[0] * im.size[1]
# The exif info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif",b"")) + 5 )
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5)
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)], bufsize)
def _save_cjpeg(im, fp, filename):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
import os
file = im._dump()
os.system("cjpeg %s >%s" % (file, filename))
try: os.unlink(file)
except: pass
import subprocess
tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
try:
os.unlink(file)
except:
pass
##
# Factory for making JPEG and MPO instances
def jpeg_factory(fp=None, filename=None):
im = JpegImageFile(fp, filename)
mpheader = im._getmp()
try:
if mpheader[45057] > 1:
# It's actually an MPO
from .MpoImagePlugin import MpoImageFile
im = MpoImageFile(fp, filename)
except (TypeError, IndexError):
# It is really a JPEG
pass
return im
# -------------------------------------------------------------------q-
# Registry stuff
Image.register_open("JPEG", JpegImageFile, _accept)
Image.register_open("JPEG", jpeg_factory, _accept)
Image.register_save("JPEG", _save)
Image.register_extension("JPEG", ".jfif")

View File

@ -48,8 +48,8 @@ You can get the quantization tables of a JPEG with::
im.quantization
This will return a dict with a number of arrays. You can pass this dict directly
as the qtables argument when saving a JPEG.
This will return a dict with a number of arrays. You can pass this dict
directly as the qtables argument when saving a JPEG.
The tables format between im.quantization and quantization in presets differ in
3 ways:
@ -238,4 +238,4 @@ presets = {
15, 12, 12, 12, 12, 12, 12, 12,
15, 12, 12, 12, 12, 12, 12, 12]
]},
}
}

View File

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

View File

@ -31,6 +31,7 @@ from PIL.OleFileIO import *
def _accept(prefix):
return prefix[:8] == MAGIC
##
# Image plugin for Microsoft's Image Composer file format.

View File

@ -18,6 +18,7 @@ __version__ = "0.1"
from PIL import Image, ImageFile
from PIL._binary import i8
#
# Bitstream parser
@ -38,13 +39,13 @@ class BitStream:
self.bits = 0
continue
self.bitbuffer = (self.bitbuffer << 8) + c
self.bits = self.bits + 8
self.bits += 8
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
def skip(self, bits):
while self.bits < bits:
self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1))
self.bits = self.bits + 8
self.bits += 8
self.bits = self.bits - bits
def read(self, bits):
@ -52,6 +53,7 @@ class BitStream:
self.bits = self.bits - bits
return v
##
# Image plugin for MPEG streams. This plugin can identify a stream,
# but it cannot read it.

90
PIL/MpoImagePlugin.py Normal file
View File

@ -0,0 +1,90 @@
#
# The Python Imaging Library.
# $Id$
#
# MPO file handling
#
# See "Multi-Picture Format" (CIPA DC-007-Translation 2009, Standard of the
# Camera & Imaging Products Association)
#
# The multi-picture object combines multiple JPEG images (with a modified EXIF
# data format) into a single file. While it can theoretically be used much like
# a GIF animation, it is commonly used to represent 3D photographs and is (as
# of this writing) the most commonly used format by 3D cameras.
#
# History:
# 2014-03-13 Feneric Created
#
# See the README file for information on usage and redistribution.
#
__version__ = "0.1"
from PIL import Image, JpegImagePlugin
def _accept(prefix):
return JpegImagePlugin._accept(prefix)
def _save(im, fp, filename):
# Note that we can only save the current frame at present
return JpegImagePlugin._save(im, fp, filename)
##
# Image plugin for MPO images.
class MpoImageFile(JpegImagePlugin.JpegImageFile):
format = "MPO"
format_description = "MPO (CIPA DC-007)"
def _open(self):
self.fp.seek(0) # prep the fp in order to pass the JPEG test
JpegImagePlugin.JpegImageFile._open(self)
self.mpinfo = self._getmp()
self.__framecount = self.mpinfo[0xB001]
self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset']
for mpent in self.mpinfo[0xB002]]
self.__mpoffsets[0] = 0
# Note that the following assertion will only be invalid if something
# gets broken within JpegImagePlugin.
assert self.__framecount == len(self.__mpoffsets)
del self.info['mpoffset'] # no longer needed
self.__fp = self.fp # FIXME: hack
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self.__frame = 0
self.offset = 0
# for now we can only handle reading and individual frame extraction
self.readonly = 1
def load_seek(self, pos):
self.__fp.seek(pos)
def seek(self, frame):
if frame < 0 or frame >= self.__framecount:
raise EOFError("no more images in MPO file")
else:
self.fp = self.__fp
self.offset = self.__mpoffsets[frame]
self.tile = [
("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
]
self.__frame = frame
def tell(self):
return self.__frame
# -------------------------------------------------------------------q-
# Registry stuff
# Note that since MPO shares a factory with JPEG, we do not need to do a
# separate registration for it here.
# Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
Image.register_save("MPO", _save)
Image.register_extension("MPO", ".mpo")
Image.register_mime("MPO", "image/mpo")

View File

@ -27,9 +27,11 @@ from PIL import Image, ImageFile, _binary
i16 = _binary.i16le
def _accept(prefix):
return prefix[:4] in [b"DanM", b"LinS"]
##
# Image plugin for Windows MSP images. This plugin supports both
# uncompressed (Windows 1.0).
@ -57,15 +59,16 @@ class MspImageFile(ImageFile.ImageFile):
self.size = i16(s[4:]), i16(s[6:])
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:
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)
o16 = _binary.o16le
def _save(im, fp, filename):
if im.mode != "1":
@ -74,7 +77,7 @@ def _save(im, fp, filename):
# create MSP header
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[4], header[5] = 1, 1
header[6], header[7] = 1, 1
@ -83,14 +86,14 @@ def _save(im, fp, filename):
sum = 0
for h in header:
sum = sum ^ h
header[12] = sum # FIXME: is this the right field?
header[12] = sum # FIXME: is this the right field?
# header
for h in header:
fp.write(o16(h))
# 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

351
PIL/OleFileIO-README.md Normal file
View File

@ -0,0 +1,351 @@
OleFileIO_PL
============
[OleFileIO_PL](http://www.decalage.info/python/olefileio) is a Python module to parse and read [Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format)](http://en.wikipedia.org/wiki/Compound_File_Binary_Format), such as Microsoft Office documents, Image Composer and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats ...
This is an improved version of the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent Python Imaging Library, created and maintained by Fredrik Lundh. The API is still compatible with PIL, but since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust design.
As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules)
OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-pillow.github.io/), the friendly fork of PIL.
OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL.
News
----
Follow all updates and news on Twitter: <https://twitter.com/decalage2>
- **2014-02-04 v0.30**: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work.
- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed parsing of direntry timestamps
- 2013-05-27 v0.25: improved metadata extraction, properties parsing and exception handling, fixed [issue #12](https://bitbucket.org/decalage/olefileio_pl/issue/12/error-when-converting-timestamps-in-ole)
- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved getproperties to convert timestamps to Python datetime
- 2012-10-09: published [python-oletools](http://www.decalage.info/python/oletools), a package of analysis tools based on OleFileIO_PL
- 2012-09-11 v0.23: added support for file-like objects, fixed [issue #8](https://bitbucket.org/decalage/olefileio_pl/issue/8/bug-with-file-object)
- 2012-02-17 v0.22: fixed issues #7 (bug in getproperties) and #2 (added close method)
- 2011-10-20: code hosted on bitbucket to ease contributions and bug tracking
- 2010-01-24 v0.21: fixed support for big-endian CPUs, such as PowerPC Macs.
- 2009-12-11 v0.20: small bugfix in OleFileIO.open when filename is not plain str.
- 2009-12-10 v0.19: fixed support for 64 bits platforms (thanks to Ben G. and Martijn for reporting the bug)
- see changelog in source code for more info.
Download
--------
The archive is available on [the project page](https://bitbucket.org/decalage/olefileio_pl/downloads).
Features
--------
- Parse and read any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls, PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes, Zeiss AxioVision ZVI files, Olympus FluoView OIB files, ...
- List all the streams and storages contained in an OLE file
- Open streams as files
- Parse and read property streams, containing metadata of the file
- Portable, pure Python module, no dependency
Main improvements over the original version of OleFileIO in PIL:
----------------------------------------------------------------
- Compatible with Python 3.x and 2.6+
- Many bug fixes
- Support for files larger than 6.8MB
- Support for 64 bits platforms and big-endian CPUs
- Robust: many checks to detect malformed files
- Runtime option to choose if malformed files should be parsed or raise exceptions
- Improved API
- Metadata extraction, stream/storage timestamps (e.g. for document forensics)
- Can open file-like objects
- Added setup.py and install.bat to ease installation
- More convenient slash-based syntax for stream paths
How to use this module
----------------------
OleFileIO_PL can be used as an independent module or with PIL. The main functions and methods are explained below.
For more information, see also the file **OleFileIO_PL.html**, sample code at the end of the module itself, and docstrings within the code.
### About the structure of OLE files ###
An OLE file can be seen as a mini file system or a Zip archive: It contains **streams** of data that look like files embedded within the OLE file. Each stream has a name. For example, the main stream of a MS Word document containing its text is named "WordDocument".
An OLE file can also contain **storages**. A storage is a folder that contains streams or other storages. For example, a MS Word document with VBA macros has a storage called "Macros".
Special streams can contain **properties**. A property is a specific value that can be used to store information such as the metadata of a document (title, author, creation date, etc). Property stream names usually start with the character '\x05'.
For example, a typical MS Word document may look like this:
\x05DocumentSummaryInformation (stream)
\x05SummaryInformation (stream)
WordDocument (stream)
Macros (storage)
PROJECT (stream)
PROJECTwm (stream)
VBA (storage)
Module1 (stream)
ThisDocument (stream)
_VBA_PROJECT (stream)
dir (stream)
ObjectPool (storage)
### Import OleFileIO_PL ###
:::python
import OleFileIO_PL
As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. If your application needs to be compatible with Python 2.5 or older, you may use the following code to load the old version when needed:
:::python
try:
import OleFileIO_PL
except:
import OleFileIO_PL2 as OleFileIO_PL
If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
### Test if a file is an OLE container ###
Use isOleFile to check if the first bytes of the file contain the Magic for OLE files, before opening it. isOleFile returns True if it is an OLE file, False otherwise (new in v0.16).
:::python
assert OleFileIO_PL.isOleFile('myfile.doc')
### Open an OLE file from disk ###
Create an OleFileIO object with the file path as parameter:
:::python
ole = OleFileIO_PL.OleFileIO('myfile.doc')
### Open an OLE file from a file-like object ###
This is useful if the file is not on disk, e.g. already stored in a string or as a file-like object.
:::python
ole = OleFileIO_PL.OleFileIO(f)
For example the code below reads a file into a string, then uses BytesIO to turn it into a file-like object.
:::python
data = open('myfile.doc', 'rb').read()
f = io.BytesIO(data) # or StringIO.StringIO for Python 2.x
ole = OleFileIO_PL.OleFileIO(f)
### How to handle malformed OLE files ###
By default, the parser is configured to be as robust and permissive as possible, allowing to parse most malformed OLE files. Only fatal errors will raise an exception. It is possible to tell the parser to be more strict in order to raise exceptions for files that do not fully conform to the OLE specifications, using the raise_defect option (new in v0.14):
:::python
ole = OleFileIO_PL.OleFileIO('myfile.doc', raise_defects=DEFECT_INCORRECT)
When the parsing is done, the list of non-fatal issues detected is available as a list in the parsing_issues attribute of the OleFileIO object (new in 0.25):
:::python
print('Non-fatal issues raised during parsing:')
if ole.parsing_issues:
for exctype, msg in ole.parsing_issues:
print('- %s: %s' % (exctype.__name__, msg))
else:
print('None')
### Syntax for stream and storage path ###
Two different syntaxes are allowed for methods that need or return the path of streams and storages:
1) Either a **list of strings** including all the storages from the root up to the stream/storage name. For example a stream called "WordDocument" at the root will have ['WordDocument'] as full path. A stream called "ThisDocument" located in the storage "Macros/VBA" will be ['Macros', 'VBA', 'ThisDocument']. This is the original syntax from PIL. While hard to read and not very convenient, this syntax works in all cases.
2) Or a **single string with slashes** to separate storage and stream names (similar to the Unix path syntax). The previous examples would be 'WordDocument' and 'Macros/VBA/ThisDocument'. This syntax is easier, but may fail if a stream or storage name contains a slash. (new in v0.15)
Both are case-insensitive.
Switching between the two is easy:
:::python
slash_path = '/'.join(list_path)
list_path = slash_path.split('/')
### Get the list of streams ###
listdir() returns a list of all the streams contained in the OLE file, including those stored in storages. Each stream is listed itself as a list, as described above.
:::python
print(ole.listdir())
Sample result:
:::python
[['\x01CompObj'], ['\x05DocumentSummaryInformation'], ['\x05SummaryInformation']
, ['1Table'], ['Macros', 'PROJECT'], ['Macros', 'PROJECTwm'], ['Macros', 'VBA',
'Module1'], ['Macros', 'VBA', 'ThisDocument'], ['Macros', 'VBA', '_VBA_PROJECT']
, ['Macros', 'VBA', 'dir'], ['ObjectPool'], ['WordDocument']]
As an option it is possible to choose if storages should also be listed, with or without streams (new in v0.26):
:::python
ole.listdir (streams=False, storages=True)
### Test if known streams/storages exist: ###
exists(path) checks if a given stream or storage exists in the OLE file (new in v0.16).
:::python
if ole.exists('worddocument'):
print("This is a Word document.")
if ole.exists('macros/vba'):
print("This document seems to contain VBA macros.")
### Read data from a stream ###
openstream(path) opens a stream as a file-like object.
The following example extracts the "Pictures" stream from a PPT file:
:::python
pics = ole.openstream('Pictures')
data = pics.read()
### Get information about a stream/storage ###
Several methods can provide the size, type and timestamps of a given stream/storage:
get_size(path) returns the size of a stream in bytes (new in v0.16):
:::python
s = ole.get_size('WordDocument')
get_type(path) returns the type of a stream/storage, as one of the following constants: STGTY\_STREAM for a stream, STGTY\_STORAGE for a storage, STGTY\_ROOT for the root entry, and False for a non existing path (new in v0.15).
:::python
t = ole.get_type('WordDocument')
get\_ctime(path) and get\_mtime(path) return the creation and modification timestamps of a stream/storage, as a Python datetime object with UTC timezone. Please note that these timestamps are only present if the application that created the OLE file explicitly stored them, which is rarely the case. When not present, these methods return None (new in v0.26).
:::python
c = ole.get_ctime('WordDocument')
m = ole.get_mtime('WordDocument')
The root storage is a special case: You can get its creation and modification timestamps using the OleFileIO.root attribute (new in v0.26):
:::python
c = ole.root.getctime()
m = ole.root.getmtime()
### Extract metadata ###
get_metadata() will check if standard property streams exist, parse all the properties they contain, and return an OleMetadata object with the found properties as attributes (new in v0.24).
:::python
meta = ole.get_metadata()
print('Author:', meta.author)
print('Title:', meta.title)
print('Creation date:', meta.create_time)
# print all metadata:
meta.dump()
Available attributes include:
codepage, title, subject, author, keywords, comments, template,
last_saved_by, revision_number, total_edit_time, last_printed, create_time,
last_saved_time, num_pages, num_words, num_chars, thumbnail,
creating_application, security, codepage_doc, category, presentation_target,
bytes, lines, paragraphs, slides, notes, hidden_slides, mm_clips,
scale_crop, heading_pairs, titles_of_parts, manager, company, links_dirty,
chars_with_spaces, unused, shared_doc, link_base, hlinks, hlinks_changed,
version, dig_sig, content_type, content_status, language, doc_version
See the source code of the OleMetadata class for more information.
### Parse a property stream ###
get\_properties(path) can be used to parse any property stream that is not handled by get\_metadata. It returns a dictionary indexed by integers. Each integer is the index of the property, pointing to its value. For example in the standard property stream '\x05SummaryInformation', the document title is property #2, and the subject is #3.
:::python
p = ole.getproperties('specialprops')
By default as in the original PIL version, timestamp properties are converted into a number of seconds since Jan 1,1601. With the option convert\_time, you can obtain more convenient Python datetime objects (UTC timezone). If some time properties should not be converted (such as total editing time in '\x05SummaryInformation'), the list of indexes can be passed as no_conversion (new in v0.25):
:::python
p = ole.getproperties('specialprops', convert_time=True, no_conversion=[10])
### Close the OLE file ###
Unless your application is a simple script that terminates after processing an OLE file, do not forget to close each OleFileIO object after parsing to close the file on disk. (new in v0.22)
:::python
ole.close()
### Use OleFileIO_PL as a script ###
OleFileIO_PL can also be used as a script from the command-line to display the structure of an OLE file and its metadata, for example:
OleFileIO_PL.py myfile.doc
You can use the option -c to check that all streams can be read fully, and -d to generate very verbose debugging information.
## Real-life examples ##
A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/).
See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features OleFileIO_PL.
About Python 2 and 3
--------------------
OleFileIO\_PL used to support only Python 2.x. As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. See above the "import" section for a workaround.
If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
How to contribute
-----------------
The code is available in [a Mercurial repository on bitbucket](https://bitbucket.org/decalage/olefileio_pl). You may use it to submit enhancements or to report any issue.
If you would like to help us improve this module, or simply provide feedback, please [contact me](http://decalage.info/contact). You can help in many ways:
- test this module on different platforms / Python versions
- find and report bugs
- improve documentation, code samples, docstrings
- write unittest test cases
- provide tricky malformed files
How to report bugs
------------------
To report a bug, for example a normal file which is not parsed correctly, please use the [issue reporting page](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open), or if you prefer to do it privately, use this [contact form](http://decalage.info/contact). Please provide all the information about the context and how to reproduce the bug.
If possible please join the debugging output of OleFileIO_PL. For this, launch the following command :
OleFileIO_PL.py -d -c file >debug.txt
License
-------
OleFileIO_PL is open-source.
OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec.
The Python Imaging Library (PIL) is
- Copyright (c) 1997-2005 by Secret Labs AB
- Copyright (c) 1995-2005 by Fredrik Lundh
By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission.
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

2130
PIL/OleFileIO.py Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ from __future__ import print_function
from PIL import EpsImagePlugin
##
# Simple Postscript graphics interface.
@ -34,7 +35,7 @@ class PSDraw:
fp = sys.stdout
self.fp = fp
def begin_document(self, id = None):
def begin_document(self, id=None):
"""Set up printing of a document. (Write Postscript DSC header.)"""
# FIXME: incomplete
self.fp.write("%!PS-Adobe-3.0\n"
@ -42,7 +43,7 @@ class PSDraw:
"/showpage { } def\n"
"%%EndComments\n"
"%%BeginDocument\n")
#self.fp.write(ERROR_PS) # debugging!
# self.fp.write(ERROR_PS) # debugging!
self.fp.write(EDROFF_PS)
self.fp.write(VDI_PS)
self.fp.write("%%EndProlog\n")
@ -65,7 +66,7 @@ class PSDraw:
"""
if font not in self.isofont:
# reencode font
self.fp.write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %\
self.fp.write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %
(font, font))
self.isofont[font] = 1
# rough
@ -73,9 +74,8 @@ class PSDraw:
def setink(self, ink):
"""
.. warning::
.. warning:: This has been in the PIL API for ages but was never implemented.
This has been in the PIL API for ages but was never implemented.
"""
print("*** NOT YET IMPLEMENTED ***")
@ -113,14 +113,14 @@ class PSDraw:
xy = xy + (text,)
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."""
# default resolution depends on mode
if not dpi:
if im.mode == "1":
dpi = 200 # fax
dpi = 200 # fax
else:
dpi = 100 # greyscale
dpi = 100 # greyscale
# image size (on paper)
x = float(im.size[0] * 72) / dpi
y = float(im.size[1] * 72) / dpi
@ -128,9 +128,11 @@ class PSDraw:
xmax = float(box[2] - box[0])
ymax = float(box[3] - box[1])
if x > xmax:
y = y * xmax / x; x = xmax
y = y * xmax / x
x = xmax
if y > ymax:
x = x * ymax / y; y = ymax
x = x * ymax / y
y = ymax
dx = (xmax - x) / 2 + box[0]
dy = (ymax - y) / 2 + box[1]
self.fp.write("gsave\n%f %f translate\n" % (dx, dy))

View File

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

View File

@ -12,74 +12,75 @@ __version__ = "1.0"
from PIL import Image, ImageFile, _binary
_Palm8BitColormapValues = (
( 255, 255, 255 ), ( 255, 204, 255 ), ( 255, 153, 255 ), ( 255, 102, 255 ),
( 255, 51, 255 ), ( 255, 0, 255 ), ( 255, 255, 204 ), ( 255, 204, 204 ),
( 255, 153, 204 ), ( 255, 102, 204 ), ( 255, 51, 204 ), ( 255, 0, 204 ),
( 255, 255, 153 ), ( 255, 204, 153 ), ( 255, 153, 153 ), ( 255, 102, 153 ),
( 255, 51, 153 ), ( 255, 0, 153 ), ( 204, 255, 255 ), ( 204, 204, 255 ),
( 204, 153, 255 ), ( 204, 102, 255 ), ( 204, 51, 255 ), ( 204, 0, 255 ),
( 204, 255, 204 ), ( 204, 204, 204 ), ( 204, 153, 204 ), ( 204, 102, 204 ),
( 204, 51, 204 ), ( 204, 0, 204 ), ( 204, 255, 153 ), ( 204, 204, 153 ),
( 204, 153, 153 ), ( 204, 102, 153 ), ( 204, 51, 153 ), ( 204, 0, 153 ),
( 153, 255, 255 ), ( 153, 204, 255 ), ( 153, 153, 255 ), ( 153, 102, 255 ),
( 153, 51, 255 ), ( 153, 0, 255 ), ( 153, 255, 204 ), ( 153, 204, 204 ),
( 153, 153, 204 ), ( 153, 102, 204 ), ( 153, 51, 204 ), ( 153, 0, 204 ),
( 153, 255, 153 ), ( 153, 204, 153 ), ( 153, 153, 153 ), ( 153, 102, 153 ),
( 153, 51, 153 ), ( 153, 0, 153 ), ( 102, 255, 255 ), ( 102, 204, 255 ),
( 102, 153, 255 ), ( 102, 102, 255 ), ( 102, 51, 255 ), ( 102, 0, 255 ),
( 102, 255, 204 ), ( 102, 204, 204 ), ( 102, 153, 204 ), ( 102, 102, 204 ),
( 102, 51, 204 ), ( 102, 0, 204 ), ( 102, 255, 153 ), ( 102, 204, 153 ),
( 102, 153, 153 ), ( 102, 102, 153 ), ( 102, 51, 153 ), ( 102, 0, 153 ),
( 51, 255, 255 ), ( 51, 204, 255 ), ( 51, 153, 255 ), ( 51, 102, 255 ),
( 51, 51, 255 ), ( 51, 0, 255 ), ( 51, 255, 204 ), ( 51, 204, 204 ),
( 51, 153, 204 ), ( 51, 102, 204 ), ( 51, 51, 204 ), ( 51, 0, 204 ),
( 51, 255, 153 ), ( 51, 204, 153 ), ( 51, 153, 153 ), ( 51, 102, 153 ),
( 51, 51, 153 ), ( 51, 0, 153 ), ( 0, 255, 255 ), ( 0, 204, 255 ),
( 0, 153, 255 ), ( 0, 102, 255 ), ( 0, 51, 255 ), ( 0, 0, 255 ),
( 0, 255, 204 ), ( 0, 204, 204 ), ( 0, 153, 204 ), ( 0, 102, 204 ),
( 0, 51, 204 ), ( 0, 0, 204 ), ( 0, 255, 153 ), ( 0, 204, 153 ),
( 0, 153, 153 ), ( 0, 102, 153 ), ( 0, 51, 153 ), ( 0, 0, 153 ),
( 255, 255, 102 ), ( 255, 204, 102 ), ( 255, 153, 102 ), ( 255, 102, 102 ),
( 255, 51, 102 ), ( 255, 0, 102 ), ( 255, 255, 51 ), ( 255, 204, 51 ),
( 255, 153, 51 ), ( 255, 102, 51 ), ( 255, 51, 51 ), ( 255, 0, 51 ),
( 255, 255, 0 ), ( 255, 204, 0 ), ( 255, 153, 0 ), ( 255, 102, 0 ),
( 255, 51, 0 ), ( 255, 0, 0 ), ( 204, 255, 102 ), ( 204, 204, 102 ),
( 204, 153, 102 ), ( 204, 102, 102 ), ( 204, 51, 102 ), ( 204, 0, 102 ),
( 204, 255, 51 ), ( 204, 204, 51 ), ( 204, 153, 51 ), ( 204, 102, 51 ),
( 204, 51, 51 ), ( 204, 0, 51 ), ( 204, 255, 0 ), ( 204, 204, 0 ),
( 204, 153, 0 ), ( 204, 102, 0 ), ( 204, 51, 0 ), ( 204, 0, 0 ),
( 153, 255, 102 ), ( 153, 204, 102 ), ( 153, 153, 102 ), ( 153, 102, 102 ),
( 153, 51, 102 ), ( 153, 0, 102 ), ( 153, 255, 51 ), ( 153, 204, 51 ),
( 153, 153, 51 ), ( 153, 102, 51 ), ( 153, 51, 51 ), ( 153, 0, 51 ),
( 153, 255, 0 ), ( 153, 204, 0 ), ( 153, 153, 0 ), ( 153, 102, 0 ),
( 153, 51, 0 ), ( 153, 0, 0 ), ( 102, 255, 102 ), ( 102, 204, 102 ),
( 102, 153, 102 ), ( 102, 102, 102 ), ( 102, 51, 102 ), ( 102, 0, 102 ),
( 102, 255, 51 ), ( 102, 204, 51 ), ( 102, 153, 51 ), ( 102, 102, 51 ),
( 102, 51, 51 ), ( 102, 0, 51 ), ( 102, 255, 0 ), ( 102, 204, 0 ),
( 102, 153, 0 ), ( 102, 102, 0 ), ( 102, 51, 0 ), ( 102, 0, 0 ),
( 51, 255, 102 ), ( 51, 204, 102 ), ( 51, 153, 102 ), ( 51, 102, 102 ),
( 51, 51, 102 ), ( 51, 0, 102 ), ( 51, 255, 51 ), ( 51, 204, 51 ),
( 51, 153, 51 ), ( 51, 102, 51 ), ( 51, 51, 51 ), ( 51, 0, 51 ),
( 51, 255, 0 ), ( 51, 204, 0 ), ( 51, 153, 0 ), ( 51, 102, 0 ),
( 51, 51, 0 ), ( 51, 0, 0 ), ( 0, 255, 102 ), ( 0, 204, 102 ),
( 0, 153, 102 ), ( 0, 102, 102 ), ( 0, 51, 102 ), ( 0, 0, 102 ),
( 0, 255, 51 ), ( 0, 204, 51 ), ( 0, 153, 51 ), ( 0, 102, 51 ),
( 0, 51, 51 ), ( 0, 0, 51 ), ( 0, 255, 0 ), ( 0, 204, 0 ),
( 0, 153, 0 ), ( 0, 102, 0 ), ( 0, 51, 0 ), ( 17, 17, 17 ),
( 34, 34, 34 ), ( 68, 68, 68 ), ( 85, 85, 85 ), ( 119, 119, 119 ),
( 136, 136, 136 ), ( 170, 170, 170 ), ( 187, 187, 187 ), ( 221, 221, 221 ),
( 238, 238, 238 ), ( 192, 192, 192 ), ( 128, 0, 0 ), ( 128, 0, 128 ),
( 0, 128, 0 ), ( 0, 128, 128 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ))
(255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255),
(255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204),
(255, 153, 204), (255, 102, 204), (255, 51, 204), (255, 0, 204),
(255, 255, 153), (255, 204, 153), (255, 153, 153), (255, 102, 153),
(255, 51, 153), (255, 0, 153), (204, 255, 255), (204, 204, 255),
(204, 153, 255), (204, 102, 255), (204, 51, 255), (204, 0, 255),
(204, 255, 204), (204, 204, 204), (204, 153, 204), (204, 102, 204),
(204, 51, 204), (204, 0, 204), (204, 255, 153), (204, 204, 153),
(204, 153, 153), (204, 102, 153), (204, 51, 153), (204, 0, 153),
(153, 255, 255), (153, 204, 255), (153, 153, 255), (153, 102, 255),
(153, 51, 255), (153, 0, 255), (153, 255, 204), (153, 204, 204),
(153, 153, 204), (153, 102, 204), (153, 51, 204), (153, 0, 204),
(153, 255, 153), (153, 204, 153), (153, 153, 153), (153, 102, 153),
(153, 51, 153), (153, 0, 153), (102, 255, 255), (102, 204, 255),
(102, 153, 255), (102, 102, 255), (102, 51, 255), (102, 0, 255),
(102, 255, 204), (102, 204, 204), (102, 153, 204), (102, 102, 204),
(102, 51, 204), (102, 0, 204), (102, 255, 153), (102, 204, 153),
(102, 153, 153), (102, 102, 153), (102, 51, 153), (102, 0, 153),
( 51, 255, 255), ( 51, 204, 255), ( 51, 153, 255), ( 51, 102, 255),
( 51, 51, 255), ( 51, 0, 255), ( 51, 255, 204), ( 51, 204, 204),
( 51, 153, 204), ( 51, 102, 204), ( 51, 51, 204), ( 51, 0, 204),
( 51, 255, 153), ( 51, 204, 153), ( 51, 153, 153), ( 51, 102, 153),
( 51, 51, 153), ( 51, 0, 153), ( 0, 255, 255), ( 0, 204, 255),
( 0, 153, 255), ( 0, 102, 255), ( 0, 51, 255), ( 0, 0, 255),
( 0, 255, 204), ( 0, 204, 204), ( 0, 153, 204), ( 0, 102, 204),
( 0, 51, 204), ( 0, 0, 204), ( 0, 255, 153), ( 0, 204, 153),
( 0, 153, 153), ( 0, 102, 153), ( 0, 51, 153), ( 0, 0, 153),
(255, 255, 102), (255, 204, 102), (255, 153, 102), (255, 102, 102),
(255, 51, 102), (255, 0, 102), (255, 255, 51), (255, 204, 51),
(255, 153, 51), (255, 102, 51), (255, 51, 51), (255, 0, 51),
(255, 255, 0), (255, 204, 0), (255, 153, 0), (255, 102, 0),
(255, 51, 0), (255, 0, 0), (204, 255, 102), (204, 204, 102),
(204, 153, 102), (204, 102, 102), (204, 51, 102), (204, 0, 102),
(204, 255, 51), (204, 204, 51), (204, 153, 51), (204, 102, 51),
(204, 51, 51), (204, 0, 51), (204, 255, 0), (204, 204, 0),
(204, 153, 0), (204, 102, 0), (204, 51, 0), (204, 0, 0),
(153, 255, 102), (153, 204, 102), (153, 153, 102), (153, 102, 102),
(153, 51, 102), (153, 0, 102), (153, 255, 51), (153, 204, 51),
(153, 153, 51), (153, 102, 51), (153, 51, 51), (153, 0, 51),
(153, 255, 0), (153, 204, 0), (153, 153, 0), (153, 102, 0),
(153, 51, 0), (153, 0, 0), (102, 255, 102), (102, 204, 102),
(102, 153, 102), (102, 102, 102), (102, 51, 102), (102, 0, 102),
(102, 255, 51), (102, 204, 51), (102, 153, 51), (102, 102, 51),
(102, 51, 51), (102, 0, 51), (102, 255, 0), (102, 204, 0),
(102, 153, 0), (102, 102, 0), (102, 51, 0), (102, 0, 0),
( 51, 255, 102), ( 51, 204, 102), ( 51, 153, 102), ( 51, 102, 102),
( 51, 51, 102), ( 51, 0, 102), ( 51, 255, 51), ( 51, 204, 51),
( 51, 153, 51), ( 51, 102, 51), ( 51, 51, 51), ( 51, 0, 51),
( 51, 255, 0), ( 51, 204, 0), ( 51, 153, 0), ( 51, 102, 0),
( 51, 51, 0), ( 51, 0, 0), ( 0, 255, 102), ( 0, 204, 102),
( 0, 153, 102), ( 0, 102, 102), ( 0, 51, 102), ( 0, 0, 102),
( 0, 255, 51), ( 0, 204, 51), ( 0, 153, 51), ( 0, 102, 51),
( 0, 51, 51), ( 0, 0, 51), ( 0, 255, 0), ( 0, 204, 0),
( 0, 153, 0), ( 0, 102, 0), ( 0, 51, 0), ( 17, 17, 17),
( 34, 34, 34), ( 68, 68, 68), ( 85, 85, 85), (119, 119, 119),
(136, 136, 136), (170, 170, 170), (187, 187, 187), (221, 221, 221),
(238, 238, 238), (192, 192, 192), (128, 0, 0), (128, 0, 128),
( 0, 128, 0), ( 0, 128, 128), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0))
# so build a prototype image to be used for palette resampling
def build_prototype_image():
image = Image.new("L", (1,len(_Palm8BitColormapValues),))
image = Image.new("L", (1, len(_Palm8BitColormapValues),))
image.putdata(list(range(len(_Palm8BitColormapValues))))
palettedata = ()
for i in range(len(_Palm8BitColormapValues)):
@ -91,7 +92,8 @@ def build_prototype_image():
Palm8BitColormapImage = build_prototype_image()
# OK, we now have in Palm8BitColormapImage, a "P"-mode image with the right palette
# OK, we now have in Palm8BitColormapImage,
# a "P"-mode image with the right palette
#
# --------------------------------------------------------------------
@ -110,6 +112,7 @@ _COMPRESSION_TYPES = {
o8 = _binary.o8
o16b = _binary.o16be
#
# --------------------------------------------------------------------
@ -127,12 +130,16 @@ def _save(im, fp, filename, check=0):
bpp = 8
version = 1
elif im.mode == "L" and "bpp" in im.encoderinfo and im.encoderinfo["bpp"] in (1, 2, 4):
elif (im.mode == "L" and
"bpp" in im.encoderinfo and
im.encoderinfo["bpp"] in (1, 2, 4)):
# this is 8-bit grayscale, so we shift it to get the high-order bits, and invert it because
# this is 8-bit grayscale, so we shift it to get the high-order bits,
# and invert it because
# Palm does greyscale from white (0) to black (1)
bpp = im.encoderinfo["bpp"]
im = im.point(lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift))
im = im.point(
lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift))
# we ignore the palette here
im.mode = "P"
rawmode = "P;" + str(bpp)
@ -140,8 +147,9 @@ def _save(im, fp, filename, check=0):
elif im.mode == "L" and "bpp" in im.info and im.info["bpp"] in (1, 2, 4):
# here we assume that even though the inherent mode is 8-bit grayscale, only
# the lower bpp bits are significant. We invert them to match the Palm.
# here we assume that even though the inherent mode is 8-bit grayscale,
# only the lower bpp bits are significant.
# We invert them to match the Palm.
bpp = im.info["bpp"]
im = im.point(lambda x, maxval=(1 << bpp)-1: maxval - (x & maxval))
# we ignore the palette here
@ -172,21 +180,21 @@ def _save(im, fp, filename, check=0):
cols = im.size[0]
rows = im.size[1]
rowbytes = ((cols + (16//bpp - 1)) / (16 // bpp)) * 2;
rowbytes = int((cols + (16//bpp - 1)) / (16 // bpp)) * 2
transparent_index = 0
compression_type = _COMPRESSION_TYPES["none"]
flags = 0;
flags = 0
if im.mode == "P" and "custom-colormap" in im.info:
flags = flags & _FLAGS["custom-colormap"]
colormapsize = 4 * 256 + 2;
colormapsize = 4 * 256 + 2
colormapmode = im.palette.mode
colormap = im.getdata().getpalette()
else:
colormapsize = 0
if "offset" in im.info:
offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4;
offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4
else:
offset = 0
@ -205,12 +213,19 @@ def _save(im, fp, filename, check=0):
for i in range(256):
fp.write(o8(i))
if colormapmode == 'RGB':
fp.write(o8(colormap[3 * i]) + o8(colormap[3 * i + 1]) + o8(colormap[3 * i + 2]))
fp.write(
o8(colormap[3 * i]) +
o8(colormap[3 * i + 1]) +
o8(colormap[3 * i + 2]))
elif colormapmode == 'RGBA':
fp.write(o8(colormap[4 * i]) + o8(colormap[4 * i + 1]) + o8(colormap[4 * i + 2]))
fp.write(
o8(colormap[4 * i]) +
o8(colormap[4 * i + 1]) +
o8(colormap[4 * i + 2]))
# now convert data to raw form
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, rowbytes, 1))])
ImageFile._save(
im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))])
fp.flush()

View File

@ -22,6 +22,7 @@ from PIL import Image, ImageFile, _binary
i8 = _binary.i8
##
# Image plugin for PhotoCD images. This plugin only reads the 768x512
# image from the file; higher resolutions are encoded in a proprietary
@ -43,13 +44,13 @@ class PcdImageFile(ImageFile.ImageFile):
orientation = i8(s[1538]) & 3
if orientation == 1:
self.tile_post_rotate = 90 # hack
self.tile_post_rotate = 90 # hack
elif orientation == 3:
self.tile_post_rotate = -90
self.mode = "RGB"
self.size = 768, 512 # FIXME: not correct for rotated images!
self.tile = [("pcd", (0,0)+self.size, 96*2048, None)]
self.size = 768, 512 # FIXME: not correct for rotated images!
self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)]
def draft(self, mode, size):
@ -60,7 +61,7 @@ class PcdImageFile(ImageFile.ImageFile):
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)]:
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]

View File

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

View File

@ -33,9 +33,11 @@ i8 = _binary.i8
i16 = _binary.i16le
o8 = _binary.o8
def _accept(prefix):
return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
##
# Image plugin for Paintbrush images.
@ -52,17 +54,22 @@ class PcxImageFile(ImageFile.ImageFile):
raise SyntaxError("not a PCX file")
# 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]:
raise SyntaxError("bad PCX image size")
if Image.DEBUG:
print ("BBox: %s %s %s %s" % bbox)
# format
version = i8(s[1])
bits = i8(s[3])
planes = i8(s[65])
stride = i16(s,66)
stride = i16(s, 66)
if Image.DEBUG:
print ("PCX version %s, bits %s, planes %s, stride %s" %
(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:
mode = rawmode = "1"
@ -98,6 +105,8 @@ class PcxImageFile(ImageFile.ImageFile):
self.size = bbox[2]-bbox[0], bbox[3]-bbox[1]
bbox = (0, 0) + self.size
if Image.DEBUG:
print ("size: %sx%s" % self.size)
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
@ -114,6 +123,7 @@ SAVE = {
o16 = _binary.o16le
def _save(im, fp, filename, check=0):
try:
@ -126,6 +136,15 @@ def _save(im, fp, filename, check=0):
# bytes per plane
stride = (im.size[0] * bits + 7) // 8
# stride should be even
stride += stride % 2
# Stride needs to be kept in sync with the PcxEncode.c version.
# Ideally it should be passed in in the state, but the bytes value
# gets overwritten.
if Image.DEBUG:
print ("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
im.size[0], bits, stride))
# under windows, we could determine the current screen size with
# "Image.core.display_mode()[1]", but I think that's overkill...
@ -145,13 +164,13 @@ def _save(im, fp, filename, check=0):
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))])
if im.mode == "P":
# colour palette
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":
# greyscale palette
fp.write(o8(12))

View File

@ -46,9 +46,11 @@ def _obj(fp, obj, **dict):
fp.write("/%s %s\n" % (k, v))
fp.write(">>\n")
def _endobj(fp):
fp.write("endobj\n")
##
# (Internal) Image save plugin for the PDF format.
@ -59,13 +61,15 @@ def _save(im, fp, filename):
# make sure image data is available
im.load()
xref = [0]*(5+1) # placeholders
xref = [0]*(5+1) # placeholders
class TextWriter:
def __init__(self, fp):
self.fp = fp
def __getattr__(self, name):
return getattr(self.fp, name)
def write(self, value):
self.fp.write(value.encode('latin-1'))
@ -89,13 +93,13 @@ def _save(im, fp, filename):
if im.mode == "1":
filter = "/ASCIIHexDecode"
colorspace = "/DeviceGray"
procset = "/ImageB" # grayscale
procset = "/ImageB" # grayscale
bits = 1
elif im.mode == "L":
filter = "/DCTDecode"
# params = "<< /Predictor 15 /Columns %d >>" % (width-2)
colorspace = "/DeviceGray"
procset = "/ImageB" # grayscale
procset = "/ImageB" # grayscale
elif im.mode == "P":
filter = "/ASCIIHexDecode"
colorspace = "[ /Indexed /DeviceRGB 255 <"
@ -104,17 +108,17 @@ def _save(im, fp, filename):
r = i8(palette[i*3])
g = i8(palette[i*3+1])
b = i8(palette[i*3+2])
colorspace = colorspace + "%02x%02x%02x " % (r, g, b)
colorspace = colorspace + b"> ]"
procset = "/ImageI" # indexed color
colorspace += "%02x%02x%02x " % (r, g, b)
colorspace += "> ]"
procset = "/ImageI" # indexed color
elif im.mode == "RGB":
filter = "/DCTDecode"
colorspace = "/DeviceRGB"
procset = "/ImageC" # color images
procset = "/ImageC" # color images
elif im.mode == "CMYK":
filter = "/DCTDecode"
colorspace = "/DeviceCMYK"
procset = "/ImageC" # color images
procset = "/ImageC" # color images
else:
raise ValueError("cannot save mode %s" % im.mode)
@ -122,17 +126,21 @@ def _save(im, fp, filename):
# catalogue
xref[1] = fp.tell()
_obj(fp, 1, Type = "/Catalog",
Pages = "2 0 R")
_obj(
fp, 1,
Type="/Catalog",
Pages="2 0 R")
_endobj(fp)
#
# pages
xref[2] = fp.tell()
_obj(fp, 2, Type = "/Pages",
Count = 1,
Kids = "[4 0 R]")
_obj(
fp, 2,
Type="/Pages",
Count=1,
Kids="[4 0 R]")
_endobj(fp)
#
@ -144,29 +152,31 @@ def _save(im, fp, filename):
if bits == 1:
# FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way...
data = im.tostring("raw", "1")
data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None)
im.putdata(data)
ImageFile._save(im, op, [("hex", (0,0)+im.size, 0, im.mode)])
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0,0)+im.size, 0, im.mode)])
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0,0)+im.size, 0, im.mode)])
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else:
raise ValueError("unsupported PDF filter (%s)" % filter)
xref[3] = fp.tell()
_obj(fp, 3, Type = "/XObject",
Subtype = "/Image",
Width = width, # * 72.0 / resolution,
Height = height, # * 72.0 / resolution,
Length = len(op.getvalue()),
Filter = filter,
BitsPerComponent = bits,
DecodeParams = params,
ColorSpace = colorspace)
_obj(
fp, 3,
Type="/XObject",
Subtype="/Image",
Width=width, # * 72.0 / resolution,
Height=height, # * 72.0 / resolution,
Length=len(op.getvalue()),
Filter=filter,
BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
fp.write("stream\n")
fp.fp.write(op.getvalue())
@ -179,11 +189,14 @@ def _save(im, fp, filename):
xref[4] = fp.tell()
_obj(fp, 4)
fp.write("<<\n/Type /Page\n/Parent 2 0 R\n"\
"/Resources <<\n/ProcSet [ /PDF %s ]\n"\
"/XObject << /image 3 0 R >>\n>>\n"\
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" %\
(procset, int(width * 72.0 /resolution) , int(height * 72.0 / resolution)))
fp.write(
"<<\n/Type /Page\n/Parent 2 0 R\n"
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/XObject << /image 3 0 R >>\n>>\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % (
procset,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
_endobj(fp)
#
@ -191,10 +204,13 @@ def _save(im, fp, filename):
op = TextWriter(io.BytesIO())
op.write("q %d 0 0 %d 0 0 cm /image Do Q\n" % (int(width * 72.0 / resolution), int(height * 72.0 / resolution)))
op.write(
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
xref[5] = fp.tell()
_obj(fp, 5, Length = len(op.fp.getvalue()))
_obj(fp, 5, Length=len(op.fp.getvalue()))
fp.write("stream\n")
fp.fp.write(op.fp.getvalue())

View File

@ -29,6 +29,7 @@ from PIL import Image, ImageFile, _binary
i16 = _binary.i16le
i32 = _binary.i32le
##
# Image plugin for PIXAR raster images.
@ -57,7 +58,7 @@ class PixarImageFile(ImageFile.ImageFile):
# FIXME: to be continued...
# 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,26 +52,27 @@ _MAGIC = b"\211PNG\r\n\032\n"
_MODES = {
# supported bits/color combinations, and corresponding modes/rawmodes
(1, 0): ("1", "1"),
(2, 0): ("L", "L;2"),
(4, 0): ("L", "L;4"),
(8, 0): ("L", "L"),
(16,0): ("I", "I;16B"),
(8, 2): ("RGB", "RGB"),
(16,2): ("RGB", "RGB;16B"),
(1, 3): ("P", "P;1"),
(2, 3): ("P", "P;2"),
(4, 3): ("P", "P;4"),
(8, 3): ("P", "P"),
(8, 4): ("LA", "LA"),
(16,4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
(8, 6): ("RGBA", "RGBA"),
(16,6): ("RGBA", "RGBA;16B"),
(1, 0): ("1", "1"),
(2, 0): ("L", "L;2"),
(4, 0): ("L", "L;4"),
(8, 0): ("L", "L"),
(16, 0): ("I", "I;16B"),
(8, 2): ("RGB", "RGB"),
(16, 2): ("RGB", "RGB;16B"),
(1, 3): ("P", "P;1"),
(2, 3): ("P", "P;2"),
(4, 3): ("P", "P;4"),
(8, 3): ("P", "P"),
(8, 4): ("LA", "LA"),
(16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
(8, 6): ("RGBA", "RGBA"),
(16, 6): ("RGBA", "RGBA;16B"),
}
_simple_palette = re.compile(b'^\xff+\x00\xff*$')
# --------------------------------------------------------------------
# Support classes. Suitable for PNG and related formats like MNG etc.
@ -89,33 +90,33 @@ class ChunkStream:
"Fetch a new chunk. Returns header information."
if self.queue:
cid, pos, len = self.queue[-1]
cid, pos, length = self.queue[-1]
del self.queue[-1]
self.fp.seek(pos)
else:
s = self.fp.read(8)
cid = s[4:]
pos = self.fp.tell()
len = i32(s)
length = i32(s)
if not is_cid(cid):
raise SyntaxError("broken PNG file (chunk %s)" % repr(cid))
return cid, pos, len
return cid, pos, length
def close(self):
self.queue = self.crc = self.fp = None
def push(self, cid, pos, len):
def push(self, cid, pos, length):
self.queue.append((cid, pos, len))
self.queue.append((cid, pos, length))
def call(self, cid, pos, len):
def call(self, cid, pos, length):
"Call the appropriate chunk handler"
if Image.DEBUG:
print("STREAM", cid, pos, len)
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, len)
print("STREAM", cid, pos, length)
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length)
def crc(self, cid, data):
"Read and verify checksum"
@ -123,15 +124,15 @@ class ChunkStream:
crc1 = Image.core.crc32(data, Image.core.crc32(cid))
crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
if crc1 != crc2:
raise SyntaxError("broken PNG file"\
"(bad header checksum in %s)" % cid)
raise SyntaxError("broken PNG file"
"(bad header checksum in %s)" % cid)
def crc_skip(self, cid, data):
"Read checksum. Used if the C module is not present"
self.fp.read(4)
def verify(self, endchunk = b"IEND"):
def verify(self, endchunk=b"IEND"):
# Simple approach; just calculate checksum for all remaining
# blocks. Must be called directly after open.
@ -139,15 +140,28 @@ class ChunkStream:
cids = []
while True:
cid, pos, len = self.read()
cid, pos, length = self.read()
if cid == endchunk:
break
self.crc(cid, ImageFile._safe_read(self.fp, len))
self.crc(cid, ImageFile._safe_read(self.fp, length))
cids.append(cid)
return cids
# --------------------------------------------------------------------
# Subclass of string to allow iTXt chunks to look like strings while
# keeping their extra information
class iTXt(str):
@staticmethod
def __new__(cls, text, lang, tkey):
self = str.__new__(cls, text)
self.lang = lang
self.tkey = tkey
return self
# --------------------------------------------------------------------
# PNG chunk container (for use with save(pnginfo=))
@ -159,20 +173,43 @@ class PngInfo:
def add(self, cid, data):
self.chunks.append((cid, data))
def add_itxt(self, key, value, lang="", tkey="", zip=False):
if not isinstance(key, bytes):
key = key.encode("latin-1", "strict")
if not isinstance(value, bytes):
value = value.encode("utf-8", "strict")
if not isinstance(lang, bytes):
lang = lang.encode("utf-8", "strict")
if not isinstance(tkey, bytes):
tkey = tkey.encode("utf-8", "strict")
if zip:
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" +
zlib.compress(value))
else:
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" +
value)
def add_text(self, key, value, zip=0):
if isinstance(value, iTXt):
return self.add_itxt(key, value, value.lang, value.tkey, bool(zip))
# The tEXt chunk stores latin-1 text
if not isinstance(value, bytes):
try:
value = value.encode('latin-1', 'strict')
except UnicodeError:
return self.add_itxt(key, value, zip=bool(zip))
if not isinstance(key, bytes):
key = key.encode('latin-1', 'strict')
if not isinstance(value, bytes):
value = value.encode('latin-1', 'replace')
if zip:
import zlib
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
else:
self.add(b"tEXt", key + b"\0" + value)
# --------------------------------------------------------------------
# PNG image stream (IHDR/IEND)
@ -185,15 +222,15 @@ class PngStream(ChunkStream):
# local copies of Image attributes
self.im_info = {}
self.im_text = {}
self.im_size = (0,0)
self.im_size = (0, 0)
self.im_mode = None
self.im_tile = None
self.im_palette = None
def chunk_iCCP(self, pos, len):
def chunk_iCCP(self, pos, length):
# ICC profile
s = ImageFile._safe_read(self.fp, len)
s = ImageFile._safe_read(self.fp, length)
# according to PNG spec, the iCCP chunk contains:
# Profile name 1-79 bytes (character string)
# Null separator 1 byte (null character)
@ -205,18 +242,19 @@ class PngStream(ChunkStream):
print("Compression method", i8(s[i]))
comp_method = i8(s[i])
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:
icc_profile = zlib.decompress(s[i+2:])
except zlib.error:
icc_profile = None # FIXME
icc_profile = None # FIXME
self.im_info["icc_profile"] = icc_profile
return s
def chunk_IHDR(self, pos, len):
def chunk_IHDR(self, pos, length):
# image header
s = ImageFile._safe_read(self.fp, len)
s = ImageFile._safe_read(self.fp, length)
self.im_size = i32(s), i32(s[4:])
try:
self.im_mode, self.im_rawmode = _MODES[(i8(s[8]), i8(s[9]))]
@ -228,30 +266,30 @@ class PngStream(ChunkStream):
raise SyntaxError("unknown filter category")
return s
def chunk_IDAT(self, pos, len):
def chunk_IDAT(self, pos, length):
# image data
self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
self.im_idat = len
self.im_tile = [("zip", (0, 0)+self.im_size, pos, self.im_rawmode)]
self.im_idat = length
raise EOFError
def chunk_IEND(self, pos, len):
def chunk_IEND(self, pos, length):
# end of PNG image
raise EOFError
def chunk_PLTE(self, pos, len):
def chunk_PLTE(self, pos, length):
# palette
s = ImageFile._safe_read(self.fp, len)
s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P":
self.im_palette = "RGB", s
return s
def chunk_tRNS(self, pos, len):
def chunk_tRNS(self, pos, length):
# transparency
s = ImageFile._safe_read(self.fp, len)
s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P":
if _simple_palette.match(s):
i = s.find(b"\0")
@ -265,34 +303,36 @@ class PngStream(ChunkStream):
self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
return s
def chunk_gAMA(self, pos, len):
def chunk_gAMA(self, pos, length):
# gamma setting
s = ImageFile._safe_read(self.fp, len)
s = ImageFile._safe_read(self.fp, length)
self.im_info["gamma"] = i32(s) / 100000.0
return s
def chunk_pHYs(self, pos, len):
def chunk_pHYs(self, pos, length):
# pixels per unit
s = ImageFile._safe_read(self.fp, len)
s = ImageFile._safe_read(self.fp, length)
px, py = i32(s), i32(s[4:])
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)
self.im_info["dpi"] = dpi
elif unit == 0:
self.im_info["aspect"] = px, py
return s
def chunk_tEXt(self, pos, len):
def chunk_tEXt(self, pos, length):
# text
s = ImageFile._safe_read(self.fp, len)
s = ImageFile._safe_read(self.fp, length)
try:
k, v = s.split(b"\0", 1)
except ValueError:
k = s; v = b"" # fallback for broken tEXt tags
# fallback for broken tEXt tags
k = s
v = b""
if k:
if bytes is not str:
k = k.decode('latin-1', 'strict')
@ -301,21 +341,22 @@ class PngStream(ChunkStream):
self.im_info[k] = self.im_text[k] = v
return s
def chunk_zTXt(self, pos, len):
def chunk_zTXt(self, pos, length):
# compressed text
s = ImageFile._safe_read(self.fp, len)
s = ImageFile._safe_read(self.fp, length)
try:
k, v = s.split(b"\0", 1)
except ValueError:
k = s; v = b""
k = s
v = b""
if v:
comp_method = i8(v[0])
else:
comp_method = 0
if comp_method != 0:
raise SyntaxError("Unknown compression method %s in zTXt chunk" % comp_method)
import zlib
raise SyntaxError("Unknown compression method %s in zTXt chunk" %
comp_method)
try:
v = zlib.decompress(v[1:])
except zlib.error:
@ -329,12 +370,50 @@ class PngStream(ChunkStream):
self.im_info[k] = self.im_text[k] = v
return s
def chunk_iTXt(self, pos, length):
# international text
r = s = ImageFile._safe_read(self.fp, length)
try:
k, r = r.split(b"\0", 1)
except ValueError:
return s
if len(r) < 2:
return s
cf, cm, r = i8(r[0]), i8(r[1]), r[2:]
try:
lang, tk, v = r.split(b"\0", 2)
except ValueError:
return s
if cf != 0:
if cm == 0:
try:
v = zlib.decompress(v)
except zlib.error:
return s
else:
return s
if bytes is not str:
try:
k = k.decode("latin-1", "strict")
lang = lang.decode("utf-8", "strict")
tk = tk.decode("utf-8", "strict")
v = v.decode("utf-8", "strict")
except UnicodeError:
return s
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
return s
# --------------------------------------------------------------------
# PNG reader
def _accept(prefix):
return prefix[:8] == _MAGIC
##
# Image plugin for PNG images.
@ -358,16 +437,16 @@ class PngImageFile(ImageFile.ImageFile):
#
# get next chunk
cid, pos, len = self.png.read()
cid, pos, length = self.png.read()
try:
s = self.png.call(cid, pos, len)
s = self.png.call(cid, pos, length)
except EOFError:
break
except AttributeError:
if Image.DEBUG:
print(cid, pos, len, "(unknown)")
s = ImageFile._safe_read(self.fp, len)
print(cid, pos, length, "(unknown)")
s = ImageFile._safe_read(self.fp, length)
self.png.crc(cid, s)
@ -381,15 +460,14 @@ class PngImageFile(ImageFile.ImageFile):
self.mode = self.png.im_mode
self.size = self.png.im_size
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
if self.png.im_palette:
rawmode, data = self.png.im_palette
self.palette = ImagePalette.raw(rawmode, data)
self.__idat = len # used by load_read()
self.__idat = length # used by load_read()
def verify(self):
"Verify PNG file"
@ -413,32 +491,31 @@ class PngImageFile(ImageFile.ImageFile):
ImageFile.ImageFile.load_prepare(self)
def load_read(self, bytes):
def load_read(self, read_bytes):
"internal: read more image data"
while self.__idat == 0:
# end of chunk, skip forward to next one
self.fp.read(4) # CRC
self.fp.read(4) # CRC
cid, pos, len = self.png.read()
cid, pos, length = self.png.read()
if cid not in [b"IDAT", b"DDAT"]:
self.png.push(cid, pos, len)
self.png.push(cid, pos, length)
return b""
self.__idat = len # empty chunks are allowed
self.__idat = length # empty chunks are allowed
# read more data from this chunk
if bytes <= 0:
bytes = self.__idat
if read_bytes <= 0:
read_bytes = self.__idat
else:
bytes = min(bytes, self.__idat)
read_bytes = min(read_bytes, self.__idat)
self.__idat = self.__idat - bytes
return self.fp.read(bytes)
self.__idat = self.__idat - read_bytes
return self.fp.read(read_bytes)
def load_end(self):
"internal: finished reading image data"
@ -456,21 +533,22 @@ o32 = _binary.o32be
_OUTMODES = {
# supported PIL modes, and corresponding rawmodes/bits/color combinations
"1": ("1", b'\x01\x00'),
"L;1": ("L;1", b'\x01\x00'),
"L;2": ("L;2", b'\x02\x00'),
"L;4": ("L;4", b'\x04\x00'),
"L": ("L", b'\x08\x00'),
"LA": ("LA", b'\x08\x04'),
"I": ("I;16B", b'\x10\x00'),
"P;1": ("P;1", b'\x01\x03'),
"P;2": ("P;2", b'\x02\x03'),
"P;4": ("P;4", b'\x04\x03'),
"P": ("P", b'\x08\x03'),
"RGB": ("RGB", b'\x08\x02'),
"RGBA":("RGBA", b'\x08\x06'),
"1": ("1", b'\x01\x00'),
"L;1": ("L;1", b'\x01\x00'),
"L;2": ("L;2", b'\x02\x00'),
"L;4": ("L;4", b'\x04\x00'),
"L": ("L", b'\x08\x00'),
"LA": ("LA", b'\x08\x04'),
"I": ("I;16B", b'\x10\x00'),
"P;1": ("P;1", b'\x01\x03'),
"P;2": ("P;2", b'\x02\x03'),
"P;4": ("P;4", b'\x04\x03'),
"P": ("P", b'\x08\x03'),
"RGB": ("RGB", b'\x08\x02'),
"RGBA": ("RGBA", b'\x08\x06'),
}
def putchunk(fp, cid, *data):
"Write a PNG chunk (including CRC field)"
@ -481,15 +559,18 @@ def putchunk(fp, cid, *data):
hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
fp.write(o16(hi) + o16(lo))
class _idat:
# wrap output from the encoder in IDAT chunks
def __init__(self, fp, chunk):
self.fp = fp
self.chunk = chunk
def write(self, data):
self.chunk(self.fp, b"IDAT", data)
def _save(im, fp, filename, chunk=putchunk, check=0):
# save an image to disk (called by the save method)
@ -505,7 +586,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
else:
# check palette contents
if im.palette:
colors = len(im.palette.getdata()[1])//3
colors = max(min(len(im.palette.getdata()[1])//3, 256), 2)
else:
colors = 256
@ -527,9 +608,9 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
dictionary = b""
im.encoderconfig = ("optimize" in im.encoderinfo,
im.encoderinfo.get("compress_level", -1),
im.encoderinfo.get("compress_type", -1),
dictionary)
im.encoderinfo.get("compress_level", -1),
im.encoderinfo.get("compress_type", -1),
dictionary)
# get the corresponding PNG mode
try:
@ -546,8 +627,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
fp.write(_MAGIC)
chunk(fp, b"IHDR",
o32(im.size[0]), o32(im.size[1]), # 0: size
mode, # 8: depth/type
o32(im.size[0]), o32(im.size[1]), # 0: size
mode, # 8: depth/type
b'\0', # 10: compression
b'\0', # 11: filter category
b'\0') # 12: interlace flag
@ -559,24 +640,30 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
palette_bytes += b'\0'
chunk(fp, b"PLTE", palette_bytes)
if "transparency" in im.encoderinfo:
transparency = im.encoderinfo.get('transparency',
im.info.get('transparency', None))
if transparency or transparency == 0:
if im.mode == "P":
# limit to actual palette size
alpha_bytes = 2**bits
if isinstance(im.encoderinfo["transparency"], bytes):
chunk(fp, b"tRNS", im.encoderinfo["transparency"][:alpha_bytes])
if isinstance(transparency, bytes):
chunk(fp, b"tRNS", transparency[:alpha_bytes])
else:
transparency = max(0, min(255, im.encoderinfo["transparency"]))
transparency = max(0, min(255, transparency))
alpha = b'\xFF' * transparency + b'\0'
chunk(fp, b"tRNS", alpha[:alpha_bytes])
elif im.mode == "L":
transparency = max(0, min(65535, im.encoderinfo["transparency"]))
transparency = max(0, min(65535, transparency))
chunk(fp, b"tRNS", o16(transparency))
elif im.mode == "RGB":
red, green, blue = im.encoderinfo["transparency"]
red, green, blue = transparency
chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue))
else:
raise IOError("cannot use transparency for this mode")
if "transparency" in im.encoderinfo:
# don't bother with transparency if it's an RGBA
# and it's in the info dict. It's probably just stale.
raise IOError("cannot use transparency for this mode")
else:
if im.mode == "P" and im.im.getpalettemode() == "RGBA":
alpha = im.im.getpalette("RGBA", "A")
@ -600,23 +687,19 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
chunk(fp, cid, data)
# ICC profile writing support -- 2008-06-06 Florian Hoech
if "icc_profile" in im.info:
if im.info.get("icc_profile"):
# ICC profile
# according to PNG spec, the iCCP chunk contains:
# Profile name 1-79 bytes (character string)
# Null separator 1 byte (null character)
# Compression method 1 byte (0)
# Compressed profile n bytes (zlib with deflate compression)
try:
import ICCProfile
p = ICCProfile.ICCProfile(im.info["icc_profile"])
name = p.tags.desc.get("ASCII", p.tags.desc.get("Unicode", p.tags.desc.get("Macintosh", p.tags.desc.get("en", {}).get("US", "ICC Profile")))).encode("latin1", "replace")[:79]
except ImportError:
name = b"ICC Profile"
name = b"ICC Profile"
data = name + b"\0\0" + zlib.compress(im.info["icc_profile"])
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"")
@ -634,8 +717,10 @@ def getchunks(im, **params):
class collector:
data = []
def write(self, data):
pass
def append(self, chunk):
self.data.append(chunk)

View File

@ -27,12 +27,13 @@ from PIL import Image, ImageFile
b_whitespace = string.whitespace
try:
import locale
locale_lang,locale_enc = locale.getlocale()
locale_lang, locale_enc = locale.getlocale()
if locale_enc is None:
locale_lang,locale_enc = locale.getdefaultlocale()
locale_lang, locale_enc = locale.getdefaultlocale()
b_whitespace = b_whitespace.decode(locale_enc)
except: pass
b_whitespace = b_whitespace.encode('ascii','ignore')
except:
pass
b_whitespace = b_whitespace.encode('ascii', 'ignore')
MODES = {
# standard
@ -47,9 +48,11 @@ MODES = {
b"PyCMYK": "CMYK"
}
def _accept(prefix):
return prefix[0:1] == b"P" and prefix[1] in b"0456y"
##
# Image plugin for PBM, PGM, and PPM images.
@ -58,12 +61,16 @@ class PpmImageFile(ImageFile.ImageFile):
format = "PPM"
format_description = "Pbmplus image"
def _token(self, s = b""):
while True: # read until next whitespace
def _token(self, s=b""):
while True: # read until next whitespace
c = self.fp.read(1)
if not c or c in b_whitespace:
break
if c > b'\x79':
raise ValueError("Expected ASCII value, found binary")
s = s + c
if (len(s) > 9):
raise ValueError("Expected int, got > 9 digits")
return s
def _open(self):
@ -96,6 +103,17 @@ class PpmImageFile(ImageFile.ImageFile):
ysize = s
if mode == "1":
break
elif ix == 2:
# maxgrey
if s > 255:
if not mode == 'L':
raise ValueError("Too many colors for band: %s" % s)
if s < 2**16:
self.mode = 'I'
rawmode = 'I;16B'
else:
self.mode = 'I'
rawmode = 'I;32B'
self.size = xsize, ysize
self.tile = [("raw",
@ -108,6 +126,7 @@ class PpmImageFile(ImageFile.ImageFile):
# self.mode = self.im.mode
# self.size = self.im.size
#
# --------------------------------------------------------------------
@ -116,6 +135,11 @@ def _save(im, fp, filename):
rawmode, head = "1;I", b"P4"
elif im.mode == "L":
rawmode, head = "L", b"P5"
elif im.mode == "I":
if im.getextrema()[1] < 2**16:
rawmode, head = "I;16B", b"P5"
else:
rawmode, head = "I;32B", b"P5"
elif im.mode == "RGB":
rawmode, head = "RGB", b"P6"
elif im.mode == "RGBA":
@ -123,9 +147,16 @@ def _save(im, fp, filename):
else:
raise IOError("cannot write mode %s as PPM" % im.mode)
fp.write(head + ("\n%d %d\n" % im.size).encode('ascii'))
if head != b"P4":
if head == b"P6":
fp.write(b"255\n")
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))])
if head == b"P5":
if rawmode == "L":
fp.write(b"255\n")
elif rawmode == "I;16B":
fp.write(b"65535\n")
elif rawmode == "I;32B":
fp.write(b"2147483648\n")
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
# ALTERNATIVE: save via builtin debug function
# im._dump(filename)

View File

@ -28,8 +28,8 @@ MODES = {
(2, 8): ("P", 1),
(3, 8): ("RGB", 3),
(4, 8): ("CMYK", 4),
(7, 8): ("L", 1), # FIXME: multilayer
(8, 8): ("L", 1), # duotone
(7, 8): ("L", 1), # FIXME: multilayer
(8, 8): ("L", 1), # duotone
(9, 8): ("LAB", 3)
}
@ -40,12 +40,14 @@ i8 = _binary.i8
i16 = _binary.i16be
i32 = _binary.i32be
# --------------------------------------------------------------------.
# read PSD images
def _accept(prefix):
return prefix[:4] == b"8BPS"
##
# Image plugin for Photoshop images.
@ -100,12 +102,12 @@ class PsdImageFile(ImageFile.ImageFile):
id = i16(read(2))
name = read(i8(read(1)))
if not (len(name) & 1):
read(1) # padding
read(1) # padding
data = read(i32(read(4)))
if (len(data) & 1):
read(1) # padding
read(1) # padding
self.resources.append((id, name, data))
if id == 1039: # ICC profile
if id == 1039: # ICC profile
self.info["icc_profile"] = data
#
@ -159,6 +161,7 @@ class PsdImageFile(ImageFile.ImageFile):
if self.mode == "P":
Image.Image.load(self)
def _layerinfo(file):
# read layerinfo block
layers = []
@ -166,8 +169,10 @@ def _layerinfo(file):
for i in range(abs(i16(read(2)))):
# bounding box
y0 = i32(read(4)); x0 = i32(read(4))
y1 = i32(read(4)); x1 = i32(read(4))
y0 = i32(read(4))
x0 = i32(read(4))
y1 = i32(read(4))
x1 = i32(read(4))
# image info
info = []
@ -197,7 +202,7 @@ def _layerinfo(file):
elif mode == ["A", "B", "G", "R"]:
mode = "RGBA"
else:
mode = None # unknown
mode = None # unknown
# skip over blend flags and extra information
filler = read(12)
@ -207,8 +212,10 @@ def _layerinfo(file):
if size:
length = i32(read(4))
if length:
mask_y = i32(read(4)); mask_x = i32(read(4))
mask_h = i32(read(4)) - mask_y; mask_w = i32(read(4)) - mask_x
mask_y = i32(read(4))
mask_x = i32(read(4))
mask_h = i32(read(4)) - mask_y
mask_w = i32(read(4)) - mask_x
file.seek(length - 16, 1)
combined += length + 4
@ -219,7 +226,8 @@ def _layerinfo(file):
length = i8(read(1))
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')
combined += length + 1
@ -235,10 +243,11 @@ def _layerinfo(file):
if t:
tile.extend(t)
layers[i] = name, mode, bbox, tile
i = i + 1
i += 1
return layers
def _maketile(file, mode, bbox, channels):
tile = None
@ -258,7 +267,7 @@ def _maketile(file, mode, bbox, channels):
for channel in range(channels):
layer = mode[channel]
if mode == "CMYK":
layer = layer + ";I"
layer += ";I"
tile.append(("raw", bbox, offset, layer))
offset = offset + xsize*ysize
@ -272,18 +281,18 @@ def _maketile(file, mode, bbox, channels):
for channel in range(channels):
layer = mode[channel]
if mode == "CMYK":
layer = layer + ";I"
layer += ";I"
tile.append(
("packbits", bbox, offset, layer)
)
for y in range(ysize):
offset = offset + i16(bytecount[i:i+2])
i = i + 2
i += 2
file.seek(offset)
if offset & 1:
read(1) # padding
read(1) # padding
return tile

313
PIL/PyAccess.py Normal file
View File

@ -0,0 +1,313 @@
#
# The Python Imaging Library
# Pillow fork
#
# Python implementation of the PixelAccess Object
#
# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved.
# Copyright (c) 1995-2009 by Fredrik Lundh.
# Copyright (c) 2013 Eric Soroos
#
# See the README file for information on usage and redistribution
#
# Notes:
#
# * Implements the pixel access object following Access.
# * Does not implement the line functions, as they don't appear to be used
# * 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.
#
from __future__ import print_function
from cffi import FFI
import sys
DEBUG = 0
defs = """
struct Pixel_RGBA {
unsigned char r,g,b,a;
};
struct Pixel_I16 {
unsigned char l,r;
};
"""
ffi = FFI()
ffi.cdef(defs)
class PyAccess(object):
def __init__(self, img, readonly=False):
vals = dict(img.im.unsafe_ptrs)
self.readonly = readonly
self.image8 = ffi.cast('unsigned char **', vals['image8'])
self.image32 = ffi.cast('int **', vals['image32'])
self.image = ffi.cast('unsigned char **', vals['image'])
self.xsize = vals['xsize']
self.ysize = vals['ysize']
if DEBUG:
print (vals)
self._post_init()
def _post_init():
pass
def __setitem__(self, xy, color):
"""
Modifies the pixel at x,y. The color is given as a single
numerical value for single band images, and a tuple for
multi-band images
:param xy: The pixel coordinate, given as (x, y).
:param value: The pixel value.
"""
if self.readonly:
raise ValueError('Attempt to putpixel a read only image')
(x, y) = self.check_xy(xy)
return self.set_pixel(x, y, color)
def __getitem__(self, xy):
"""
Returns the pixel at x,y. The pixel is returned as a single
value for single band images or a tuple for multiple band
images
:param xy: The pixel coordinate, given as (x, y).
"""
(x, y) = self.check_xy(xy)
return self.get_pixel(x, y)
putpixel = __setitem__
getpixel = __getitem__
def check_xy(self, xy):
(x, y) = xy
if not (0 <= x < self.xsize and 0 <= y < self.ysize):
raise ValueError('pixel location out of range')
return xy
class _PyAccess32_2(PyAccess):
""" PA, LA, stored in first and last bytes of a 32 bit word """
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x, y):
pixel = self.pixels[y][x]
return (pixel.r, pixel.a)
def set_pixel(self, x, y, color):
pixel = self.pixels[y][x]
# tuple
pixel.r = min(color[0], 255)
pixel.a = min(color[1], 255)
class _PyAccess32_3(PyAccess):
""" RGB and friends, stored in the first three bytes of a 32 bit word """
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x, y):
pixel = self.pixels[y][x]
return (pixel.r, pixel.g, pixel.b)
def set_pixel(self, x, y, color):
pixel = self.pixels[y][x]
# tuple
pixel.r = min(color[0], 255)
pixel.g = min(color[1], 255)
pixel.b = min(color[2], 255)
class _PyAccess32_4(PyAccess):
""" RGBA etc, all 4 bytes of a 32 bit word """
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x, y):
pixel = self.pixels[y][x]
return (pixel.r, pixel.g, pixel.b, pixel.a)
def set_pixel(self, x, y, color):
pixel = self.pixels[y][x]
# tuple
pixel.r = min(color[0], 255)
pixel.g = min(color[1], 255)
pixel.b = min(color[2], 255)
pixel.a = min(color[3], 255)
class _PyAccess8(PyAccess):
""" 1, L, P, 8 bit images stored as uint8 """
def _post_init(self, *args, **kwargs):
self.pixels = self.image8
def get_pixel(self, x, y):
return self.pixels[y][x]
def set_pixel(self, x, y, color):
try:
# integer
self.pixels[y][x] = min(color, 255)
except:
# tuple
self.pixels[y][x] = min(color[0], 255)
class _PyAccessI16_N(PyAccess):
""" I;16 access, native bitendian without conversion """
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('unsigned short **', self.image)
def get_pixel(self, x, y):
return self.pixels[y][x]
def set_pixel(self, x, y, color):
try:
# integer
self.pixels[y][x] = min(color, 65535)
except:
# tuple
self.pixels[y][x] = min(color[0], 65535)
class _PyAccessI16_L(PyAccess):
""" I;16L access, with conversion """
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
def get_pixel(self, x, y):
pixel = self.pixels[y][x]
return pixel.l + pixel.r * 256
def set_pixel(self, x, y, color):
pixel = self.pixels[y][x]
try:
color = min(color, 65535)
except:
color = min(color[0], 65535)
pixel.l = color & 0xFF
pixel.r = color >> 8
class _PyAccessI16_B(PyAccess):
""" I;16B access, with conversion """
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
def get_pixel(self, x, y):
pixel = self.pixels[y][x]
return pixel.l * 256 + pixel.r
def set_pixel(self, x, y, color):
pixel = self.pixels[y][x]
try:
color = min(color, 65535)
except:
color = min(color[0], 65535)
pixel.l = color >> 8
pixel.r = color & 0xFF
class _PyAccessI32_N(PyAccess):
""" Signed Int32 access, native endian """
def _post_init(self, *args, **kwargs):
self.pixels = self.image32
def get_pixel(self, x, y):
return self.pixels[y][x]
def set_pixel(self, x, y, color):
self.pixels[y][x] = color
class _PyAccessI32_Swap(PyAccess):
""" I;32L/B access, with byteswapping conversion """
def _post_init(self, *args, **kwargs):
self.pixels = self.image32
def reverse(self, i):
orig = ffi.new('int *', i)
chars = ffi.cast('unsigned char *', orig)
chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], \
chars[1], chars[0]
return ffi.cast('int *', chars)[0]
def get_pixel(self, x, y):
return self.reverse(self.pixels[y][x])
def set_pixel(self, x, y, color):
self.pixels[y][x] = self.reverse(color)
class _PyAccessF(PyAccess):
""" 32 bit float access """
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('float **', self.image32)
def get_pixel(self, x, y):
return self.pixels[y][x]
def set_pixel(self, x, y, color):
try:
# not a tuple
self.pixels[y][x] = color
except:
# tuple
self.pixels[y][x] = color[0]
mode_map = {'1': _PyAccess8,
'L': _PyAccess8,
'P': _PyAccess8,
'LA': _PyAccess32_2,
'PA': _PyAccess32_2,
'RGB': _PyAccess32_3,
'LAB': _PyAccess32_3,
'HSV': _PyAccess32_3,
'YCbCr': _PyAccess32_3,
'RGBA': _PyAccess32_4,
'RGBa': _PyAccess32_4,
'RGBX': _PyAccess32_4,
'CMYK': _PyAccess32_4,
'F': _PyAccessF,
'I': _PyAccessI32_N,
}
if sys.byteorder == 'little':
mode_map['I;16'] = _PyAccessI16_N
mode_map['I;16L'] = _PyAccessI16_N
mode_map['I;16B'] = _PyAccessI16_B
mode_map['I;32L'] = _PyAccessI32_N
mode_map['I;32B'] = _PyAccessI32_Swap
else:
mode_map['I;16'] = _PyAccessI16_L
mode_map['I;16L'] = _PyAccessI16_L
mode_map['I;16B'] = _PyAccessI16_N
mode_map['I;32L'] = _PyAccessI32_Swap
mode_map['I;32B'] = _PyAccessI32_N
def new(img, readonly=False):
access_type = mode_map.get(img.mode, None)
if not access_type:
if DEBUG:
print("PyAccess Not Implemented: %s" % img.mode)
return None
if DEBUG:
print("New PyAccess: %s" % img.mode)
return access_type(img, readonly)
# End of file

View File

@ -31,6 +31,7 @@ i32 = _binary.i32be
def _accept(prefix):
return i16(prefix) == 474
##
# Image plugin for SGI images.
@ -44,7 +45,7 @@ class SgiImageFile(ImageFile.ImageFile):
# HEAD
s = self.fp.read(512)
if i16(s) != 474:
raise SyntaxError("not an SGI image file")
raise ValueError("Not an SGI image file")
# relevant header entries
compression = i8(s[2])
@ -60,22 +61,22 @@ class SgiImageFile(ImageFile.ImageFile):
elif layout == (1, 3, 4):
self.mode = "RGBA"
else:
raise SyntaxError("unsupported SGI image mode")
raise ValueError("Unsupported SGI image mode")
# size
self.size = i16(s[6:]), i16(s[8:])
# decoder info
if compression == 0:
offset = 512
pagesize = self.size[0]*self.size[1]*layout[0]
self.tile = []
for layer in self.mode:
self.tile.append(("raw", (0,0)+self.size, offset, (layer,0,-1)))
self.tile.append(
("raw", (0, 0)+self.size, offset, (layer, 0, -1)))
offset = offset + pagesize
elif compression == 1:
self.tile = [("sgi_rle", (0,0)+self.size, 512, (self.mode, 0, -1))]
raise ValueError("SGI RLE encoding not supported")
#
# registry
@ -85,5 +86,6 @@ Image.register_open("SGI", SgiImageFile, _accept)
Image.register_extension("SGI", ".bw")
Image.register_extension("SGI", ".rgb")
Image.register_extension("SGI", ".rgba")
Image.register_extension("SGI", ".sgi")
Image.register_extension("SGI", ".sgi") # really?
# End of file

View File

@ -36,17 +36,23 @@
from __future__ import print_function
from PIL import Image, ImageFile
import os, struct, sys
import os
import struct
import sys
def isInt(f):
try:
i = int(f)
if f-i == 0: return 1
else: return 0
if f-i == 0:
return 1
else:
return 0
except:
return 0
iforms = [1,3,-11,-12,-21,-22]
iforms = [1, 3, -11, -12, -21, -22]
# There is no magic number to identify Spider files, so just check a
# series of header locations to see if they have reasonable values.
@ -56,30 +62,32 @@ iforms = [1,3,-11,-12,-21,-22]
def isSpiderHeader(t):
h = (99,) + t # add 1 value so can use spider header index start=1
# header values 1,2,5,12,13,22,23 should be integers
for i in [1,2,5,12,13,22,23]:
if not isInt(h[i]): return 0
for i in [1, 2, 5, 12, 13, 22, 23]:
if not isInt(h[i]):
return 0
# check iform
iform = int(h[5])
if not iform in iforms: return 0
if iform not in iforms:
return 0
# check other header values
labrec = int(h[13]) # no. records in file header
labbyt = int(h[22]) # total no. of bytes in header
lenbyt = int(h[23]) # record length in bytes
#print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt)
if labbyt != (labrec * lenbyt): return 0
# print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt)
if labbyt != (labrec * lenbyt):
return 0
# looks like a valid header
return labbyt
def isSpiderImage(filename):
fp = open(filename,'rb')
fp = open(filename, 'rb')
f = fp.read(92) # read 23 * 4 bytes
fp.close()
bigendian = 1
t = struct.unpack('>23f',f) # try big-endian first
t = struct.unpack('>23f', f) # try big-endian first
hdrlen = isSpiderHeader(t)
if hdrlen == 0:
bigendian = 0
t = struct.unpack('<23f',f) # little-endian
t = struct.unpack('<23f', f) # little-endian
hdrlen = isSpiderHeader(t)
return hdrlen
@ -96,11 +104,11 @@ class SpiderImageFile(ImageFile.ImageFile):
try:
self.bigendian = 1
t = struct.unpack('>27f',f) # try big-endian first
t = struct.unpack('>27f', f) # try big-endian first
hdrlen = isSpiderHeader(t)
if hdrlen == 0:
self.bigendian = 0
t = struct.unpack('<27f',f) # little-endian
t = struct.unpack('<27f', f) # little-endian
hdrlen = isSpiderHeader(t)
if hdrlen == 0:
raise SyntaxError("not a valid Spider file")
@ -112,7 +120,7 @@ class SpiderImageFile(ImageFile.ImageFile):
if iform != 1:
raise SyntaxError("not a Spider 2D image")
self.size = int(h[12]), int(h[2]) # size in pixels (width, height)
self.size = int(h[12]), int(h[2]) # size in pixels (width, height)
self.istack = int(h[24])
self.imgnumber = int(h[27])
@ -141,9 +149,10 @@ class SpiderImageFile(ImageFile.ImageFile):
self.rawmode = "F;32F"
self.mode = "F"
self.tile = [("raw", (0, 0) + self.size, offset,
(self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack
self.tile = [
("raw", (0, 0) + self.size, offset,
(self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack
# 1st image index is zero (although SPIDER imgnumber starts at 1)
def tell(self):
@ -176,13 +185,14 @@ class SpiderImageFile(ImageFile.ImageFile):
from PIL import ImageTk
return ImageTk.PhotoImage(self.convert2byte(), palette=256)
# --------------------------------------------------------------------
# Image series
# given a list of filenames, return a list of images
def loadImageSeries(filelist=None):
" create a list of Image.images for use in montage "
if filelist == None or len(filelist) < 1:
if filelist is None or len(filelist) < 1:
return
imglist = []
@ -200,17 +210,19 @@ def loadImageSeries(filelist=None):
imglist.append(im)
return imglist
# --------------------------------------------------------------------
# For saving images in Spider format
def makeSpiderHeader(im):
nsam,nrow = im.size
nsam, nrow = im.size
lenbyt = nsam * 4 # There are labrec records in the header
labrec = 1024 / lenbyt
if 1024%lenbyt != 0: labrec += 1
if 1024 % lenbyt != 0:
labrec += 1
labbyt = labrec * lenbyt
hdr = []
nvalues = labbyt / 4
nvalues = int(labbyt / 4)
for i in range(nvalues):
hdr.append(0.0)
@ -218,13 +230,13 @@ def makeSpiderHeader(im):
return []
# NB these are Fortran indices
hdr[1] = 1.0 # nslice (=1 for an image)
hdr[2] = float(nrow) # number of rows per slice
hdr[5] = 1.0 # iform for 2D image
hdr[12] = float(nsam) # number of pixels per line
hdr[13] = float(labrec) # number of records in file header
hdr[22] = float(labbyt) # total number of bytes in header
hdr[23] = float(lenbyt) # record length in bytes
hdr[1] = 1.0 # nslice (=1 for an image)
hdr[2] = float(nrow) # number of rows per slice
hdr[5] = 1.0 # iform for 2D image
hdr[12] = float(nsam) # number of pixels per line
hdr[13] = float(labrec) # number of records in file header
hdr[22] = float(labbyt) # total number of bytes in header
hdr[23] = float(lenbyt) # record length in bytes
# adjust for Fortran indexing
hdr = hdr[1:]
@ -232,9 +244,10 @@ def makeSpiderHeader(im):
# pack binary data into a string
hdrstr = []
for v in hdr:
hdrstr.append(struct.pack('f',v))
hdrstr.append(struct.pack('f', v))
return hdrstr
def _save(im, fp, filename):
if im.mode[0] != "F":
im = im.convert('F')
@ -250,11 +263,12 @@ def _save(im, fp, filename):
raise IOError("Unable to open %s for writing" % filename)
fp.writelines(hdr)
rawmode = "F;32NF" #32-bit native floating point
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode,0,1))])
rawmode = "F;32NF" # 32-bit native floating point
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
fp.close()
def _save_spider(im, fp, filename):
# get the filename extension and register it with Image
fn, ext = os.path.splitext(filename)
@ -292,5 +306,7 @@ if __name__ == "__main__":
if outfile != "":
# perform some image operation
im = im.transpose(Image.FLIP_LEFT_RIGHT)
print("saving a flipped version of %s as %s " % (os.path.basename(filename), outfile))
print(
"saving a flipped version of %s as %s " %
(os.path.basename(filename), outfile))
im.save(outfile, "SPIDER")

View File

@ -29,6 +29,7 @@ i32 = _binary.i32be
def _accept(prefix):
return i32(prefix) == 0x59a66a95
##
# Image plugin for Sun raster files.
@ -70,9 +71,9 @@ class SunImageFile(ImageFile.ImageFile):
stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3)
if compression == 1:
self.tile = [("raw", (0,0)+self.size, offset, (rawmode, stride))]
self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))]
elif compression == 2:
self.tile = [("sun_rle", (0,0)+self.size, offset, rawmode)]
self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)]
#
# registry

View File

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

View File

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

View File

@ -49,13 +49,18 @@ from PIL import _binary
from PIL._util import isStringType
import warnings
import array, sys
import array
import sys
import collections
import itertools
import os
II = b"II" # little-endian (intel-style)
MM = b"MM" # big-endian (motorola-style)
# Set these to true to force use of libtiff for reading or writing.
READ_LIBTIFF = False
WRITE_LIBTIFF = False
II = b"II" # little-endian (Intel style)
MM = b"MM" # big-endian (Motorola style)
i8 = _binary.i8
o8 = _binary.o8
@ -105,8 +110,8 @@ EXTRASAMPLES = 338
SAMPLEFORMAT = 339
JPEGTABLES = 347
COPYRIGHT = 33432
IPTC_NAA_CHUNK = 33723 # newsphoto properties
PHOTOSHOP_CHUNK = 34377 # photoshop properties
IPTC_NAA_CHUNK = 33723 # newsphoto properties
PHOTOSHOP_CHUNK = 34377 # photoshop properties
ICCPROFILE = 34675
EXIFIFD = 34665
XMP = 700
@ -122,10 +127,10 @@ COMPRESSION_INFO = {
3: "group3",
4: "group4",
5: "tiff_lzw",
6: "tiff_jpeg", # obsolete
6: "tiff_jpeg", # obsolete
7: "jpeg",
8: "tiff_adobe_deflate",
32771: "tiff_raw_16", # 16-bit padding
32771: "tiff_raw_16", # 16-bit padding
32773: "packbits",
32809: "tiff_thunderscan",
32946: "tiff_deflate",
@ -133,7 +138,7 @@ COMPRESSION_INFO = {
34677: "tiff_sgilog24",
}
COMPRESSION_INFO_REV = dict([(v,k) for (k,v) in COMPRESSION_INFO.items()])
COMPRESSION_INFO_REV = dict([(v, k) for (k, v) in COMPRESSION_INFO.items()])
OPEN_INFO = {
# (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,
@ -142,23 +147,26 @@ OPEN_INFO = {
(II, 0, 1, 2, (1,), ()): ("1", "1;IR"),
(II, 0, 1, 1, (8,), ()): ("L", "L;I"),
(II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
(II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
(II, 1, 1, 1, (1,), ()): ("1", "1"),
(II, 1, 1, 1, (4,), ()): ("L", "L;4"),
(II, 1, 1, 2, (1,), ()): ("1", "1;R"),
(II, 1, 1, 1, (8,), ()): ("L", "L"),
(II, 1, 1, 1, (8,8), (2,)): ("LA", "LA"),
(II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
(II, 1, 1, 2, (8,), ()): ("L", "L;R"),
(II, 1, 1, 1, (12,), ()): ("I;16", "I;12"),
(II, 1, 1, 1, (16,), ()): ("I;16", "I;16"),
(II, 1, 2, 1, (16,), ()): ("I;16S", "I;16S"),
(II, 1, 1, 1, (32,), ()): ("I", "I;32N"),
(II, 1, 2, 1, (32,), ()): ("I", "I;32S"),
(II, 1, 3, 1, (32,), ()): ("F", "F;32F"),
(II, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"),
(II, 2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"),
(II, 2, 1, 1, (8,8,8,8), ()): ("RGBA", "RGBA"), # missing ExtraSamples
(II, 2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"),
(II, 2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"),
(II, 2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"),
(II, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10
(II, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"),
(II, 2, 1, 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
(II, 2, 1, 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples
(II, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
(II, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
(II, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
(II, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
(II, 3, 1, 1, (1,), ()): ("P", "P;1"),
(II, 3, 1, 2, (1,), ()): ("P", "P;1R"),
(II, 3, 1, 1, (2,), ()): ("P", "P;2"),
@ -166,11 +174,11 @@ OPEN_INFO = {
(II, 3, 1, 1, (4,), ()): ("P", "P;4"),
(II, 3, 1, 2, (4,), ()): ("P", "P;4R"),
(II, 3, 1, 1, (8,), ()): ("P", "P"),
(II, 3, 1, 1, (8,8), (2,)): ("PA", "PA"),
(II, 3, 1, 1, (8, 8), (2,)): ("PA", "PA"),
(II, 3, 1, 2, (8,), ()): ("P", "P;R"),
(II, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"),
(II, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"),
(II, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"),
(II, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
(II, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
(II, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"),
(MM, 0, 1, 1, (1,), ()): ("1", "1;I"),
(MM, 0, 1, 2, (1,), ()): ("1", "1;IR"),
@ -179,18 +187,18 @@ OPEN_INFO = {
(MM, 1, 1, 1, (1,), ()): ("1", "1"),
(MM, 1, 1, 2, (1,), ()): ("1", "1;R"),
(MM, 1, 1, 1, (8,), ()): ("L", "L"),
(MM, 1, 1, 1, (8,8), (2,)): ("LA", "LA"),
(MM, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
(MM, 1, 1, 2, (8,), ()): ("L", "L;R"),
(MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"),
(MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"),
(MM, 1, 2, 1, (32,), ()): ("I;32BS", "I;32BS"),
(MM, 1, 3, 1, (32,), ()): ("F", "F;32BF"),
(MM, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"),
(MM, 2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"),
(MM, 2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"),
(MM, 2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"),
(MM, 2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"),
(MM, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10
(MM, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"),
(MM, 2, 1, 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
(MM, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
(MM, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
(MM, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
(MM, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
(MM, 3, 1, 1, (1,), ()): ("P", "P;1"),
(MM, 3, 1, 2, (1,), ()): ("P", "P;1R"),
(MM, 3, 1, 1, (2,), ()): ("P", "P;2"),
@ -198,28 +206,64 @@ OPEN_INFO = {
(MM, 3, 1, 1, (4,), ()): ("P", "P;4"),
(MM, 3, 1, 2, (4,), ()): ("P", "P;4R"),
(MM, 3, 1, 1, (8,), ()): ("P", "P"),
(MM, 3, 1, 1, (8,8), (2,)): ("PA", "PA"),
(MM, 3, 1, 1, (8, 8), (2,)): ("PA", "PA"),
(MM, 3, 1, 2, (8,), ()): ("P", "P;R"),
(MM, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"),
(MM, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"),
(MM, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"),
(MM, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
(MM, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
(MM, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"),
}
PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"]
def _accept(prefix):
return prefix[:4] in PREFIXES
##
# Wrapper for TIFF IFDs.
class ImageFileDirectory(collections.MutableMapping):
""" This class represents a TIFF tag directory. To speed things
up, we don't decode tags unless they're asked for.
# represents a TIFF tag directory. to speed things up,
# we don't decode tags unless they're asked for.
Exposes a dictionary interface of the tags in the directory
ImageFileDirectory[key] = value
value = ImageFileDirectory[key]
def __init__(self, prefix):
Also contains a dictionary of tag types as read from the tiff
image file, 'ImageFileDirectory.tagtype'
Data Structures:
'public'
* self.tagtype = {} Key: numerical tiff tag number
Value: integer corresponding to the data type from
`TiffTags.TYPES`
'internal'
* self.tags = {} Key: numerical tiff tag number
Value: Decoded data, Generally a tuple.
* If set from __setval__ -- always a tuple
* Numeric types -- always a tuple
* String type -- not a tuple, returned as string
* Undefined data -- not a tuple, returned as bytes
* Byte -- not a tuple, returned as byte.
* self.tagdata = {} Key: numerical tiff tag number
Value: undecoded byte string from file
Tags will be found in either self.tags or self.tagdata, but
not both. The union of the two should contain all the tags
from the Tiff image file. External classes shouldn't
reference these unless they're really sure what they're doing.
"""
def __init__(self, prefix=II):
"""
:prefix: 'II'|'MM' tiff endianness
"""
self.prefix = prefix[:2]
if self.prefix == MM:
self.i16, self.i32 = ib16, ib32
@ -236,8 +280,9 @@ class ImageFileDirectory(collections.MutableMapping):
#: For a complete dictionary, use the as_dict method.
self.tags = {}
self.tagdata = {}
self.tagtype = {} # added 2008-06-05 by Florian Hoech
self.tagtype = {} # added 2008-06-05 by Florian Hoech
self.next = None
self.offset = None
def __str__(self):
return str(self.as_dict())
@ -247,7 +292,9 @@ class ImageFileDirectory(collections.MutableMapping):
return dict(self.items())
def named(self):
"""Returns the complete tag dictionary, with named tags where posible."""
"""
Returns the complete tag dictionary, with named tags where posible.
"""
from PIL import TiffTags
result = {}
for tag_code, value in self.items():
@ -255,7 +302,6 @@ class ImageFileDirectory(collections.MutableMapping):
result[tag_name] = value
return result
# dictionary API
def __len__(self):
@ -265,7 +311,8 @@ class ImageFileDirectory(collections.MutableMapping):
try:
return self.tags[tag]
except KeyError:
type, data = self.tagdata[tag] # unpack on the fly
data = self.tagdata[tag] # unpack on the fly
type = self.tagtype[tag]
size, handler = self.load_dispatch[type]
self.tags[tag] = data = handler(self, data)
del self.tagdata[tag]
@ -278,7 +325,7 @@ class ImageFileDirectory(collections.MutableMapping):
if tag == SAMPLEFORMAT:
# work around broken (?) matrox library
# (from Ted Wright, via Bob Klimek)
raise KeyError # use default
raise KeyError # use default
raise ValueError("not a scalar")
return value[0]
except KeyError:
@ -294,6 +341,9 @@ class ImageFileDirectory(collections.MutableMapping):
return tag in self
def __setitem__(self, tag, value):
# tags are tuples for integers
# tags are not tuples for byte, string, and undefined data.
# see load_*
if not isinstance(value, tuple):
value = (value,)
self.tags[tag] = value
@ -367,6 +417,7 @@ class ImageFileDirectory(collections.MutableMapping):
# load tag dictionary
self.reset()
self.offset = fp.tell()
i16 = self.i16
i32 = self.i32
@ -389,7 +440,7 @@ class ImageFileDirectory(collections.MutableMapping):
except KeyError:
if Image.DEBUG:
print("- unsupported type", typ)
continue # ignore unsupported type
continue # ignore unsupported type
size, handler = dispatch
@ -398,21 +449,28 @@ class ImageFileDirectory(collections.MutableMapping):
# Get and expand tag value
if size > 4:
here = fp.tell()
if Image.DEBUG:
print("Tag Location: %s" % here)
fp.seek(i32(ifd, 8))
if Image.DEBUG:
print("Data Location: %s" % fp.tell())
data = ImageFile._safe_read(fp, size)
fp.seek(here)
else:
data = ifd[8:8+size]
if len(data) != size:
warnings.warn("Possibly corrupt EXIF data. Expecting to read %d bytes but only got %d. Skipping tag %s" % (size, len(data), tag))
warnings.warn("Possibly corrupt EXIF data. "
"Expecting to read %d bytes but only got %d. "
"Skipping tag %s" % (size, len(data), tag))
continue
self.tagdata[tag] = typ, data
self.tagdata[tag] = data
self.tagtype[tag] = typ
if Image.DEBUG:
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP):
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK,
ICCPROFILE, XMP):
print("- value: <table: %d bytes>" % size)
else:
print("- value:", self[tag])
@ -446,24 +504,41 @@ class ImageFileDirectory(collections.MutableMapping):
if tag in self.tagtype:
typ = self.tagtype[tag]
if Image.DEBUG:
print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
if typ == 1:
# byte data
data = value
if isinstance(value, tuple):
data = value = value[-1]
else:
data = value
elif typ == 7:
# untyped data
data = value = b"".join(value)
elif isinstance(value[0], str):
elif isStringType(value[0]):
# string data
if isinstance(value, tuple):
value = value[-1]
typ = 2
data = value = b"\0".join(value.encode('ascii', 'replace')) + b"\0"
# was b'\0'.join(str), which led to \x00a\x00b sorts
# of strings which I don't see in in the wild tiffs
# and doesn't match the tiff spec: 8-bit byte that
# contains a 7-bit ASCII code; the last byte must be
# NUL (binary zero). Also, I don't think this was well
# excersized before.
data = value = b"" + value.encode('ascii', 'replace') + b"\0"
else:
# integer data
if tag == STRIPOFFSETS:
stripoffsets = len(directory)
typ = 4 # to avoid catch-22
elif tag in (X_RESOLUTION, Y_RESOLUTION):
typ = 4 # to avoid catch-22
elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ == 5:
# identify rational data fields
typ = 5
if isinstance(value[0], tuple):
# long name for flatten
value = tuple(itertools.chain.from_iterable(value))
elif not typ:
typ = 3
for v in value:
@ -480,7 +555,8 @@ class ImageFileDirectory(collections.MutableMapping):
typname = TiffTags.TYPES.get(typ, "unknown")
print("save: %s (%d)" % (tagname, tag), end=' ')
print("- type: %s (%d)" % (typname, typ), end=' ')
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP):
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK,
ICCPROFILE, XMP):
size = len(data)
print("- value: <table: %d bytes>" % size)
else:
@ -495,10 +571,11 @@ class ImageFileDirectory(collections.MutableMapping):
count = len(value)
if typ == 5:
count = count // 2 # adjust for rational data field
append((tag, typ, count, o32(offset), data))
offset = offset + len(data)
offset += len(data)
if offset & 1:
offset = offset + 1 # word padding
offset += 1 # word padding
# update strip offset data to point beyond auxiliary data
if stripoffsets is not None:
@ -514,7 +591,7 @@ class ImageFileDirectory(collections.MutableMapping):
fp.write(o16(tag) + o16(typ) + o32(count) + value)
# -- overwrite here for multi-page --
fp.write(b"\0\0\0\0") # end of directory
fp.write(b"\0\0\0\0") # end of directory
# pass 3: write auxiliary data to file
for tag, typ, count, value, data in directory:
@ -524,6 +601,7 @@ class ImageFileDirectory(collections.MutableMapping):
return offset
##
# Image plugin for TIFF files.
@ -554,23 +632,25 @@ class TiffImageFile(ImageFile.ImageFile):
print ("- __first:", self.__first)
print ("- ifh: ", ifh)
# and load the first frame
# and load the first frame
self._seek(0)
def seek(self, frame):
"Select a given frame as current image"
if frame < 0:
frame = 0
self._seek(frame)
# Create a new core image object on second and
# subsequent frames in the image. Image may be
# different size/mode.
Image._decompression_bomb_check(self.size)
self.im = Image.core.new(self.mode, self.size)
def tell(self):
"Return the current frame number"
return self._tell()
def _seek(self, frame):
self.fp = self.__fp
if frame < self.__frame:
# rewind file
@ -579,14 +659,21 @@ class TiffImageFile(ImageFile.ImageFile):
while self.__frame < frame:
if not self.__next:
raise EOFError("no more images in TIFF file")
if Image.DEBUG:
print("Seeking to frame %s, on frame %s, __next %s, location: %s" %
(frame, self.__frame, self.__next, self.fp.tell()))
# reset python3 buffered io handle in case fp
# was passed to libtiff, invalidating the buffer
self.fp.tell()
self.fp.seek(self.__next)
if Image.DEBUG:
print("Loading tags, location: %s" % self.fp.tell())
self.tag.load(self.fp)
self.__next = self.tag.next
self.__frame = self.__frame + 1
self.__frame += 1
self._setup()
def _tell(self):
return self.__frame
def _decoder(self, rawmode, layer, tile=None):
@ -617,8 +704,8 @@ class TiffImageFile(ImageFile.ImageFile):
return args
def _load_libtiff(self):
""" Overload method triggered when we detect a g3/g4 tiff
Calls out to lib tiff """
""" Overload method triggered when we detect a compressed tiff
Calls out to libtiff """
pixel = Image.Image.load(self)
@ -632,10 +719,14 @@ class TiffImageFile(ImageFile.ImageFile):
if not len(self.tile) == 1:
raise IOError("Not exactly one tile")
d, e, o, a = self.tile[0]
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
# (self._compression, (extents tuple),
# 0, (rawmode, self._compression, fp))
ignored, extents, ignored_2, args = self.tile[0]
args = args + (self.ifd.offset,)
decoder = Image._getdecoder(self.mode, 'libtiff', args,
self.decoderconfig)
try:
d.setimage(self.im, e)
decoder.setimage(self.im, extents)
except ValueError:
raise IOError("Couldn't set the image")
@ -643,35 +734,39 @@ class TiffImageFile(ImageFile.ImageFile):
# We've got a stringio like thing passed in. Yay for all in memory.
# The decoder needs the entire file in one shot, so there's not
# a lot we can do here other than give it the entire file.
# unless we could do something like get the address of the underlying
# string for stringio.
# unless we could do something like get the address of the
# underlying string for stringio.
#
# Rearranging for supporting byteio items, since they have a fileno
# that returns an IOError if there's no underlying fp. Easier to deal
# with here by reordering.
# that returns an IOError if there's no underlying fp. Easier to
# dea. with here by reordering.
if Image.DEBUG:
print ("have getvalue. just sending in a string from getvalue")
n,e = d.decode(self.fp.getvalue())
n, err = decoder.decode(self.fp.getvalue())
elif hasattr(self.fp, "fileno"):
# we've got a actual file on disk, pass in the fp.
if Image.DEBUG:
print ("have fileno, calling fileno version of the decoder.")
self.fp.seek(0)
n,e = d.decode(b"fpfp") # 4 bytes, otherwise the trace might error out
# 4 bytes, otherwise the trace might error out
n, err = decoder.decode(b"fpfp")
else:
# we have something else.
if Image.DEBUG:
print ("don't have fileno or getvalue. just reading")
# UNDONE -- so much for that buffer size thing.
n, e = d.decode(self.fp.read())
n, err = decoder.decode(self.fp.read())
self.tile = []
self.readonly = 0
self.fp = None # might be shared
# libtiff closed the fp in a, we need to close self.fp, if possible
if hasattr(self.fp, 'close'):
if not self.__next:
self.fp.close()
self.fp = None # might be shared
if e < 0:
raise IOError(e)
if err < 0:
raise IOError(err)
self.load_end()
@ -744,11 +839,11 @@ class TiffImageFile(ImageFile.ImageFile):
xres = xres[0] / (xres[1] or 1)
yres = yres[0] / (yres[1] or 1)
resunit = getscalar(RESOLUTION_UNIT, 1)
if resunit == 2: # dots per inch
if resunit == 2: # dots per inch
self.info["dpi"] = xres, yres
elif resunit == 3: # dots per centimeter. convert to dpi
elif resunit == 3: # dots per centimeter. convert to dpi
self.info["dpi"] = xres * 2.54, yres * 2.54
else: # No absolute unit of measurement
else: # No absolute unit of measurement
self.info["resolution"] = xres, yres
# build tile descriptors
@ -759,13 +854,16 @@ class TiffImageFile(ImageFile.ImageFile):
offsets = self.tag[STRIPOFFSETS]
h = getscalar(ROWSPERSTRIP, ysize)
w = self.size[0]
if self._compression in ["tiff_ccitt", "group3", "group4",
"tiff_jpeg", "tiff_adobe_deflate",
"tiff_thunderscan", "tiff_deflate",
"tiff_sgilog", "tiff_sgilog24",
"tiff_raw_16"]:
## if Image.DEBUG:
## print "Activating g4 compression for whole file"
if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3",
"group4", "tiff_jpeg",
"tiff_adobe_deflate",
"tiff_thunderscan",
"tiff_deflate",
"tiff_sgilog",
"tiff_sgilog24",
"tiff_raw_16"]:
# if Image.DEBUG:
# print "Activating g4 compression for whole file"
# Decoder expects entire file as one tile.
# There's a buffer size limit in load (64k)
@ -784,7 +882,12 @@ class TiffImageFile(ImageFile.ImageFile):
# libtiff closes the file descriptor, so pass in a dup.
try:
fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno())
fp = hasattr(self.fp, "fileno") and \
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:
# io.BytesIO have a fileno, but returns an IOError if
# it doesn't use a file descriptor.
@ -793,7 +896,7 @@ class TiffImageFile(ImageFile.ImageFile):
# libtiff handles the fillmode for us, so 1;IR should
# actually be 1;I. Including the R double reverses the
# bits, so stripes of the image are reversed. See
# https://github.com/python-imaging/Pillow/issues/279
# https://github.com/python-pillow/Pillow/issues/279
if fillorder == 2:
key = (
self.tag.prefix, photo, format, 1,
@ -810,12 +913,12 @@ class TiffImageFile(ImageFile.ImageFile):
# we're expecting image byte order. So, if the rawmode
# contains I;16, we need to convert from native to image
# byte order.
if self.mode in ('I;16B', 'I;16'):
if self.mode in ('I;16B', 'I;16') and 'I;16' in rawmode:
rawmode = 'I;16N'
# Offset in the tile tuple is 0, we go from 0,0 to
# w,h, and we only do this once -- eds
a = (rawmode, self._compression, fp )
a = (rawmode, self._compression, fp)
self.tile.append(
(self._compression,
(0, 0, w, ysize),
@ -827,14 +930,14 @@ class TiffImageFile(ImageFile.ImageFile):
a = self._decoder(rawmode, l, i)
self.tile.append(
(self._compression,
(0, min(y, ysize), w, min(y+h, ysize)),
offsets[i], a))
(0, min(y, ysize), w, min(y+h, ysize)),
offsets[i], a))
if Image.DEBUG:
print ("tiles: ", self.tile)
y = y + h
if y >= self.size[1]:
x = y = 0
l = l + 1
l += 1
a = None
elif TILEOFFSETS in self.tag:
# tiled image
@ -848,14 +951,14 @@ class TiffImageFile(ImageFile.ImageFile):
# is not a multiple of the tile size...
self.tile.append(
(self._compression,
(x, y, x+w, y+h),
o, a))
(x, y, x+w, y+h),
o, a))
x = x + w
if x >= self.size[0]:
x, y = 0, y + h
if y >= self.size[1]:
x = y = 0
l = l + 1
l += 1
a = None
else:
if Image.DEBUG:
@ -871,25 +974,27 @@ class TiffImageFile(ImageFile.ImageFile):
# --------------------------------------------------------------------
# Write TIFF files
# little endian is default except for image modes with explict big endian byte-order
# little endian is default except for image modes with
# explict big endian byte-order
SAVE_INFO = {
# mode => rawmode, byteorder, photometrics, sampleformat, bitspersample, extra
# mode => rawmode, byteorder, photometrics,
# sampleformat, bitspersample, extra
"1": ("1", II, 1, 1, (1,), None),
"L": ("L", II, 1, 1, (8,), None),
"LA": ("LA", II, 1, 1, (8,8), 2),
"LA": ("LA", II, 1, 1, (8, 8), 2),
"P": ("P", II, 3, 1, (8,), None),
"PA": ("PA", II, 3, 1, (8,8), 2),
"PA": ("PA", II, 3, 1, (8, 8), 2),
"I": ("I;32S", II, 1, 2, (32,), None),
"I;16": ("I;16", II, 1, 1, (16,), None),
"I;16S": ("I;16S", II, 1, 2, (16,), None),
"F": ("F;32F", II, 1, 3, (32,), None),
"RGB": ("RGB", II, 2, 1, (8,8,8), None),
"RGBX": ("RGBX", II, 2, 1, (8,8,8,8), 0),
"RGBA": ("RGBA", II, 2, 1, (8,8,8,8), 2),
"CMYK": ("CMYK", II, 5, 1, (8,8,8,8), None),
"YCbCr": ("YCbCr", II, 6, 1, (8,8,8), None),
"LAB": ("LAB", II, 8, 1, (8,8,8), None),
"RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
"RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
"RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2),
"CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
"YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
"LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
"I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
"I;16B": ("I;16B", MM, 1, 1, (16,), None),
@ -897,6 +1002,7 @@ SAVE_INFO = {
"F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
}
def _cvt_res(value):
# convert value to TIFF rational number -- (numerator, denominator)
if isinstance(value, collections.Sequence):
@ -907,6 +1013,7 @@ def _cvt_res(value):
value = float(value)
return (int(value * 65536), 65536)
def _save(im, fp, filename):
try:
@ -916,12 +1023,13 @@ def _save(im, fp, filename):
ifd = ImageFileDirectory(prefix)
compression = im.encoderinfo.get('compression',im.info.get('compression','raw'))
libtiff = compression in ["tiff_ccitt", "group3", "group4",
"tiff_jpeg", "tiff_adobe_deflate",
"tiff_thunderscan", "tiff_deflate",
"tiff_sgilog", "tiff_sgilog24",
"tiff_raw_16"]
compression = im.encoderinfo.get('compression', im.info.get('compression',
'raw'))
libtiff = WRITE_LIBTIFF or compression != 'raw'
# required for color libtiff images
ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1)
# -- multi-page -- skip TIFF header on subsequent pages
if not libtiff and fp.tell() == 0:
@ -932,28 +1040,38 @@ def _save(im, fp, filename):
ifd[IMAGEWIDTH] = im.size[0]
ifd[IMAGELENGTH] = im.size[1]
# write any arbitrary tags passed in as an ImageFileDirectory
info = im.encoderinfo.get("tiffinfo", {})
if Image.DEBUG:
print("Tiffinfo Keys: %s" % info.keys)
keys = list(info.keys())
for key in keys:
ifd[key] = info.get(key)
try:
ifd.tagtype[key] = info.tagtype[key]
except:
pass # might not be an IFD, Might not have populated type
# additions written by Greg Couch, gregc@cgl.ucsf.edu
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
if hasattr(im, 'tag'):
# preserve tags from original TIFF image file
for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION):
if key in im.tag.tagdata:
ifd[key] = im.tag.tagdata.get(key)
# preserve some more tags from original TIFF image file
# -- 2008-06-06 Florian Hoech
ifd.tagtype = im.tag.tagtype
for key in (IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION,
IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
if key in im.tag:
ifd[key] = im.tag[key]
ifd.tagtype[key] = im.tag.tagtype.get(key, None)
# preserve ICC profile (should also work when saving other formats
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech
if "icc_profile" in im.info:
ifd[ICCPROFILE] = im.info["icc_profile"]
if "description" in im.encoderinfo:
ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"]
if "resolution" in im.encoderinfo:
ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \
= _cvt_res(im.encoderinfo["resolution"])
= _cvt_res(im.encoderinfo["resolution"])
if "x resolution" in im.encoderinfo:
ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"])
if "y resolution" in im.encoderinfo:
@ -1000,8 +1118,9 @@ def _save(im, fp, filename):
stride = len(bits) * ((im.size[0]*bits[0]+7)//8)
ifd[ROWSPERSTRIP] = im.size[1]
ifd[STRIPBYTECOUNTS] = stride * im.size[1]
ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression,1) # no compression by default
ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
# no compression by default:
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
if libtiff:
if Image.DEBUG:
@ -1012,31 +1131,41 @@ def _save(im, fp, filename):
fp.seek(0)
_fp = os.dup(fp.fileno())
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes.
atts={}
# ICC Profile crashes.
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE]
atts = {}
# bits per sample is a single short in the tiff directory, not a list.
atts[BITSPERSAMPLE] = bits[0]
# Merge the ones that we have with (optional) more bits from
# the original file, e.g x,y resolution so that we can
# save(load('')) == original file.
for k,v in itertools.chain(ifd.items(), getattr(im, 'ifd', {}).items()):
for k, v in itertools.chain(ifd.items(),
getattr(im, 'ifd', {}).items()):
if k not in atts and k not in blocklist:
if type(v[0]) == tuple and len(v) > 1:
# A tuple of more than one rational tuples
# flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL
# flatten to floats,
# following tiffcp.c->cpTag->TIFF_RATIONAL
atts[k] = [float(elt[0])/float(elt[1]) for elt in v]
continue
if type(v[0]) == tuple and len(v) == 1:
# A tuple of one rational tuples
# flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL
# flatten to floats,
# following tiffcp.c->cpTag->TIFF_RATIONAL
atts[k] = float(v[0][0])/float(v[0][1])
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?
# BitsPerSample is one example, I get (8,8,8)
# UNDONE
# Avoid divide by zero in next if-clause
if type(v[0]) in (int, float):
atts[k] = list(v)
continue
if type(v) == tuple and len(v) == 2:
# one rational tuple
# flatten to float, following tiffcp.c->cpTag->TIFF_RATIONAL
# flatten to float,
# following tiffcp.c->cpTag->TIFF_RATIONAL
atts[k] = float(v[0])/float(v[1])
continue
if type(v) == tuple and len(v) == 1:
@ -1052,8 +1181,8 @@ def _save(im, fp, filename):
if Image.DEBUG:
print (atts)
# libtiff always returns the bytes in native order.
# we're expecting image byte order. So, if the rawmode
# libtiff always expects the bytes in native order.
# we're storing image byte order. So, if the rawmode
# contains I;16, we need to convert from native to image
# byte order.
if im.mode in ('I;16B', 'I;16'):
@ -1061,10 +1190,11 @@ def _save(im, fp, filename):
a = (rawmode, compression, _fp, filename, atts)
# print (im.mode, compression, a, im.encoderconfig)
e = Image._getencoder(im.mode, compression, a, im.encoderconfig)
e.setimage(im.im, (0,0)+im.size)
while 1:
l, s, d = e.encode(16*1024) # undone, change to self.decodermaxblock
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
e.setimage(im.im, (0, 0)+im.size)
while True:
# undone, change to self.decodermaxblock:
l, s, d = e.encode(16*1024)
if not _fp:
fp.write(d)
if s:
@ -1076,13 +1206,12 @@ def _save(im, fp, filename):
offset = ifd.save(fp)
ImageFile._save(im, fp, [
("raw", (0,0)+im.size, offset, (rawmode, stride, 1))
("raw", (0, 0)+im.size, offset, (rawmode, stride, 1))
])
# -- helper for multi-page save --
if "_debug_multipage" in im.encoderinfo:
#just to access o32 and o16 (using correct byte order)
# just to access o32 and o16 (using correct byte order)
im._debug_multipage = ifd
#

View File

@ -46,8 +46,8 @@ TAGS = {
(262, 5): "CMYK",
(262, 6): "YCbCr",
(262, 8): "CieLAB",
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
(262, 32892): "LinearRaw", # Adobe DNG
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
(262, 32892): "LinearRaw", # Adobe DNG
263: "Thresholding",
264: "CellWidth",
@ -147,6 +147,100 @@ TAGS = {
# ICC Profile
34675: "ICCProfile",
# Additional Exif Info
33434: "ExposureTime",
33437: "FNumber",
34850: "ExposureProgram",
34852: "SpectralSensitivity",
34853: "GPSInfoIFD",
34855: "ISOSpeedRatings",
34856: "OECF",
34864: "SensitivityType",
34865: "StandardOutputSensitivity",
34866: "RecommendedExposureIndex",
34867: "ISOSpeed",
34868: "ISOSpeedLatitudeyyy",
34869: "ISOSpeedLatitudezzz",
36864: "ExifVersion",
36867: "DateTimeOriginal",
36868: "DateTImeDigitized",
37121: "ComponentsConfiguration",
37122: "CompressedBitsPerPixel",
37377: "ShutterSpeedValue",
37378: "ApertureValue",
37379: "BrightnessValue",
37380: "ExposureBiasValue",
37381: "MaxApertureValue",
37382: "SubjectDistance",
37383: "MeteringMode",
37384: "LightSource",
37385: "Flash",
37386: "FocalLength",
37396: "SubjectArea",
37500: "MakerNote",
37510: "UserComment",
37520: "SubSec",
37521: "SubSecTimeOriginal",
37522: "SubsecTimeDigitized",
40960: "FlashPixVersion",
40961: "ColorSpace",
40962: "PixelXDimension",
40963: "PixelYDimension",
40964: "RelatedSoundFile",
40965: "InteroperabilityIFD",
41483: "FlashEnergy",
41484: "SpatialFrequencyResponse",
41486: "FocalPlaneXResolution",
41487: "FocalPlaneYResolution",
41488: "FocalPlaneResolutionUnit",
41492: "SubjectLocation",
41493: "ExposureIndex",
41495: "SensingMethod",
41728: "FileSource",
41729: "SceneType",
41730: "CFAPattern",
41985: "CustomRendered",
41986: "ExposureMode",
41987: "WhiteBalance",
41988: "DigitalZoomRatio",
41989: "FocalLengthIn35mmFilm",
41990: "SceneCaptureType",
41991: "GainControl",
41992: "Contrast",
41993: "Saturation",
41994: "Sharpness",
41995: "DeviceSettingDescription",
41996: "SubjectDistanceRange",
42016: "ImageUniqueID",
42032: "CameraOwnerName",
42033: "BodySerialNumber",
42034: "LensSpecification",
42035: "LensMake",
42036: "LensModel",
42037: "LensSerialNumber",
42240: "Gamma",
# MP Info
45056: "MPFVersion",
45057: "NumberOfImages",
45058: "MPEntry",
45059: "ImageUIDList",
45060: "TotalFrames",
45313: "MPIndividualNum",
45569: "PanOrientation",
45570: "PanOverlap_H",
45571: "PanOverlap_V",
45572: "BaseViewpointNum",
45573: "ConvergenceAngle",
45574: "BaselineLength",
45575: "VerticalDivergence",
45576: "AxisDistance_X",
45577: "AxisDistance_Y",
45578: "AxisDistance_Z",
45579: "YawAngle",
45580: "PitchAngle",
45581: "RollAngle",
# Adobe DNG
50706: "DNGVersion",
50707: "DNGBackwardVersion",
@ -161,7 +255,6 @@ TAGS = {
50716: "BlackLevelDeltaV",
50717: "WhiteLevel",
50718: "DefaultScale",
50741: "BestQualityScale",
50719: "DefaultCropOrigin",
50720: "DefaultCropSize",
50778: "CalibrationIlluminant1",
@ -186,10 +279,11 @@ TAGS = {
50738: "AntiAliasStrength",
50740: "DNGPrivateData",
50741: "MakerNoteSafety",
50780: "BestQualityScale",
#ImageJ
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
50839: "ImageJMetaData", # private tag registered with Adobe
# ImageJ
50838: "ImageJMetaDataByteCounts", # 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.
# $Id$
#
@ -14,11 +12,11 @@
#
# NOTE: This format cannot be automatically recognized, so the reader
# is not registered for use with Image.open(). To open a WEL file, use
# is not registered for use with Image.open(). To open a WAL file, use
# the WalImageFile.open() function instead.
# This reader is based on the specification available from:
# http://www.flipcode.com/tutorials/tut_q2levels.shtml
# http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
# and has been tested with a few sample files found using google.
from __future__ import print_function
@ -33,6 +31,7 @@ except ImportError:
i32 = _binary.i32le
##
# Load texture from a Quake2 WAL texture file.
# <p>
@ -75,7 +74,7 @@ def open(filename):
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"\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"

View File

@ -12,7 +12,7 @@ _VALID_WEBP_MODES = {
_VP8_MODES_BY_IDENTIFIER = {
b"VP8 ": "RGB",
b"VP8X": "RGBA",
b"VP8L": "RGBA", # lossless
b"VP8L": "RGBA", # lossless
}
@ -30,10 +30,13 @@ class WebPImageFile(ImageFile.ImageFile):
format_description = "WebP image"
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())
self.info["icc_profile"] = icc_profile
self.info["exif"] = exif
if icc_profile:
self.info["icc_profile"] = icc_profile
if exif:
self.info["exif"] = exif
self.size = width, height
self.fp = BytesIO(data)

View File

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

View File

@ -35,9 +35,11 @@ xbm_head = re.compile(
b"[\\000-\\377]*_bits\\[\\]"
)
def _accept(prefix):
return prefix.lstrip()[:7] == b"#define"
##
# Image plugin for X11 bitmaps.
@ -81,7 +83,7 @@ def _save(im, fp, filename):
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")

View File

@ -29,6 +29,7 @@ xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)")
def _accept(prefix):
return prefix[:9] == b"/* XPM */"
##
# Image plugin for X11 pixel maps.
@ -86,9 +87,9 @@ class XpmImageFile(ImageFile.ImageFile):
elif rgb[0:1] == b"#":
# FIXME: handle colour names (see ImagePalette.py)
rgb = int(rgb[1:], 16)
palette[c] = o8((rgb >> 16) & 255) +\
o8((rgb >> 8) & 255) +\
o8(rgb & 255)
palette[c] = (o8((rgb >> 16) & 255) +
o8((rgb >> 8) & 255) +
o8(rgb & 255))
else:
# unknown colour
raise ValueError("cannot read this XPM file")

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