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:
if 0:
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:
irc: "chat.freenode.net#pil"
env: MAX_CONCURRENCY=4
python:
- "pypy"
- "pypy3"
- 2.6
- 2.7
- "2.7_with_system_site_packages" # For PyQt4
- 3.2
- 3.3
- 3.4
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 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
- pushd depends && ./install_webp.sh && popd
@ -25,21 +32,31 @@ install:
script:
- coverage erase
- 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)
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi
- 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
- coverage run --append --include=PIL/* selftest.py
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
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
- coveralls
- coveralls-merge coverage.c.json
- pip install pep8 pyflakes
- pep8 PIL/*.py
- pyflakes PIL/*.py
- pep8 Tests/*.py
- pyflakes Tests/*.py
- pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py
- pyflakes PIL/*.py | tee >(wc -l)
- pyflakes Tests/*.py | tee >(wc -l)
# Coverage and quality reports on just the latest diff.
# (Installation is very slow on Py3, so just do it for Py2.)
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi

View File

@ -1,38 +1,194 @@
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
[hugovk]
- Use libtiff to write any compressed tiff files
[wiredfool]
- Support for pickling Image objects
[hugovk]
- Fixed resolution handling for EPS thumbnails
[eliempje]
- Fixed rendering of some binary EPS files (Issue #302)
[eliempje]
[eliempje]
- Rename variables not to use built-in function names
[hugovk]
- Ignore junk JPEG markers
- Ignore junk JPEG markers
[hugovk]
- Change default interpolation for Image.thumbnail to Image.ANTIALIAS
[hugovk]
- Add tests and fixes for saving PDFs
[hugovk]
- Remove transparency resource after P->RGBA conversion
[hugovk]
- Clean up preprocessor cruft for Windows
[CounterPillow]
@ -42,13 +198,13 @@ Changelog (Pillow)
- Added Image.close, context manager support.
[wiredfool]
- Added support for 16 bit PGM files.
- Added support for 16 bit PGM files.
[wiredfool]
- Updated OleFileIO to version 0.30 from upstream
[hugovk]
- Added support for additional TIFF floating point format
- Added support for additional TIFF floating point format
[Hijackal]
- Have the tempfile use a suffix with a dot
@ -78,7 +234,7 @@ Changelog (Pillow)
- Added support for JPEG 2000
[al45tair]
- Add more detailed error messages to Image.py
- Add more detailed error messages to Image.py
[larsmans]
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
@ -106,7 +262,7 @@ Changelog (Pillow)
[wiredfool]
- 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
[d-schmidt]
@ -116,7 +272,7 @@ Changelog (Pillow)
- Fixed DOS with invalid palette size or invalid image size in BMP file
[wiredfool]
- Added support for BMP version 4 and 5
[eddwardo, wiredfool]
@ -149,7 +305,13 @@ Changelog (Pillow)
- Prefer homebrew freetype over X11 freetype (but still allow both)
[dmckeone]
2.3.2 (2014-08-13)
------------------
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport)
[Andrew Drake]
2.3.1 (2014-03-14)
------------------
@ -274,7 +436,7 @@ Changelog (Pillow)
[nikmolnar]
- Fix for encoding of b_whitespace, similar to closed issue #272
[mhogg]
[mhogg]
- Fix #273: Add numpy array interface support for 16 and 32 bit integer modes
[cgohlke]
@ -431,10 +593,14 @@ Changelog (Pillow)
2.0.0 (2013-03-15)
------------------
.. Note:: Special thanks to Christoph Gohlke and Eric Soroos for assisting with a pre-PyCon 2013 release!
- Many other bug fixes and enhancements by many other people.
- Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.)
[fluggo]
- Add PyPy support (experimental, please see: https://github.com/python-imaging/Pillow/issues/67)
- Add PyPy support (experimental, please see: https://github.com/python-pillow/Pillow/issues/67)
- Add WebP support.
[lqs]
@ -454,10 +620,6 @@ Changelog (Pillow)
- Added support for PNG images with transparency palette.
[d-schmidt]
- Many other bug fixes and enhancements by many other people (see commit log and/or docs/CONTRIBUTORS.txt).
- Special thanks to Christoph Gohlke and Eric Soroos for rallying around the effort to get a release out for PyCon 2013.
1.7.8 (2012-11-01)
------------------
@ -530,44 +692,55 @@ Changelog (Pillow)
[elro]
- Doc fixes
[aclark]
1.5 (11/28/2010)
----------------
- Module and package fixes
[aclark]
1.4 (11/28/2010)
----------------
- Doc fixes
[aclark]
1.3 (11/28/2010)
----------------
- Add support for /lib64 and /usr/lib64 library directories on Linux
[aclark]
- Doc fixes
[aclark]
1.2 (08/02/2010)
----------------
- On OS X also check for freetype2 in the X11 path [jezdez]
- Doc fixes [aclark]
- On OS X also check for freetype2 in the X11 path
[jezdez]
- Doc fixes
[aclark]
1.1 (07/31/2010)
----------------
- Removed setuptools_hg requirement
[aclark]
- Doc fixes
[aclark]
1.0 (07/30/2010)
----------------
- Forked PIL based on Hanno Schlichting's re-packaging
(http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz)
- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required.
- Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
[aclark]
- Remove support for importing from the standard namespace
.. Note:: What follows is the original PIL 1.1.7 CHANGES file contents
.. Note:: What follows is the original PIL 1.1.7 CHANGES
::

26
CONTRIBUTING.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,11 +31,18 @@ from PIL import VERSION, PILLOW_VERSION, _plugins
import warnings
class DecompressionBombWarning(RuntimeWarning):
pass
class _imaging_not_installed:
# module placeholder
def __getattr__(self, id):
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:
# give Tk a chance to set up the environment, in case we're
# using an _imaging module linked against libtcl/libtk (use
@ -213,6 +220,7 @@ _MODEINFO = {
"CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
"YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
"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
# 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
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)):
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
except TypeError:
pass
@ -532,7 +540,7 @@ class Image:
try:
self.fp.close()
except Exception as msg:
if Image.DEBUG:
if DEBUG:
print ("Error closing: %s" % msg)
# Instead of simply setting to None, we're setting up a
@ -547,7 +555,6 @@ class Image:
self.readonly = 0
def _dump(self, file=None, format=None):
import os
import tempfile
suffix = ''
if format:
@ -566,6 +573,8 @@ class Image:
return file
def __eq__(self, other):
if self.__class__.__name__ != other.__class__.__name__:
return False
a = (self.mode == other.mode)
b = (self.size == other.size)
c = (self.getpalette() == other.getpalette())
@ -1529,7 +1538,7 @@ class Image:
clockwise around its centre.
: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),
:py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
environment), or :py:attr:`PIL.Image.BICUBIC`
@ -1550,7 +1559,6 @@ class Image:
math.cos(angle), math.sin(angle), 0.0,
-math.sin(angle), math.cos(angle), 0.0
]
def transform(x, y, matrix=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
"""
"Load image from bytes or buffer"
# may pass tuple instead of argument list
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")
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"):
"""
Opens and identifies the given image file.
@ -2210,7 +2231,9 @@ def open(fp, mode="r"):
factory, accept = OPEN[i]
if not accept or accept(prefix):
fp.seek(0)
return factory(fp, filename)
im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError):
# import traceback
# traceback.print_exc()
@ -2223,7 +2246,9 @@ def open(fp, mode="r"):
factory, accept = OPEN[i]
if not accept or accept(prefix):
fp.seek(0)
return factory(fp, filename)
im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError):
# import traceback
# traceback.print_exc()

View File

@ -1,19 +1,19 @@
#
# The Python Imaging Library.
# $Id$
#
# optional color managment support, based on Kevin Cazabon's PyCMS
# library.
#
# History:
# 2009-03-08 fl Added to PIL.
#
# Copyright (C) 2002-2003 Kevin Cazabon
# Copyright (c) 2009 by Fredrik Lundh
#
# See the README file for information on usage and redistribution. See
# below for the original description.
#
## The Python Imaging Library.
## $Id$
## Optional color managment support, based on Kevin Cazabon's PyCMS
## library.
## History:
## 2009-03-08 fl Added to PIL.
## Copyright (C) 2002-2003 Kevin Cazabon
## 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.
from __future__ import print_function
@ -66,7 +66,8 @@ pyCMS
Added try/except statements arount type() checks of
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.
Additional fixes in DLL, see DLL code for details.
@ -88,9 +89,9 @@ try:
from PIL import _imagingcms
except ImportError as ex:
# Allow error import for doc purposes, but error out when accessing
# anything in core.
from _util import deferred_error
_imagingcms = deferred_error(ex)
# anything in core.
from _util import import_err
_imagingcms = import_err(ex)
from PIL._util import isStringType
core = _imagingcms
@ -113,22 +114,24 @@ DIRECTION_PROOF = 2
FLAGS = {
"MATRIXINPUT": 1,
"MATRIXOUTPUT": 2,
"MATRIXONLY": (1|2),
"NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
"NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use)
"GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
"NOTCACHE": 64, # Inhibit 1-pixel cache
"MATRIXONLY": (1 | 2),
"NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
# Don't create prelinearization tables on precalculated transforms
# (internal use):
"NOPRELINEARIZATION": 16,
"GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
"NOTCACHE": 64, # Inhibit 1-pixel cache
"NOTPRECALC": 256,
"NULLTRANSFORM": 512, # Don't transform anyway
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces
"NULLTRANSFORM": 512, # Don't transform anyway
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces
"WHITEBLACKCOMPENSATION": 8192,
"BLACKPOINTCOMPENSATION": 8192,
"GAMUTCHECK": 4096, # Out of Gamut alarm
"SOFTPROOFING": 16384, # Do softproofing
"PRESERVEBLACK": 32768, # Black preservation
"NODEFAULTRESOURCEDEF": 16777216, # CRD special
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints
"GAMUTCHECK": 4096, # Out of Gamut alarm
"SOFTPROOFING": 16384, # Do softproofing
"PRESERVEBLACK": 32768, # Black preservation
"NODEFAULTRESOURCEDEF": 16777216, # CRD special
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints
}
_MAX_FLAG = 0
@ -136,6 +139,7 @@ for flag in FLAGS.values():
if isinstance(flag, int):
_MAX_FLAG = _MAX_FLAG | flag
# --------------------------------------------------------------------.
# Experimental PIL-level API
# --------------------------------------------------------------------.
@ -146,51 +150,71 @@ for flag in FLAGS.values():
class ImageCmsProfile:
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):
self._set(core.profile_open(profile), profile)
elif hasattr(profile, "read"):
self._set(core.profile_frombytes(profile.read()))
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):
self.profile = profile
self.filename = filename
if profile:
self.product_name = None #profile.product_name
self.product_info = None #profile.product_info
self.product_name = None # profile.product_name
self.product_info = None # profile.product_info
else:
self.product_name = 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):
"""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,
intent=INTENT_PERCEPTUAL,
proof=None, proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
intent=INTENT_PERCEPTUAL, proof=None,
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
if proof is None:
self.transform = core.buildTransform(
input.profile, output.profile,
input_mode, output_mode,
intent,
flags
)
)
else:
self.transform = core.buildProofTransform(
input.profile, output.profile, proof.profile,
input_mode, output_mode,
intent, proof_intent,
flags
)
)
# Note: inputMode and outputMode are for pyCMS compatibility only
self.input_mode = self.inputMode = input_mode
self.output_mode = self.outputMode = output_mode
self.output_profile = output
def point(self, im):
return self.apply(im)
@ -198,21 +222,24 @@ class ImageCmsTransform(Image.ImagePointHandler):
im.load()
if imOut is 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
def apply_in_place(self, im):
im.load()
if im.mode != self.output_mode:
raise ValueError("mode mismatch") # wrong output mode
result = self.transform.apply(im.im.id, im.im.id)
raise ValueError("mode mismatch") # wrong output mode
self.transform.apply(im.im.id, im.im.id)
im.info['icc_profile'] = self.output_profile.tobytes()
return im
def get_display_profile(handle=None):
""" (experimental) Fetches the profile for the current display device.
:returns: None if the profile is not known.
"""
import sys
if sys.platform == "win32":
from PIL import ImageWin
@ -229,15 +256,21 @@ def get_display_profile(handle=None):
profile = get()
return ImageCmsProfile(profile)
# --------------------------------------------------------------------.
# pyCMS compatible layer
# --------------------------------------------------------------------.
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
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
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
profile must handle CMYK data.
:param im: An open PIL image object (i.e. Image.new(...) or Image.open(...), etc.)
: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 im: An open PIL image object (i.e. Image.new(...) or
Image.open(...), etc.)
: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
profile you wish to use for this image, or a profile object
:param renderingIntent: Integer (0-3) specifying the rendering intent you wish
to use for the transform
:param renderingIntent: Integer (0-3) specifying the rendering intent you
wish to use for the transform
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
see the pyCMS documentation for details on rendering intents and what they do.
:param outputMode: A valid PIL mode for the output image (i.e. "RGB", "CMYK",
etc.). Note: if rendering the image "inPlace", outputMode MUST be the
same mode as the input, or omitted completely. If omitted, the outputMode
will be the same as the mode of the input image (im.mode)
: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.
see the pyCMS documentation for details on rendering intents and what
they do.
:param outputMode: A valid PIL mode for the output image (i.e. "RGB",
"CMYK", etc.). Note: if rendering the image "inPlace", outputMode
MUST be the same mode as the input, or omitted completely. If
omitted, the outputMode will be the same as the mode of the input
image (im.mode)
: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
: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:
"""
if outputMode is None:
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")
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:
if not isinstance(inputProfile, ImageCmsProfile):
@ -300,8 +338,9 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER
if not isinstance(outputProfile, ImageCmsProfile):
outputProfile = ImageCmsProfile(outputProfile)
transform = ImageCmsTransform(
inputProfile, outputProfile, im.mode, outputMode, renderingIntent, flags=flags
)
inputProfile, outputProfile, im.mode, outputMode,
renderingIntent, flags=flags
)
if inPlace:
transform.apply_in_place(im)
imOut = None
@ -323,8 +362,8 @@ def getOpenProfile(profileFilename):
If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
will be raised.
:param profileFilename: String, as a valid filename path to the ICC profile you
wish to open, or a file-like object.
:param profileFilename: String, as a valid filename path to the ICC profile
you wish to open, or a file-like object.
:returns: A CmsProfile class object.
:exception PyCMSError:
"""
@ -334,7 +373,10 @@ def getOpenProfile(profileFilename):
except (IOError, TypeError, ValueError) as 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
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
time that would be of use, or would even work).
:param inputProfile: String, as a valid filename path to the ICC input profile
you wish to use for this transform, or a profile object
:param inputProfile: String, as a valid filename path to the ICC input
profile you wish to use for this transform, or a profile object
:param outputProfile: String, as a valid filename path to the ICC output
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
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param outMode: String, as a valid PIL mode that the appropriate profile also
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param inMode: String, as a valid PIL mode that the appropriate profile
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param outMode: String, as a valid PIL mode that the appropriate profile
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param renderingIntent: Integer (0-3) specifying the rendering intent you
wish to use for the transform
@ -383,28 +425,37 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
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
:returns: A CmsTransform class object.
: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")
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:
if not isinstance(inputProfile, ImageCmsProfile):
inputProfile = ImageCmsProfile(inputProfile)
if not isinstance(outputProfile, ImageCmsProfile):
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:
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
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
device, you may obtain marginal results.
:param inputProfile: String, as a valid filename path to the ICC input profile
you wish to use for this transform, or a profile object
:param inputProfile: String, as a valid filename path to the ICC input
profile you wish to use for this transform, or a profile object
:param outputProfile: String, as a valid filename path to the ICC output
(monitor, usually) profile you wish to use for this transform, or a
profile object
:param proofProfile: String, as a valid filename path to the ICC proof 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
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param outMode: String, as a valid PIL mode that the appropriate profile also
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param proofProfile: String, as a valid filename path to the ICC proof
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 supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param outMode: String, as a valid PIL mode that the appropriate profile
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
:param renderingIntent: Integer (0-3) specifying the rendering intent you
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_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
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_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
:returns: A CmsTransform class object.
: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")
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:
if not isinstance(inputProfile, ImageCmsProfile):
@ -490,13 +544,16 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
outputProfile = ImageCmsProfile(outputProfile)
if not isinstance(proofProfile, ImageCmsProfile):
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:
raise PyCMSError(v)
buildTransformFromOpenProfiles = buildTransform
buildProofTransformFromOpenProfiles = buildProofTransform
def applyTransform(im, transform, inPlace=0):
"""
(pyCMS) Applies a transform to a given image.
@ -514,8 +571,8 @@ def applyTransform(im, transform, inPlace=0):
is raised.
This function applies a pre-calculated transform (from
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) to an
image. The transform can be used for multiple images, saving
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
to an image. The transform can be used for multiple images, saving
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
@ -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
supported by the transform.
:param transform: A valid CmsTransform class object
:param inPlace: Bool (1 == True, 0 or None == False). If True, im is modified
in place and None is returned, if False, a new Image object with the
transform applied is returned (and im is not changed). The default is False.
:returns: Either None, or a new PIL Image object, depending on the value of inPlace
:param inPlace: Bool (1 == True, 0 or None == False). If True, im is
modified in place and None is returned, if False, a new Image object
with the transform applied is returned (and im is not changed). The
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:
"""
@ -546,6 +605,7 @@ def applyTransform(im, transform, inPlace=0):
return imOut
def createProfile(colorSpace, colorTemp=-1):
"""
(pyCMS) Creates a profile.
@ -562,36 +622,42 @@ def createProfile(colorSpace, colorTemp=-1):
ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
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.
: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
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB profiles,
and is ignored for XYZ and sRGB.
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
profiles, and is ignored for XYZ and sRGB.
:returns: A CmsProfile class object
:exception PyCMSError:
"""
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":
try:
colorTemp = float(colorTemp)
except:
raise PyCMSError("Color temperature must be numeric, \"%s\" not valid" % colorTemp)
raise PyCMSError(
"Color temperature must be numeric, \"%s\" not valid"
% colorTemp)
try:
return core.createProfile(colorSpace, colorTemp)
except (TypeError, ValueError) as v:
raise PyCMSError(v)
def getProfileName(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
name tag, a PyCMSError is raised.
@ -600,10 +666,10 @@ def getProfileName(profile):
profile was originally created. Sometimes this tag also contains
additional information supplied by the creator.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
of an ICC profile.
:returns: A string containing the internal name of the profile as stored in an
ICC tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the
filename of an ICC profile.
:returns: A string containing the internal name of the profile as stored
in an ICC tag.
:exception PyCMSError:
"""
@ -612,14 +678,14 @@ def getProfileName(profile):
if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile)
# do it in python, not c.
# // name was "%s - %s" (model, manufacturer) || Description ,
# // but if the Model and Manufacturer were the same or the model
# // name was "%s - %s" (model, manufacturer) || Description ,
# // but if the Model and Manufacturer were the same or the model
# // was long, Just the model, in 1.x
model = profile.profile.product_model
manufacturer = profile.profile.product_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:
return model + "\n"
return "%s - %s\n" % (model, manufacturer)
@ -627,6 +693,7 @@ def getProfileName(profile):
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
def getProfileInfo(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
was created, as supplied by the creator.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC
tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the
filename of an ICC profile.
:returns: A string containing the internal profile information stored in
an ICC tag.
:exception PyCMSError:
"""
try:
if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile)
# 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
description = profile.profile.product_description
cpright = profile.profile.product_copyright
@ -660,7 +728,7 @@ def getProfileInfo(profile):
for elt in (description, cpright):
if 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:
raise PyCMSError(v)
@ -677,12 +745,12 @@ def getProfileCopyright(profile):
is raised
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
of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC
tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the
filename of an ICC profile.
:returns: A string containing the internal profile information stored in
an ICC tag.
:exception PyCMSError:
"""
try:
@ -693,6 +761,7 @@ def getProfileCopyright(profile):
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
def getProfileManufacturer(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,
a PyCMSError is raised.
If an error occurs while trying to obtain the manufacturer tag, a PyCMSError
is raised
If an error occurs while trying to obtain the manufacturer tag, a
PyCMSError is raised
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
of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC
tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the
filename of an ICC profile.
:returns: A string containing the internal profile information stored in
an ICC tag.
:exception PyCMSError:
"""
try:
@ -720,23 +789,24 @@ def getProfileManufacturer(profile):
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
def getProfileModel(profile):
"""
(pyCMS) Gets the model for the given 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 model tag, a PyCMSError
is raised
Use this function to obtain the information stored in the profile's
model tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC
tag.
model tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the
filename of an ICC profile.
:returns: A string containing the internal profile information stored in
an ICC tag.
:exception PyCMSError:
"""
@ -748,6 +818,7 @@ def getProfileModel(profile):
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
def getProfileDescription(profile):
"""
(pyCMS) Gets the description for the given profile.
@ -759,12 +830,12 @@ def getProfileDescription(profile):
is raised
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
of an ICC profile.
:returns: A string containing the internal profile information stored in an ICC
tag.
:param profile: EITHER a valid CmsProfile object, OR a string of the
filename of an ICC profile.
:returns: A string containing the internal profile information stored in an
ICC tag.
:exception PyCMSError:
"""
@ -793,16 +864,18 @@ def getDefaultIntent(profile):
If you wish to use a different intent than returned, use
ImageCms.isIntentSupported() to verify it will work first.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
of an ICC profile.
:returns: Integer 0-3 specifying the default rendering intent for this profile.
:param profile: EITHER a valid CmsProfile object, OR a string of the
filename of an ICC profile.
:returns: Integer 0-3 specifying the default rendering intent for this
profile.
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
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:
"""
@ -813,6 +886,7 @@ def getDefaultIntent(profile):
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
def isIntentSupported(profile, intent, direction):
"""
(pyCMS) Checks if a given intent is supported.
@ -822,23 +896,24 @@ def isIntentSupported(profile, intent, direction):
input/output/proof profile as you desire.
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
to create a transform with them (using this function), or catch the
potential PyCMSError that will occur if they don't support the modes
you select.
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
of an ICC profile.
:param intent: Integer (0-3) specifying the rendering intent you wish to use
with this profile
:param profile: EITHER a valid CmsProfile object, OR a string of the
filename of an ICC profile.
:param intent: Integer (0-3) specifying the rendering intent you wish to
use with this profile
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
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,
output, or proof
@ -862,15 +937,17 @@ def isIntentSupported(profile, intent, direction):
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
def versions():
"""
(pyCMS) Fetches versions.
"""
import sys
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
print(__doc__)
for f in dir(pyCMS):
print("="*80)
print("%s" %f)
for f in dir(ImageCms):
doc = None
try:
exec ("doc = ImageCms.%s.__doc__" %(f))
exec("doc = %s.__doc__" % (f))
if "pyCMS" in doc:
# so we don't get the __doc__ string for imported modules
print("=" * 80)
print("%s" % f)
print(doc)
except AttributeError:
except (AttributeError, TypeError):
pass
# End of file

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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)
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.
@ -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.
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
# -*- coding: latin-1 -*-
"""
OleFileIO_PL:
Module to read Microsoft OLE2 files (also called Structured Storage or
Microsoft Compound Document File Format), such as Microsoft Office
documents, Image Composer and FlashPix files, Outlook messages, ...
This version is compatible with Python 2.6+ and 3.x
## OleFileIO_PL:
## Module to read Microsoft OLE2 files (also called Structured Storage or
## Microsoft Compound Document File Format), such as Microsoft Office
## documents, Image Composer and FlashPix files, Outlook messages, ...
## 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
See: http://www.pythonware.com/products/pil/index.htm
## Improved version of the OleFileIO module from PIL library v1.1.6
## See: http://www.pythonware.com/products/pil/index.htm
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
## The Python Imaging Library (PIL) is
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
# This import enables print() as a function rather than a keyword
@ -370,8 +371,9 @@ for key in list(vars().keys()):
def isOleFile (filename):
"""
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')
header = f.read(len(MAGIC))
@ -397,8 +399,8 @@ def i16(c, o = 0):
"""
Converts a 2-bytes (16 bits) string to an integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
:param c: string containing bytes to convert
:param o: offset of bytes to convert in string
"""
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.
c: string containing bytes to convert
o: offset of bytes to convert in string
:param c: string containing bytes to convert
: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))
## # [PL]: added int() because "<<" gives long int since Python 2.4
@ -419,7 +421,8 @@ def i32(c, o = 0):
def _clsid(clsid):
"""
Converts a CLSID to a human-readable string.
clsid: string of length 16.
:param clsid: string of length 16.
"""
assert len(clsid) == 16
# 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)
s: UTF-16LE unicode string to convert to Latin-1
errors: 'replace', 'ignore' or 'strict'.
:param s: UTF-16LE unicode string to convert to Latin-1
:param errors: 'replace', 'ignore' or 'strict'.
"""
#TODO: test if it OleFileIO works with Unicode strings, instead of
# converting to Latin-1.
@ -650,14 +653,14 @@ class _OleStream(io.BytesIO):
"""
Constructor for _OleStream class.
fp : file object, the OLE container or the MiniFAT stream
sect : sector index of first sector in the stream
size : total size of the stream
offset : offset in bytes for the first FAT or MiniFAT sector
sectorsize: size of one sector
fat : array/list of sector indexes (FAT or MiniFAT)
filesize : size of OLE file (for debugging)
return : a BytesIO instance containing the OLE stream
:param fp : file object, the OLE container or the MiniFAT stream
:param sect : sector index of first sector in the stream
:param size : total size of the stream
:param offset : offset in bytes for the first FAT or MiniFAT sector
:param sectorsize: size of one sector
:param fat : array/list of sector indexes (FAT or MiniFAT)
:param filesize : size of OLE file (for debugging)
:returns : a BytesIO instance containing the OLE stream
"""
debug('_OleStream.__init__:')
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.
Parses a 128-bytes entry from the OLE Directory stream.
entry : string (must be 128 bytes long)
sid : index of this directory entry in the OLE file directory
olefile: OleFileIO containing this directory entry
:param entry : string (must be 128 bytes long)
:param sid : index of this directory entry in the OLE file directory
:param olefile: OleFileIO containing this directory entry
"""
self.sid = sid
# ref to olefile is stored for future use
@ -989,7 +992,7 @@ class _OleDirectoryEntry:
"""
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)
new in version 0.26
@ -1003,7 +1006,7 @@ class _OleDirectoryEntry:
"""
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)
new in version 0.26
@ -1020,7 +1023,8 @@ class OleFileIO:
OLE container object
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.
Object names are given as a list of strings, one for each subentry
@ -1048,8 +1052,8 @@ class OleFileIO:
"""
Constructor for OleFileIO class.
filename: file to open.
raise_defects: minimal level for defects to be raised as exceptions.
:param filename: file to open.
:param raise_defects: minimal level for defects to be raised as exceptions.
(use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a
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
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_POTENTIAL : a potential defect
DEFECT_INCORRECT : an error according to specifications, but parsing can go on
DEFECT_FATAL : an error which cannot be ignored, parsing is impossible
message: string describing the defect, used with raised exception.
exception_type: exception class to be raised, IOError by default
:param message: string describing the defect, used with raised exception.
:param exception_type: exception class to be raised, IOError by default
"""
# added by [PL]
if defect_level >= self._raise_defects_level:
@ -1089,7 +1093,7 @@ class OleFileIO:
Open an OLE2 file.
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:
# (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.
This method should only be called once for each known stream, and only
if stream size is not null.
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 first_sect: index of first sector of the stream in FAT
:param minifat: if True, stream is located in the MiniFAT, else in the FAT
"""
if minifat:
debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect)
@ -1371,8 +1375,9 @@ class OleFileIO:
def loadfat_sect(self, sect):
"""
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.
if isinstance(sect, array.array):
@ -1505,8 +1510,9 @@ class OleFileIO:
def getsect(self, sect):
"""
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
# 512 bytes:
@ -1530,7 +1536,8 @@ class OleFileIO:
def loaddirectory(self, sect):
"""
Load the directory.
sect: sector index of directory stream.
:param sect: sector index of directory stream.
"""
# The directory is stored in a standard
# substream, independent of its size.
@ -1567,9 +1574,10 @@ class OleFileIO:
Load a directory entry from the directory.
This method should only be called once for each storage/stream when
loading the directory.
sid: index of storage/stream in the directory.
return: a _OleDirectoryEntry object
raise: IOError if the entry has always been referenced.
:param sid: index of storage/stream in the directory.
:returns: a _OleDirectoryEntry object
:exception IOError: if the entry has always been referenced.
"""
# check if SID is OK:
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.
(openstream helper)
start: index of first sector
size: size of stream (or nothing if size is unknown)
force_FAT: if False (default), stream will be opened in FAT or MiniFAT
:param start: index of first sector
:param size: size of stream (or nothing if size is unknown)
: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.
"""
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):
"""
(listdir helper)
files: list of files to fill in
prefix: current location in storage tree (list of names)
node: current node (_OleDirectoryEntry object)
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 files: list of files to fill in
:param prefix: current location in storage tree (list of names)
:param node: current node (_OleDirectoryEntry object)
:param streams: bool, include streams if True (True 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)
"""
prefix = prefix + [node.name]
@ -1657,9 +1665,9 @@ class OleFileIO:
"""
Return a list of streams stored in this file
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
(note: the root storage is never included)
:param streams: bool, include streams if True (True 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)
"""
files = []
self._list(files, [], self.root, streams, storages)
@ -1671,12 +1679,13 @@ class OleFileIO:
Returns directory entry of given filename. (openstream helper)
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:
'storage_1/storage_1.2/stream'
- a list of storage filenames, path to the desired stream/storage.
Example: ['storage_1', 'storage_1.2', 'stream']
return: sid of requested filename
:returns: sid of requested filename
raise IOError if file not found
"""
@ -1700,13 +1709,15 @@ class OleFileIO:
"""
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:
'storage_1/storage_1.2/stream'
- a list of storage filenames, path to the desired stream/storage.
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)
entry = self.direntries[sid]
@ -1720,8 +1731,9 @@ class OleFileIO:
Test if given filename exists as a stream or a storage in the OLE
container, and return its type.
filename: path of stream in storage tree. (see openstream for syntax)
return: False if object does not exist, its entry type (>0) otherwise:
:param filename: path of stream in storage tree. (see openstream for syntax)
:returns: False if object does not exist, its entry type (>0) otherwise:
- STGTY_STREAM: a stream
- STGTY_STORAGE: a storage
- STGTY_ROOT: the root entry
@ -1738,10 +1750,10 @@ class OleFileIO:
"""
Return modification time of a stream/storage.
filename: path of stream/storage in storage tree. (see openstream for
syntax)
return: None if modification time is null, a python datetime object
otherwise (UTC timezone)
:param filename: path of stream/storage in storage tree. (see openstream for
syntax)
:returns: None if modification time is null, a python datetime object
otherwise (UTC timezone)
new in version 0.26
"""
@ -1754,10 +1766,10 @@ class OleFileIO:
"""
Return creation time of a stream/storage.
filename: path of stream/storage in storage tree. (see openstream for
syntax)
return: None if creation time is null, a python datetime object
otherwise (UTC timezone)
:param filename: path of stream/storage in storage tree. (see openstream for
syntax)
:returns: None if creation time is null, a python datetime object
otherwise (UTC timezone)
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
container.
filename: path of stream in storage tree. (see openstream for syntax)
return: True if object exist, else False.
:param filename: path of stream in storage tree. (see openstream for syntax)
:returns: True if object exist, else False.
"""
try:
sid = self._find(filename)
@ -1785,9 +1797,10 @@ class OleFileIO:
"""
Return size of a stream in the OLE container, in bytes.
filename: path of stream in storage tree (see openstream for syntax)
return: size in bytes (long integer)
raise: IOError if file not found, TypeError if this is not a stream.
:param filename: path of stream in storage tree (see openstream for syntax)
:returns: size in bytes (long integer)
:exception IOError: if file not found
:exception TypeError: if this is not a stream
"""
sid = self._find(filename)
entry = self.direntries[sid]
@ -1809,11 +1822,11 @@ class OleFileIO:
"""
Return properties described in substream.
filename: path of stream in storage tree (see openstream for syntax)
convert_time: bool, if True timestamps will be converted to Python datetime
no_conversion: None or list of int, timestamps not to be converted
(for example total editing time is not a real timestamp)
return: a dictionary of values indexed by id (integer)
:param filename: path of stream in storage tree (see openstream for syntax)
:param convert_time: bool, if True timestamps will be converted to Python datetime
:param no_conversion: None or list of int, timestamps not to be converted
(for example total editing time is not a real timestamp)
:returns: a dictionary of values indexed by id (integer)
"""
# make sure no_conversion is a list, just to simplify code below:
if no_conversion == None:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,20 +3,25 @@ import os
if bytes is str:
def isStringType(t):
return isinstance(t, basestring)
def isPath(f):
return isinstance(f, basestring)
else:
def isStringType(t):
return isinstance(t, str)
def isPath(f):
return isinstance(f, (bytes, str))
# Checks if an object is a string, and that it points to a directory.
def isDirectory(f):
return isPath(f) and os.path.isdir(f)
class deferred_error(object):
def __init__(self, ex):
self.ex = ex
def __getattr__(self, elt):
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)*
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
:target: https://travis-ci.org/python-imaging/Pillow
.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
:target: https://travis-ci.org/python-pillow/Pillow
:alt: Travis CI build status
.. 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/
:alt: Number of PyPI downloads
.. image:: https://coveralls.io/repos/python-imaging/Pillow/badge.png?branch=master
:target: https://coveralls.io/r/python-imaging/Pillow?branch=master
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?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)
================================================================================
The SANE module provides an interface to the SANE scanner and frame
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.
To build this module, type (in the Sane directory):
To build this module, type (in the Sane directory)::
python setup.py build
In order to install the module type:
In order to install the module type::
python setup.py install

View File

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

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:
break
ix = ix + 1
ix += 1
if html:
html.write("</body>\n</html>\n")

View File

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

View File

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

View File

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

View File

@ -18,25 +18,6 @@ import sys
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
@ -56,10 +37,6 @@ class UI(Label):
else:
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)
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 Image
import time
def iterate_get(size, access):
(w,h) = size
(w, h) = size
for x in range(w):
for y in range(h):
access[(x,y)]
access[(x, y)]
def iterate_set(size, access):
(w,h) = size
(w, h) = size
for x in range(w):
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):
iterations = 5000
@ -25,27 +27,34 @@ def timer(func, label, *args):
for x in range(iterations):
func(*args)
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
if x == iterations-1:
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)
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)
def test_direct(self):
im = lena()
im.load()
# im = Image.new( "RGB", (2000, 2000), (1, 3, 2))
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
sys.path.insert(0, ".")
import tester
import helper
import timeit
def bench(mode):
im = tester.lena(mode)
im = helper.lena(mode)
get = im.im.getpixel
xy = 50, 50 # position shouldn't really matter
xy = 50, 50 # position shouldn't really matter
t0 = timeit.default_timer()
for i in range(1000000):
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