Merge from master

This commit is contained in:
wiredfool 2014-08-22 14:50:33 -07:00
commit c7b90f39f4
316 changed files with 11705 additions and 7240 deletions

View File

@ -9,3 +9,6 @@ exclude_lines =
# Don't complain if non-runnable code isn't run: # Don't complain if non-runnable code isn't run:
if 0: if 0:
if __name__ == .__main__.: if __name__ == .__main__.:
# Don't complain about debug code
if Image.DEBUG:
if DEBUG:

6
.gitignore vendored
View File

@ -60,3 +60,9 @@ docs/_build/
\#*# \#*#
.#* .#*
#Komodo
*.komodoproject
#OS
.DS_Store

View File

@ -3,18 +3,25 @@ language: python
notifications: notifications:
irc: "chat.freenode.net#pil" irc: "chat.freenode.net#pil"
env: MAX_CONCURRENCY=4
python: python:
- "pypy" - "pypy"
- "pypy3"
- 2.6 - 2.6
- 2.7 - 2.7
- "2.7_with_system_site_packages" # For PyQt4
- 3.2 - 3.2
- 3.3 - 3.3
- 3.4 - 3.4
install: install:
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick lcov"
- "pip install cffi" - "pip install cffi"
- "pip install coveralls" - "pip install coveralls nose coveralls-merge"
- "gem install coveralls-lcov"
- travis_retry pip install pyroma
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi
# webp # webp
- pushd depends && ./install_webp.sh && popd - pushd depends && ./install_webp.sh && popd
@ -25,21 +32,31 @@ install:
script: script:
- coverage erase - coverage erase
- python setup.py clean - python setup.py clean
- python setup.py build_ext --inplace - CFLAGS="-coverage" python setup.py build_ext --inplace
# Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - coverage run --append --include=PIL/* selftest.py
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi - coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi
# Cover the others
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi
after_success: after_success:
# gather the coverage data
- lcov --capture --directory . -b . --output-file coverage.info
# filter to remove system headers
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
# convert to json
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
- coverage report - coverage report
- coveralls - coveralls-merge coverage.c.json
- pip install pep8 pyflakes - pip install pep8 pyflakes
- pep8 PIL/*.py - pep8 --statistics --count PIL/*.py
- pyflakes PIL/*.py - pep8 --statistics --count Tests/*.py
- pep8 Tests/*.py - pyflakes PIL/*.py | tee >(wc -l)
- pyflakes Tests/*.py - pyflakes Tests/*.py | tee >(wc -l)
# Coverage and quality reports on just the latest diff.
# (Installation is very slow on Py3, so just do it for Py2.)
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi

View File

@ -1,38 +1,194 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
2.5.0 (unreleased) 2.6.0 (unreleased)
------------------ ------------------
- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport)
[Andrew Drake]
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin
[Andrew Drake]
- setup.py: Close open file handle before deleting #844
[divergentdave]
- Return Profile with Transformed Images #837
[wiredfool]
- Changed docstring to refer to the correct function #836
[MatMoore]
- Adding coverage support for C code tests #833
[wiredfool]
- PyPy performance improvements #821
[wiredfool]
- Added support for reading MPO files
[Feneric]
- Added support for encoding and decoding iTXt chunks #818
[dolda2000]
- HSV Support #816
[wiredfool]
- Removed unusable ImagePalette.new()
[hugovk]
- Fix Scrambled XPM #808
[wiredfool]
- Doc cleanup
[wiredfool]
- Fix `ImageStat` docs
[akx]
- Added docs for ExifTags
[Wintermute3]
- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
[hugovk]
- Fix return value of FreeTypeFont.textsize() does not include font offsets
[tk0miya]
- Fix dispose calculations for animated GIFs #765
[larsjsol]
- Added class checking to Image __eq__ function #775
[radarhere, hugovk]
- Test PalmImagePlugin and method to skip known bad tests #776
[hugovk, wiredfool]
2.5.3 (2014-08-18)
------------------
- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport)
[Andrew Drake]
2.5.2 (2014-08-13)
------------------
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport)
[Andrew Drake]
2.5.1 (2014-07-10)
------------------
- Fixed install issue if Multiprocessing.Pool is not available
[wiredfool]
- 32bit mult overflow fix #782
[wiredfool]
2.5.0 (2014-07-01)
------------------
- Imagedraw rewrite
[terseus, wiredfool]
- Add support for multithreaded test execution
[wiredfool]
- Prevent shell injection #748
[mbrown1413, wiredfool]
- Support for Resolution in BMP files #734
[gcq]
- Fix error in setup.py for Python 3
[matthew-brett]
- Pyroma fix and add Python 3.4 to setup metadata #742
[wirefool]
- Top level flake8 fixes #741
[aclark]
- Remove obsolete Animated Raster Graphics (ARG) support
[hugovk]
- Fix test_imagedraw failures #727
[cgohlke]
- Fix AttributeError: class Image has no attribute 'DEBUG' #726
[cgohlke]
- Fix msvc warning: 'inline' : macro redefinition #725
[cgohlke]
- Cleanup #654
[dvska, hugovk, wiredfool]
- 16-bit monochrome support for JPEG2000
[videan42]
- Fixed ImagePalette.save
[brightpisces]
- Support JPEG qtables
[csinchok]
- Add binary morphology addon
[dov, wiredfool]
- Decompression bomb protection
[hugovk]
- Put images in a single directory
[hugovk]
- Support OpenJpeg 2.1
[al45tair]
- Remove unistd.h #include for all platforms
[wiredfool]
- Use unittest for tests
[hugovk]
- ImageCms fixes
[hugovk]
- Added more ImageDraw tests
[hugovk]
- Added tests for Spider files - Added tests for Spider files
[hugovk] [hugovk]
- Use libtiff to write any compressed tiff files - Use libtiff to write any compressed tiff files
[wiredfool] [wiredfool]
- Support for pickling Image objects - Support for pickling Image objects
[hugovk] [hugovk]
- Fixed resolution handling for EPS thumbnails - Fixed resolution handling for EPS thumbnails
[eliempje] [eliempje]
- Fixed rendering of some binary EPS files (Issue #302) - Fixed rendering of some binary EPS files (Issue #302)
[eliempje] [eliempje]
- Rename variables not to use built-in function names - Rename variables not to use built-in function names
[hugovk] [hugovk]
- Ignore junk JPEG markers - Ignore junk JPEG markers
[hugovk] [hugovk]
- Change default interpolation for Image.thumbnail to Image.ANTIALIAS - Change default interpolation for Image.thumbnail to Image.ANTIALIAS
[hugovk] [hugovk]
- Add tests and fixes for saving PDFs - Add tests and fixes for saving PDFs
[hugovk] [hugovk]
- Remove transparency resource after P->RGBA conversion - Remove transparency resource after P->RGBA conversion
[hugovk] [hugovk]
- Clean up preprocessor cruft for Windows - Clean up preprocessor cruft for Windows
[CounterPillow] [CounterPillow]
@ -42,13 +198,13 @@ Changelog (Pillow)
- Added Image.close, context manager support. - Added Image.close, context manager support.
[wiredfool] [wiredfool]
- Added support for 16 bit PGM files. - Added support for 16 bit PGM files.
[wiredfool] [wiredfool]
- Updated OleFileIO to version 0.30 from upstream - Updated OleFileIO to version 0.30 from upstream
[hugovk] [hugovk]
- Added support for additional TIFF floating point format - Added support for additional TIFF floating point format
[Hijackal] [Hijackal]
- Have the tempfile use a suffix with a dot - Have the tempfile use a suffix with a dot
@ -78,7 +234,7 @@ Changelog (Pillow)
- Added support for JPEG 2000 - Added support for JPEG 2000
[al45tair] [al45tair]
- Add more detailed error messages to Image.py - Add more detailed error messages to Image.py
[larsmans] [larsmans]
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538 - Avoid conflicting _expand functions in PIL & MINGW, fixes #538
@ -106,7 +262,7 @@ Changelog (Pillow)
[wiredfool] [wiredfool]
- Fixed palette handling when converting from mode P->RGB->P - Fixed palette handling when converting from mode P->RGB->P
[d_schmidt] [d_schmidt]
- Fixed saving mode P image as a PNG with transparency = palette color 0 - Fixed saving mode P image as a PNG with transparency = palette color 0
[d-schmidt] [d-schmidt]
@ -116,7 +272,7 @@ Changelog (Pillow)
- Fixed DOS with invalid palette size or invalid image size in BMP file - Fixed DOS with invalid palette size or invalid image size in BMP file
[wiredfool] [wiredfool]
- Added support for BMP version 4 and 5 - Added support for BMP version 4 and 5
[eddwardo, wiredfool] [eddwardo, wiredfool]
@ -149,7 +305,13 @@ Changelog (Pillow)
- Prefer homebrew freetype over X11 freetype (but still allow both) - Prefer homebrew freetype over X11 freetype (but still allow both)
[dmckeone] [dmckeone]
2.3.2 (2014-08-13)
------------------
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport)
[Andrew Drake]
2.3.1 (2014-03-14) 2.3.1 (2014-03-14)
------------------ ------------------
@ -274,7 +436,7 @@ Changelog (Pillow)
[nikmolnar] [nikmolnar]
- Fix for encoding of b_whitespace, similar to closed issue #272 - 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 - Fix #273: Add numpy array interface support for 16 and 32 bit integer modes
[cgohlke] [cgohlke]
@ -431,10 +593,14 @@ Changelog (Pillow)
2.0.0 (2013-03-15) 2.0.0 (2013-03-15)
------------------ ------------------
.. Note:: Special thanks to Christoph Gohlke and Eric Soroos for assisting with a pre-PyCon 2013 release!
- Many other bug fixes and enhancements by many other people.
- Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.) - Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.)
[fluggo] [fluggo]
- Add PyPy support (experimental, please see: https://github.com/python-imaging/Pillow/issues/67) - Add PyPy support (experimental, please see: https://github.com/python-pillow/Pillow/issues/67)
- Add WebP support. - Add WebP support.
[lqs] [lqs]
@ -454,10 +620,6 @@ Changelog (Pillow)
- Added support for PNG images with transparency palette. - Added support for PNG images with transparency palette.
[d-schmidt] [d-schmidt]
- Many other bug fixes and enhancements by many other people (see commit log and/or docs/CONTRIBUTORS.txt).
- Special thanks to Christoph Gohlke and Eric Soroos for rallying around the effort to get a release out for PyCon 2013.
1.7.8 (2012-11-01) 1.7.8 (2012-11-01)
------------------ ------------------
@ -530,44 +692,55 @@ Changelog (Pillow)
[elro] [elro]
- Doc fixes - Doc fixes
[aclark]
1.5 (11/28/2010) 1.5 (11/28/2010)
---------------- ----------------
- Module and package fixes - Module and package fixes
[aclark]
1.4 (11/28/2010) 1.4 (11/28/2010)
---------------- ----------------
- Doc fixes - Doc fixes
[aclark]
1.3 (11/28/2010) 1.3 (11/28/2010)
---------------- ----------------
- Add support for /lib64 and /usr/lib64 library directories on Linux - Add support for /lib64 and /usr/lib64 library directories on Linux
[aclark]
- Doc fixes - Doc fixes
[aclark]
1.2 (08/02/2010) 1.2 (08/02/2010)
---------------- ----------------
- On OS X also check for freetype2 in the X11 path [jezdez] - On OS X also check for freetype2 in the X11 path
- Doc fixes [aclark] [jezdez]
- Doc fixes
[aclark]
1.1 (07/31/2010) 1.1 (07/31/2010)
---------------- ----------------
- Removed setuptools_hg requirement - Removed setuptools_hg requirement
[aclark]
- Doc fixes - Doc fixes
[aclark]
1.0 (07/30/2010) 1.0 (07/30/2010)
---------------- ----------------
- Forked PIL based on Hanno Schlichting's re-packaging - Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required.
(http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz) - Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
[aclark]
- Remove support for importing from the standard namespace .. Note:: What follows is the original PIL 1.1.7 CHANGES
.. Note:: What follows is the original PIL 1.1.7 CHANGES file contents
:: ::

26
CONTRIBUTING.md Normal file
View File

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

View File

@ -1,7 +1,11 @@
include *.c include *.c
include *.h include *.h
include *.md
include *.py include *.py
include *.rst include *.rst
include *.txt
include .coveragerc
include .gitattributes include .gitattributes
include .travis.yml include .travis.yml
include Makefile include Makefile
@ -23,33 +27,59 @@ recursive-include Images *.xpm
recursive-include PIL *.md recursive-include PIL *.md
recursive-include Sane *.c recursive-include Sane *.c
recursive-include Sane *.py recursive-include Sane *.py
recursive-include Sane *.rst
recursive-include Sane *.txt recursive-include Sane *.txt
recursive-include Sane CHANGES recursive-include Sane CHANGES
recursive-include Sane README recursive-include Sane README
recursive-include Scripts *.py recursive-include Scripts *.py
recursive-include Scripts *.rst
recursive-include Scripts *.sh
recursive-include Scripts README recursive-include Scripts README
recursive-include Tests *.bdf
recursive-include Tests *.bin recursive-include Tests *.bin
recursive-include Tests *.bmp recursive-include Tests *.bmp
recursive-include Tests *.bw
recursive-include Tests *.cur
recursive-include Tests *.dcx
recursive-include Tests *.doc
recursive-include Tests *.eps recursive-include Tests *.eps
recursive-include Tests *.fli
recursive-include Tests *.gif recursive-include Tests *.gif
recursive-include Tests *.gnuplot recursive-include Tests *.gnuplot
recursive-include Tests *.html recursive-include Tests *.html
recursive-include Tests *.icm recursive-include Tests *.icm
recursive-include Tests *.icns recursive-include Tests *.icns
recursive-include Tests *.ico recursive-include Tests *.ico
recursive-include Tests *.j2k
recursive-include Tests *.jp2 recursive-include Tests *.jp2
recursive-include Tests *.jpg recursive-include Tests *.jpg
recursive-include Tests *.lut
recursive-include Tests *.mpo
recursive-include Tests *.pbm
recursive-include Tests *.pcf recursive-include Tests *.pcf
recursive-include Tests *.pcx recursive-include Tests *.pcx
recursive-include Tests *.pgm
recursive-include Tests *.pil
recursive-include Tests *.png recursive-include Tests *.png
recursive-include Tests *.ppm recursive-include Tests *.ppm
recursive-include Tests *.psd
recursive-include Tests *.py recursive-include Tests *.py
recursive-include Tests *.ras
recursive-include Tests *.rgb
recursive-include Tests *.rst
recursive-include Tests *.sgi
recursive-include Tests *.spider
recursive-include Tests *.tar
recursive-include Tests *.tif recursive-include Tests *.tif
recursive-include Tests *.tiff recursive-include Tests *.tiff
recursive-include Tests *.ttf recursive-include Tests *.ttf
recursive-include Tests *.txt recursive-include Tests *.txt
recursive-include Tests *.webp
recursive-include Tests *.xpm
recursive-include Tk *.c recursive-include Tk *.c
recursive-include Tk *.rst
recursive-include Tk *.txt recursive-include Tk *.txt
recursive-include depends *.rst
recursive-include depends *.sh recursive-include depends *.sh
recursive-include docs *.bat recursive-include docs *.bat
recursive-include docs *.gitignore recursive-include docs *.gitignore
@ -57,10 +87,10 @@ recursive-include docs *.html
recursive-include docs *.py recursive-include docs *.py
recursive-include docs *.rst recursive-include docs *.rst
recursive-include docs *.txt recursive-include docs *.txt
recursive-include docs Guardfile
recursive-include docs Makefile
recursive-include docs BUILDME recursive-include docs BUILDME
recursive-include docs COPYING recursive-include docs COPYING
recursive-include docs Guardfile
recursive-include docs LICENSE recursive-include docs LICENSE
recursive-include docs Makefile
recursive-include libImaging *.c recursive-include libImaging *.c
recursive-include libImaging *.h recursive-include libImaging *.h

View File

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

View File

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

View File

@ -128,5 +128,5 @@ class BdfFontFile(FontFile.FontFile):
if not c: if not c:
break break
id, ch, (xy, dst, src), im = c id, ch, (xy, dst, src), im = c
if ch >= 0 and ch < len(self.glyph): if 0 <= ch < len(self.glyph):
self.glyph[ch] = xy, dst, src, im self.glyph[ch] = xy, dst, src, im

View File

@ -28,6 +28,7 @@ __version__ = "0.7"
from PIL import Image, ImageFile, ImagePalette, _binary from PIL import Image, ImageFile, ImagePalette, _binary
import math
i8 = _binary.i8 i8 = _binary.i8
i16 = _binary.i16le i16 = _binary.i16le
@ -88,6 +89,7 @@ class BmpImageFile(ImageFile.ImageFile):
bits = i16(s[14:]) bits = i16(s[14:])
self.size = i32(s[4:]), i32(s[8:]) self.size = i32(s[4:]), i32(s[8:])
compression = i32(s[16:]) compression = i32(s[16:])
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
lutsize = 4 lutsize = 4
colors = i32(s[32:]) colors = i32(s[32:])
direction = -1 direction = -1
@ -95,6 +97,8 @@ class BmpImageFile(ImageFile.ImageFile):
# upside-down storage # upside-down storage
self.size = self.size[0], 2**32 - self.size[1] self.size = self.size[0], 2**32 - self.size[1]
direction = 0 direction = 0
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm))
else: else:
raise IOError("Unsupported BMP header type (%d)" % len(s)) raise IOError("Unsupported BMP header type (%d)" % len(s))
@ -203,30 +207,37 @@ def _save(im, fp, filename, check=0):
if check: if check:
return check return check
info = im.encoderinfo
dpi = info.get("dpi", (96, 96))
# 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
stride = ((im.size[0]*bits+7)//8+3)&(~3) stride = ((im.size[0]*bits+7)//8+3)&(~3)
header = 40 # or 64 for OS/2 version 2 header = 40 # or 64 for OS/2 version 2
offset = 14 + header + colors * 4 offset = 14 + header + colors * 4
image = stride * im.size[1] image = stride * im.size[1]
# bitmap header # bitmap header
fp.write(b"BM" + # file type (magic) fp.write(b"BM" + # file type (magic)
o32(offset+image) + # file size o32(offset+image) + # file size
o32(0) + # reserved o32(0) + # reserved
o32(offset)) # image data offset o32(offset)) # image data offset
# bitmap info header # bitmap info header
fp.write(o32(header) + # info header size fp.write(o32(header) + # info header size
o32(im.size[0]) + # width o32(im.size[0]) + # width
o32(im.size[1]) + # height o32(im.size[1]) + # height
o16(1) + # planes o16(1) + # planes
o16(bits) + # depth o16(bits) + # depth
o32(0) + # compression (0=uncompressed) o32(0) + # compression (0=uncompressed)
o32(image) + # size of bitmap o32(image) + # size of bitmap
o32(1) + o32(1) + # resolution o32(ppm[0]) + o32(ppm[1]) + # resolution
o32(colors) + # colors used o32(colors) + # colors used
o32(colors)) # colors important o32(colors)) # colors important
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": if im.mode == "1":
for i in (0, 255): for i in (0, 255):

View File

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

View File

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

View File

@ -104,7 +104,7 @@ def Ghostscript(tile, size, fp, scale=1):
s = fp.read(min(lengthfile, 100*1024)) s = fp.read(min(lengthfile, 100*1024))
if not s: if not s:
break break
lengthfile = lengthfile - len(s) length -= len(s)
f.write(s) f.write(s)
# Build ghostscript command # Build ghostscript command
@ -161,7 +161,7 @@ class PSFile:
def tell(self): def tell(self):
pos = self.fp.tell() pos = self.fp.tell()
if self.char: if self.char:
pos = pos - 1 pos -= 1
return pos return pos
def readline(self): def readline(self):
s = b"" s = b""

View File

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

View File

@ -105,7 +105,7 @@ class FliImageFile(ImageFile.ImageFile):
g = i8(s[n+1]) << shift g = i8(s[n+1]) << shift
b = i8(s[n+2]) << shift b = i8(s[n+2]) << shift
palette[i] = (r, g, b) palette[i] = (r, g, b)
i = i + 1 i += 1
def seek(self, frame): def seek(self, frame):

View File

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

View File

@ -84,7 +84,7 @@ class FpxImageFile(ImageFile.ImageFile):
i = 1 i = 1
while size > 64: while size > 64:
size = size / 2 size = size / 2
i = i + 1 i += 1
self.maxid = i - 1 self.maxid = i - 1
# mode. instead of using a single field for this, flashpix # mode. instead of using a single field for this, flashpix

View File

@ -96,8 +96,15 @@ class GifImageFile(ImageFile.ImageFile):
# rewind # rewind
self.__offset = 0 self.__offset = 0
self.dispose = None self.dispose = None
self.dispose_extent = [0, 0, 0, 0] #x0, y0, x1, y1
self.__frame = -1 self.__frame = -1
self.__fp.seek(self.__rewind) self.__fp.seek(self.__rewind)
self._prev_im = None
self.disposal_method = 0
else:
# ensure that the previous frame was loaded
if not self.im:
self.load()
if frame != self.__frame + 1: if frame != self.__frame + 1:
raise ValueError("cannot seek to frame %d" % frame) raise ValueError("cannot seek to frame %d" % frame)
@ -114,8 +121,7 @@ class GifImageFile(ImageFile.ImageFile):
self.__offset = 0 self.__offset = 0
if self.dispose: if self.dispose:
self.im = self.dispose self.im.paste(self.dispose, self.dispose_extent)
self.dispose = None
from copy import copy from copy import copy
self.palette = copy(self.global_palette) self.palette = copy(self.global_palette)
@ -140,17 +146,16 @@ class GifImageFile(ImageFile.ImageFile):
if flags & 1: if flags & 1:
self.info["transparency"] = i8(block[3]) self.info["transparency"] = i8(block[3])
self.info["duration"] = i16(block[1:3]) * 10 self.info["duration"] = i16(block[1:3]) * 10
try:
# disposal methods # disposal method - find the value of bits 4 - 6
if flags & 8: dispose_bits = 0b00011100 & flags
# replace with background colour dispose_bits = dispose_bits >> 2
self.dispose = Image.core.fill("P", self.size, if dispose_bits:
self.info["background"]) # only set the dispose if it is not
elif flags & 16: # unspecified. I'm not sure if this is
# replace with previous contents # correct, but it seems to prevent the last
self.dispose = self.im.copy() # frame from looking odd for some animations
except (AttributeError, KeyError): self.disposal_method = dispose_bits
pass
elif i8(s) == 255: elif i8(s) == 255:
# #
# application extension # application extension
@ -172,6 +177,7 @@ class GifImageFile(ImageFile.ImageFile):
# extent # extent
x0, y0 = i16(s[0:]), i16(s[2:]) x0, y0 = i16(s[0:]), i16(s[2:])
x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:]) x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:])
self.dispose_extent = x0, y0, x1, y1
flags = i8(s[8]) flags = i8(s[8])
interlace = (flags & 64) != 0 interlace = (flags & 64) != 0
@ -194,6 +200,26 @@ class GifImageFile(ImageFile.ImageFile):
pass pass
# raise IOError, "illegal GIF tag `%x`" % i8(s) # raise IOError, "illegal GIF tag `%x`" % i8(s)
try:
if self.disposal_method < 2:
# do not dispose or none specified
self.dispose = None
elif self.disposal_method == 2:
# replace with background colour
self.dispose = Image.core.fill("P", self.size,
self.info["background"])
else:
# replace with previous contents
if self.im:
self.dispose = self.im.copy()
# only dispose the extent in this frame
if self.dispose:
self.dispose = self.dispose.crop(self.dispose_extent)
except (AttributeError, KeyError):
pass
if not self.tile: if not self.tile:
# self.__fp = None # self.__fp = None
raise EOFError("no more images in GIF file") raise EOFError("no more images in GIF file")
@ -205,6 +231,18 @@ class GifImageFile(ImageFile.ImageFile):
def tell(self): def tell(self):
return self.__frame return self.__frame
def load_end(self):
ImageFile.ImageFile.load_end(self)
# if the disposal method is 'do not dispose', transparent
# pixels should show the content of the previous frame
if self._prev_im and self.disposal_method == 1:
# we do this by pasting the updated area onto the previous
# frame which we then use as the current image content
updated = self.im.crop(self.dispose_extent)
self._prev_im.paste(updated, self.dispose_extent, updated.convert('RGBA'))
self.im = self._prev_im
self._prev_im = self.im.copy()
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write GIF files # Write GIF files
@ -230,10 +268,9 @@ def _save(im, fp, filename):
except IOError: except IOError:
pass # write uncompressed file pass # write uncompressed file
try: if im.mode in RAWMODE:
rawmode = RAWMODE[im.mode]
imOut = im imOut = im
except KeyError: else:
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL # convert on the fly (EXPERIMENTAL -- I'm not sure PIL
# should automatically convert images on save...) # should automatically convert images on save...)
if Image.getmodebase(im.mode) == "RGB": if Image.getmodebase(im.mode) == "RGB":
@ -241,10 +278,8 @@ def _save(im, fp, filename):
if im.palette: if im.palette:
palette_size = len(im.palette.getdata()[1]) // 3 palette_size = len(im.palette.getdata()[1]) // 3
imOut = im.convert("P", palette=1, colors=palette_size) imOut = im.convert("P", palette=1, colors=palette_size)
rawmode = "P"
else: else:
imOut = im.convert("L") imOut = im.convert("L")
rawmode = "L"
# header # header
try: try:
@ -252,12 +287,6 @@ def _save(im, fp, filename):
except KeyError: except KeyError:
palette = None palette = None
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
if im.encoderinfo["optimize"]:
# When the mode is L, and we optimize, we end up with
# im.mode == P and rawmode = L, which fails.
# If we're optimizing the palette, we're going to be
# in a rawmode of P anyway.
rawmode = 'P'
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo) header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
for s in header: for s in header:
@ -314,7 +343,7 @@ def _save(im, fp, filename):
o8(8)) # bits o8(8)) # bits
imOut.encoderconfig = (8, interlace) imOut.encoderconfig = (8, interlace)
ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, rawmode)]) ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, RAWMODE[imOut.mode])])
fp.write(b"\0") # end of image data fp.write(b"\0") # end of image data
@ -333,13 +362,41 @@ def _save_netpbm(im, fp, filename):
# below for information on how to enable this. # below for information on how to enable this.
import os import os
from subprocess import Popen, check_call, PIPE, CalledProcessError
import tempfile
file = im._dump() file = im._dump()
if im.mode != "RGB": if im.mode != "RGB":
os.system("ppmtogif %s >%s" % (file, filename)) with open(filename, 'wb') as f:
stderr = tempfile.TemporaryFile()
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
else: else:
os.system("ppmquant 256 %s | ppmtogif >%s" % (file, filename)) with open(filename, 'wb') as f:
try: os.unlink(file)
except: pass # Pipe ppmquant output into ppmtogif
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
quant_cmd = ["ppmquant", "256", file]
togif_cmd = ["ppmtogif"]
stderr = tempfile.TemporaryFile()
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
stderr = tempfile.TemporaryFile()
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=stderr)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
quant_proc.stdout.close()
retcode = quant_proc.wait()
if retcode:
raise CalledProcessError(retcode, quant_cmd)
retcode = togif_proc.wait()
if retcode:
raise CalledProcessError(retcode, togif_cmd)
try:
os.unlink(file)
except:
pass
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -68,7 +68,7 @@ class GradientFile:
x = i / float(entries-1) x = i / float(entries-1)
while x1 < x: while x1 < x:
ix = ix + 1 ix += 1
x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix] x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
w = x1 - x0 w = x1 - x0

View File

@ -52,7 +52,7 @@ class GimpPaletteFile:
if 0 <= i <= 255: if 0 <= i <= 255:
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2]) self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
i = i + 1 i += 1
self.palette = b"".join(self.palette) self.palette = b"".join(self.palette)

View File

@ -70,7 +70,7 @@ def read_32(fobj, start_length, size):
else: else:
blocksize = byte + 1 blocksize = byte + 1
data.append(fobj.read(blocksize)) data.append(fobj.read(blocksize))
bytesleft = bytesleft - blocksize bytesleft -= blocksize
if bytesleft <= 0: if bytesleft <= 0:
break break
if bytesleft != 0: if bytesleft != 0:
@ -179,11 +179,13 @@ class IcnsFile:
i = HEADERSIZE i = HEADERSIZE
while i < filesize: while i < filesize:
sig, blocksize = nextheader(fobj) sig, blocksize = nextheader(fobj)
i = i + HEADERSIZE if blocksize <= 0:
blocksize = blocksize - HEADERSIZE raise SyntaxError('invalid block header')
i += HEADERSIZE
blocksize -= HEADERSIZE
dct[sig] = (i, blocksize) dct[sig] = (i, blocksize)
fobj.seek(blocksize, 1) fobj.seek(blocksize, 1)
i = i + blocksize i += blocksize
def itersizes(self): def itersizes(self):
sizes = [] sizes = []

View File

@ -182,7 +182,7 @@ class ImImageFile(ImageFile.ImageFile):
self.info[k] = v self.info[k] = v
if k in TAGS: if k in TAGS:
n = n + 1 n += 1
else: else:

View File

@ -31,11 +31,18 @@ from PIL import VERSION, PILLOW_VERSION, _plugins
import warnings import warnings
class DecompressionBombWarning(RuntimeWarning):
pass
class _imaging_not_installed: class _imaging_not_installed:
# module placeholder # module placeholder
def __getattr__(self, id): def __getattr__(self, id):
raise ImportError("The _imaging C module is not installed") raise ImportError("The _imaging C module is not installed")
# Limit to around a quarter gigabyte for a 24 bit (3 bpp) image
MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 / 4 / 3)
try: try:
# give Tk a chance to set up the environment, in case we're # give Tk a chance to set up the environment, in case we're
# using an _imaging module linked against libtcl/libtk (use # using an _imaging module linked against libtcl/libtk (use
@ -213,6 +220,7 @@ _MODEINFO = {
"CMYK": ("RGB", "L", ("C", "M", "Y", "K")), "CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
"YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")), "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
"LAB": ("RGB", "L", ("L", "A", "B")), "LAB": ("RGB", "L", ("L", "A", "B")),
"HSV": ("RGB", "L", ("H", "S", "V")),
# Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and # Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and
# BGR;24. Use these modes only if you know exactly what you're # BGR;24. Use these modes only if you know exactly what you're
@ -448,7 +456,7 @@ def _getscaleoffset(expr):
(a, b, c) = data # simplified syntax (a, b, c) = data # simplified syntax
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)): if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)):
return c, 0.0 return c, 0.0
if (a is stub and b == "__add__" and isinstance(c, numbers.Number)): if a is stub and b == "__add__" and isinstance(c, numbers.Number):
return 1.0, c return 1.0, c
except TypeError: except TypeError:
pass pass
@ -532,7 +540,7 @@ class Image:
try: try:
self.fp.close() self.fp.close()
except Exception as msg: except Exception as msg:
if Image.DEBUG: if DEBUG:
print ("Error closing: %s" % msg) print ("Error closing: %s" % msg)
# Instead of simply setting to None, we're setting up a # Instead of simply setting to None, we're setting up a
@ -547,7 +555,6 @@ class Image:
self.readonly = 0 self.readonly = 0
def _dump(self, file=None, format=None): def _dump(self, file=None, format=None):
import os
import tempfile import tempfile
suffix = '' suffix = ''
if format: if format:
@ -566,6 +573,8 @@ class Image:
return file return file
def __eq__(self, other): def __eq__(self, other):
if self.__class__.__name__ != other.__class__.__name__:
return False
a = (self.mode == other.mode) a = (self.mode == other.mode)
b = (self.size == other.size) b = (self.size == other.size)
c = (self.getpalette() == other.getpalette()) c = (self.getpalette() == other.getpalette())
@ -1529,7 +1538,7 @@ class Image:
clockwise around its centre. clockwise around its centre.
:param angle: In degrees counter clockwise. :param angle: In degrees counter clockwise.
:param filter: An optional resampling filter. This can be :param resample: An optional resampling filter. This can be
one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
:py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
environment), or :py:attr:`PIL.Image.BICUBIC` environment), or :py:attr:`PIL.Image.BICUBIC`
@ -1550,7 +1559,6 @@ class Image:
math.cos(angle), math.sin(angle), 0.0, math.cos(angle), math.sin(angle), 0.0,
-math.sin(angle), math.cos(angle), 0.0 -math.sin(angle), math.cos(angle), 0.0
] ]
def transform(x, y, matrix=matrix): def transform(x, y, matrix=matrix):
(a, b, c, d, e, f) = matrix (a, b, c, d, e, f) = matrix
@ -2075,7 +2083,6 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
.. versionadded:: 1.1.4 .. versionadded:: 1.1.4
""" """
"Load image from bytes or buffer"
# may pass tuple instead of argument list # may pass tuple instead of argument list
if len(args) == 1 and isinstance(args[0], tuple): if len(args) == 1 and isinstance(args[0], tuple):
@ -2173,6 +2180,20 @@ _fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I")
_fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F") _fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F")
def _decompression_bomb_check(size):
if MAX_IMAGE_PIXELS is None:
return
pixels = size[0] * size[1]
if pixels > MAX_IMAGE_PIXELS:
warnings.warn(
"Image size (%d pixels) exceeds limit of %d pixels, "
"could be decompression bomb DOS attack." %
(pixels, MAX_IMAGE_PIXELS),
DecompressionBombWarning)
def open(fp, mode="r"): def open(fp, mode="r"):
""" """
Opens and identifies the given image file. Opens and identifies the given image file.
@ -2210,7 +2231,9 @@ def open(fp, mode="r"):
factory, accept = OPEN[i] factory, accept = OPEN[i]
if not accept or accept(prefix): if not accept or accept(prefix):
fp.seek(0) fp.seek(0)
return factory(fp, filename) im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError): except (SyntaxError, IndexError, TypeError):
# import traceback # import traceback
# traceback.print_exc() # traceback.print_exc()
@ -2223,7 +2246,9 @@ def open(fp, mode="r"):
factory, accept = OPEN[i] factory, accept = OPEN[i]
if not accept or accept(prefix): if not accept or accept(prefix):
fp.seek(0) fp.seek(0)
return factory(fp, filename) im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError): except (SyntaxError, IndexError, TypeError):
# import traceback # import traceback
# traceback.print_exc() # traceback.print_exc()

View File

@ -1,19 +1,19 @@
# ## The Python Imaging Library.
# The Python Imaging Library. ## $Id$
# $Id$
# ## Optional color managment support, based on Kevin Cazabon's PyCMS
# optional color managment support, based on Kevin Cazabon's PyCMS ## library.
# library.
# ## History:
# History:
# 2009-03-08 fl Added to PIL. ## 2009-03-08 fl Added to PIL.
#
# Copyright (C) 2002-2003 Kevin Cazabon ## Copyright (C) 2002-2003 Kevin Cazabon
# Copyright (c) 2009 by Fredrik Lundh ## Copyright (c) 2009 by Fredrik Lundh
# ## Copyright (c) 2013 by Eric Soroos
# See the README file for information on usage and redistribution. See
# below for the original description. ## See the README file for information on usage and redistribution. See
# ## below for the original description.
from __future__ import print_function from __future__ import print_function
@ -66,7 +66,8 @@ pyCMS
Added try/except statements arount type() checks of Added try/except statements arount type() checks of
potential CObjects... Python won't let you use type() potential CObjects... Python won't let you use type()
on them, and raises a TypeError (stupid, if you ask me!) on them, and raises a TypeError (stupid, if you ask
me!)
Added buildProofTransformFromOpenProfiles() function. Added buildProofTransformFromOpenProfiles() function.
Additional fixes in DLL, see DLL code for details. Additional fixes in DLL, see DLL code for details.
@ -88,9 +89,9 @@ try:
from PIL import _imagingcms from PIL import _imagingcms
except ImportError as ex: except ImportError as ex:
# Allow error import for doc purposes, but error out when accessing # Allow error import for doc purposes, but error out when accessing
# anything in core. # anything in core.
from _util import deferred_error from _util import import_err
_imagingcms = deferred_error(ex) _imagingcms = import_err(ex)
from PIL._util import isStringType from PIL._util import isStringType
core = _imagingcms core = _imagingcms
@ -113,22 +114,24 @@ DIRECTION_PROOF = 2
FLAGS = { FLAGS = {
"MATRIXINPUT": 1, "MATRIXINPUT": 1,
"MATRIXOUTPUT": 2, "MATRIXOUTPUT": 2,
"MATRIXONLY": (1|2), "MATRIXONLY": (1 | 2),
"NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
"NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use) # Don't create prelinearization tables on precalculated transforms
"GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) # (internal use):
"NOTCACHE": 64, # Inhibit 1-pixel cache "NOPRELINEARIZATION": 16,
"GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
"NOTCACHE": 64, # Inhibit 1-pixel cache
"NOTPRECALC": 256, "NOTPRECALC": 256,
"NULLTRANSFORM": 512, # Don't transform anyway "NULLTRANSFORM": 512, # Don't transform anyway
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy "HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces "LOWRESPRECALC": 2048, # Use less memory to minimize resouces
"WHITEBLACKCOMPENSATION": 8192, "WHITEBLACKCOMPENSATION": 8192,
"BLACKPOINTCOMPENSATION": 8192, "BLACKPOINTCOMPENSATION": 8192,
"GAMUTCHECK": 4096, # Out of Gamut alarm "GAMUTCHECK": 4096, # Out of Gamut alarm
"SOFTPROOFING": 16384, # Do softproofing "SOFTPROOFING": 16384, # Do softproofing
"PRESERVEBLACK": 32768, # Black preservation "PRESERVEBLACK": 32768, # Black preservation
"NODEFAULTRESOURCEDEF": 16777216, # CRD special "NODEFAULTRESOURCEDEF": 16777216, # CRD special
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints
} }
_MAX_FLAG = 0 _MAX_FLAG = 0
@ -136,6 +139,7 @@ for flag in FLAGS.values():
if isinstance(flag, int): if isinstance(flag, int):
_MAX_FLAG = _MAX_FLAG | flag _MAX_FLAG = _MAX_FLAG | flag
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
# Experimental PIL-level API # Experimental PIL-level API
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
@ -146,51 +150,71 @@ for flag in FLAGS.values():
class ImageCmsProfile: class ImageCmsProfile:
def __init__(self, profile): def __init__(self, profile):
# accepts a string (filename), a file-like object, or a low-level """
# profile object :param profile: Either a string representing a filename,
a file like object containing a profile or a
low-level profile object
"""
if isStringType(profile): if isStringType(profile):
self._set(core.profile_open(profile), profile) self._set(core.profile_open(profile), profile)
elif hasattr(profile, "read"): elif hasattr(profile, "read"):
self._set(core.profile_frombytes(profile.read())) self._set(core.profile_frombytes(profile.read()))
else: else:
self._set(profile) # assume it's already a profile self._set(profile) # assume it's already a profile
def _set(self, profile, filename=None): def _set(self, profile, filename=None):
self.profile = profile self.profile = profile
self.filename = filename self.filename = filename
if profile: if profile:
self.product_name = None #profile.product_name self.product_name = None # profile.product_name
self.product_info = None #profile.product_info self.product_info = None # profile.product_info
else: else:
self.product_name = None self.product_name = None
self.product_info = None self.product_info = None
def tobytes(self):
"""
Returns the profile in a format suitable for embedding in
saved images.
:returns: a bytes object containing the ICC profile.
"""
return core.profile_tobytes(self.profile)
class ImageCmsTransform(Image.ImagePointHandler): class ImageCmsTransform(Image.ImagePointHandler):
"""Transform. This can be used with the procedural API, or with the
standard Image.point() method. # Transform. This can be used with the procedural API, or with the
""" # standard Image.point() method.
#
# Will return the output profile in the output.info['icc_profile'].
def __init__(self, input, output, input_mode, output_mode, def __init__(self, input, output, input_mode, output_mode,
intent=INTENT_PERCEPTUAL, intent=INTENT_PERCEPTUAL, proof=None,
proof=None, proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0): proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
if proof is None: if proof is None:
self.transform = core.buildTransform( self.transform = core.buildTransform(
input.profile, output.profile, input.profile, output.profile,
input_mode, output_mode, input_mode, output_mode,
intent, intent,
flags flags
) )
else: else:
self.transform = core.buildProofTransform( self.transform = core.buildProofTransform(
input.profile, output.profile, proof.profile, input.profile, output.profile, proof.profile,
input_mode, output_mode, input_mode, output_mode,
intent, proof_intent, intent, proof_intent,
flags flags
) )
# Note: inputMode and outputMode are for pyCMS compatibility only # Note: inputMode and outputMode are for pyCMS compatibility only
self.input_mode = self.inputMode = input_mode self.input_mode = self.inputMode = input_mode
self.output_mode = self.outputMode = output_mode self.output_mode = self.outputMode = output_mode
self.output_profile = output
def point(self, im): def point(self, im):
return self.apply(im) return self.apply(im)
@ -198,21 +222,24 @@ class ImageCmsTransform(Image.ImagePointHandler):
im.load() im.load()
if imOut is None: if imOut is None:
imOut = Image.new(self.output_mode, im.size, None) imOut = Image.new(self.output_mode, im.size, None)
result = self.transform.apply(im.im.id, imOut.im.id) self.transform.apply(im.im.id, imOut.im.id)
imOut.info['icc_profile'] = self.output_profile.tobytes()
return imOut return imOut
def apply_in_place(self, im): def apply_in_place(self, im):
im.load() im.load()
if im.mode != self.output_mode: if im.mode != self.output_mode:
raise ValueError("mode mismatch") # wrong output mode raise ValueError("mode mismatch") # wrong output mode
result = self.transform.apply(im.im.id, im.im.id) self.transform.apply(im.im.id, im.im.id)
im.info['icc_profile'] = self.output_profile.tobytes()
return im return im
def get_display_profile(handle=None): def get_display_profile(handle=None):
""" (experimental) Fetches the profile for the current display device. """ (experimental) Fetches the profile for the current display device.
:returns: None if the profile is not known. :returns: None if the profile is not known.
""" """
import sys import sys
if sys.platform == "win32": if sys.platform == "win32":
from PIL import ImageWin from PIL import ImageWin
@ -229,15 +256,21 @@ def get_display_profile(handle=None):
profile = get() profile = get()
return ImageCmsProfile(profile) return ImageCmsProfile(profile)
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
# pyCMS compatible layer # pyCMS compatible layer
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
class PyCMSError(Exception): class PyCMSError(Exception):
""" (pyCMS) Exception class. This is used for all errors in the pyCMS API. """
""" (pyCMS) Exception class.
This is used for all errors in the pyCMS API. """
pass pass
def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, outputMode=None, inPlace=0, flags=0):
def profileToProfile(
im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL,
outputMode=None, inPlace=0, flags=0):
""" """
(pyCMS) Applies an ICC transformation to a given image, mapping from (pyCMS) Applies an ICC transformation to a given image, mapping from
inputProfile to outputProfile. inputProfile to outputProfile.
@ -259,40 +292,45 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER
profiles, the input profile must handle RGB data, and the output profiles, the input profile must handle RGB data, and the output
profile must handle CMYK data. profile must handle CMYK data.
:param im: An open PIL image object (i.e. Image.new(...) or Image.open(...), etc.) :param im: An open PIL image object (i.e. Image.new(...) or
:param inputProfile: String, as a valid filename path to the ICC input profile Image.open(...), etc.)
you wish to use for this image, or a profile object :param inputProfile: String, as a valid filename path to the ICC input
profile you wish to use for this image, or a profile object
:param outputProfile: String, as a valid filename path to the ICC output :param outputProfile: String, as a valid filename path to the ICC output
profile you wish to use for this image, or a profile object profile you wish to use for this image, or a profile object
:param renderingIntent: Integer (0-3) specifying the rendering intent you wish :param renderingIntent: Integer (0-3) specifying the rendering intent you
to use for the transform wish to use for the transform
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
see the pyCMS documentation for details on rendering intents and what they do. see the pyCMS documentation for details on rendering intents and what
:param outputMode: A valid PIL mode for the output image (i.e. "RGB", "CMYK", they do.
etc.). Note: if rendering the image "inPlace", outputMode MUST be the :param outputMode: A valid PIL mode for the output image (i.e. "RGB",
same mode as the input, or omitted completely. If omitted, the outputMode "CMYK", etc.). Note: if rendering the image "inPlace", outputMode
will be the same as the mode of the input image (im.mode) MUST be the same mode as the input, or omitted completely. If
:param inPlace: Boolean (1 = True, None or 0 = False). If True, the original omitted, the outputMode will be the same as the mode of the input
image is modified in-place, and None is returned. If False (default), a image (im.mode)
new Image object is returned with the transform applied. :param inPlace: Boolean (1 = True, None or 0 = False). If True, the
original image is modified in-place, and None is returned. If False
(default), a new Image object is returned with the transform applied.
:param flags: Integer (0-...) specifying additional flags :param flags: Integer (0-...) specifying additional flags
:returns: Either None or a new PIL image object, depending on value of inPlace :returns: Either None or a new PIL image object, depending on value of
inPlace
:exception PyCMSError: :exception PyCMSError:
""" """
if outputMode is None: if outputMode is None:
outputMode = im.mode outputMode = im.mode
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3): if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
raise PyCMSError("renderingIntent must be an integer between 0 and 3") raise PyCMSError("renderingIntent must be an integer between 0 and 3")
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) raise PyCMSError(
"flags must be an integer between 0 and %s" + _MAX_FLAG)
try: try:
if not isinstance(inputProfile, ImageCmsProfile): if not isinstance(inputProfile, ImageCmsProfile):
@ -300,8 +338,9 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER
if not isinstance(outputProfile, ImageCmsProfile): if not isinstance(outputProfile, ImageCmsProfile):
outputProfile = ImageCmsProfile(outputProfile) outputProfile = ImageCmsProfile(outputProfile)
transform = ImageCmsTransform( transform = ImageCmsTransform(
inputProfile, outputProfile, im.mode, outputMode, renderingIntent, flags=flags inputProfile, outputProfile, im.mode, outputMode,
) renderingIntent, flags=flags
)
if inPlace: if inPlace:
transform.apply_in_place(im) transform.apply_in_place(im)
imOut = None imOut = None
@ -323,8 +362,8 @@ def getOpenProfile(profileFilename):
If profileFilename is not a vaild filename for an ICC profile, a PyCMSError If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
will be raised. will be raised.
:param profileFilename: String, as a valid filename path to the ICC profile you :param profileFilename: String, as a valid filename path to the ICC profile
wish to open, or a file-like object. you wish to open, or a file-like object.
:returns: A CmsProfile class object. :returns: A CmsProfile class object.
:exception PyCMSError: :exception PyCMSError:
""" """
@ -334,7 +373,10 @@ def getOpenProfile(profileFilename):
except (IOError, TypeError, ValueError) as v: except (IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, flags=0):
def buildTransform(
inputProfile, outputProfile, inMode, outMode,
renderingIntent=INTENT_PERCEPTUAL, flags=0):
""" """
(pyCMS) Builds an ICC transform mapping from the inputProfile to the (pyCMS) Builds an ICC transform mapping from the inputProfile to the
outputProfile. Use applyTransform to apply the transform to a given outputProfile. Use applyTransform to apply the transform to a given
@ -367,14 +409,14 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent
manually overridden if you really want to, but I don't know of any manually overridden if you really want to, but I don't know of any
time that would be of use, or would even work). time that would be of use, or would even work).
:param inputProfile: String, as a valid filename path to the ICC input profile :param inputProfile: String, as a valid filename path to the ICC input
you wish to use for this transform, or a profile object profile you wish to use for this transform, or a profile object
:param outputProfile: String, as a valid filename path to the ICC output :param outputProfile: String, as a valid filename path to the ICC output
profile you wish to use for this transform, or a profile object profile you wish to use for this transform, or a profile object
:param inMode: String, as a valid PIL mode that the appropriate profile also :param inMode: String, as a valid PIL mode that the appropriate profile
supports (i.e. "RGB", "RGBA", "CMYK", etc.) also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param outMode: String, as a valid PIL mode that the appropriate profile also :param outMode: String, as a valid PIL mode that the appropriate profile
supports (i.e. "RGB", "RGBA", "CMYK", etc.) also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param renderingIntent: Integer (0-3) specifying the rendering intent you :param renderingIntent: Integer (0-3) specifying the rendering intent you
wish to use for the transform wish to use for the transform
@ -383,28 +425,37 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
see the pyCMS documentation for details on rendering intents and what they do. see the pyCMS documentation for details on rendering intents and what
they do.
:param flags: Integer (0-...) specifying additional flags :param flags: Integer (0-...) specifying additional flags
:returns: A CmsTransform class object. :returns: A CmsTransform class object.
:exception PyCMSError: :exception PyCMSError:
""" """
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3): if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
raise PyCMSError("renderingIntent must be an integer between 0 and 3") raise PyCMSError("renderingIntent must be an integer between 0 and 3")
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) raise PyCMSError(
"flags must be an integer between 0 and %s" + _MAX_FLAG)
try: try:
if not isinstance(inputProfile, ImageCmsProfile): if not isinstance(inputProfile, ImageCmsProfile):
inputProfile = ImageCmsProfile(inputProfile) inputProfile = ImageCmsProfile(inputProfile)
if not isinstance(outputProfile, ImageCmsProfile): if not isinstance(outputProfile, ImageCmsProfile):
outputProfile = ImageCmsProfile(outputProfile) outputProfile = ImageCmsProfile(outputProfile)
return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags) return ImageCmsTransform(
inputProfile, outputProfile, inMode, outMode,
renderingIntent, flags=flags)
except (IOError, TypeError, ValueError) as v: except (IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, flags=FLAGS["SOFTPROOFING"]):
def buildProofTransform(
inputProfile, outputProfile, proofProfile, inMode, outMode,
renderingIntent=INTENT_PERCEPTUAL,
proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC,
flags=FLAGS["SOFTPROOFING"]):
""" """
(pyCMS) Builds an ICC transform mapping from the inputProfile to the (pyCMS) Builds an ICC transform mapping from the inputProfile to the
outputProfile, but tries to simulate the result that would be outputProfile, but tries to simulate the result that would be
@ -443,17 +494,17 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
when the simulated device has a much wider gamut than the output when the simulated device has a much wider gamut than the output
device, you may obtain marginal results. device, you may obtain marginal results.
:param inputProfile: String, as a valid filename path to the ICC input profile :param inputProfile: String, as a valid filename path to the ICC input
you wish to use for this transform, or a profile object profile you wish to use for this transform, or a profile object
:param outputProfile: String, as a valid filename path to the ICC output :param outputProfile: String, as a valid filename path to the ICC output
(monitor, usually) profile you wish to use for this transform, or a (monitor, usually) profile you wish to use for this transform, or a
profile object profile object
:param proofProfile: String, as a valid filename path to the ICC proof profile :param proofProfile: String, as a valid filename path to the ICC proof
you wish to use for this transform, or a profile object profile you wish to use for this transform, or a profile object
:param inMode: String, as a valid PIL mode that the appropriate profile also :param inMode: String, as a valid PIL mode that the appropriate profile
supports (i.e. "RGB", "RGBA", "CMYK", etc.) also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param outMode: String, as a valid PIL mode that the appropriate profile also :param outMode: String, as a valid PIL mode that the appropriate profile
supports (i.e. "RGB", "RGBA", "CMYK", etc.) also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param renderingIntent: Integer (0-3) specifying the rendering intent you :param renderingIntent: Integer (0-3) specifying the rendering intent you
wish to use for the input->proof (simulated) transform wish to use for the input->proof (simulated) transform
@ -462,7 +513,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
see the pyCMS documentation for details on rendering intents and what they do. see the pyCMS documentation for details on rendering intents and what
they do.
:param proofRenderingIntent: Integer (0-3) specifying the rendering intent you :param proofRenderingIntent: Integer (0-3) specifying the rendering intent you
wish to use for proof->output transform wish to use for proof->output transform
@ -471,17 +523,19 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
see the pyCMS documentation for details on rendering intents and what they do. see the pyCMS documentation for details on rendering intents and what
they do.
:param flags: Integer (0-...) specifying additional flags :param flags: Integer (0-...) specifying additional flags
:returns: A CmsTransform class object. :returns: A CmsTransform class object.
:exception PyCMSError: :exception PyCMSError:
""" """
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3): if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
raise PyCMSError("renderingIntent must be an integer between 0 and 3") raise PyCMSError("renderingIntent must be an integer between 0 and 3")
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) raise PyCMSError(
"flags must be an integer between 0 and %s" + _MAX_FLAG)
try: try:
if not isinstance(inputProfile, ImageCmsProfile): if not isinstance(inputProfile, ImageCmsProfile):
@ -490,13 +544,16 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
outputProfile = ImageCmsProfile(outputProfile) outputProfile = ImageCmsProfile(outputProfile)
if not isinstance(proofProfile, ImageCmsProfile): if not isinstance(proofProfile, ImageCmsProfile):
proofProfile = ImageCmsProfile(proofProfile) proofProfile = ImageCmsProfile(proofProfile)
return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, proofProfile, proofRenderingIntent, flags) return ImageCmsTransform(
inputProfile, outputProfile, inMode, outMode, renderingIntent,
proofProfile, proofRenderingIntent, flags)
except (IOError, TypeError, ValueError) as v: except (IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
buildTransformFromOpenProfiles = buildTransform buildTransformFromOpenProfiles = buildTransform
buildProofTransformFromOpenProfiles = buildProofTransform buildProofTransformFromOpenProfiles = buildProofTransform
def applyTransform(im, transform, inPlace=0): def applyTransform(im, transform, inPlace=0):
""" """
(pyCMS) Applies a transform to a given image. (pyCMS) Applies a transform to a given image.
@ -514,8 +571,8 @@ def applyTransform(im, transform, inPlace=0):
is raised. is raised.
This function applies a pre-calculated transform (from This function applies a pre-calculated transform (from
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) to an ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
image. The transform can be used for multiple images, saving to an image. The transform can be used for multiple images, saving
considerable calcuation time if doing the same conversion multiple times. considerable calcuation time if doing the same conversion multiple times.
If you want to modify im in-place instead of receiving a new image as If you want to modify im in-place instead of receiving a new image as
@ -528,10 +585,12 @@ def applyTransform(im, transform, inPlace=0):
:param im: A PIL Image object, and im.mode must be the same as the inMode :param im: A PIL Image object, and im.mode must be the same as the inMode
supported by the transform. supported by the transform.
:param transform: A valid CmsTransform class object :param transform: A valid CmsTransform class object
:param inPlace: Bool (1 == True, 0 or None == False). If True, im is modified :param inPlace: Bool (1 == True, 0 or None == False). If True, im is
in place and None is returned, if False, a new Image object with the modified in place and None is returned, if False, a new Image object
transform applied is returned (and im is not changed). The default is False. with the transform applied is returned (and im is not changed). The
:returns: Either None, or a new PIL Image object, depending on the value of inPlace default is False.
:returns: Either None, or a new PIL Image object, depending on the value of
inPlace. The profile will be returned in the image's info['icc_profile'].
:exception PyCMSError: :exception PyCMSError:
""" """
@ -546,6 +605,7 @@ def applyTransform(im, transform, inPlace=0):
return imOut return imOut
def createProfile(colorSpace, colorTemp=-1): def createProfile(colorSpace, colorTemp=-1):
""" """
(pyCMS) Creates a profile. (pyCMS) Creates a profile.
@ -562,36 +622,42 @@ def createProfile(colorSpace, colorTemp=-1):
ImageCms.buildTransformFromOpenProfiles() to create a transform to apply ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
to images. to images.
:param colorSpace: String, the color space of the profile you wish to create. :param colorSpace: String, the color space of the profile you wish to
create.
Currently only "LAB", "XYZ", and "sRGB" are supported. Currently only "LAB", "XYZ", and "sRGB" are supported.
:param colorTemp: Positive integer for the white point for the profile, in :param colorTemp: Positive integer for the white point for the profile, in
degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50 degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB profiles, illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
and is ignored for XYZ and sRGB. profiles, and is ignored for XYZ and sRGB.
:returns: A CmsProfile class object :returns: A CmsProfile class object
:exception PyCMSError: :exception PyCMSError:
""" """
if colorSpace not in ["LAB", "XYZ", "sRGB"]: if colorSpace not in ["LAB", "XYZ", "sRGB"]:
raise PyCMSError("Color space not supported for on-the-fly profile creation (%s)" % colorSpace) raise PyCMSError(
"Color space not supported for on-the-fly profile creation (%s)"
% colorSpace)
if colorSpace == "LAB": if colorSpace == "LAB":
try: try:
colorTemp = float(colorTemp) colorTemp = float(colorTemp)
except: except:
raise PyCMSError("Color temperature must be numeric, \"%s\" not valid" % colorTemp) raise PyCMSError(
"Color temperature must be numeric, \"%s\" not valid"
% colorTemp)
try: try:
return core.createProfile(colorSpace, colorTemp) return core.createProfile(colorSpace, colorTemp)
except (TypeError, ValueError) as v: except (TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def getProfileName(profile): def getProfileName(profile):
""" """
(pyCMS) Gets the internal product name for the given profile. (pyCMS) Gets the internal product name for the given profile.
If profile isn't a valid CmsProfile object or filename to a profile, If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised If an error occurs while trying to obtain the a PyCMSError is raised If an error occurs while trying to obtain the
name tag, a PyCMSError is raised. name tag, a PyCMSError is raised.
@ -600,10 +666,10 @@ def getProfileName(profile):
profile was originally created. Sometimes this tag also contains profile was originally created. Sometimes this tag also contains
additional information supplied by the creator. additional information supplied by the creator.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename :param profile: EITHER a valid CmsProfile object, OR a string of the
of an ICC profile. filename of an ICC profile.
:returns: A string containing the internal name of the profile as stored in an :returns: A string containing the internal name of the profile as stored
ICC tag. in an ICC tag.
:exception PyCMSError: :exception PyCMSError:
""" """
@ -612,14 +678,14 @@ def getProfileName(profile):
if not isinstance(profile, ImageCmsProfile): if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
# do it in python, not c. # do it in python, not c.
# // name was "%s - %s" (model, manufacturer) || Description , # // name was "%s - %s" (model, manufacturer) || Description ,
# // but if the Model and Manufacturer were the same or the model # // but if the Model and Manufacturer were the same or the model
# // was long, Just the model, in 1.x # // was long, Just the model, in 1.x
model = profile.profile.product_model model = profile.profile.product_model
manufacturer = profile.profile.product_manufacturer manufacturer = profile.profile.product_manufacturer
if not (model or manufacturer): if not (model or manufacturer):
return profile.profile.product_description+"\n" return profile.profile.product_description + "\n"
if not manufacturer or len(model) > 30: if not manufacturer or len(model) > 30:
return model + "\n" return model + "\n"
return "%s - %s\n" % (model, manufacturer) return "%s - %s\n" % (model, manufacturer)
@ -627,6 +693,7 @@ def getProfileName(profile):
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def getProfileInfo(profile): def getProfileInfo(profile):
""" """
(pyCMS) Gets the internal product information for the given profile. (pyCMS) Gets the internal product information for the given profile.
@ -641,18 +708,19 @@ def getProfileInfo(profile):
info tag. This often contains details about the profile, and how it info tag. This often contains details about the profile, and how it
was created, as supplied by the creator. was created, as supplied by the creator.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename :param profile: EITHER a valid CmsProfile object, OR a string of the
of an ICC profile. filename of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC :returns: A string containing the internal profile information stored in
tag. an ICC tag.
:exception PyCMSError: :exception PyCMSError:
""" """
try: try:
if not isinstance(profile, ImageCmsProfile): if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
# add an extra newline to preserve pyCMS compatibility # add an extra newline to preserve pyCMS compatibility
# Python, not C. the white point bits weren't working well, so skipping. # Python, not C. the white point bits weren't working well,
# so skipping.
# // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint # // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
description = profile.profile.product_description description = profile.profile.product_description
cpright = profile.profile.product_copyright cpright = profile.profile.product_copyright
@ -660,7 +728,7 @@ def getProfileInfo(profile):
for elt in (description, cpright): for elt in (description, cpright):
if elt: if elt:
arr.append(elt) arr.append(elt)
return "\r\n\r\n".join(arr)+"\r\n\r\n" return "\r\n\r\n".join(arr) + "\r\n\r\n"
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
@ -677,12 +745,12 @@ def getProfileCopyright(profile):
is raised is raised
Use this function to obtain the information stored in the profile's Use this function to obtain the information stored in the profile's
copyright tag. copyright tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename :param profile: EITHER a valid CmsProfile object, OR a string of the
of an ICC profile. filename of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC :returns: A string containing the internal profile information stored in
tag. an ICC tag.
:exception PyCMSError: :exception PyCMSError:
""" """
try: try:
@ -693,6 +761,7 @@ def getProfileCopyright(profile):
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def getProfileManufacturer(profile): def getProfileManufacturer(profile):
""" """
(pyCMS) Gets the manufacturer for the given profile. (pyCMS) Gets the manufacturer for the given profile.
@ -700,16 +769,16 @@ def getProfileManufacturer(profile):
If profile isn't a valid CmsProfile object or filename to a profile, If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised. a PyCMSError is raised.
If an error occurs while trying to obtain the manufacturer tag, a PyCMSError If an error occurs while trying to obtain the manufacturer tag, a
is raised PyCMSError is raised
Use this function to obtain the information stored in the profile's Use this function to obtain the information stored in the profile's
manufacturer tag. manufacturer tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename :param profile: EITHER a valid CmsProfile object, OR a string of the
of an ICC profile. filename of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC :returns: A string containing the internal profile information stored in
tag. an ICC tag.
:exception PyCMSError: :exception PyCMSError:
""" """
try: try:
@ -720,23 +789,24 @@ def getProfileManufacturer(profile):
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def getProfileModel(profile): def getProfileModel(profile):
""" """
(pyCMS) Gets the model for the given profile. (pyCMS) Gets the model for the given profile.
If profile isn't a valid CmsProfile object or filename to a profile, If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised. a PyCMSError is raised.
If an error occurs while trying to obtain the model tag, a PyCMSError If an error occurs while trying to obtain the model tag, a PyCMSError
is raised is raised
Use this function to obtain the information stored in the profile's Use this function to obtain the information stored in the profile's
model tag. model tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename :param profile: EITHER a valid CmsProfile object, OR a string of the
of an ICC profile. filename of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC :returns: A string containing the internal profile information stored in
tag. an ICC tag.
:exception PyCMSError: :exception PyCMSError:
""" """
@ -748,6 +818,7 @@ def getProfileModel(profile):
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def getProfileDescription(profile): def getProfileDescription(profile):
""" """
(pyCMS) Gets the description for the given profile. (pyCMS) Gets the description for the given profile.
@ -759,12 +830,12 @@ def getProfileDescription(profile):
is raised is raised
Use this function to obtain the information stored in the profile's Use this function to obtain the information stored in the profile's
description tag. description tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename :param profile: EITHER a valid CmsProfile object, OR a string of the
of an ICC profile. filename of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC :returns: A string containing the internal profile information stored in an
tag. ICC tag.
:exception PyCMSError: :exception PyCMSError:
""" """
@ -793,16 +864,18 @@ def getDefaultIntent(profile):
If you wish to use a different intent than returned, use If you wish to use a different intent than returned, use
ImageCms.isIntentSupported() to verify it will work first. ImageCms.isIntentSupported() to verify it will work first.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename :param profile: EITHER a valid CmsProfile object, OR a string of the
of an ICC profile. filename of an ICC profile.
:returns: Integer 0-3 specifying the default rendering intent for this profile. :returns: Integer 0-3 specifying the default rendering intent for this
profile.
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
see the pyCMS documentation for details on rendering intents and what they do. see the pyCMS documentation for details on rendering intents and what
they do.
:exception PyCMSError: :exception PyCMSError:
""" """
@ -813,6 +886,7 @@ def getDefaultIntent(profile):
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def isIntentSupported(profile, intent, direction): def isIntentSupported(profile, intent, direction):
""" """
(pyCMS) Checks if a given intent is supported. (pyCMS) Checks if a given intent is supported.
@ -822,23 +896,24 @@ def isIntentSupported(profile, intent, direction):
input/output/proof profile as you desire. input/output/proof profile as you desire.
Some profiles are created specifically for one "direction", can cannot Some profiles are created specifically for one "direction", can cannot
be used for others. Some profiles can only be used for certain be used for others. Some profiles can only be used for certain
rendering intents... so it's best to either verify this before trying rendering intents... so it's best to either verify this before trying
to create a transform with them (using this function), or catch the to create a transform with them (using this function), or catch the
potential PyCMSError that will occur if they don't support the modes potential PyCMSError that will occur if they don't support the modes
you select. you select.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename :param profile: EITHER a valid CmsProfile object, OR a string of the
of an ICC profile. filename of an ICC profile.
:param intent: Integer (0-3) specifying the rendering intent you wish to use :param intent: Integer (0-3) specifying the rendering intent you wish to
with this profile use with this profile
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
see the pyCMS documentation for details on rendering intents and what they do. see the pyCMS documentation for details on rendering intents and what
they do.
:param direction: Integer specifing if the profile is to be used for input, :param direction: Integer specifing if the profile is to be used for input,
output, or proof output, or proof
@ -862,15 +937,17 @@ def isIntentSupported(profile, intent, direction):
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
def versions(): def versions():
""" """
(pyCMS) Fetches versions. (pyCMS) Fetches versions.
""" """
import sys import sys
return ( return (
VERSION, core.littlecms_version, sys.version.split()[0], Image.VERSION VERSION, core.littlecms_version,
) sys.version.split()[0], Image.VERSION
)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -880,14 +957,16 @@ if __name__ == "__main__":
from PIL import ImageCms from PIL import ImageCms
print(__doc__) print(__doc__)
for f in dir(pyCMS): for f in dir(ImageCms):
print("="*80) doc = None
print("%s" %f)
try: try:
exec ("doc = ImageCms.%s.__doc__" %(f)) exec("doc = %s.__doc__" % (f))
if "pyCMS" in doc: if "pyCMS" in doc:
# so we don't get the __doc__ string for imported modules # so we don't get the __doc__ string for imported modules
print("=" * 80)
print("%s" % f)
print(doc) print(doc)
except AttributeError: except (AttributeError, TypeError):
pass pass
# End of file

View File

@ -6,7 +6,7 @@
# #
# For a background, see "Image Processing By Interpolation and # For a background, see "Image Processing By Interpolation and
# Extrapolation", Paul Haeberli and Douglas Voorhies. Available # Extrapolation", Paul Haeberli and Douglas Voorhies. Available
# at http://www.sgi.com/grafica/interp/index.html # at http://www.graficaobscura.com/interp/index.html
# #
# History: # History:
# 1996-03-23 fl Created # 1996-03-23 fl Created

View File

@ -133,11 +133,27 @@ class ImageFile(Image.Image):
return pixel return pixel
self.map = None self.map = None
use_mmap = self.filename and len(self.tile) == 1
# As of pypy 2.1.0, memory mapping was failing here.
use_mmap = use_mmap and not hasattr(sys, 'pypy_version_info')
readonly = 0 readonly = 0
if self.filename and len(self.tile) == 1 and not hasattr(sys, 'pypy_version_info'): # look for read/seek overrides
# As of pypy 2.1.0, memory mapping was failing here. try:
read = self.load_read
# don't use mmap if there are custom read/seek functions
use_mmap = False
except AttributeError:
read = self.fp.read
try:
seek = self.load_seek
use_mmap = False
except AttributeError:
seek = self.fp.seek
if use_mmap:
# try memory mapping # try memory mapping
d, e, o, a = self.tile[0] d, e, o, a = self.tile[0]
if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES: if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
@ -165,19 +181,7 @@ class ImageFile(Image.Image):
self.load_prepare() self.load_prepare()
# look for read/seek overrides
try:
read = self.load_read
except AttributeError:
read = self.fp.read
try:
seek = self.load_seek
except AttributeError:
seek = self.fp.seek
if not self.map: if not self.map:
# sort tiles in file order # sort tiles in file order
self.tile.sort(key=_tilesort) self.tile.sort(key=_tilesort)
@ -502,5 +506,5 @@ def _safe_read(fp, size):
if not block: if not block:
break break
data.append(block) data.append(block)
size = size - len(block) size -= len(block)
return b"".join(data) return b"".join(data)

View File

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

View File

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

245
PIL/ImageMorph.py Normal file
View File

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

View File

@ -94,7 +94,7 @@ def autocontrast(image, cutoff=0, ignore=None):
cut = cut - h[lo] cut = cut - h[lo]
h[lo] = 0 h[lo] = 0
else: else:
h[lo] = h[lo] - cut h[lo] -= cut
cut = 0 cut = 0
if cut <= 0: if cut <= 0:
break break
@ -105,7 +105,7 @@ def autocontrast(image, cutoff=0, ignore=None):
cut = cut - h[hi] cut = cut - h[hi]
h[hi] = 0 h[hi] = 0
else: else:
h[hi] = h[hi] - cut h[hi] -= cut
cut = 0 cut = 0
if cut <= 0: if cut <= 0:
break break
@ -392,7 +392,7 @@ def solarize(image, threshold=128):
""" """
Invert all pixel values above a threshold. Invert all pixel values above a threshold.
:param image: The image to posterize. :param image: The image to solarize.
:param threshold: All pixels above this greyscale level are inverted. :param threshold: All pixels above this greyscale level are inverted.
:return: An image. :return: An image.
""" """

View File

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

View File

@ -17,7 +17,7 @@ from __future__ import print_function
from PIL import Image from PIL import Image
import os, sys import os, sys
if(sys.version_info >= (3, 3)): if sys.version_info >= (3, 3):
from shlex import quote from shlex import quote
else: else:
from pipes import quote from pipes import quote
@ -160,7 +160,7 @@ else:
# imagemagick's display command instead. # imagemagick's display command instead.
command = executable = "xv" command = executable = "xv"
if title: if title:
command = command + " -name %s" % quote(title) command += " -name %s" % quote(title)
return command, executable return command, executable
if which("xv"): if which("xv"):

View File

@ -21,7 +21,6 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image
import operator, math import operator, math
from functools import reduce from functools import reduce
@ -81,7 +80,7 @@ class Stat:
for i in range(0, len(self.h), 256): for i in range(0, len(self.h), 256):
sum = 0.0 sum = 0.0
for j in range(256): for j in range(256):
sum = sum + j * self.h[i+j] sum += j * self.h[i + j]
v.append(sum) v.append(sum)
return v return v
@ -92,7 +91,7 @@ class Stat:
for i in range(0, len(self.h), 256): for i in range(0, len(self.h), 256):
sum2 = 0.0 sum2 = 0.0
for j in range(256): for j in range(256):
sum2 = sum2 + (j ** 2) * float(self.h[i+j]) sum2 += (j ** 2) * float(self.h[i + j])
v.append(sum2) v.append(sum2)
return v return v

View File

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

View File

@ -15,20 +15,21 @@
__version__ = "0.1" __version__ = "0.1"
from PIL import Image, ImageFile, _binary from PIL import Image, ImageFile
import struct import struct
import os import os
import io import io
def _parse_codestream(fp): def _parse_codestream(fp):
"""Parse the JPEG 2000 codestream to extract the size and component """Parse the JPEG 2000 codestream to extract the size and component
count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
hdr = fp.read(2) hdr = fp.read(2)
lsiz = struct.unpack('>H', hdr)[0] lsiz = struct.unpack('>H', hdr)[0]
siz = hdr + fp.read(lsiz - 2) siz = hdr + fp.read(lsiz - 2)
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \ lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
xtosiz, ytosiz, csiz \ xtosiz, ytosiz, csiz \
= struct.unpack('>HHIIIIIIIIH', siz[:38]) = struct.unpack('>HHIIIIIIIIH', siz[:38])
ssiz = [None]*csiz ssiz = [None]*csiz
xrsiz = [None]*csiz xrsiz = [None]*csiz
@ -39,22 +40,26 @@ def _parse_codestream(fp):
size = (xsiz - xosiz, ysiz - yosiz) size = (xsiz - xosiz, ysiz - yosiz)
if csiz == 1: if csiz == 1:
mode = 'L' if (yrsiz[0] & 0x7f) > 8:
mode = 'I;16'
else:
mode = 'L'
elif csiz == 2: elif csiz == 2:
mode = 'LA' mode = 'LA'
elif csiz == 3: elif csiz == 3:
mode = 'RGB' mode = 'RGB'
elif csiz == 4: elif csiz == 4:
mode == 'RGBA' mode = 'RGBA'
else: else:
mode = None mode = None
return (size, mode) return (size, mode)
def _parse_jp2_header(fp): def _parse_jp2_header(fp):
"""Parse the JP2 header box to extract size, component count and """Parse the JP2 header box to extract size, component count and
color space information, returning a PIL (size, mode) tuple.""" color space information, returning a PIL (size, mode) tuple."""
# Find the JP2 header box # Find the JP2 header box
header = None header = None
while True: while True:
@ -65,6 +70,9 @@ def _parse_jp2_header(fp):
else: else:
hlen = 8 hlen = 8
if lbox < hlen:
raise SyntaxError('Invalid JP2 header length')
if tbox == b'jp2h': if tbox == b'jp2h':
header = fp.read(lbox - hlen) header = fp.read(lbox - hlen)
break break
@ -76,7 +84,8 @@ def _parse_jp2_header(fp):
size = None size = None
mode = None mode = None
bpc = None
hio = io.BytesIO(header) hio = io.BytesIO(header)
while True: while True:
lbox, tbox = struct.unpack('>I4s', hio.read(8)) lbox, tbox = struct.unpack('>I4s', hio.read(8))
@ -90,10 +99,12 @@ def _parse_jp2_header(fp):
if tbox == b'ihdr': if tbox == b'ihdr':
height, width, nc, bpc, c, unkc, ipr \ height, width, nc, bpc, c, unkc, ipr \
= struct.unpack('>IIHBBBB', content) = struct.unpack('>IIHBBBB', content)
size = (width, height) size = (width, height)
if unkc: if unkc:
if nc == 1: if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L' mode = 'L'
elif nc == 2: elif nc == 2:
mode = 'LA' mode = 'LA'
@ -107,22 +118,28 @@ def _parse_jp2_header(fp):
if meth == 1: if meth == 1:
cs = struct.unpack('>I', content[3:7])[0] cs = struct.unpack('>I', content[3:7])[0]
if cs == 16: # sRGB if cs == 16: # sRGB
if nc == 3: if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L'
elif nc == 3:
mode = 'RGB' mode = 'RGB'
elif nc == 4: elif nc == 4:
mode = 'RGBA' mode = 'RGBA'
break break
elif cs == 17: # grayscale elif cs == 17: # grayscale
if nc == 1: if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L' mode = 'L'
elif nc == 2: elif nc == 2:
mode = 'LA' mode = 'LA'
break break
elif cs == 18: # sYCC elif cs == 18: # sYCC
if nc == 3: if nc == 3:
mode = 'RGB' mode = 'RGB'
elif nc == 4: elif nc == 4:
mode == 'RGBA' mode = 'RGBA'
break break
return (size, mode) return (size, mode)
@ -130,6 +147,7 @@ def _parse_jp2_header(fp):
## ##
# Image plugin for JPEG2000 images. # Image plugin for JPEG2000 images.
class Jpeg2KImageFile(ImageFile.ImageFile): class Jpeg2KImageFile(ImageFile.ImageFile):
format = "JPEG2000" format = "JPEG2000"
format_description = "JPEG 2000 (ISO 15444)" format_description = "JPEG 2000 (ISO 15444)"
@ -141,29 +159,37 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
self.size, self.mode = _parse_codestream(self.fp) self.size, self.mode = _parse_codestream(self.fp)
else: else:
sig = sig + self.fp.read(8) sig = sig + self.fp.read(8)
if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
self.codec = "jp2" self.codec = "jp2"
self.size, self.mode = _parse_jp2_header(self.fp) self.size, self.mode = _parse_jp2_header(self.fp)
else: else:
raise SyntaxError('not a JPEG 2000 file') raise SyntaxError('not a JPEG 2000 file')
if self.size is None or self.mode is None: if self.size is None or self.mode is None:
raise SyntaxError('unable to determine size/mode') raise SyntaxError('unable to determine size/mode')
self.reduce = 0 self.reduce = 0
self.layers = 0 self.layers = 0
fd = -1 fd = -1
length = -1
if hasattr(self.fp, "fileno"): try:
fd = self.fp.fileno()
length = os.fstat(fd).st_size
except:
fd = -1
try: try:
fd = self.fp.fileno() pos = self.fp.tell()
self.fp.seek(0, 2)
length = self.fp.tell()
self.fp.seek(pos, 0)
except: except:
fd = -1 length = -1
self.tile = [('jpeg2k', (0, 0) + self.size, 0, self.tile = [('jpeg2k', (0, 0) + self.size, 0,
(self.codec, self.reduce, self.layers, fd))] (self.codec, self.reduce, self.layers, fd, length))]
def load(self): def load(self):
if self.reduce: if self.reduce:
@ -175,15 +201,17 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
if self.tile: if self.tile:
# Update the reduce and layers settings # Update the reduce and layers settings
t = self.tile[0] t = self.tile[0]
t3 = (t[3][0], self.reduce, self.layers, t[3][3]) t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4])
self.tile = [(t[0], (0, 0) + self.size, t[2], t3)] self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
ImageFile.ImageFile.load(self) ImageFile.ImageFile.load(self)
def _accept(prefix): def _accept(prefix):
return (prefix[:4] == b'\xff\x4f\xff\x51' return (prefix[:4] == b'\xff\x4f\xff\x51'
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a') or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
# ------------------------------------------------------------ # ------------------------------------------------------------
# Save support # Save support
@ -214,7 +242,7 @@ def _save(im, fp, filename):
fd = fp.fileno() fd = fp.fileno()
except: except:
fd = -1 fd = -1
im.encoderconfig = ( im.encoderconfig = (
offset, offset,
tile_offset, tile_offset,
@ -228,10 +256,10 @@ def _save(im, fp, filename):
progression, progression,
cinema_mode, cinema_mode,
fd fd
) )
ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)]) ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
# ------------------------------------------------------------ # ------------------------------------------------------------
# Registry stuff # Registry stuff

View File

@ -36,7 +36,9 @@ __version__ = "0.6"
import array import array
import struct import struct
from PIL import Image, ImageFile, _binary import io
from struct import unpack
from PIL import Image, ImageFile, TiffImagePlugin, _binary
from PIL.JpegPresets import presets from PIL.JpegPresets import presets
from PIL._util import isStringType from PIL._util import isStringType
@ -110,6 +112,11 @@ def APP(self, marker):
pass pass
else: else:
self.info["adobe_transform"] = adobe_transform self.info["adobe_transform"] = adobe_transform
elif marker == 0xFFE2 and s[:4] == b"MPF\0":
# extract MPO information
self.info["mp"] = s[4:]
# offset is current location minus buffer size plus constant header size
self.info["mpoffset"] = self.fp.tell() - n + 4
def COM(self, marker): def COM(self, marker):
@ -354,12 +361,13 @@ class JpegImageFile(ImageFile.ImageFile):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities # ALTERNATIVE: handle JPEGs via the IJG command line utilities
import subprocess
import tempfile import tempfile
import os import os
f, path = tempfile.mkstemp() f, path = tempfile.mkstemp()
os.close(f) os.close(f)
if os.path.exists(self.filename): if os.path.exists(self.filename):
os.system("djpeg '%s' >'%s'" % (self.filename, path)) subprocess.check_call(["djpeg", "-outfile", path, self.filename])
else: else:
raise ValueError("Invalid Filename") raise ValueError("Invalid Filename")
@ -379,18 +387,22 @@ class JpegImageFile(ImageFile.ImageFile):
def _getexif(self): def _getexif(self):
return _getexif(self) return _getexif(self)
def _getmp(self):
return _getmp(self)
def _fixup(value):
# Helper function for _getexif() and _getmp()
if len(value) == 1:
return value[0]
return value
def _getexif(self): def _getexif(self):
# Extract EXIF information. This method is highly experimental, # Extract EXIF information. This method is highly experimental,
# and is likely to be replaced with something better in a future # and is likely to be replaced with something better in a future
# version. # version.
from PIL import TiffImagePlugin
import io
def fixup(value):
if len(value) == 1:
return value[0]
return value
# The EXIF record consists of a TIFF file embedded in a JPEG # The EXIF record consists of a TIFF file embedded in a JPEG
# application marker (!). # application marker (!).
try: try:
@ -404,7 +416,7 @@ def _getexif(self):
info = TiffImagePlugin.ImageFileDirectory(head) info = TiffImagePlugin.ImageFileDirectory(head)
info.load(file) info.load(file)
for key, value in info.items(): for key, value in info.items():
exif[key] = fixup(value) exif[key] = _fixup(value)
# get exif extension # get exif extension
try: try:
file.seek(exif[0x8769]) file.seek(exif[0x8769])
@ -414,7 +426,7 @@ def _getexif(self):
info = TiffImagePlugin.ImageFileDirectory(head) info = TiffImagePlugin.ImageFileDirectory(head)
info.load(file) info.load(file)
for key, value in info.items(): for key, value in info.items():
exif[key] = fixup(value) exif[key] = _fixup(value)
# get gpsinfo extension # get gpsinfo extension
try: try:
file.seek(exif[0x8825]) file.seek(exif[0x8825])
@ -425,9 +437,77 @@ def _getexif(self):
info.load(file) info.load(file)
exif[0x8825] = gps = {} exif[0x8825] = gps = {}
for key, value in info.items(): for key, value in info.items():
gps[key] = fixup(value) gps[key] = _fixup(value)
return exif return exif
def _getmp(self):
# Extract MP information. This method was inspired by the "highly
# experimental" _getexif version that's been in use for years now,
# itself based on the ImageFileDirectory class in the TIFF plug-in.
# The MP record essentially consists of a TIFF file embedded in a JPEG
# application marker.
try:
data = self.info["mp"]
except KeyError:
return None
file = io.BytesIO(data)
head = file.read(8)
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
mp = {}
# process dictionary
info = TiffImagePlugin.ImageFileDirectory(head)
info.load(file)
for key, value in info.items():
mp[key] = _fixup(value)
# it's an error not to have a number of images
try:
quant = mp[0xB001]
except KeyError:
raise SyntaxError("malformed MP Index (no number of images)")
# get MP entries
try:
mpentries = []
for entrynum in range(0, quant):
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2')
mpentry = dict(zip(labels, unpackedentry))
mpentryattr = {
'DependentParentImageFlag': bool(mpentry['Attribute'] & (1<<31)),
'DependentChildImageFlag': bool(mpentry['Attribute'] & (1<<30)),
'RepresentativeImageFlag': bool(mpentry['Attribute'] & (1<<29)),
'Reserved': (mpentry['Attribute'] & (3<<27)) >> 27,
'ImageDataFormat': (mpentry['Attribute'] & (7<<24)) >> 24,
'MPType': mpentry['Attribute'] & 0x00FFFFFF
}
if mpentryattr['ImageDataFormat'] == 0:
mpentryattr['ImageDataFormat'] = 'JPEG'
else:
raise SyntaxError("unsupported picture format in MPO")
mptypemap = {
0x000000: 'Undefined',
0x010001: 'Large Thumbnail (VGA Equivalent)',
0x010002: 'Large Thumbnail (Full HD Equivalent)',
0x020001: 'Multi-Frame Image (Panorama)',
0x020002: 'Multi-Frame Image: (Disparity)',
0x020003: 'Multi-Frame Image: (Multi-Angle)',
0x030000: 'Baseline MP Primary Image'
}
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
'Unknown')
mpentry['Attribute'] = mpentryattr
mpentries.append(mpentry)
mp[0xB002] = mpentries
except KeyError:
raise SyntaxError("malformed MP Index (bad MP Entry)")
# Next we should try and parse the individual image unique ID list;
# we don't because I've never seen this actually used in a real MPO
# file and so can't test it.
return mp
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# stuff to save JPEG files # stuff to save JPEG files
@ -498,7 +578,7 @@ def _save(im, fp, filename):
else: else:
if subsampling in presets: if subsampling in presets:
subsampling = presets[subsampling].get('subsampling', -1) subsampling = presets[subsampling].get('subsampling', -1)
if qtables in presets: if isStringType(qtables) and qtables in presets:
qtables = presets[qtables].get('quantization') qtables = presets[qtables].get('quantization')
if subsampling == "4:4:4": if subsampling == "4:4:4":
@ -561,8 +641,8 @@ def _save(im, fp, filename):
i = 1 i = 1
for marker in markers: for marker in markers:
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker)) size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
extra = extra + (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker) extra += b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker
i = i + 1 i += 1
# get keyword arguments # get keyword arguments
im.encoderconfig = ( im.encoderconfig = (
@ -602,17 +682,35 @@ def _save(im, fp, filename):
def _save_cjpeg(im, fp, filename): def _save_cjpeg(im, fp, filename):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities. # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
import os import os
file = im._dump() import subprocess
os.system("cjpeg %s >%s" % (file, filename)) tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
try: try:
os.unlink(file) os.unlink(file)
except: except:
pass pass
##
# Factory for making JPEG and MPO instances
def jpeg_factory(fp=None, filename=None):
im = JpegImageFile(fp, filename)
mpheader = im._getmp()
try:
if mpheader[45057] > 1:
# It's actually an MPO
from .MpoImagePlugin import MpoImageFile
im = MpoImageFile(fp, filename)
except (TypeError, IndexError):
# It is really a JPEG
pass
return im
# -------------------------------------------------------------------q- # -------------------------------------------------------------------q-
# Registry stuff # Registry stuff
Image.register_open("JPEG", JpegImageFile, _accept) Image.register_open("JPEG", jpeg_factory, _accept)
Image.register_save("JPEG", _save) Image.register_save("JPEG", _save)
Image.register_extension("JPEG", ".jfif") Image.register_extension("JPEG", ".jfif")

View File

@ -38,13 +38,13 @@ class BitStream:
self.bits = 0 self.bits = 0
continue continue
self.bitbuffer = (self.bitbuffer << 8) + c self.bitbuffer = (self.bitbuffer << 8) + c
self.bits = self.bits + 8 self.bits += 8
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1 return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
def skip(self, bits): def skip(self, bits):
while self.bits < bits: while self.bits < bits:
self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1)) self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1))
self.bits = self.bits + 8 self.bits += 8
self.bits = self.bits - bits self.bits = self.bits - bits
def read(self, bits): def read(self, bits):

87
PIL/MpoImagePlugin.py Normal file
View File

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

View File

@ -7,7 +7,7 @@ This is an improved version of the OleFileIO module from [PIL](http://www.python
As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules) As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules)
OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-imaging.github.io/), the friendly fork of PIL. OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-pillow.github.io/), the friendly fork of PIL.
OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL. OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL.
@ -348,4 +348,4 @@ By obtaining, using, and/or copying this software and/or its associated document
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. 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. 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.

View File

@ -1,28 +1,29 @@
#!/usr/local/bin/python #!/usr/local/bin/python
# -*- coding: latin-1 -*- # -*- coding: latin-1 -*-
""" ## OleFileIO_PL:
OleFileIO_PL: ## Module to read Microsoft OLE2 files (also called Structured Storage or
Module to read Microsoft OLE2 files (also called Structured Storage or ## Microsoft Compound Document File Format), such as Microsoft Office
Microsoft Compound Document File Format), such as Microsoft Office ## documents, Image Composer and FlashPix files, Outlook messages, ...
documents, Image Composer and FlashPix files, Outlook messages, ... ## This version is compatible with Python 2.6+ and 3.x
This version is compatible with Python 2.6+ and 3.x
version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info ## version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info
Project website: http://www.decalage.info/python/olefileio ## Project website: http://www.decalage.info/python/olefileio
Improved version of the OleFileIO module from PIL library v1.1.6 ## Improved version of the OleFileIO module from PIL library v1.1.6
See: http://www.pythonware.com/products/pil/index.htm ## See: http://www.pythonware.com/products/pil/index.htm
The Python Imaging Library (PIL) is ## The Python Imaging Library (PIL) is
Copyright (c) 1997-2005 by Secret Labs AB
Copyright (c) 1995-2005 by Fredrik Lundh
OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
See source code and LICENSE.txt for information on usage and redistribution. ## Copyright (c) 1997-2005 by Secret Labs AB
## Copyright (c) 1995-2005 by Fredrik Lundh
## OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
## See source code and LICENSE.txt for information on usage and redistribution.
## WARNING: THIS IS (STILL) WORK IN PROGRESS.
WARNING: THIS IS (STILL) WORK IN PROGRESS.
"""
# Starting with OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported # Starting with OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported
# This import enables print() as a function rather than a keyword # This import enables print() as a function rather than a keyword
@ -370,8 +371,9 @@ for key in list(vars().keys()):
def isOleFile (filename): def isOleFile (filename):
""" """
Test if file is an OLE container (according to its header). Test if file is an OLE container (according to its header).
filename: file name or path (str, unicode)
return: True if OLE, False otherwise. :param filename: file name or path (str, unicode)
:returns: True if OLE, False otherwise.
""" """
f = open(filename, 'rb') f = open(filename, 'rb')
header = f.read(len(MAGIC)) header = f.read(len(MAGIC))
@ -397,8 +399,8 @@ def i16(c, o = 0):
""" """
Converts a 2-bytes (16 bits) string to an integer. Converts a 2-bytes (16 bits) string to an integer.
c: string containing bytes to convert :param c: string containing bytes to convert
o: offset of bytes to convert in string :param o: offset of bytes to convert in string
""" """
return i8(c[o]) | (i8(c[o+1])<<8) return i8(c[o]) | (i8(c[o+1])<<8)
@ -407,8 +409,8 @@ def i32(c, o = 0):
""" """
Converts a 4-bytes (32 bits) string to an integer. Converts a 4-bytes (32 bits) string to an integer.
c: string containing bytes to convert :param c: string containing bytes to convert
o: offset of bytes to convert in string :param o: offset of bytes to convert in string
""" """
## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24)) ## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24))
## # [PL]: added int() because "<<" gives long int since Python 2.4 ## # [PL]: added int() because "<<" gives long int since Python 2.4
@ -419,7 +421,8 @@ def i32(c, o = 0):
def _clsid(clsid): def _clsid(clsid):
""" """
Converts a CLSID to a human-readable string. Converts a CLSID to a human-readable string.
clsid: string of length 16.
:param clsid: string of length 16.
""" """
assert len(clsid) == 16 assert len(clsid) == 16
# if clsid is only made of null bytes, return an empty string: # if clsid is only made of null bytes, return an empty string:
@ -439,8 +442,8 @@ def _unicode(s, errors='replace'):
""" """
Map unicode string to Latin 1. (Python with Unicode support) Map unicode string to Latin 1. (Python with Unicode support)
s: UTF-16LE unicode string to convert to Latin-1 :param s: UTF-16LE unicode string to convert to Latin-1
errors: 'replace', 'ignore' or 'strict'. :param errors: 'replace', 'ignore' or 'strict'.
""" """
#TODO: test if it OleFileIO works with Unicode strings, instead of #TODO: test if it OleFileIO works with Unicode strings, instead of
# converting to Latin-1. # converting to Latin-1.
@ -650,14 +653,14 @@ class _OleStream(io.BytesIO):
""" """
Constructor for _OleStream class. Constructor for _OleStream class.
fp : file object, the OLE container or the MiniFAT stream :param fp : file object, the OLE container or the MiniFAT stream
sect : sector index of first sector in the stream :param sect : sector index of first sector in the stream
size : total size of the stream :param size : total size of the stream
offset : offset in bytes for the first FAT or MiniFAT sector :param offset : offset in bytes for the first FAT or MiniFAT sector
sectorsize: size of one sector :param sectorsize: size of one sector
fat : array/list of sector indexes (FAT or MiniFAT) :param fat : array/list of sector indexes (FAT or MiniFAT)
filesize : size of OLE file (for debugging) :param filesize : size of OLE file (for debugging)
return : a BytesIO instance containing the OLE stream :returns : a BytesIO instance containing the OLE stream
""" """
debug('_OleStream.__init__:') debug('_OleStream.__init__:')
debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s' debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s'
@ -793,9 +796,9 @@ class _OleDirectoryEntry:
Constructor for an _OleDirectoryEntry object. Constructor for an _OleDirectoryEntry object.
Parses a 128-bytes entry from the OLE Directory stream. Parses a 128-bytes entry from the OLE Directory stream.
entry : string (must be 128 bytes long) :param entry : string (must be 128 bytes long)
sid : index of this directory entry in the OLE file directory :param sid : index of this directory entry in the OLE file directory
olefile: OleFileIO containing this directory entry :param olefile: OleFileIO containing this directory entry
""" """
self.sid = sid self.sid = sid
# ref to olefile is stored for future use # ref to olefile is stored for future use
@ -989,7 +992,7 @@ class _OleDirectoryEntry:
""" """
Return modification time of a directory entry. Return modification time of a directory entry.
return: None if modification time is null, a python datetime object :returns: None if modification time is null, a python datetime object
otherwise (UTC timezone) otherwise (UTC timezone)
new in version 0.26 new in version 0.26
@ -1003,7 +1006,7 @@ class _OleDirectoryEntry:
""" """
Return creation time of a directory entry. Return creation time of a directory entry.
return: None if modification time is null, a python datetime object :returns: None if modification time is null, a python datetime object
otherwise (UTC timezone) otherwise (UTC timezone)
new in version 0.26 new in version 0.26
@ -1020,7 +1023,8 @@ class OleFileIO:
OLE container object OLE container object
This class encapsulates the interface to an OLE 2 structured This class encapsulates the interface to an OLE 2 structured
storage file. Use the {@link listdir} and {@link openstream} methods to storage file. Use the :py:meth:`~PIL.OleFileIO.OleFileIO.listdir` and
:py:meth:`~PIL.OleFileIO.OleFileIO.openstream` methods to
access the contents of this file. access the contents of this file.
Object names are given as a list of strings, one for each subentry Object names are given as a list of strings, one for each subentry
@ -1048,8 +1052,8 @@ class OleFileIO:
""" """
Constructor for OleFileIO class. Constructor for OleFileIO class.
filename: file to open. :param filename: file to open.
raise_defects: minimal level for defects to be raised as exceptions. :param raise_defects: minimal level for defects to be raised as exceptions.
(use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a (use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a
security-oriented application, see source code for details) security-oriented application, see source code for details)
""" """
@ -1068,13 +1072,13 @@ class OleFileIO:
It may raise an IOError exception according to the minimal level chosen It may raise an IOError exception according to the minimal level chosen
for the OleFileIO object. for the OleFileIO object.
defect_level: defect level, possible values are: :param defect_level: defect level, possible values are:
DEFECT_UNSURE : a case which looks weird, but not sure it's a defect DEFECT_UNSURE : a case which looks weird, but not sure it's a defect
DEFECT_POTENTIAL : a potential defect DEFECT_POTENTIAL : a potential defect
DEFECT_INCORRECT : an error according to specifications, but parsing can go on DEFECT_INCORRECT : an error according to specifications, but parsing can go on
DEFECT_FATAL : an error which cannot be ignored, parsing is impossible DEFECT_FATAL : an error which cannot be ignored, parsing is impossible
message: string describing the defect, used with raised exception. :param message: string describing the defect, used with raised exception.
exception_type: exception class to be raised, IOError by default :param exception_type: exception class to be raised, IOError by default
""" """
# added by [PL] # added by [PL]
if defect_level >= self._raise_defects_level: if defect_level >= self._raise_defects_level:
@ -1089,7 +1093,7 @@ class OleFileIO:
Open an OLE2 file. Open an OLE2 file.
Reads the header, FAT and directory. Reads the header, FAT and directory.
filename: string-like or file-like object :param filename: string-like or file-like object
""" """
#[PL] check if filename is a string-like or file-like object: #[PL] check if filename is a string-like or file-like object:
# (it is better to check for a read() method) # (it is better to check for a read() method)
@ -1276,8 +1280,8 @@ class OleFileIO:
Checks if a stream has not been already referenced elsewhere. Checks if a stream has not been already referenced elsewhere.
This method should only be called once for each known stream, and only This method should only be called once for each known stream, and only
if stream size is not null. if stream size is not null.
first_sect: index of first sector of the stream in FAT :param first_sect: index of first sector of the stream in FAT
minifat: if True, stream is located in the MiniFAT, else in the FAT :param minifat: if True, stream is located in the MiniFAT, else in the FAT
""" """
if minifat: if minifat:
debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect) debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect)
@ -1371,8 +1375,9 @@ class OleFileIO:
def loadfat_sect(self, sect): def loadfat_sect(self, sect):
""" """
Adds the indexes of the given sector to the FAT Adds the indexes of the given sector to the FAT
sect: string containing the first FAT sector, or array of long integers
return: index of last FAT sector. :param sect: string containing the first FAT sector, or array of long integers
:returns: index of last FAT sector.
""" """
# a FAT sector is an array of ulong integers. # a FAT sector is an array of ulong integers.
if isinstance(sect, array.array): if isinstance(sect, array.array):
@ -1505,8 +1510,9 @@ class OleFileIO:
def getsect(self, sect): def getsect(self, sect):
""" """
Read given sector from file on disk. Read given sector from file on disk.
sect: sector index
returns a string containing the sector data. :param sect: sector index
:returns: a string containing the sector data.
""" """
# [PL] this original code was wrong when sectors are 4KB instead of # [PL] this original code was wrong when sectors are 4KB instead of
# 512 bytes: # 512 bytes:
@ -1530,7 +1536,8 @@ class OleFileIO:
def loaddirectory(self, sect): def loaddirectory(self, sect):
""" """
Load the directory. Load the directory.
sect: sector index of directory stream.
:param sect: sector index of directory stream.
""" """
# The directory is stored in a standard # The directory is stored in a standard
# substream, independent of its size. # substream, independent of its size.
@ -1567,9 +1574,10 @@ class OleFileIO:
Load a directory entry from the directory. Load a directory entry from the directory.
This method should only be called once for each storage/stream when This method should only be called once for each storage/stream when
loading the directory. loading the directory.
sid: index of storage/stream in the directory.
return: a _OleDirectoryEntry object :param sid: index of storage/stream in the directory.
raise: IOError if the entry has always been referenced. :returns: a _OleDirectoryEntry object
:exception IOError: if the entry has always been referenced.
""" """
# check if SID is OK: # check if SID is OK:
if sid<0 or sid>=len(self.direntries): if sid<0 or sid>=len(self.direntries):
@ -1598,9 +1606,9 @@ class OleFileIO:
Open a stream, either in FAT or MiniFAT according to its size. Open a stream, either in FAT or MiniFAT according to its size.
(openstream helper) (openstream helper)
start: index of first sector :param start: index of first sector
size: size of stream (or nothing if size is unknown) :param size: size of stream (or nothing if size is unknown)
force_FAT: if False (default), stream will be opened in FAT or MiniFAT :param force_FAT: if False (default), stream will be opened in FAT or MiniFAT
according to size. If True, it will always be opened in FAT. according to size. If True, it will always be opened in FAT.
""" """
debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' % debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' %
@ -1630,11 +1638,11 @@ class OleFileIO:
def _list(self, files, prefix, node, streams=True, storages=False): def _list(self, files, prefix, node, streams=True, storages=False):
""" """
(listdir helper) (listdir helper)
files: list of files to fill in :param files: list of files to fill in
prefix: current location in storage tree (list of names) :param prefix: current location in storage tree (list of names)
node: current node (_OleDirectoryEntry object) :param node: current node (_OleDirectoryEntry object)
streams: bool, include streams if True (True by default) - new in v0.26 :param streams: bool, include streams if True (True by default) - new in v0.26
storages: bool, include storages if True (False by default) - new in v0.26 :param storages: bool, include storages if True (False by default) - new in v0.26
(note: the root storage is never included) (note: the root storage is never included)
""" """
prefix = prefix + [node.name] prefix = prefix + [node.name]
@ -1657,9 +1665,9 @@ class OleFileIO:
""" """
Return a list of streams stored in this file Return a list of streams stored in this file
streams: bool, include streams if True (True by default) - new in v0.26 :param streams: bool, include streams if True (True by default) - new in v0.26
storages: bool, include storages if True (False by default) - new in v0.26 :param storages: bool, include storages if True (False by default) - new in v0.26
(note: the root storage is never included) (note: the root storage is never included)
""" """
files = [] files = []
self._list(files, [], self.root, streams, storages) self._list(files, [], self.root, streams, storages)
@ -1671,12 +1679,13 @@ class OleFileIO:
Returns directory entry of given filename. (openstream helper) Returns directory entry of given filename. (openstream helper)
Note: this method is case-insensitive. Note: this method is case-insensitive.
filename: path of stream in storage tree (except root entry), either: :param filename: path of stream in storage tree (except root entry), either:
- a string using Unix path syntax, for example: - a string using Unix path syntax, for example:
'storage_1/storage_1.2/stream' 'storage_1/storage_1.2/stream'
- a list of storage filenames, path to the desired stream/storage. - a list of storage filenames, path to the desired stream/storage.
Example: ['storage_1', 'storage_1.2', 'stream'] Example: ['storage_1', 'storage_1.2', 'stream']
return: sid of requested filename :returns: sid of requested filename
raise IOError if file not found raise IOError if file not found
""" """
@ -1700,13 +1709,15 @@ class OleFileIO:
""" """
Open a stream as a read-only file object (BytesIO). Open a stream as a read-only file object (BytesIO).
filename: path of stream in storage tree (except root entry), either: :param filename: path of stream in storage tree (except root entry), either:
- a string using Unix path syntax, for example: - a string using Unix path syntax, for example:
'storage_1/storage_1.2/stream' 'storage_1/storage_1.2/stream'
- a list of storage filenames, path to the desired stream/storage. - a list of storage filenames, path to the desired stream/storage.
Example: ['storage_1', 'storage_1.2', 'stream'] Example: ['storage_1', 'storage_1.2', 'stream']
return: file object (read-only)
raise IOError if filename not found, or if this is not a stream. :returns: file object (read-only)
:exception IOError: if filename not found, or if this is not a stream.
""" """
sid = self._find(filename) sid = self._find(filename)
entry = self.direntries[sid] entry = self.direntries[sid]
@ -1720,8 +1731,9 @@ class OleFileIO:
Test if given filename exists as a stream or a storage in the OLE Test if given filename exists as a stream or a storage in the OLE
container, and return its type. container, and return its type.
filename: path of stream in storage tree. (see openstream for syntax) :param filename: path of stream in storage tree. (see openstream for syntax)
return: False if object does not exist, its entry type (>0) otherwise: :returns: False if object does not exist, its entry type (>0) otherwise:
- STGTY_STREAM: a stream - STGTY_STREAM: a stream
- STGTY_STORAGE: a storage - STGTY_STORAGE: a storage
- STGTY_ROOT: the root entry - STGTY_ROOT: the root entry
@ -1738,10 +1750,10 @@ class OleFileIO:
""" """
Return modification time of a stream/storage. Return modification time of a stream/storage.
filename: path of stream/storage in storage tree. (see openstream for :param filename: path of stream/storage in storage tree. (see openstream for
syntax) syntax)
return: None if modification time is null, a python datetime object :returns: None if modification time is null, a python datetime object
otherwise (UTC timezone) otherwise (UTC timezone)
new in version 0.26 new in version 0.26
""" """
@ -1754,10 +1766,10 @@ class OleFileIO:
""" """
Return creation time of a stream/storage. Return creation time of a stream/storage.
filename: path of stream/storage in storage tree. (see openstream for :param filename: path of stream/storage in storage tree. (see openstream for
syntax) syntax)
return: None if creation time is null, a python datetime object :returns: None if creation time is null, a python datetime object
otherwise (UTC timezone) otherwise (UTC timezone)
new in version 0.26 new in version 0.26
""" """
@ -1771,8 +1783,8 @@ class OleFileIO:
Test if given filename exists as a stream or a storage in the OLE Test if given filename exists as a stream or a storage in the OLE
container. container.
filename: path of stream in storage tree. (see openstream for syntax) :param filename: path of stream in storage tree. (see openstream for syntax)
return: True if object exist, else False. :returns: True if object exist, else False.
""" """
try: try:
sid = self._find(filename) sid = self._find(filename)
@ -1785,9 +1797,10 @@ class OleFileIO:
""" """
Return size of a stream in the OLE container, in bytes. Return size of a stream in the OLE container, in bytes.
filename: path of stream in storage tree (see openstream for syntax) :param filename: path of stream in storage tree (see openstream for syntax)
return: size in bytes (long integer) :returns: size in bytes (long integer)
raise: IOError if file not found, TypeError if this is not a stream. :exception IOError: if file not found
:exception TypeError: if this is not a stream
""" """
sid = self._find(filename) sid = self._find(filename)
entry = self.direntries[sid] entry = self.direntries[sid]
@ -1809,11 +1822,11 @@ class OleFileIO:
""" """
Return properties described in substream. Return properties described in substream.
filename: path of stream in storage tree (see openstream for syntax) :param filename: path of stream in storage tree (see openstream for syntax)
convert_time: bool, if True timestamps will be converted to Python datetime :param convert_time: bool, if True timestamps will be converted to Python datetime
no_conversion: None or list of int, timestamps not to be converted :param no_conversion: None or list of int, timestamps not to be converted
(for example total editing time is not a real timestamp) (for example total editing time is not a real timestamp)
return: a dictionary of values indexed by id (integer) :returns: a dictionary of values indexed by id (integer)
""" """
# make sure no_conversion is a list, just to simplify code below: # make sure no_conversion is a list, just to simplify code below:
if no_conversion == None: if no_conversion == None:

View File

@ -73,9 +73,8 @@ class PSDraw:
def setink(self, ink): def setink(self, ink):
""" """
.. warning:: .. warning:: This has been in the PIL API for ages but was never implemented.
This has been in the PIL API for ages but was never implemented.
""" """
print("*** NOT YET IMPLEMENTED ***") print("*** NOT YET IMPLEMENTED ***")

View File

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

View File

@ -135,7 +135,7 @@ def _save(im, fp, filename, check=0):
# bytes per plane # bytes per plane
stride = (im.size[0] * bits + 7) // 8 stride = (im.size[0] * bits + 7) // 8
# stride should be even # stride should be even
stride = stride + (stride % 2) stride += stride % 2
# Stride needs to be kept in sync with the PcxEncode.c version. # Stride needs to be kept in sync with the PcxEncode.c version.
# Ideally it should be passed in in the state, but the bytes value # Ideally it should be passed in in the state, but the bytes value
# gets overwritten. # gets overwritten.

View File

@ -108,8 +108,8 @@ def _save(im, fp, filename):
r = i8(palette[i*3]) r = i8(palette[i*3])
g = i8(palette[i*3+1]) g = i8(palette[i*3+1])
b = i8(palette[i*3+2]) b = i8(palette[i*3+2])
colorspace = colorspace + "%02x%02x%02x " % (r, g, b) colorspace += "%02x%02x%02x " % (r, g, b)
colorspace = colorspace + "> ]" colorspace += "> ]"
procset = "/ImageI" # indexed color procset = "/ImageI" # indexed color
elif im.mode == "RGB": elif im.mode == "RGB":
filter = "/DCTDecode" filter = "/DCTDecode"

View File

@ -147,6 +147,17 @@ class ChunkStream:
return cids return cids
# --------------------------------------------------------------------
# Subclass of string to allow iTXt chunks to look like strings while
# keeping their extra information
class iTXt(str):
@staticmethod
def __new__(cls, text, lang, tkey):
self = str.__new__(cls, text)
self.lang = lang
self.tkey = tkey
return self
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG chunk container (for use with save(pnginfo=)) # PNG chunk container (for use with save(pnginfo=))
@ -159,14 +170,36 @@ class PngInfo:
def add(self, cid, data): def add(self, cid, data):
self.chunks.append((cid, data)) self.chunks.append((cid, data))
def add_itxt(self, key, value, lang="", tkey="", zip=False):
if not isinstance(key, bytes):
key = key.encode("latin-1", "strict")
if not isinstance(value, bytes):
value = value.encode("utf-8", "strict")
if not isinstance(lang, bytes):
lang = lang.encode("utf-8", "strict")
if not isinstance(tkey, bytes):
tkey = tkey.encode("utf-8", "strict")
if zip:
import zlib
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value))
else:
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)
def add_text(self, key, value, zip=0): def add_text(self, key, value, zip=0):
if isinstance(value, iTXt):
return self.add_itxt(key, value, value.lang, value.tkey, bool(zip))
# The tEXt chunk stores latin-1 text # The tEXt chunk stores latin-1 text
if not isinstance(value, bytes):
try:
value = value.encode('latin-1', 'strict')
except UnicodeError:
return self.add_itxt(key, value, zip=bool(zip))
if not isinstance(key, bytes): if not isinstance(key, bytes):
key = key.encode('latin-1', 'strict') key = key.encode('latin-1', 'strict')
if not isinstance(value, bytes):
value = value.encode('latin-1', 'replace')
if zip: if zip:
import zlib import zlib
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value)) self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
@ -329,6 +362,43 @@ class PngStream(ChunkStream):
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
return s return s
def chunk_iTXt(self, pos, length):
# international text
r = s = ImageFile._safe_read(self.fp, length)
try:
k, r = r.split(b"\0", 1)
except ValueError:
return s
if len(r) < 2:
return s
cf, cm, r = i8(r[0]), i8(r[1]), r[2:]
try:
lang, tk, v = r.split(b"\0", 2)
except ValueError:
return s
if cf != 0:
if cm == 0:
import zlib
try:
v = zlib.decompress(v)
except zlib.error:
return s
else:
return s
if bytes is not str:
try:
k = k.decode("latin-1", "strict")
lang = lang.decode("utf-8", "strict")
tk = tk.decode("utf-8", "strict")
v = v.decode("utf-8", "strict")
except UnicodeError:
return s
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
return s
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG reader # PNG reader

View File

@ -235,7 +235,7 @@ def _layerinfo(file):
if t: if t:
tile.extend(t) tile.extend(t)
layers[i] = name, mode, bbox, tile layers[i] = name, mode, bbox, tile
i = i + 1 i += 1
return layers return layers
@ -258,7 +258,7 @@ def _maketile(file, mode, bbox, channels):
for channel in range(channels): for channel in range(channels):
layer = mode[channel] layer = mode[channel]
if mode == "CMYK": if mode == "CMYK":
layer = layer + ";I" layer += ";I"
tile.append(("raw", bbox, offset, layer)) tile.append(("raw", bbox, offset, layer))
offset = offset + xsize*ysize offset = offset + xsize*ysize
@ -272,13 +272,13 @@ def _maketile(file, mode, bbox, channels):
for channel in range(channels): for channel in range(channels):
layer = mode[channel] layer = mode[channel]
if mode == "CMYK": if mode == "CMYK":
layer = layer + ";I" layer += ";I"
tile.append( tile.append(
("packbits", bbox, offset, layer) ("packbits", bbox, offset, layer)
) )
for y in range(ysize): for y in range(ysize):
offset = offset + i16(bytecount[i:i+2]) offset = offset + i16(bytecount[i:i+2])
i = i + 2 i += 2
file.seek(offset) file.seek(offset)

View File

@ -261,6 +261,7 @@ mode_map = {'1': _PyAccess8,
'PA': _PyAccess32_2, 'PA': _PyAccess32_2,
'RGB': _PyAccess32_3, 'RGB': _PyAccess32_3,
'LAB': _PyAccess32_3, 'LAB': _PyAccess32_3,
'HSV': _PyAccess32_3,
'YCbCr': _PyAccess32_3, 'YCbCr': _PyAccess32_3,
'RGBA': _PyAccess32_4, 'RGBA': _PyAccess32_4,
'RGBa': _PyAccess32_4, 'RGBa': _PyAccess32_4,

View File

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

View File

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

View File

@ -49,17 +49,18 @@ from PIL import _binary
from PIL._util import isStringType from PIL._util import isStringType
import warnings import warnings
import array, sys import array
import sys
import collections import collections
import itertools import itertools
import os import os
# Set these to true to force use of libtiff for reading or writing. # Set these to true to force use of libtiff for reading or writing.
READ_LIBTIFF = False READ_LIBTIFF = False
WRITE_LIBTIFF= False WRITE_LIBTIFF = False
II = b"II" # little-endian (intel-style) II = b"II" # little-endian (Intel style)
MM = b"MM" # big-endian (motorola-style) MM = b"MM" # big-endian (Motorola style)
i8 = _binary.i8 i8 = _binary.i8
o8 = _binary.o8 o8 = _binary.o8
@ -109,8 +110,8 @@ EXTRASAMPLES = 338
SAMPLEFORMAT = 339 SAMPLEFORMAT = 339
JPEGTABLES = 347 JPEGTABLES = 347
COPYRIGHT = 33432 COPYRIGHT = 33432
IPTC_NAA_CHUNK = 33723 # newsphoto properties IPTC_NAA_CHUNK = 33723 # newsphoto properties
PHOTOSHOP_CHUNK = 34377 # photoshop properties PHOTOSHOP_CHUNK = 34377 # photoshop properties
ICCPROFILE = 34675 ICCPROFILE = 34675
EXIFIFD = 34665 EXIFIFD = 34665
XMP = 700 XMP = 700
@ -126,10 +127,10 @@ COMPRESSION_INFO = {
3: "group3", 3: "group3",
4: "group4", 4: "group4",
5: "tiff_lzw", 5: "tiff_lzw",
6: "tiff_jpeg", # obsolete 6: "tiff_jpeg", # obsolete
7: "jpeg", 7: "jpeg",
8: "tiff_adobe_deflate", 8: "tiff_adobe_deflate",
32771: "tiff_raw_16", # 16-bit padding 32771: "tiff_raw_16", # 16-bit padding
32773: "packbits", 32773: "packbits",
32809: "tiff_thunderscan", 32809: "tiff_thunderscan",
32946: "tiff_deflate", 32946: "tiff_deflate",
@ -137,7 +138,7 @@ COMPRESSION_INFO = {
34677: "tiff_sgilog24", 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 = { OPEN_INFO = {
# (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample, # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,
@ -150,7 +151,7 @@ OPEN_INFO = {
(II, 1, 1, 1, (1,), ()): ("1", "1"), (II, 1, 1, 1, (1,), ()): ("1", "1"),
(II, 1, 1, 2, (1,), ()): ("1", "1;R"), (II, 1, 1, 2, (1,), ()): ("1", "1;R"),
(II, 1, 1, 1, (8,), ()): ("L", "L"), (II, 1, 1, 1, (8,), ()): ("L", "L"),
(II, 1, 1, 1, (8,8), (2,)): ("LA", "LA"), (II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
(II, 1, 1, 2, (8,), ()): ("L", "L;R"), (II, 1, 1, 2, (8,), ()): ("L", "L;R"),
(II, 1, 1, 1, (12,), ()): ("I;16", "I;12"), (II, 1, 1, 1, (12,), ()): ("I;16", "I;12"),
(II, 1, 1, 1, (16,), ()): ("I;16", "I;16"), (II, 1, 1, 1, (16,), ()): ("I;16", "I;16"),
@ -158,13 +159,13 @@ OPEN_INFO = {
(II, 1, 1, 1, (32,), ()): ("I", "I;32N"), (II, 1, 1, 1, (32,), ()): ("I", "I;32N"),
(II, 1, 2, 1, (32,), ()): ("I", "I;32S"), (II, 1, 2, 1, (32,), ()): ("I", "I;32S"),
(II, 1, 3, 1, (32,), ()): ("F", "F;32F"), (II, 1, 3, 1, (32,), ()): ("F", "F;32F"),
(II, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"), (II, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"),
(II, 2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"), (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), ()): ("RGBA", "RGBA"), # missing ExtraSamples
(II, 2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"), (II, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
(II, 2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"), (II, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
(II, 2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"), (II, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
(II, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10 (II, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
(II, 3, 1, 1, (1,), ()): ("P", "P;1"), (II, 3, 1, 1, (1,), ()): ("P", "P;1"),
(II, 3, 1, 2, (1,), ()): ("P", "P;1R"), (II, 3, 1, 2, (1,), ()): ("P", "P;1R"),
(II, 3, 1, 1, (2,), ()): ("P", "P;2"), (II, 3, 1, 1, (2,), ()): ("P", "P;2"),
@ -172,11 +173,11 @@ OPEN_INFO = {
(II, 3, 1, 1, (4,), ()): ("P", "P;4"), (II, 3, 1, 1, (4,), ()): ("P", "P;4"),
(II, 3, 1, 2, (4,), ()): ("P", "P;4R"), (II, 3, 1, 2, (4,), ()): ("P", "P;4R"),
(II, 3, 1, 1, (8,), ()): ("P", "P"), (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, 3, 1, 2, (8,), ()): ("P", "P;R"),
(II, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"), (II, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
(II, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"), (II, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
(II, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"), (II, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"),
(MM, 0, 1, 1, (1,), ()): ("1", "1;I"), (MM, 0, 1, 1, (1,), ()): ("1", "1;I"),
(MM, 0, 1, 2, (1,), ()): ("1", "1;IR"), (MM, 0, 1, 2, (1,), ()): ("1", "1;IR"),
@ -185,18 +186,18 @@ OPEN_INFO = {
(MM, 1, 1, 1, (1,), ()): ("1", "1"), (MM, 1, 1, 1, (1,), ()): ("1", "1"),
(MM, 1, 1, 2, (1,), ()): ("1", "1;R"), (MM, 1, 1, 2, (1,), ()): ("1", "1;R"),
(MM, 1, 1, 1, (8,), ()): ("L", "L"), (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, 2, (8,), ()): ("L", "L;R"),
(MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"), (MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"),
(MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"), (MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"),
(MM, 1, 2, 1, (32,), ()): ("I;32BS", "I;32BS"), (MM, 1, 2, 1, (32,), ()): ("I;32BS", "I;32BS"),
(MM, 1, 3, 1, (32,), ()): ("F", "F;32BF"), (MM, 1, 3, 1, (32,), ()): ("F", "F;32BF"),
(MM, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"), (MM, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"),
(MM, 2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"), (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), (0,)): ("RGBX", "RGBX"),
(MM, 2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"), (MM, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
(MM, 2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"), (MM, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
(MM, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10 (MM, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
(MM, 3, 1, 1, (1,), ()): ("P", "P;1"), (MM, 3, 1, 1, (1,), ()): ("P", "P;1"),
(MM, 3, 1, 2, (1,), ()): ("P", "P;1R"), (MM, 3, 1, 2, (1,), ()): ("P", "P;1R"),
(MM, 3, 1, 1, (2,), ()): ("P", "P;2"), (MM, 3, 1, 1, (2,), ()): ("P", "P;2"),
@ -204,19 +205,21 @@ OPEN_INFO = {
(MM, 3, 1, 1, (4,), ()): ("P", "P;4"), (MM, 3, 1, 1, (4,), ()): ("P", "P;4"),
(MM, 3, 1, 2, (4,), ()): ("P", "P;4R"), (MM, 3, 1, 2, (4,), ()): ("P", "P;4R"),
(MM, 3, 1, 1, (8,), ()): ("P", "P"), (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, 3, 1, 2, (8,), ()): ("P", "P;R"),
(MM, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"), (MM, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
(MM, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"), (MM, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
(MM, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"), (MM, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"),
} }
PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"] PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"]
def _accept(prefix): def _accept(prefix):
return prefix[:4] in PREFIXES return prefix[:4] in PREFIXES
## ##
# Wrapper for TIFF IFDs. # Wrapper for TIFF IFDs.
@ -238,7 +241,7 @@ class ImageFileDirectory(collections.MutableMapping):
Value: integer corresponding to the data type from Value: integer corresponding to the data type from
`TiffTags.TYPES` `TiffTags.TYPES`
'internal' 'internal'
* self.tags = {} Key: numerical tiff tag number * self.tags = {} Key: numerical tiff tag number
Value: Decoded data, Generally a tuple. Value: Decoded data, Generally a tuple.
* If set from __setval__ -- always a tuple * If set from __setval__ -- always a tuple
@ -276,7 +279,7 @@ class ImageFileDirectory(collections.MutableMapping):
#: For a complete dictionary, use the as_dict method. #: For a complete dictionary, use the as_dict method.
self.tags = {} self.tags = {}
self.tagdata = {} self.tagdata = {}
self.tagtype = {} # added 2008-06-05 by Florian Hoech self.tagtype = {} # added 2008-06-05 by Florian Hoech
self.next = None self.next = None
def __str__(self): def __str__(self):
@ -287,7 +290,9 @@ class ImageFileDirectory(collections.MutableMapping):
return dict(self.items()) return dict(self.items())
def named(self): def named(self):
"""Returns the complete tag dictionary, with named tags where posible.""" """
Returns the complete tag dictionary, with named tags where posible.
"""
from PIL import TiffTags from PIL import TiffTags
result = {} result = {}
for tag_code, value in self.items(): for tag_code, value in self.items():
@ -295,7 +300,6 @@ class ImageFileDirectory(collections.MutableMapping):
result[tag_name] = value result[tag_name] = value
return result return result
# dictionary API # dictionary API
def __len__(self): def __len__(self):
@ -305,7 +309,7 @@ class ImageFileDirectory(collections.MutableMapping):
try: try:
return self.tags[tag] return self.tags[tag]
except KeyError: except KeyError:
data = self.tagdata[tag] # unpack on the fly data = self.tagdata[tag] # unpack on the fly
type = self.tagtype[tag] type = self.tagtype[tag]
size, handler = self.load_dispatch[type] size, handler = self.load_dispatch[type]
self.tags[tag] = data = handler(self, data) self.tags[tag] = data = handler(self, data)
@ -319,7 +323,7 @@ class ImageFileDirectory(collections.MutableMapping):
if tag == SAMPLEFORMAT: if tag == SAMPLEFORMAT:
# work around broken (?) matrox library # work around broken (?) matrox library
# (from Ted Wright, via Bob Klimek) # (from Ted Wright, via Bob Klimek)
raise KeyError # use default raise KeyError # use default
raise ValueError("not a scalar") raise ValueError("not a scalar")
return value[0] return value[0]
except KeyError: except KeyError:
@ -433,7 +437,7 @@ class ImageFileDirectory(collections.MutableMapping):
except KeyError: except KeyError:
if Image.DEBUG: if Image.DEBUG:
print("- unsupported type", typ) print("- unsupported type", typ)
continue # ignore unsupported type continue # ignore unsupported type
size, handler = dispatch size, handler = dispatch
@ -449,14 +453,17 @@ class ImageFileDirectory(collections.MutableMapping):
data = ifd[8:8+size] data = ifd[8:8+size]
if len(data) != size: if len(data) != size:
warnings.warn("Possibly corrupt EXIF data. Expecting to read %d bytes but only got %d. Skipping tag %s" % (size, len(data), tag)) warnings.warn("Possibly corrupt EXIF data. "
"Expecting to read %d bytes but only got %d. "
"Skipping tag %s" % (size, len(data), tag))
continue continue
self.tagdata[tag] = data self.tagdata[tag] = data
self.tagtype[tag] = typ self.tagtype[tag] = typ
if Image.DEBUG: if Image.DEBUG:
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP): if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK,
ICCPROFILE, XMP):
print("- value: <table: %d bytes>" % size) print("- value: <table: %d bytes>" % size)
else: else:
print("- value:", self[tag]) print("- value:", self[tag])
@ -489,10 +496,10 @@ class ImageFileDirectory(collections.MutableMapping):
if tag in self.tagtype: if tag in self.tagtype:
typ = self.tagtype[tag] typ = self.tagtype[tag]
if Image.DEBUG: if Image.DEBUG:
print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
if typ == 1: if typ == 1:
# byte data # byte data
if isinstance(value, tuple): if isinstance(value, tuple):
@ -512,14 +519,14 @@ class ImageFileDirectory(collections.MutableMapping):
# and doesn't match the tiff spec: 8-bit byte that # and doesn't match the tiff spec: 8-bit byte that
# contains a 7-bit ASCII code; the last byte must be # contains a 7-bit ASCII code; the last byte must be
# NUL (binary zero). Also, I don't think this was well # NUL (binary zero). Also, I don't think this was well
# excersized before. # excersized before.
data = value = b"" + value.encode('ascii', 'replace') + b"\0" data = value = b"" + value.encode('ascii', 'replace') + b"\0"
else: else:
# integer data # integer data
if tag == STRIPOFFSETS: if tag == STRIPOFFSETS:
stripoffsets = len(directory) stripoffsets = len(directory)
typ = 4 # to avoid catch-22 typ = 4 # to avoid catch-22
elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ==5: elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ == 5:
# identify rational data fields # identify rational data fields
typ = 5 typ = 5
if isinstance(value[0], tuple): if isinstance(value[0], tuple):
@ -541,7 +548,8 @@ class ImageFileDirectory(collections.MutableMapping):
typname = TiffTags.TYPES.get(typ, "unknown") typname = TiffTags.TYPES.get(typ, "unknown")
print("save: %s (%d)" % (tagname, tag), end=' ') print("save: %s (%d)" % (tagname, tag), end=' ')
print("- type: %s (%d)" % (typname, typ), end=' ') print("- type: %s (%d)" % (typname, typ), end=' ')
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP): if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK,
ICCPROFILE, XMP):
size = len(data) size = len(data)
print("- value: <table: %d bytes>" % size) print("- value: <table: %d bytes>" % size)
else: else:
@ -558,9 +566,9 @@ class ImageFileDirectory(collections.MutableMapping):
count = count // 2 # adjust for rational data field count = count // 2 # adjust for rational data field
append((tag, typ, count, o32(offset), data)) append((tag, typ, count, o32(offset), data))
offset = offset + len(data) offset += len(data)
if offset & 1: if offset & 1:
offset = offset + 1 # word padding offset += 1 # word padding
# update strip offset data to point beyond auxiliary data # update strip offset data to point beyond auxiliary data
if stripoffsets is not None: if stripoffsets is not None:
@ -576,7 +584,7 @@ class ImageFileDirectory(collections.MutableMapping):
fp.write(o16(tag) + o16(typ) + o32(count) + value) fp.write(o16(tag) + o16(typ) + o32(count) + value)
# -- overwrite here for multi-page -- # -- 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 # pass 3: write auxiliary data to file
for tag, typ, count, value, data in directory: for tag, typ, count, value, data in directory:
@ -586,6 +594,7 @@ class ImageFileDirectory(collections.MutableMapping):
return offset return offset
## ##
# Image plugin for TIFF files. # Image plugin for TIFF files.
@ -616,7 +625,7 @@ class TiffImageFile(ImageFile.ImageFile):
print ("- __first:", self.__first) print ("- __first:", self.__first)
print ("- ifh: ", ifh) print ("- ifh: ", ifh)
# and load the first frame # and load the first frame
self._seek(0) self._seek(0)
def seek(self, frame): def seek(self, frame):
@ -644,7 +653,7 @@ class TiffImageFile(ImageFile.ImageFile):
self.fp.seek(self.__next) self.fp.seek(self.__next)
self.tag.load(self.fp) self.tag.load(self.fp)
self.__next = self.tag.next self.__next = self.tag.next
self.__frame = self.__frame + 1 self.__frame += 1
self._setup() self._setup()
def _tell(self): def _tell(self):
@ -694,9 +703,11 @@ class TiffImageFile(ImageFile.ImageFile):
if not len(self.tile) == 1: if not len(self.tile) == 1:
raise IOError("Not exactly one tile") raise IOError("Not exactly one tile")
# (self._compression, (extents tuple), 0, (rawmode, self._compression, fp)) # (self._compression, (extents tuple),
# 0, (rawmode, self._compression, fp))
ignored, extents, ignored_2, args = self.tile[0] ignored, extents, ignored_2, args = self.tile[0]
decoder = Image._getdecoder(self.mode, 'libtiff', args, self.decoderconfig) decoder = Image._getdecoder(self.mode, 'libtiff', args,
self.decoderconfig)
try: try:
decoder.setimage(self.im, extents) decoder.setimage(self.im, extents)
except ValueError: except ValueError:
@ -706,35 +717,35 @@ class TiffImageFile(ImageFile.ImageFile):
# We've got a stringio like thing passed in. Yay for all in memory. # We've got a stringio like thing passed in. Yay for all in memory.
# The decoder needs the entire file in one shot, so there's not # The decoder needs the entire file in one shot, so there's not
# a lot we can do here other than give it the entire file. # a lot we can do here other than give it the entire file.
# unless we could do something like get the address of the underlying # unless we could do something like get the address of the
# string for stringio. # underlying string for stringio.
# #
# Rearranging for supporting byteio items, since they have a fileno # Rearranging for supporting byteio items, since they have a fileno
# that returns an IOError if there's no underlying fp. Easier to deal # that returns an IOError if there's no underlying fp. Easier to
# with here by reordering. # dea. with here by reordering.
if Image.DEBUG: if Image.DEBUG:
print ("have getvalue. just sending in a string from getvalue") print ("have getvalue. just sending in a string from getvalue")
n,err = decoder.decode(self.fp.getvalue()) n, err = decoder.decode(self.fp.getvalue())
elif hasattr(self.fp, "fileno"): elif hasattr(self.fp, "fileno"):
# we've got a actual file on disk, pass in the fp. # we've got a actual file on disk, pass in the fp.
if Image.DEBUG: if Image.DEBUG:
print ("have fileno, calling fileno version of the decoder.") print ("have fileno, calling fileno version of the decoder.")
self.fp.seek(0) self.fp.seek(0)
n,err = decoder.decode(b"fpfp") # 4 bytes, otherwise the trace might error out # 4 bytes, otherwise the trace might error out
n, err = decoder.decode(b"fpfp")
else: else:
# we have something else. # we have something else.
if Image.DEBUG: if Image.DEBUG:
print ("don't have fileno or getvalue. just reading") print ("don't have fileno or getvalue. just reading")
# UNDONE -- so much for that buffer size thing. # UNDONE -- so much for that buffer size thing.
n,err = decoder.decode(self.fp.read()) n, err = decoder.decode(self.fp.read())
self.tile = [] self.tile = []
self.readonly = 0 self.readonly = 0
# libtiff closed the fp in a, we need to close self.fp, if possible # libtiff closed the fp in a, we need to close self.fp, if possible
if hasattr(self.fp, 'close'): if hasattr(self.fp, 'close'):
self.fp.close() self.fp.close()
self.fp = None # might be shared self.fp = None # might be shared
if err < 0: if err < 0:
raise IOError(err) raise IOError(err)
@ -810,11 +821,11 @@ class TiffImageFile(ImageFile.ImageFile):
xres = xres[0] / (xres[1] or 1) xres = xres[0] / (xres[1] or 1)
yres = yres[0] / (yres[1] or 1) yres = yres[0] / (yres[1] or 1)
resunit = getscalar(RESOLUTION_UNIT, 1) resunit = getscalar(RESOLUTION_UNIT, 1)
if resunit == 2: # dots per inch if resunit == 2: # dots per inch
self.info["dpi"] = xres, yres 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 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 self.info["resolution"] = xres, yres
# build tile descriptors # build tile descriptors
@ -825,13 +836,16 @@ class TiffImageFile(ImageFile.ImageFile):
offsets = self.tag[STRIPOFFSETS] offsets = self.tag[STRIPOFFSETS]
h = getscalar(ROWSPERSTRIP, ysize) h = getscalar(ROWSPERSTRIP, ysize)
w = self.size[0] w = self.size[0]
if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3", "group4", if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3",
"tiff_jpeg", "tiff_adobe_deflate", "group4", "tiff_jpeg",
"tiff_thunderscan", "tiff_deflate", "tiff_adobe_deflate",
"tiff_sgilog", "tiff_sgilog24", "tiff_thunderscan",
"tiff_deflate",
"tiff_sgilog",
"tiff_sgilog24",
"tiff_raw_16"]: "tiff_raw_16"]:
## if Image.DEBUG: # if Image.DEBUG:
## print "Activating g4 compression for whole file" # print "Activating g4 compression for whole file"
# Decoder expects entire file as one tile. # Decoder expects entire file as one tile.
# There's a buffer size limit in load (64k) # There's a buffer size limit in load (64k)
@ -850,7 +864,8 @@ class TiffImageFile(ImageFile.ImageFile):
# libtiff closes the file descriptor, so pass in a dup. # libtiff closes the file descriptor, so pass in a dup.
try: try:
fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno()) fp = hasattr(self.fp, "fileno") and \
os.dup(self.fp.fileno())
except IOError: except IOError:
# io.BytesIO have a fileno, but returns an IOError if # io.BytesIO have a fileno, but returns an IOError if
# it doesn't use a file descriptor. # it doesn't use a file descriptor.
@ -859,7 +874,7 @@ class TiffImageFile(ImageFile.ImageFile):
# libtiff handles the fillmode for us, so 1;IR should # libtiff handles the fillmode for us, so 1;IR should
# actually be 1;I. Including the R double reverses the # actually be 1;I. Including the R double reverses the
# bits, so stripes of the image are reversed. See # bits, so stripes of the image are reversed. See
# https://github.com/python-imaging/Pillow/issues/279 # https://github.com/python-pillow/Pillow/issues/279
if fillorder == 2: if fillorder == 2:
key = ( key = (
self.tag.prefix, photo, format, 1, self.tag.prefix, photo, format, 1,
@ -881,7 +896,7 @@ class TiffImageFile(ImageFile.ImageFile):
# Offset in the tile tuple is 0, we go from 0,0 to # Offset in the tile tuple is 0, we go from 0,0 to
# w,h, and we only do this once -- eds # w,h, and we only do this once -- eds
a = (rawmode, self._compression, fp ) a = (rawmode, self._compression, fp)
self.tile.append( self.tile.append(
(self._compression, (self._compression,
(0, 0, w, ysize), (0, 0, w, ysize),
@ -893,14 +908,14 @@ class TiffImageFile(ImageFile.ImageFile):
a = self._decoder(rawmode, l, i) a = self._decoder(rawmode, l, i)
self.tile.append( self.tile.append(
(self._compression, (self._compression,
(0, min(y, ysize), w, min(y+h, ysize)), (0, min(y, ysize), w, min(y+h, ysize)),
offsets[i], a)) offsets[i], a))
if Image.DEBUG: if Image.DEBUG:
print ("tiles: ", self.tile) print ("tiles: ", self.tile)
y = y + h y = y + h
if y >= self.size[1]: if y >= self.size[1]:
x = y = 0 x = y = 0
l = l + 1 l += 1
a = None a = None
elif TILEOFFSETS in self.tag: elif TILEOFFSETS in self.tag:
# tiled image # tiled image
@ -914,14 +929,14 @@ class TiffImageFile(ImageFile.ImageFile):
# is not a multiple of the tile size... # is not a multiple of the tile size...
self.tile.append( self.tile.append(
(self._compression, (self._compression,
(x, y, x+w, y+h), (x, y, x+w, y+h),
o, a)) o, a))
x = x + w x = x + w
if x >= self.size[0]: if x >= self.size[0]:
x, y = 0, y + h x, y = 0, y + h
if y >= self.size[1]: if y >= self.size[1]:
x = y = 0 x = y = 0
l = l + 1 l += 1
a = None a = None
else: else:
if Image.DEBUG: if Image.DEBUG:
@ -937,25 +952,27 @@ class TiffImageFile(ImageFile.ImageFile):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write TIFF files # Write TIFF files
# little endian is default except for image modes with explict big endian byte-order # little endian is default except for image modes with
# explict big endian byte-order
SAVE_INFO = { SAVE_INFO = {
# mode => rawmode, byteorder, photometrics, sampleformat, bitspersample, extra # mode => rawmode, byteorder, photometrics,
# sampleformat, bitspersample, extra
"1": ("1", II, 1, 1, (1,), None), "1": ("1", II, 1, 1, (1,), None),
"L": ("L", II, 1, 1, (8,), None), "L": ("L", II, 1, 1, (8,), None),
"LA": ("LA", II, 1, 1, (8,8), 2), "LA": ("LA", II, 1, 1, (8, 8), 2),
"P": ("P", II, 3, 1, (8,), None), "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": ("I;32S", II, 1, 2, (32,), None),
"I;16": ("I;16", II, 1, 1, (16,), None), "I;16": ("I;16", II, 1, 1, (16,), None),
"I;16S": ("I;16S", II, 1, 2, (16,), None), "I;16S": ("I;16S", II, 1, 2, (16,), None),
"F": ("F;32F", II, 1, 3, (32,), None), "F": ("F;32F", II, 1, 3, (32,), None),
"RGB": ("RGB", II, 2, 1, (8,8,8), None), "RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
"RGBX": ("RGBX", II, 2, 1, (8,8,8,8), 0), "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
"RGBA": ("RGBA", II, 2, 1, (8,8,8,8), 2), "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2),
"CMYK": ("CMYK", II, 5, 1, (8,8,8,8), None), "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
"YCbCr": ("YCbCr", II, 6, 1, (8,8,8), None), "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
"LAB": ("LAB", II, 8, 1, (8,8,8), None), "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
"I;32BS": ("I;32BS", MM, 1, 2, (32,), None), "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
"I;16B": ("I;16B", MM, 1, 1, (16,), None), "I;16B": ("I;16B", MM, 1, 1, (16,), None),
@ -963,6 +980,7 @@ SAVE_INFO = {
"F;32BF": ("F;32BF", MM, 1, 3, (32,), None), "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
} }
def _cvt_res(value): def _cvt_res(value):
# convert value to TIFF rational number -- (numerator, denominator) # convert value to TIFF rational number -- (numerator, denominator)
if isinstance(value, collections.Sequence): if isinstance(value, collections.Sequence):
@ -973,6 +991,7 @@ def _cvt_res(value):
value = float(value) value = float(value)
return (int(value * 65536), 65536) return (int(value * 65536), 65536)
def _save(im, fp, filename): def _save(im, fp, filename):
try: try:
@ -982,13 +1001,14 @@ def _save(im, fp, filename):
ifd = ImageFileDirectory(prefix) ifd = ImageFileDirectory(prefix)
compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) compression = im.encoderinfo.get('compression', im.info.get('compression',
'raw'))
libtiff = WRITE_LIBTIFF or compression != 'raw' libtiff = WRITE_LIBTIFF or compression != 'raw'
# required for color libtiff images # required for color libtiff images
ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1)
# -- multi-page -- skip TIFF header on subsequent pages # -- multi-page -- skip TIFF header on subsequent pages
if not libtiff and fp.tell() == 0: if not libtiff and fp.tell() == 0:
# tiff header (write via IFD to get everything right) # tiff header (write via IFD to get everything right)
@ -999,17 +1019,16 @@ def _save(im, fp, filename):
ifd[IMAGELENGTH] = im.size[1] ifd[IMAGELENGTH] = im.size[1]
# write any arbitrary tags passed in as an ImageFileDirectory # write any arbitrary tags passed in as an ImageFileDirectory
info = im.encoderinfo.get("tiffinfo",{}) info = im.encoderinfo.get("tiffinfo", {})
if Image.DEBUG: if Image.DEBUG:
print ("Tiffinfo Keys: %s"% info.keys) print("Tiffinfo Keys: %s" % info.keys)
keys = list(info.keys()) keys = list(info.keys())
for key in keys: for key in keys:
ifd[key] = info.get(key) ifd[key] = info.get(key)
try: try:
ifd.tagtype[key] = info.tagtype[key] ifd.tagtype[key] = info.tagtype[key]
except: except:
pass # might not be an IFD, Might not have populated type pass # might not be an IFD, Might not have populated type
# additions written by Greg Couch, gregc@cgl.ucsf.edu # additions written by Greg Couch, gregc@cgl.ucsf.edu
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
@ -1025,12 +1044,12 @@ def _save(im, fp, filename):
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
if "icc_profile" in im.info: if "icc_profile" in im.info:
ifd[ICCPROFILE] = im.info["icc_profile"] ifd[ICCPROFILE] = im.info["icc_profile"]
if "description" in im.encoderinfo: if "description" in im.encoderinfo:
ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"]
if "resolution" in im.encoderinfo: if "resolution" in im.encoderinfo:
ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \ ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \
= _cvt_res(im.encoderinfo["resolution"]) = _cvt_res(im.encoderinfo["resolution"])
if "x resolution" in im.encoderinfo: if "x resolution" in im.encoderinfo:
ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"]) ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"])
if "y resolution" in im.encoderinfo: if "y resolution" in im.encoderinfo:
@ -1077,8 +1096,9 @@ def _save(im, fp, filename):
stride = len(bits) * ((im.size[0]*bits[0]+7)//8) stride = len(bits) * ((im.size[0]*bits[0]+7)//8)
ifd[ROWSPERSTRIP] = im.size[1] ifd[ROWSPERSTRIP] = im.size[1]
ifd[STRIPBYTECOUNTS] = stride * im.size[1] ifd[STRIPBYTECOUNTS] = stride * im.size[1]
ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression,1) # no compression by default # no compression by default:
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
if libtiff: if libtiff:
if Image.DEBUG: if Image.DEBUG:
@ -1089,23 +1109,27 @@ def _save(im, fp, filename):
fp.seek(0) fp.seek(0)
_fp = os.dup(fp.fileno()) _fp = os.dup(fp.fileno())
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. # ICC Profile crashes.
atts={} blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE]
# bits per sample is a single short in the tiff directory, not a list. atts = {}
# bits per sample is a single short in the tiff directory, not a list.
atts[BITSPERSAMPLE] = bits[0] atts[BITSPERSAMPLE] = bits[0]
# Merge the ones that we have with (optional) more bits from # Merge the ones that we have with (optional) more bits from
# the original file, e.g x,y resolution so that we can # the original file, e.g x,y resolution so that we can
# save(load('')) == original file. # save(load('')) == original file.
for k,v in itertools.chain(ifd.items(), getattr(im, 'ifd', {}).items()): for k, v in itertools.chain(ifd.items(),
getattr(im, 'ifd', {}).items()):
if k not in atts and k not in blocklist: if k not in atts and k not in blocklist:
if type(v[0]) == tuple and len(v) > 1: if type(v[0]) == tuple and len(v) > 1:
# A tuple of more than one rational tuples # A tuple of more than one rational tuples
# flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL # flatten to floats,
# following tiffcp.c->cpTag->TIFF_RATIONAL
atts[k] = [float(elt[0])/float(elt[1]) for elt in v] atts[k] = [float(elt[0])/float(elt[1]) for elt in v]
continue continue
if type(v[0]) == tuple and len(v) == 1: if type(v[0]) == tuple and len(v) == 1:
# A tuple of one rational tuples # A tuple of one rational tuples
# flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL # flatten to floats,
# following tiffcp.c->cpTag->TIFF_RATIONAL
atts[k] = float(v[0][0])/float(v[0][1]) atts[k] = float(v[0][0])/float(v[0][1])
continue continue
if type(v) == tuple and len(v) > 2: if type(v) == tuple and len(v) > 2:
@ -1115,7 +1139,8 @@ def _save(im, fp, filename):
continue continue
if type(v) == tuple and len(v) == 2: if type(v) == tuple and len(v) == 2:
# one rational tuple # one rational tuple
# flatten to float, following tiffcp.c->cpTag->TIFF_RATIONAL # flatten to float,
# following tiffcp.c->cpTag->TIFF_RATIONAL
atts[k] = float(v[0])/float(v[1]) atts[k] = float(v[0])/float(v[1])
continue continue
if type(v) == tuple and len(v) == 1: if type(v) == tuple and len(v) == 1:
@ -1141,9 +1166,10 @@ def _save(im, fp, filename):
a = (rawmode, compression, _fp, filename, atts) a = (rawmode, compression, _fp, filename, atts)
# print (im.mode, compression, a, im.encoderconfig) # print (im.mode, compression, a, im.encoderconfig)
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig) e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
e.setimage(im.im, (0,0)+im.size) e.setimage(im.im, (0, 0)+im.size)
while True: while True:
l, s, d = e.encode(16*1024) # undone, change to self.decodermaxblock # undone, change to self.decodermaxblock:
l, s, d = e.encode(16*1024)
if not _fp: if not _fp:
fp.write(d) fp.write(d)
if s: if s:
@ -1155,13 +1181,12 @@ def _save(im, fp, filename):
offset = ifd.save(fp) offset = ifd.save(fp)
ImageFile._save(im, 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 -- # -- helper for multi-page save --
if "_debug_multipage" in im.encoderinfo: if "_debug_multipage" in im.encoderinfo:
#just to access o32 and o16 (using correct byte order) # just to access o32 and o16 (using correct byte order)
im._debug_multipage = ifd im._debug_multipage = ifd
# #

View File

@ -147,6 +147,100 @@ TAGS = {
# ICC Profile # ICC Profile
34675: "ICCProfile", 34675: "ICCProfile",
# Additional Exif Info
33434: "ExposureTime",
33437: "FNumber",
34850: "ExposureProgram",
34852: "SpectralSensitivity",
34853: "GPSInfoIFD",
34855: "ISOSpeedRatings",
34856: "OECF",
34864: "SensitivityType",
34865: "StandardOutputSensitivity",
34866: "RecommendedExposureIndex",
34867: "ISOSpeed",
34868: "ISOSpeedLatitudeyyy",
34869: "ISOSpeedLatitudezzz",
36864: "ExifVersion",
36867: "DateTimeOriginal",
36868: "DateTImeDigitized",
37121: "ComponentsConfiguration",
37122: "CompressedBitsPerPixel",
37377: "ShutterSpeedValue",
37378: "ApertureValue",
37379: "BrightnessValue",
37380: "ExposureBiasValue",
37381: "MaxApertureValue",
37382: "SubjectDistance",
37383: "MeteringMode",
37384: "LightSource",
37385: "Flash",
37386: "FocalLength",
37396: "SubjectArea",
37500: "MakerNote",
37510: "UserComment",
37520: "SubSec",
37521: "SubSecTimeOriginal",
37522: "SubsecTimeDigitized",
40960: "FlashPixVersion",
40961: "ColorSpace",
40962: "PixelXDimension",
40963: "PixelYDimension",
40964: "RelatedSoundFile",
40965: "InteroperabilityIFD",
41483: "FlashEnergy",
41484: "SpatialFrequencyResponse",
41486: "FocalPlaneXResolution",
41487: "FocalPlaneYResolution",
41488: "FocalPlaneResolutionUnit",
41492: "SubjectLocation",
41493: "ExposureIndex",
41495: "SensingMethod",
41728: "FileSource",
41729: "SceneType",
41730: "CFAPattern",
41985: "CustomRendered",
41986: "ExposureMode",
41987: "WhiteBalance",
41988: "DigitalZoomRatio",
41989: "FocalLengthIn35mmFilm",
41990: "SceneCaptureType",
41991: "GainControl",
41992: "Contrast",
41993: "Saturation",
41994: "Sharpness",
41995: "DeviceSettingDescription",
41996: "SubjectDistanceRange",
42016: "ImageUniqueID",
42032: "CameraOwnerName",
42033: "BodySerialNumber",
42034: "LensSpecification",
42035: "LensMake",
42036: "LensModel",
42037: "LensSerialNumber",
42240: "Gamma",
# MP Info
45056: "MPFVersion",
45057: "NumberOfImages",
45058: "MPEntry",
45059: "ImageUIDList",
45060: "TotalFrames",
45313: "MPIndividualNum",
45569: "PanOrientation",
45570: "PanOverlap_H",
45571: "PanOverlap_V",
45572: "BaseViewpointNum",
45573: "ConvergenceAngle",
45574: "BaselineLength",
45575: "VerticalDivergence",
45576: "AxisDistance_X",
45577: "AxisDistance_Y",
45578: "AxisDistance_Z",
45579: "YawAngle",
45580: "PitchAngle",
45581: "RollAngle",
# Adobe DNG # Adobe DNG
50706: "DNGVersion", 50706: "DNGVersion",
50707: "DNGBackwardVersion", 50707: "DNGBackwardVersion",
@ -161,7 +255,7 @@ TAGS = {
50716: "BlackLevelDeltaV", 50716: "BlackLevelDeltaV",
50717: "WhiteLevel", 50717: "WhiteLevel",
50718: "DefaultScale", 50718: "DefaultScale",
50741: "BestQualityScale", 50741: "BestQualityScale", # FIXME! Dictionary contains duplicate keys 50741
50719: "DefaultCropOrigin", 50719: "DefaultCropOrigin",
50720: "DefaultCropSize", 50720: "DefaultCropSize",
50778: "CalibrationIlluminant1", 50778: "CalibrationIlluminant1",
@ -185,7 +279,7 @@ TAGS = {
50737: "ChromaBlurRadius", 50737: "ChromaBlurRadius",
50738: "AntiAliasStrength", 50738: "AntiAliasStrength",
50740: "DNGPrivateData", 50740: "DNGPrivateData",
50741: "MakerNoteSafety", 50741: "MakerNoteSafety", # FIXME! Dictionary contains duplicate keys 50741
#ImageJ #ImageJ
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe 50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe

View File

@ -14,11 +14,11 @@
# #
# NOTE: This format cannot be automatically recognized, so the reader # NOTE: This format cannot be automatically recognized, so the reader
# is not registered for use with Image.open(). To open a WEL file, use # is not registered for use with Image.open(). To open a WAL file, use
# the WalImageFile.open() function instead. # the WalImageFile.open() function instead.
# This reader is based on the specification available from: # This reader is based on the specification available from:
# http://www.flipcode.com/tutorials/tut_q2levels.shtml # http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
# and has been tested with a few sample files found using google. # and has been tested with a few sample files found using google.
from __future__ import print_function from __future__ import print_function

View File

@ -59,7 +59,7 @@ word = _binary.i16le
def short(c, o=0): def short(c, o=0):
v = word(c, o) v = word(c, o)
if v >= 32768: if v >= 32768:
v = v - 65536 v -= 65536
return v return v
dword = _binary.i32le dword = _binary.i32le

View File

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

View File

@ -12,10 +12,9 @@
# ;-) # ;-)
VERSION = '1.1.7' # PIL version VERSION = '1.1.7' # PIL version
PILLOW_VERSION = '2.4.0' # Pillow PILLOW_VERSION = '2.5.3' # Pillow
_plugins = ['ArgImagePlugin', _plugins = ['BmpImagePlugin',
'BmpImagePlugin',
'BufrStubImagePlugin', 'BufrStubImagePlugin',
'CurImagePlugin', 'CurImagePlugin',
'DcxImagePlugin', 'DcxImagePlugin',
@ -37,6 +36,7 @@ _plugins = ['ArgImagePlugin',
'McIdasImagePlugin', 'McIdasImagePlugin',
'MicImagePlugin', 'MicImagePlugin',
'MpegImagePlugin', 'MpegImagePlugin',
'MpoImagePlugin',
'MspImagePlugin', 'MspImagePlugin',
'PalmImagePlugin', 'PalmImagePlugin',
'PcdImagePlugin', 'PcdImagePlugin',

View File

@ -3,20 +3,25 @@ import os
if bytes is str: if bytes is str:
def isStringType(t): def isStringType(t):
return isinstance(t, basestring) return isinstance(t, basestring)
def isPath(f): def isPath(f):
return isinstance(f, basestring) return isinstance(f, basestring)
else: else:
def isStringType(t): def isStringType(t):
return isinstance(t, str) return isinstance(t, str)
def isPath(f): def isPath(f):
return isinstance(f, (bytes, str)) return isinstance(f, (bytes, str))
# Checks if an object is a string, and that it points to a directory. # Checks if an object is a string, and that it points to a directory.
def isDirectory(f): def isDirectory(f):
return isPath(f) and os.path.isdir(f) return isPath(f) and os.path.isdir(f)
class deferred_error(object): class deferred_error(object):
def __init__(self, ex): def __init__(self, ex):
self.ex = ex self.ex = ex
def __getattr__(self, elt): def __getattr__(self, elt):
raise self.ex raise self.ex

View File

@ -1,17 +0,0 @@
import unittest
class PillowTests(unittest.TestCase):
"""
Can we start moving the test suite here?
"""
def test_suite_should_move_here(self):
"""
Great idea!
"""
assert True is True
if __name__ == '__main__':
unittest.main()

View File

@ -3,10 +3,10 @@ Pillow
*Python Imaging Library (Fork)* *Python Imaging Library (Fork)*
Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. Pillow is the "friendly" PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation <http://pillow.readthedocs.org/>`_, `check the changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_ and `find out how to contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_.
.. image:: https://travis-ci.org/python-imaging/Pillow.svg?branch=master .. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
:target: https://travis-ci.org/python-imaging/Pillow :target: https://travis-ci.org/python-pillow/Pillow
:alt: Travis CI build status :alt: Travis CI build status
.. image:: https://pypip.in/v/Pillow/badge.png .. image:: https://pypip.in/v/Pillow/badge.png
@ -17,7 +17,6 @@ Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Pyt
:target: https://pypi.python.org/pypi/Pillow/ :target: https://pypi.python.org/pypi/Pillow/
:alt: Number of PyPI downloads :alt: Number of PyPI downloads
.. image:: https://coveralls.io/repos/python-imaging/Pillow/badge.png?branch=master .. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
:target: https://coveralls.io/r/python-imaging/Pillow?branch=master :target: https://coveralls.io/r/python-pillow/Pillow?branch=master
The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details, and more.

View File

@ -1,5 +1,5 @@
Python SANE module V1.1 (30 Sep. 2004) Python SANE module V1.1 (30 Sep. 2004)
================================================================================
The SANE module provides an interface to the SANE scanner and frame The SANE module provides an interface to the SANE scanner and frame
grabber interface for Linux. This module was contributed by Andrew grabber interface for Linux. This module was contributed by Andrew
@ -9,11 +9,11 @@ word 'SANE' or 'sane' in the subject of your mail, otherwise it might
be classified as spam in the future. be classified as spam in the future.
To build this module, type (in the Sane directory): To build this module, type (in the Sane directory)::
python setup.py build python setup.py build
In order to install the module type: In order to install the module type::
python setup.py install python setup.py install

View File

@ -1,6 +1,5 @@
-------
Scripts Scripts
------- =======
This directory contains a number of more or less trivial utilities This directory contains a number of more or less trivial utilities
and demo programs. and demo programs.
@ -9,50 +8,50 @@ Comments and contributions are welcome.
</F> </F>
--------------------------------------------------------------------
pildriver.py (by Eric S. Raymond) pildriver.py (by Eric S. Raymond)
--------------------------------------------------------------------
A class implementing an image-processing calculator for scripts. A class implementing an image-processing calculator for scripts.
Parses lists of commnds (or, called interactively, command-line Parses lists of commnds (or, called interactively, command-line
arguments) into image loads, transformations, and saves. arguments) into image loads, transformations, and saves.
--------------------------------------------------------------------
viewer.py viewer.py
--------------------------------------------------------------------
A simple image viewer. Can display all file formats handled by A simple image viewer. Can display all file formats handled by
PIL. Transparent images are properly handled. PIL. Transparent images are properly handled.
--------------------------------------------------------------------
thresholder.py thresholder.py
--------------------------------------------------------------------
A simple utility that demonstrates how a transparent 1-bit overlay A simple utility that demonstrates how a transparent 1-bit overlay
can be used to show the current thresholding of an 8-bit image. can be used to show the current thresholding of an 8-bit image.
--------------------------------------------------------------------
enhancer.py enhancer.py
--------------------------------------------------------------------
Illustrates the ImageEnhance module. Drag the sliders to modify the Illustrates the ImageEnhance module. Drag the sliders to modify the
images. This might be very slow on some platforms, depending on the images. This might be very slow on some platforms, depending on the
Tk version. Tk version.
--------------------------------------------------------------------
painter.py painter.py
--------------------------------------------------------------------
Illustrates how a painting program could be based on PIL and Tk. Illustrates how a painting program could be based on PIL and Tk.
Press the left mouse button and drag over the image to remove the Press the left mouse button and drag over the image to remove the
colour. Some clever tricks have been used to get decent performance colour. Some clever tricks have been used to get decent performance
when updating the screen; see the sources for details. when updating the screen; see the sources for details.
--------------------------------------------------------------------
player.py player.py
--------------------------------------------------------------------
A simple image sequence player. You can use either a sequence format A simple image sequence player. You can use either a sequence format
like FLI/FLC, GIF, or ARG, or give a number of images which are like FLI/FLC, GIF, or ARG, or give a number of images which are
interpreted as frames in a sequence. All frames must have the same interpreted as frames in a sequence. All frames must have the same
size. size.
--------------------------------------------------------------------
gifmaker.py gifmaker.py
--------------------------------------------------------------------
Convert a sequence file to a GIF animation. Convert a sequence file to a GIF animation.
@ -60,20 +59,20 @@ Note that the GIF encoder provided with this release of PIL writes
uncompressed GIF files only, so the resulting animations are rather uncompressed GIF files only, so the resulting animations are rather
large compared with these created by other tools. large compared with these created by other tools.
--------------------------------------------------------------------
explode.py explode.py
--------------------------------------------------------------------
Split a sequence file into individual frames. Split a sequence file into individual frames.
--------------------------------------------------------------------
image2py.py image2py.py
--------------------------------------------------------------------
Convert an image to a Python module containing an IMAGE variable. Convert an image to a Python module containing an IMAGE variable.
Note that the module using the module must include JPEG and ZIP Note that the module using the module must include JPEG and ZIP
decoders, unless the -u option is used. decoders, unless the -u option is used.
--------------------------------------------------------------------
olesummary.py olesummary.py
--------------------------------------------------------------------
Uses the OleFileIO module to dump the summary information from an OLE Uses the OleFileIO module to dump the summary information from an OLE
structured storage file. This works with most OLE files, including structured storage file. This works with most OLE files, including

View File

@ -0,0 +1,16 @@
import base64
import os
import sys
if __name__ == "__main__":
# create font data chunk for embedding
font = "Tests/images/courB08"
print(" f._load_pilfont_data(")
print(" # %s" % os.path.basename(font))
print(" BytesIO(base64.decodestring(b'''")
base64.encode(open(font + ".pil", "rb"), sys.stdout)
print("''')), Image.open(BytesIO(base64.decodestring(b'''")
base64.encode(open(font + ".pbm", "rb"), sys.stdout)
print("'''))))")
# End of file

7
Scripts/diffcover-install.sh Executable file
View File

@ -0,0 +1,7 @@
# Fetch the remote master branch before running diff-cover on Travis CI.
# https://github.com/edx/diff-cover#troubleshooting
git fetch origin master:refs/remotes/origin/master
# CFLAGS=-O0 means build with no optimisation.
# Makes build much quicker for lxml and other dependencies.
time CFLAGS=-O0 pip install --use-wheel diff_cover

4
Scripts/diffcover-run.sh Executable file
View File

@ -0,0 +1,4 @@
coverage xml
diff-cover coverage.xml
diff-quality --violation=pyflakes
diff-quality --violation=pep8

View File

@ -104,7 +104,7 @@ while True:
except EOFError: except EOFError:
break break
ix = ix + 1 ix += 1
if html: if html:
html.write("</body>\n</html>\n") html.write("</body>\n</html>\n")

View File

@ -100,7 +100,7 @@ def makedelta(fp, sequence):
previous = im.copy() previous = im.copy()
frames = frames + 1 frames += 1
fp.write(";") fp.write(";")

View File

@ -486,7 +486,7 @@ class PILDriver:
print("Stack: " + repr(self.stack)) print("Stack: " + repr(self.stack))
top = self.top() top = self.top()
if not isinstance(top, str): if not isinstance(top, str):
continue; continue
funcname = "do_" + top funcname = "do_" + top
if not hasattr(self, funcname): if not hasattr(self, funcname):
continue continue
@ -513,9 +513,9 @@ if __name__ == '__main__':
while True: while True:
try: try:
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3:
line = input('pildriver> '); line = input('pildriver> ')
else: else:
line = raw_input('pildriver> '); line = raw_input('pildriver> ')
except EOFError: except EOFError:
print("\nPILDriver says goodbye.") print("\nPILDriver says goodbye.")
break break

View File

@ -57,7 +57,7 @@ for o, a in opt:
elif o == "-v": elif o == "-v":
verify = 1 verify = 1
elif o == "-D": elif o == "-D":
Image.DEBUG = Image.DEBUG + 1 Image.DEBUG += 1
def globfix(files): def globfix(files):
# expand wildcards where necessary # expand wildcards where necessary

View File

@ -18,25 +18,6 @@ import sys
Image.DEBUG = 0 Image.DEBUG = 0
# --------------------------------------------------------------------
# experimental: support ARG animation scripts
import ArgImagePlugin
def applet_hook(animation, images):
app = animation(animation_display, images)
app.run()
ArgImagePlugin.APPLET_HOOK = applet_hook
class AppletDisplay:
def __init__(self, ui):
self.__ui = ui
def paste(self, im, bbox):
self.__ui.image.paste(im, bbox)
def update(self):
self.__ui.update_idletasks()
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# an image animation player # an image animation player
@ -56,10 +37,6 @@ class UI(Label):
else: else:
self.image = ImageTk.PhotoImage(im) self.image = ImageTk.PhotoImage(im)
# APPLET SUPPORT (very crude, and not 100% safe)
global animation_display
animation_display = AppletDisplay(self)
Label.__init__(self, master, image=self.image, bg="black", bd=0) Label.__init__(self, master, image=self.image, bg="black", bd=0)
self.update() self.update()

View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
from PIL import Image
import sys
if sys.maxsize < 2**32:
im = Image.new('L', (999999, 999999), 0)

45
Tests/README.rst Normal file
View File

@ -0,0 +1,45 @@
Pillow Tests
============
Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``.
Depedencies
-----------
Install::
pip install coverage nose
Execution
---------
**If Pillow has been built in-place**
To run an individual test::
python Tests/test_image.py
Run all the tests from the root of the Pillow source distribution::
nosetests -vx Tests/test_*.py
Or with coverage::
coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
coverage report
coverage html
open htmlcov/index.html
**If Pillow has been installed**
To run an individual test::
./test-installed.py Tests/test_image.py
Run all the tests from the root of the Pillow source distribution::
./test-installed.py

View File

@ -1,14 +0,0 @@
Minimalistic PIL test framework.
Test scripts are named "test_xxx" and are supposed to output "ok". That's it. To run the tests::
python setup.py develop
Run the tests from the root of the Pillow source distribution:
python selftest.py
python Tests/run.py --installed
To run an individual test:
python Tests/test_image.py

View File

@ -1,23 +1,25 @@
from tester import * from helper import *
# not running this test by default. No DOS against travis. # Not running this test by default. No DOS against Travis CI.
from PIL import PyAccess from PIL import PyAccess
from PIL import Image
import time import time
def iterate_get(size, access): def iterate_get(size, access):
(w,h) = size (w, h) = size
for x in range(w): for x in range(w):
for y in range(h): for y in range(h):
access[(x,y)] access[(x, y)]
def iterate_set(size, access): def iterate_set(size, access):
(w,h) = size (w, h) = size
for x in range(w): for x in range(w):
for y in range(h): for y in range(h):
access[(x,y)] = (x %256,y%256,0) access[(x, y)] = (x % 256, y % 256, 0)
def timer(func, label, *args): def timer(func, label, *args):
iterations = 5000 iterations = 5000
@ -25,27 +27,34 @@ def timer(func, label, *args):
for x in range(iterations): for x in range(iterations):
func(*args) func(*args)
if time.time()-starttime > 10: if time.time()-starttime > 10:
print ("%s: breaking at %s iterations, %.6f per iteration"%(label, x+1, (time.time()-starttime)/(x+1.0))) print("%s: breaking at %s iterations, %.6f per iteration" % (
label, x+1, (time.time()-starttime)/(x+1.0)))
break break
if x == iterations-1: if x == iterations-1:
endtime = time.time() endtime = time.time()
print ("%s: %.4f s %.6f per iteration" %(label, endtime-starttime, (endtime-starttime)/(x+1.0))) print("%s: %.4f s %.6f per iteration" % (
label, endtime-starttime, (endtime-starttime)/(x+1.0)))
def test_direct():
im = lena()
im.load()
#im = Image.new( "RGB", (2000,2000), (1,3,2))
caccess = im.im.pixel_access(False)
access = PyAccess.new(im, False)
assert_equal(caccess[(0,0)], access[(0,0)]) class BenchCffiAccess(PillowTestCase):
print ("Size: %sx%s" % im.size) def test_direct(self):
timer(iterate_get, 'PyAccess - get', im.size, access) im = lena()
timer(iterate_set, 'PyAccess - set', im.size, access) im.load()
timer(iterate_get, 'C-api - get', im.size, caccess) # im = Image.new( "RGB", (2000, 2000), (1, 3, 2))
timer(iterate_set, 'C-api - set', im.size, caccess) caccess = im.im.pixel_access(False)
access = PyAccess.new(im, False)
self.assertEqual(caccess[(0, 0)], access[(0, 0)])
print ("Size: %sx%s" % im.size)
timer(iterate_get, 'PyAccess - get', im.size, access)
timer(iterate_set, 'PyAccess - set', im.size, access)
timer(iterate_get, 'C-api - get', im.size, caccess)
timer(iterate_set, 'C-api - set', im.size, caccess)
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -1,13 +1,14 @@
import sys import sys
sys.path.insert(0, ".") sys.path.insert(0, ".")
import tester import helper
import timeit import timeit
def bench(mode): def bench(mode):
im = tester.lena(mode) im = helper.lena(mode)
get = im.im.getpixel get = im.im.getpixel
xy = 50, 50 # position shouldn't really matter xy = 50, 50 # position shouldn't really matter
t0 = timeit.default_timer() t0 = timeit.default_timer()
for i in range(1000000): for i in range(1000000):
get(xy) get(xy)

10
Tests/check_icns_dos.py Normal file
View File

@ -0,0 +1,10 @@
# Tests potential DOS of IcnsImagePlugin with 0 length block.
# Run from anywhere that PIL is importable.
from PIL import Image
from io import BytesIO
if bytes is str:
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00')))
else:
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00', 'latin-1')))

11
Tests/check_j2k_dos.py Normal file
View File

@ -0,0 +1,11 @@
# Tests potential DOS of Jpeg2kImagePlugin with 0 length block.
# Run from anywhere that PIL is importable.
from PIL import Image
from io import BytesIO
if bytes is str:
Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang')))
else:
Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang', 'latin-1')))

View File

@ -1,222 +0,0 @@
# PyCMSTests.py
# Examples of how to use pyCMS, as well as tests to verify it works properly
# By Kevin Cazabon (kevin@cazabon.com)
# Imports
import os
from PIL import Image
from PIL import ImageCms
# import PyCMSError separately so we can catch it
PyCMSError = ImageCms.PyCMSError
#######################################################################
# Configuration:
#######################################################################
# set this to the image you want to test with
IMAGE = "c:\\temp\\test.tif"
# set this to where you want to save the output images
OUTPUTDIR = "c:\\temp\\"
# set these to two different ICC profiles, one for input, one for output
# set the corresponding mode to the proper PIL mode for that profile
INPUT_PROFILE = "c:\\temp\\profiles\\sRGB.icm"
INMODE = "RGB"
OUTPUT_PROFILE = "c:\\temp\\profiles\\genericRGB.icm"
OUTMODE = "RGB"
PROOF_PROFILE = "c:\\temp\\profiles\\monitor.icm"
# set to True to show() images, False to save them into OUTPUT_DIRECTORY
SHOW = False
# Tests you can enable/disable
TEST_error_catching = True
TEST_profileToProfile = True
TEST_profileToProfile_inPlace = True
TEST_buildTransform = True
TEST_buildTransformFromOpenProfiles = True
TEST_buildProofTransform = True
TEST_getProfileInfo = True
TEST_misc = False
#######################################################################
# helper functions
#######################################################################
def outputImage(im, funcName = None):
# save or display the image, depending on value of SHOW_IMAGES
if SHOW:
im.show()
else:
im.save(os.path.join(OUTPUTDIR, "%s.tif" %funcName))
#######################################################################
# The tests themselves
#######################################################################
if TEST_error_catching:
im = Image.open(IMAGE)
try:
#neither of these proifles exists (unless you make them), so we should
# get an error
imOut = ImageCms.profileToProfile(im, "missingProfile.icm", "cmyk.icm")
except PyCMSError as reason:
print("We caught a PyCMSError: %s\n\n" %reason)
print("error catching test completed successfully (if you see the message \
above that we caught the error).")
if TEST_profileToProfile:
# open the image file using the standard PIL function Image.open()
im = Image.open(IMAGE)
# send the image, input/output profiles, and rendering intent to
# ImageCms.profileToProfile()
imOut = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \
outputMode = OUTMODE)
# now that the image is converted, save or display it
outputImage(imOut, "profileToProfile")
print("profileToProfile test completed successfully.")
if TEST_profileToProfile_inPlace:
# we'll do the same test as profileToProfile, but modify im in place
# instead of getting a new image returned to us
im = Image.open(IMAGE)
# send the image to ImageCms.profileToProfile(), specifying inPlace = True
result = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \
outputMode = OUTMODE, inPlace = True)
# now that the image is converted, save or display it
if result is None:
# this is the normal result when modifying in-place
outputImage(im, "profileToProfile_inPlace")
else:
# something failed...
print("profileToProfile in-place failed: %s" %result)
print("profileToProfile in-place test completed successfully.")
if TEST_buildTransform:
# make a transform using the input and output profile path strings
transform = ImageCms.buildTransform(INPUT_PROFILE, OUTPUT_PROFILE, INMODE, \
OUTMODE)
# now, use the trnsform to convert a couple images
im = Image.open(IMAGE)
# transform im normally
im2 = ImageCms.applyTransform(im, transform)
outputImage(im2, "buildTransform")
# then transform it again using the same transform, this time in-place.
result = ImageCms.applyTransform(im, transform, inPlace = True)
outputImage(im, "buildTransform_inPlace")
print("buildTransform test completed successfully.")
# and, to clean up a bit, delete the transform
# this should call the C destructor for the transform structure.
# Python should also do this automatically when it goes out of scope.
del(transform)
if TEST_buildTransformFromOpenProfiles:
# we'll actually test a couple profile open/creation functions here too
# first, get a handle to an input profile, in this case we'll create
# an sRGB profile on the fly:
inputProfile = ImageCms.createProfile("sRGB")
# then, get a handle to the output profile
outputProfile = ImageCms.getOpenProfile(OUTPUT_PROFILE)
# make a transform from these
transform = ImageCms.buildTransformFromOpenProfiles(inputProfile, \
outputProfile, INMODE, OUTMODE)
# now, use the trnsform to convert a couple images
im = Image.open(IMAGE)
# transform im normally
im2 = ImageCms.applyTransform(im, transform)
outputImage(im2, "buildTransformFromOpenProfiles")
# then do it again using the same transform, this time in-place.
result = ImageCms.applyTransform(im, transform, inPlace = True)
outputImage(im, "buildTransformFromOpenProfiles_inPlace")
print("buildTransformFromOpenProfiles test completed successfully.")
# and, to clean up a bit, delete the transform
# this should call the C destructor for the each item.
# Python should also do this automatically when it goes out of scope.
del(inputProfile)
del(outputProfile)
del(transform)
if TEST_buildProofTransform:
# make a transform using the input and output and proof profile path
# strings
# images converted with this transform will simulate the appearance
# of the output device while actually being displayed/proofed on the
# proof device. This usually means a monitor, but can also mean
# other proof-printers like dye-sub, etc.
transform = ImageCms.buildProofTransform(INPUT_PROFILE, OUTPUT_PROFILE, \
PROOF_PROFILE, INMODE, OUTMODE)
# now, use the trnsform to convert a couple images
im = Image.open(IMAGE)
# transform im normally
im2 = ImageCms.applyTransform(im, transform)
outputImage(im2, "buildProofTransform")
# then transform it again using the same transform, this time in-place.
result = ImageCms.applyTransform(im, transform, inPlace = True)
outputImage(im, "buildProofTransform_inPlace")
print("buildProofTransform test completed successfully.")
# and, to clean up a bit, delete the transform
# this should call the C destructor for the transform structure.
# Python should also do this automatically when it goes out of scope.
del(transform)
if TEST_getProfileInfo:
# get a profile handle
profile = ImageCms.getOpenProfile(INPUT_PROFILE)
# lets print some info about our input profile:
print("Profile name (retrieved from profile string path name): %s" %ImageCms.getProfileName(INPUT_PROFILE))
# or, you could do the same thing using a profile handle as the arg
print("Profile name (retrieved from profile handle): %s" %ImageCms.getProfileName(profile))
# now lets get the embedded "info" tag contents
# once again, you can use a path to a profile, or a profile handle
print("Profile info (retrieved from profile handle): %s" %ImageCms.getProfileInfo(profile))
# and what's the default intent of this profile?
print("The default intent is (this will be an integer): %d" %(ImageCms.getDefaultIntent(profile)))
# Hmmmm... but does this profile support INTENT_ABSOLUTE_COLORIMETRIC?
print("Does it support INTENT_ABSOLUTE_COLORIMETRIC?: (1 is yes, -1 is no): %s" \
%ImageCms.isIntentSupported(profile, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, \
ImageCms.DIRECTION_INPUT))
print("getProfileInfo test completed successfully.")
if TEST_misc:
# test the versions, about, and copyright functions
print("Versions: %s" %str(ImageCms.versions()))
print("About:\n\n%s" %ImageCms.about())
print("Copyright:\n\n%s" %ImageCms.copyright())
print("misc test completed successfully.")

234
Tests/helper.py Normal file
View File

@ -0,0 +1,234 @@
"""
Helper functions.
"""
from __future__ import print_function
import sys
import tempfile
import os
if sys.version_info[:2] <= (2, 6):
import unittest2 as unittest
else:
import unittest
class PillowTestCase(unittest.TestCase):
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
# holds last result object passed to run method:
self.currentResult = None
# Nicer output for --verbose
def __str__(self):
return self.__class__.__name__ + "." + self._testMethodName
def run(self, result=None):
self.currentResult = result # remember result for use later
unittest.TestCase.run(self, result) # call superclass run method
def delete_tempfile(self, path):
try:
ok = self.currentResult.wasSuccessful()
except AttributeError: # for nosetests
proxy = self.currentResult
ok = (len(proxy.errors) + len(proxy.failures) == 0)
if ok:
# only clean out tempfiles if test passed
try:
os.remove(path)
except OSError:
pass # report?
else:
print("=== orphaned temp file: %s" % path)
def assert_almost_equal(self, a, b, msg=None, eps=1e-6):
self.assertLess(
abs(a-b), eps,
msg or "got %r, expected %r" % (a, b))
def assert_deep_equal(self, a, b, msg=None):
try:
self.assertEqual(
len(a), len(b),
msg or "got length %s, expected %s" % (len(a), len(b)))
self.assertTrue(
all([x == y for x, y in zip(a, b)]),
msg or "got %s, expected %s" % (a, b))
except:
self.assertEqual(a, b, msg)
def assert_image(self, im, mode, size, msg=None):
if mode is not None:
self.assertEqual(
im.mode, mode,
msg or "got mode %r, expected %r" % (im.mode, mode))
if size is not None:
self.assertEqual(
im.size, size,
msg or "got size %r, expected %r" % (im.size, size))
def assert_image_equal(self, a, b, msg=None):
self.assertEqual(
a.mode, b.mode,
msg or "got mode %r, expected %r" % (a.mode, b.mode))
self.assertEqual(
a.size, b.size,
msg or "got size %r, expected %r" % (a.size, b.size))
if a.tobytes() != b.tobytes():
self.fail(msg or "got different content")
def assert_image_similar(self, a, b, epsilon, msg=None):
epsilon = float(epsilon)
self.assertEqual(
a.mode, b.mode,
msg or "got mode %r, expected %r" % (a.mode, b.mode))
self.assertEqual(
a.size, b.size,
msg or "got size %r, expected %r" % (a.size, b.size))
diff = 0
try:
ord(b'0')
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
diff += abs(ord(abyte)-ord(bbyte))
except:
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
diff += abs(abyte-bbyte)
ave_diff = float(diff)/(a.size[0]*a.size[1])
self.assertGreaterEqual(
epsilon, ave_diff,
(msg or '') + " average pixel value difference %.4f > epsilon %.4f" % (
ave_diff, epsilon))
def assert_warning(self, warn_class, func):
import warnings
result = None
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
# Hopefully trigger a warning.
result = func()
# Verify some things.
self.assertGreaterEqual(len(w), 1)
found = False
for v in w:
if issubclass(v.category, warn_class):
found = True
break
self.assertTrue(found)
return result
def skipKnownBadTest(self, msg=None, platform=None,
travis=None, interpreter=None):
# Skip if platform/travis matches, and
# PILLOW_RUN_KNOWN_BAD is not true in the environment.
if bool(os.environ.get('PILLOW_RUN_KNOWN_BAD', False)):
print (os.environ.get('PILLOW_RUN_KNOWN_BAD', False))
return
skip = True
if platform is not None:
skip = sys.platform.startswith(platform)
if travis is not None:
skip = skip and (travis == bool(os.environ.get('TRAVIS', False)))
if interpreter is not None:
skip = skip and (interpreter == 'pypy' and hasattr(sys, 'pypy_version_info'))
if skip:
self.skipTest(msg or "Known Bad Test")
def tempfile(self, template):
assert template[:5] in ("temp.", "temp_")
(fd, path) = tempfile.mkstemp(template[4:], template[:4])
os.close(fd)
self.addCleanup(self.delete_tempfile, path)
return path
def open_withImagemagick(self, f):
if not imagemagick_available():
raise IOError()
outfile = self.tempfile("temp.png")
if command_succeeds(['convert', f, outfile]):
from PIL import Image
return Image.open(outfile)
raise IOError()
# helpers
import sys
py3 = (sys.version_info >= (3, 0))
def fromstring(data):
from io import BytesIO
from PIL import Image
return Image.open(BytesIO(data))
def tostring(im, format, **options):
from io import BytesIO
out = BytesIO()
im.save(out, format, **options)
return out.getvalue()
def lena(mode="RGB", cache={}):
from PIL import Image
im = None
# FIXME: Implement caching to reduce reading from disk but so an original
# copy is returned each time and the cached image isn't modified by tests
# (for fast, isolated, repeatable tests).
# im = cache.get(mode)
if im is None:
if mode == "RGB":
im = Image.open("Tests/images/lena.ppm")
elif mode == "F":
im = lena("L").convert(mode)
elif mode[:4] == "I;16":
im = lena("I").convert(mode)
else:
im = lena("RGB").convert(mode)
# cache[mode] = im
return im
def command_succeeds(cmd):
"""
Runs the command, which must be a list of strings. Returns True if the
command succeeds, or False if an OSError was raised by subprocess.Popen.
"""
import os
import subprocess
with open(os.devnull, 'w') as f:
try:
subprocess.Popen(cmd, stdout=f, stderr=subprocess.STDOUT).wait()
except OSError:
return False
return True
def djpeg_available():
return command_succeeds(['djpeg', '--help'])
def cjpeg_available():
return command_succeeds(['cjpeg', '--help'])
def netpbm_available():
return (command_succeeds(["ppmquant", "--help"]) and
command_succeeds(["ppmtogif", "--help"]))
def imagemagick_available():
return command_succeeds(['convert', '-version'])
# End of file

Binary file not shown.

Binary file not shown.

BIN
Tests/images/corner.lut Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

BIN
Tests/images/dilation4.lut Normal file

Binary file not shown.

BIN
Tests/images/dilation8.lut Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
Tests/images/edge.lut Normal file

Binary file not shown.

BIN
Tests/images/erosion4.lut Normal file

Binary file not shown.

BIN
Tests/images/erosion8.lut Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
Tests/images/frozenpond.mpo Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

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