mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-12 18:26:17 +03:00
Merge remote-tracking branch 'upstream/master'
Conflicts: PIL/ImageQt.py
This commit is contained in:
commit
f3284e57a6
14
.coveragerc
Normal file
14
.coveragerc
Normal 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
73
.gitignore
vendored
|
@ -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
2
.landscape.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
strictness: medium
|
||||
test-warnings: yes
|
68
.travis.yml
68
.travis.yml
|
@ -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
|
||||
|
|
509
CHANGES.rst
509
CHANGES.rst
|
@ -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 Lagadec’s 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
26
CONTRIBUTING.md
Normal 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?
|
|
@ -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>
|
BIN
Images/lena.fli
BIN
Images/lena.fli
Binary file not shown.
BIN
Images/lena.gif
BIN
Images/lena.gif
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
BIN
Images/lena.ico
BIN
Images/lena.ico
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
BIN
Images/lena.jpg
BIN
Images/lena.jpg
Binary file not shown.
Before Width: | Height: | Size: 4.7 KiB |
BIN
Images/lena.png
BIN
Images/lena.png
Binary file not shown.
Before Width: | Height: | Size: 37 KiB |
BIN
Images/lena.ppm
BIN
Images/lena.ppm
Binary file not shown.
BIN
Images/lena.psd
BIN
Images/lena.psd
Binary file not shown.
BIN
Images/lena.tar
BIN
Images/lena.tar
Binary file not shown.
BIN
Images/lena.webp
BIN
Images/lena.webp
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB |
175
Images/lena.xpm
175
Images/lena.xpm
|
@ -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@&@&#.@*&#.#@,@+@@+@@$@@@++++@@"};
|
70
MANIFEST.in
70
MANIFEST.in
|
@ -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
|
||||
|
|
56
Makefile
56
Makefile
|
@ -1,7 +1,61 @@
|
|||
.PHONY: pre clean install test inplace coverage test-dep help docs livedocs
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " clean remove build products"
|
||||
@echo " install make and install"
|
||||
@echo " test run tests on installed pillow"
|
||||
@echo " inplace make inplace extension"
|
||||
@echo " coverage run coverage test (in progress)"
|
||||
@echo " docs make html docs"
|
||||
@echo " docserver run an http server on the docs directory"
|
||||
@echo " test-dep install coveraget and test dependencies"
|
||||
|
||||
pre:
|
||||
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&
|
|
@ -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")
|
|
@ -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
|
||||
|
|
|
@ -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))])
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -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))])
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
|
501
PIL/Image.py
501
PIL/Image.py
File diff suppressed because it is too large
Load Diff
1050
PIL/ImageCms.py
1050
PIL/ImageCms.py
File diff suppressed because it is too large
Load Diff
|
@ -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 = {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
245
PIL/ImageMorph.py
Normal file
|
@ -0,0 +1,245 @@
|
|||
# A binary morphology add-on for the Python Imaging Library
|
||||
#
|
||||
# History:
|
||||
# 2014-06-04 Initial version.
|
||||
#
|
||||
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
|
||||
|
||||
from PIL import Image
|
||||
from PIL import _imagingmorph
|
||||
import re
|
||||
|
||||
LUT_SIZE = 1 << 9
|
||||
|
||||
|
||||
class LutBuilder:
|
||||
"""A class for building a MorphLut from a descriptive language
|
||||
|
||||
The input patterns is a list of a strings sequences like these::
|
||||
|
||||
4:(...
|
||||
.1.
|
||||
111)->1
|
||||
|
||||
(whitespaces including linebreaks are ignored). The option 4
|
||||
describes a series of symmetry operations (in this case a
|
||||
4-rotation), the pattern is described by:
|
||||
|
||||
- . or X - Ignore
|
||||
- 1 - Pixel is on
|
||||
- 0 - Pixel is off
|
||||
|
||||
The result of the operation is described after "->" string.
|
||||
|
||||
The default is to return the current pixel value, which is
|
||||
returned if no other match is found.
|
||||
|
||||
Operations:
|
||||
|
||||
- 4 - 4 way rotation
|
||||
- N - Negate
|
||||
- 1 - Dummy op for no other operation (an op must always be given)
|
||||
- M - Mirroring
|
||||
|
||||
Example::
|
||||
|
||||
lb = LutBuilder(patterns = ["4:(... .1. 111)->1"])
|
||||
lut = lb.build_lut()
|
||||
|
||||
"""
|
||||
def __init__(self, patterns=None, op_name=None):
|
||||
if patterns is not None:
|
||||
self.patterns = patterns
|
||||
else:
|
||||
self.patterns = []
|
||||
self.lut = None
|
||||
if op_name is not None:
|
||||
known_patterns = {
|
||||
'corner': ['1:(... ... ...)->0',
|
||||
'4:(00. 01. ...)->1'],
|
||||
'dilation4': ['4:(... .0. .1.)->1'],
|
||||
'dilation8': ['4:(... .0. .1.)->1',
|
||||
'4:(... .0. ..1)->1'],
|
||||
'erosion4': ['4:(... .1. .0.)->0'],
|
||||
'erosion8': ['4:(... .1. .0.)->0',
|
||||
'4:(... .1. ..0)->0'],
|
||||
'edge': ['1:(... ... ...)->0',
|
||||
'4:(.0. .1. ...)->1',
|
||||
'4:(01. .1. ...)->1']
|
||||
}
|
||||
if op_name not in known_patterns:
|
||||
raise Exception('Unknown pattern '+op_name+'!')
|
||||
|
||||
self.patterns = known_patterns[op_name]
|
||||
|
||||
def add_patterns(self, patterns):
|
||||
self.patterns += patterns
|
||||
|
||||
def build_default_lut(self):
|
||||
symbols = [0, 1]
|
||||
m = 1 << 4 # pos of current pixel
|
||||
self.lut = bytearray([symbols[(i & m) > 0] for i in range(LUT_SIZE)])
|
||||
|
||||
def get_lut(self):
|
||||
return self.lut
|
||||
|
||||
def _string_permute(self, pattern, permutation):
|
||||
"""string_permute takes a pattern and a permutation and returns the
|
||||
string permuted according to the permutation list.
|
||||
"""
|
||||
assert(len(permutation) == 9)
|
||||
return ''.join([pattern[p] for p in permutation])
|
||||
|
||||
def _pattern_permute(self, basic_pattern, options, basic_result):
|
||||
"""pattern_permute takes a basic pattern and its result and clones
|
||||
the pattern according to the modifications described in the $options
|
||||
parameter. It returns a list of all cloned patterns."""
|
||||
patterns = [(basic_pattern, basic_result)]
|
||||
|
||||
# rotations
|
||||
if '4' in options:
|
||||
res = patterns[-1][1]
|
||||
for i in range(4):
|
||||
patterns.append(
|
||||
(self._string_permute(patterns[-1][0], [6, 3, 0,
|
||||
7, 4, 1,
|
||||
8, 5, 2]), res))
|
||||
# mirror
|
||||
if 'M' in options:
|
||||
n = len(patterns)
|
||||
for pattern, res in patterns[0:n]:
|
||||
patterns.append(
|
||||
(self._string_permute(pattern, [2, 1, 0,
|
||||
5, 4, 3,
|
||||
8, 7, 6]), res))
|
||||
|
||||
# negate
|
||||
if 'N' in options:
|
||||
n = len(patterns)
|
||||
for pattern, res in patterns[0:n]:
|
||||
# Swap 0 and 1
|
||||
pattern = (pattern
|
||||
.replace('0', 'Z')
|
||||
.replace('1', '0')
|
||||
.replace('Z', '1'))
|
||||
res = '%d' % (1-int(res))
|
||||
patterns.append((pattern, res))
|
||||
|
||||
return patterns
|
||||
|
||||
def build_lut(self):
|
||||
"""Compile all patterns into a morphology lut.
|
||||
|
||||
TBD :Build based on (file) morphlut:modify_lut
|
||||
"""
|
||||
self.build_default_lut()
|
||||
patterns = []
|
||||
|
||||
# Parse and create symmetries of the patterns strings
|
||||
for p in self.patterns:
|
||||
m = re.search(
|
||||
r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n', ''))
|
||||
if not m:
|
||||
raise Exception('Syntax error in pattern "'+p+'"')
|
||||
options = m.group(1)
|
||||
pattern = m.group(2)
|
||||
result = int(m.group(3))
|
||||
|
||||
# Get rid of spaces
|
||||
pattern = pattern.replace(' ', '').replace('\n', '')
|
||||
|
||||
patterns += self._pattern_permute(pattern, options, result)
|
||||
|
||||
# # Debugging
|
||||
# for p,r in patterns:
|
||||
# print p,r
|
||||
# print '--'
|
||||
|
||||
# compile the patterns into regular expressions for speed
|
||||
for i in range(len(patterns)):
|
||||
p = patterns[i][0].replace('.', 'X').replace('X', '[01]')
|
||||
p = re.compile(p)
|
||||
patterns[i] = (p, patterns[i][1])
|
||||
|
||||
# Step through table and find patterns that match.
|
||||
# Note that all the patterns are searched. The last one
|
||||
# caught overrides
|
||||
for i in range(LUT_SIZE):
|
||||
# Build the bit pattern
|
||||
bitpattern = bin(i)[2:]
|
||||
bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1]
|
||||
|
||||
for p, r in patterns:
|
||||
if p.match(bitpattern):
|
||||
self.lut[i] = [0, 1][r]
|
||||
|
||||
return self.lut
|
||||
|
||||
|
||||
class MorphOp:
|
||||
"""A class for binary morphological operators"""
|
||||
|
||||
def __init__(self,
|
||||
lut=None,
|
||||
op_name=None,
|
||||
patterns=None):
|
||||
"""Create a binary morphological operator"""
|
||||
self.lut = lut
|
||||
if op_name is not None:
|
||||
self.lut = LutBuilder(op_name=op_name).build_lut()
|
||||
elif patterns is not None:
|
||||
self.lut = LutBuilder(patterns=patterns).build_lut()
|
||||
|
||||
def apply(self, image):
|
||||
"""Run a single morphological operation on an image
|
||||
|
||||
Returns a tuple of the number of changed pixels and the
|
||||
morphed image"""
|
||||
if self.lut is None:
|
||||
raise Exception('No operator loaded')
|
||||
|
||||
outimage = Image.new(image.mode, image.size, None)
|
||||
count = _imagingmorph.apply(
|
||||
bytes(self.lut), image.im.id, outimage.im.id)
|
||||
return count, outimage
|
||||
|
||||
def match(self, image):
|
||||
"""Get a list of coordinates matching the morphological operation on
|
||||
an image.
|
||||
|
||||
Returns a list of tuples of (x,y) coordinates
|
||||
of all matching pixels."""
|
||||
if self.lut is None:
|
||||
raise Exception('No operator loaded')
|
||||
|
||||
return _imagingmorph.match(bytes(self.lut), image.im.id)
|
||||
|
||||
def get_on_pixels(self, image):
|
||||
"""Get a list of all turned on pixels in a binary image
|
||||
|
||||
Returns a list of tuples of (x,y) coordinates
|
||||
of all matching pixels."""
|
||||
|
||||
return _imagingmorph.get_on_pixels(image.im.id)
|
||||
|
||||
def load_lut(self, filename):
|
||||
"""Load an operator from an mrl file"""
|
||||
with open(filename, 'rb') as f:
|
||||
self.lut = bytearray(f.read())
|
||||
|
||||
if len(self.lut) != 8192:
|
||||
self.lut = None
|
||||
raise Exception('Wrong size operator file!')
|
||||
|
||||
def save_lut(self, filename):
|
||||
"""Save an operator to an mrl file"""
|
||||
if self.lut is None:
|
||||
raise Exception('No operator loaded')
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(self.lut)
|
||||
|
||||
def set_lut(self, lut):
|
||||
"""Set the lut from an external source"""
|
||||
self.lut = lut
|
||||
|
||||
# End of file
|
|
@ -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])"""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
277
PIL/Jpeg2KImagePlugin.py
Normal 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')
|
|
@ -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")
|
||||
|
|
|
@ -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]
|
||||
]},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ from PIL.OleFileIO import *
|
|||
def _accept(prefix):
|
||||
return prefix[:8] == MAGIC
|
||||
|
||||
|
||||
##
|
||||
# Image plugin for Microsoft's Image Composer file format.
|
||||
|
||||
|
|
|
@ -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
90
PIL/MpoImagePlugin.py
Normal 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")
|
|
@ -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
351
PIL/OleFileIO-README.md
Normal 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
2130
PIL/OleFileIO.py
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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))]
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
313
PIL/PyAccess.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
from PIL import ContainerIO
|
||||
|
||||
|
||||
##
|
||||
# A file object that provides read access to a given member of a TAR
|
||||
# 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")
|
||||
|
|
|
@ -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
|
||||
|
||||
#
|
||||
|
|
106
PIL/TiffTags.py
106
PIL/TiffTags.py
|
@ -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
|
||||
}
|
||||
|
||||
##
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user