This commit is contained in:
hugovk 2015-06-08 20:46:39 +03:00
commit 5c40cb1ba6
229 changed files with 5745 additions and 2844 deletions

View File

@ -3,8 +3,6 @@ language: python
notifications:
irc: "chat.freenode.net#pil"
env: MAX_CONCURRENCY=4
# Run slow PyPy* first, to give them a headstart and reduce waiting time.
# Run latest 3.x and 2.x next, to get quick compatibility results.
# Then run the remainder.
@ -22,9 +20,7 @@ install:
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
- "travis_retry pip install cffi"
- "travis_retry pip install coverage nose"
# Pyroma installation is slow on Py3, so just do it for Py2.
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then travis_retry pip install pyroma; fi
- "travis_retry pip install pyroma"
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
@ -56,7 +52,6 @@ after_success:
- travis_retry pip install coveralls-merge
- coveralls-merge coverage.c.json
- travis_retry pip install pep8 pyflakes
- pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py
@ -64,10 +59,53 @@ after_success:
- 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
# after_all
- |
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
python travis_after_all.py
export $(cat .to_export_back)
if [ "$BUILD_LEADER" = "YES" ]; then
if [ "$BUILD_AGGREGATE_STATUS" = "others_succeeded" ]; then
echo "All jobs succeded! Triggering OS X build..."
# Trigger an OS X build at the pillow-wheels repo
./build_children.sh
else
echo "Some jobs failed"
fi
fi
fi
after_failure:
- |
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
python travis_after_all.py
export $(cat .to_export_back)
if [ "$BUILD_LEADER" = "YES" ]; then
if [ "$BUILD_AGGREGATE_STATUS" = "others_failed" ]; then
echo "All jobs failed"
else
echo "Some jobs failed"
fi
fi
fi
after_script:
- |
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS
fi
matrix:
fast_finish: true
env:
global:
# travis encrypt AUTH_TOKEN=
secure: "Vzm7aG1Qv0SDQcqiPzZMedNLn5ZmpL7IzF0DYnqcD+/l+zmKU22SnJBcX0uVXumo+r7eZfpsShpqfcdsZvMlvmQnwz+Y6AGKQru9tCKZbTMnuRjWKKXekC+tr8Xt9CKvRVtte5PyXW31paxUI3/e+fQGBwoFjEEC+6EpEOjeRfE="

View File

@ -1,14 +1,194 @@
Changelog (Pillow)
==================
2.7.0 (unreleased)
2.9.0 (Unreleased)
------------------
- Provide n_frames attribute to multi-frame formats #1261
[anntzer, radarhere]
- Add duration and loop set to GifImagePlugin #1172
[radarhere]
- Ico files are little endian #1232
[wiredfool]
- Upgrade olefile from 0.30 to 0.42b #1226
[radarhere, decalage2]
- Setting transparency value to 0 when the tRNS contains only null byte(s) #1239
[juztin]
- Separated out feature checking from selftest #1233
[radarhere]
- Style/health fixes
[radarhere]
- Update WebP from 0.4.1 to 0.4.3 #1235
[radarhere]
- Release GIL during image load (decode) #1224
[lkesteloot]
- Added icns save #1185
[radarhere]
- Fix putdata memory leak #1196
[benoit-pierre]
- Keep user-specified ordering of icon sizes #1193
[karimbahgat]
- Tiff: allow writing floating point tag values #1113
[bpedersen2]
2.8.2 (2015-06-06)
------------------
- Bug fix: Fixed Tiff handling of bad EXIF data
[radarhere]
2.8.1 (2015-04-02)
------------------
- Bug fix: Catch struct.error on invalid JPEG, fixes #1163
[wiredfool, hugovk]
2.8.0 (2015-04-01)
------------------
- Fix 32-bit BMP loading (RGBA or RGBX)
[artscoop]
- Fix UnboundLocalError in ImageFile #1131
[davarisg]
- Re-enable test image caching
[hugovk, homm]
- Fix: Cannot identify EPS images, fixes #1104
[hugovk]
- Configure setuptools to run nosetests, fixes #729
[aclark4life]
- Style/health fixes
[radarhere, hugovk]
- Add support for HTTP response objects to Image.open()
[mfitzp]
- Improve reference docs for PIL.ImageDraw.Draw.pieslice() #1145
[audreyr]
- Added copy method font_variant() and accessible properties to truetype() #1123
[radarhere]
- Fix ImagingEffectNoise #1128
[hugovk]
- Remove unreachable code
[hugovk]
- Let Python do the endian stuff + tests #1121
[amoibos, radarhere]
- Fix webp decode memory leak #1114
[benoit-pierre]
- Fast path for opaque pixels in RGBa unpacker #1088
[bgilbert]
- Enable basic support for 'RGBa' raw encoding/decoding #1096
[immerrr]
- Fix pickling L mode images with no palette, #1095
[hugovk]
- iPython display hook #1091
[wiredfool]
- Adjust buffer size when quality=keep, fixes #148 (again)
[wiredfool]
- Fix for corrupted bitmaps embedded in truetype fonts. #1072
[jackyyf, wiredfool]
2.7.0 (2015-01-01)
------------------
- Split Sane into a separate repo: https://github.com/python-pillow/Sane
[hugovk]
- Look for OSX and Linux fonts in common places. #1054
[charleslaw]
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
[wiredfool]
- Use underscores, not spaces, in TIFF tag kwargs. #1044, #1058
[anntzer, hugovk]
- Update PSDraw for Python3, add tests. #1055
[hugovk]
- Use Bicubic filtering by default for thumbnails. Don't use Jpeg Draft mode for thumbnails. #1029
[homm]
- Fix MSVC compiler error: Use Py_ssize_t instead of ssize_t #1051
[cgohlke]
- Fix compiler error: MSVC needs variables defined at the start of the block #1048
[cgohlke]
- The GIF Palette optimization algorithm is only applicable to mode='P' or 'L' #993
[moriyoshi]
- Use PySide as an alternative to PyQt4/5.
[holg]
- Replace affine-based im.resize implementation with convolution-based im.stretch #997
[homm]
- Replace Gaussian Blur implementation with iterated fast box blur. #961 Note: Radius parameter is interpreted differently than before.
[homm]
- Better docs explaining import _imaging failure #1016, build #1017, mode #1018, PyAccess, PixelAccess objects #1019 Image.quantize #1020 and Image.save #1021
[wiredfool]
- Fix for saving TIFF image into an io.BytesIO buffer #1011
[mfergie]
- Fix antialias compilation on debug versions of Python #1010
[wiredfool]
- Fix for Image.putdata segfault #1009
[wiredfool]
- Ico save, additional tests #1007
[exherb]
- Use PyQt4 if it has already been imported, otherwise prefer PyQt5. #1003
[AurelienBallier]
- Speedup resample implementation up to 2.5 times. #977
[homm]
- Speed up rotation by using cache aware loops, added transpose to rotations. #994
[homm]
- Fix Bicubic interpolation #970
[homm]
- Support for 4-bit greyscale TIFF images #980
[hugovk]
- Updated manifest #957
[wiredfool]
- Fix PyPy 2.4 regression #952
[wiredfool]
[wiredfool]
- Webp Metadata Skip Test comments #954
[wiredfool]
@ -16,6 +196,15 @@ Changelog (Pillow)
- Fixes for things rpmlint complains about #942
[manisandro]
2.6.2 (2015-01-01)
------------------
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
[wiredfool]
- Fix Regression in PyPy 2.4 in streamio #958
[wiredfool]
2.6.1 (2014-10-11)
------------------
@ -23,7 +212,7 @@ Changelog (Pillow)
[wiredfool]
- Fix manifest to include all test files.
[aclark]
[aclark4life]
2.6.0 (2014-10-01)
------------------
@ -193,7 +382,7 @@ Changelog (Pillow)
[wirefool]
- Top level flake8 fixes #741
[aclark]
[aclark4life]
- Remove obsolete Animated Raster Graphics (ARG) support
[hugovk]
@ -322,7 +511,7 @@ Changelog (Pillow)
[larsmans]
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
[aclark]
[aclark4life]
- Merge from Philippe Lagadecs OleFileIO_PL fork
[vadmium]
@ -737,13 +926,13 @@ Changelog (Pillow)
[blueyed]
- Package cleanup and additional documentation
[aclark]
[aclark4life]
1.7.4 (2011-07-21)
------------------
- Fix brown bag release
[aclark]
[aclark4life]
1.7.3 (2011-07-20)
------------------
@ -755,19 +944,19 @@ Changelog (Pillow)
------------------
- Bug fix: Python 2.4 compat
[aclark]
[aclark4life]
1.7.1 (2011-05-31)
------------------
- More multi-arch support
[SteveM, regebro, barry, aclark]
[SteveM, regebro, barry, aclark4life]
1.7.0 (2011-05-27)
------------------
- Add support for multi-arch library directory /usr/lib/x86_64-linux-gnu
[aclark]
[aclark4life]
1.6 (12/01/2010)
----------------
@ -776,28 +965,28 @@ Changelog (Pillow)
[elro]
- Doc fixes
[aclark]
[aclark4life]
1.5 (11/28/2010)
----------------
- Module and package fixes
[aclark]
[aclark4life]
1.4 (11/28/2010)
----------------
- Doc fixes
[aclark]
[aclark4life]
1.3 (11/28/2010)
----------------
- Add support for /lib64 and /usr/lib64 library directories on Linux
[aclark]
[aclark4life]
- Doc fixes
[aclark]
[aclark4life]
1.2 (08/02/2010)
----------------
@ -806,26 +995,29 @@ Changelog (Pillow)
[jezdez]
- Doc fixes
[aclark]
[aclark4life]
1.1 (07/31/2010)
----------------
- Removed setuptools_hg requirement
[aclark]
[aclark4life]
- Doc fixes
[aclark]
[aclark4life]
1.0 (07/30/2010)
----------------
- 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]
[aclark4life]
.. Note:: What follows is the original PIL 1.1.7 CHANGES
0.2b5 - 1.1.7 (1995-2010)
-------------------------
::
-*- coding: utf-8 -*-
@ -1685,7 +1877,7 @@ Changelog (Pillow)
(1.1.2c1 and 1.1.2 final released)
+ Adapted to Python 2.1. Among other things, all uses of the
"regex" module has been repleased with "re".
"regex" module have been replaced with "re".
+ Fixed attribute error when reading large PNG files (this bug
was introduced in maintenance code released after the 1.1.1
@ -2309,7 +2501,7 @@ Changelog (Pillow)
the default value is 75.
JPEG smooth smooth dithered images. value
is strengh (1-100). default is
is strength (1-100). default is
off (0).
PNG optimize minimize output file at the

View File

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

View File

@ -2,21 +2,17 @@ include *.c
include *.h
include *.md
include *.py
include *.sh
include *.rst
include *.txt
include *.yaml
include .coveragerc
include .gitattributes
include .travis.yml
include LICENSE
include Makefile
include tox.ini
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.rst
recursive-include Scripts *.py
recursive-include Scripts *.rst
recursive-include Scripts *.sh
@ -64,6 +60,7 @@ recursive-include Tests *.ttf
recursive-include Tests *.txt
recursive-include Tests *.webp
recursive-include Tests *.xpm
recursive-include Tests *.msp
recursive-include Tk *.c
recursive-include Tk *.rst
recursive-include depends *.rst

103
Makefile
View File

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

View File

@ -69,8 +69,8 @@ def bdf_char(f):
bitmap.append(s[:-1])
bitmap = b"".join(bitmap)
[x, y, l, d] = [int(s) for s in props["BBX"].split()]
[dx, dy] = [int(s) for s in props["DWIDTH"].split()]
[x, y, l, d] = [int(p) for p in props["BBX"].split()]
[dx, dy] = [int(p) for p in props["DWIDTH"].split()]
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)

View File

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

View File

@ -19,7 +19,7 @@
# file (for example a TAR file).
class ContainerIO:
class ContainerIO(object):
##
# Create file object.

View File

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

View File

@ -157,7 +157,7 @@ def Ghostscript(tile, size, fp, scale=1):
return im
class PSFile:
class PSFile(object):
"""
Wrapper for bytesio object that treats either CR or LF as end of line.
"""
@ -248,7 +248,7 @@ class EpsImageFile(ImageFile.ImageFile):
# Note: The DSC spec says that BoundingBox
# fields should be integers, but some drivers
# put floating point values there anyway.
box = [int(float(s)) for s in v.split()]
box = [int(float(i)) for i in v.split()]
self.size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0, 0) + self.size, offset,
(length, box))]
@ -275,20 +275,20 @@ class EpsImageFile(ImageFile.ImageFile):
s = fp.readline().strip('\r\n')
if s[0] != "%":
if s[:1] != "%":
break
#
# Scan for an "ImageData" descriptor
while s[0] == "%":
while s[:1] == "%":
if len(s) > 255:
raise SyntaxError("not an EPS file")
if s[:11] == "%ImageData:":
# Encoded bitmapped image.
[x, y, bi, mo, z3, z4, en, id] = s[11:].split(None, 7)
x, y, bi, mo = s[11:].split(None, 7)[:4]
if int(bi) != 8:
break
@ -365,7 +365,7 @@ def _save(im, fp, filename, eps=1):
else:
raise ValueError("image mode is not supported")
class NoCloseStream:
class NoCloseStream(object):
def __init__(self, fp):
self.fp = fp

View File

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

View File

@ -86,9 +86,10 @@ class FliImageFile(ImageFile.ImageFile):
self.palette = ImagePalette.raw("RGB", b"".join(palette))
# set things up to decode first frame
self.frame = -1
self.__frame = -1
self.__fp = self.fp
self.__rewind = self.fp.tell()
self._n_frames = None
self.seek(0)
def _palette(self, palette, shift):
@ -109,11 +110,35 @@ class FliImageFile(ImageFile.ImageFile):
palette[i] = (r, g, b)
i += 1
def seek(self, frame):
@property
def n_frames(self):
if self._n_frames is None:
current = self.tell()
try:
while True:
self.seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current)
return self._n_frames
if frame != self.frame + 1:
def seek(self, frame):
if frame == self.__frame:
return
if frame < self.__frame:
self._seek(0)
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
def _seek(self, frame):
if frame == 0:
self.__frame = -1
self.__fp.seek(self.__rewind)
self.__offset = 128
if frame != self.__frame + 1:
raise ValueError("cannot seek to frame %d" % frame)
self.frame = frame
self.__frame = frame
# move to next frame
self.fp = self.__fp
@ -128,11 +153,10 @@ class FliImageFile(ImageFile.ImageFile):
self.decodermaxblock = framesize
self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
self.__offset = self.__offset + framesize
self.__offset += framesize
def tell(self):
return self.frame
return self.__frame
#
# registry

View File

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

View File

@ -20,7 +20,7 @@ __version__ = "0.1"
from PIL import Image, ImageFile
from PIL.OleFileIO import *
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
# we map from colour field tuples to (mode, rawmode) descriptors
@ -130,15 +130,15 @@ class FpxImageFile(ImageFile.ImageFile):
fp = self.ole.openstream(stream)
# skip prefix
p = fp.read(28)
fp.read(28)
# header stream
s = fp.read(36)
size = i32(s, 4), i32(s, 8)
tilecount = i32(s, 12)
# tilecount = i32(s, 12)
tilesize = i32(s, 16), i32(s, 20)
channels = i32(s, 24)
# channels = i32(s, 24)
offset = i32(s, 28)
length = i32(s, 32)

View File

@ -39,8 +39,8 @@ class GbrImageFile(ImageFile.ImageFile):
width = i32(self.fp.read(4))
height = i32(self.fp.read(4))
bytes = i32(self.fp.read(4))
if width <= 0 or height <= 0 or bytes != 1:
color_depth = i32(self.fp.read(4))
if width <= 0 or height <= 0 or color_depth != 1:
raise SyntaxError("not a GIMP brush")
comment = self.fp.read(header_size - 20)[:-1]

View File

@ -24,13 +24,11 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image, ImageFile, ImagePalette, _binary
__version__ = "0.9"
from PIL import Image, ImageFile, ImagePalette, _binary
# --------------------------------------------------------------------
# Helpers
@ -89,9 +87,30 @@ class GifImageFile(ImageFile.ImageFile):
self.__fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell()
self.seek(0) # get ready to read first frame
self._n_frames = None
self._seek(0) # get ready to read first frame
@property
def n_frames(self):
if self._n_frames is None:
current = self.tell()
try:
while True:
self.seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current)
return self._n_frames
def seek(self, frame):
if frame == self.__frame:
return
if frame < self.__frame:
self._seek(0)
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
def _seek(self, frame):
if frame == 0:
# rewind
@ -271,7 +290,7 @@ def _save(im, fp, filename):
pass # write uncompressed file
if im.mode in RAWMODE:
imOut = im
im_out = im
else:
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
# should automatically convert images on save...)
@ -279,9 +298,9 @@ def _save(im, fp, filename):
palette_size = 256
if im.palette:
palette_size = len(im.palette.getdata()[1]) // 3
imOut = im.convert("P", palette=1, colors=palette_size)
im_out = im.convert("P", palette=1, colors=palette_size)
else:
imOut = im.convert("L")
im_out = im.convert("L")
# header
try:
@ -290,63 +309,16 @@ def _save(im, fp, filename):
palette = None
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
header, used_palette_colors = getheader(im_out, palette, im.encoderinfo)
for s in header:
fp.write(s)
flags = 0
try:
interlace = im.encoderinfo["interlace"]
except KeyError:
interlace = 1
# workaround for @PIL153
if min(im.size) < 16:
interlace = 0
if interlace:
flags = flags | 64
try:
transparency = im.encoderinfo["transparency"]
except KeyError:
pass
else:
transparency = int(transparency)
# optimize the block away if transparent color is not used
transparentColorExists = True
# adjust the transparency index after optimize
if usedPaletteColors is not None and len(usedPaletteColors) < 256:
for i in range(len(usedPaletteColors)):
if usedPaletteColors[i] == transparency:
transparency = i
transparentColorExists = True
break
else:
transparentColorExists = False
# transparency extension block
if transparentColorExists:
fp.write(b"!" +
o8(249) + # extension intro
o8(4) + # length
o8(1) + # transparency info present
o16(0) + # duration
o8(transparency) # transparency index
+ o8(0))
# local image header
fp.write(b"," +
o16(0) + o16(0) + # bounding box
o16(im.size[0]) + # size
o16(im.size[1]) +
o8(flags) + # flags
o8(8)) # bits
get_local_header(fp, im)
imOut.encoderconfig = (8, interlace)
ImageFile._save(imOut, fp, [("gif", (0, 0)+im.size, 0,
RAWMODE[imOut.mode])])
im_out.encoderconfig = (8, get_interlace(im))
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
RAWMODE[im_out.mode])])
fp.write(b"\0") # end of image data
@ -358,6 +330,85 @@ def _save(im, fp, filename):
pass
def get_interlace(im):
try:
interlace = im.encoderinfo["interlace"]
except KeyError:
interlace = 1
# workaround for @PIL153
if min(im.size) < 16:
interlace = 0
return interlace
def get_local_header(fp, im, offset=(0, 0)):
transparent_color_exists = False
try:
transparency = im.encoderinfo["transparency"]
except KeyError:
pass
else:
transparency = int(transparency)
# optimize the block away if transparent color is not used
transparent_color_exists = True
if _get_optimize(im, im.encoderinfo):
used_palette_colors = _get_used_palette_colors(im)
# adjust the transparency index after optimize
if len(used_palette_colors) < 256:
for i in range(len(used_palette_colors)):
if used_palette_colors[i] == transparency:
transparency = i
transparent_color_exists = True
break
else:
transparent_color_exists = False
if "duration" in im.encoderinfo:
duration = int(im.encoderinfo["duration"] / 10)
else:
duration = 0
if transparent_color_exists or duration != 0:
transparency_flag = 1 if transparent_color_exists else 0
if not transparent_color_exists:
transparency = 0
fp.write(b"!" +
o8(249) + # extension intro
o8(4) + # length
o8(transparency_flag) + # transparency info present
o16(duration) + # duration
o8(transparency) + # transparency index
o8(0))
if "loop" in im.encoderinfo:
number_of_loops = im.encoderinfo["loop"]
fp.write(b"!" +
o8(255) + # extension intro
o8(11) +
b"NETSCAPE2.0" +
o8(3) +
o8(1) +
o16(number_of_loops) + # number of loops
o8(0))
flags = 0
if get_interlace(im):
flags = flags | 64
fp.write(b"," +
o16(offset[0]) + # offset
o16(offset[1]) +
o16(im.size[0]) + # size
o16(im.size[1]) +
o8(flags) + # flags
o8(8)) # bits
def _save_netpbm(im, fp, filename):
#
@ -407,11 +458,26 @@ def _save_netpbm(im, fp, filename):
# --------------------------------------------------------------------
# GIF utilities
def _get_optimize(im, info):
return im.mode in ("P", "L") and info and info.get("optimize", 0)
def _get_used_palette_colors(im):
used_palette_colors = []
# check which colors are used
i = 0
for count in im.histogram():
if count:
used_palette_colors.append(i)
i += 1
return used_palette_colors
def getheader(im, palette=None, info=None):
"""Return a list of strings representing a GIF header"""
optimize = info and info.get("optimize", 0)
# Header Block
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
header = [
@ -422,74 +488,68 @@ def getheader(im, palette=None, info=None):
if im.mode == "P":
if palette and isinstance(palette, bytes):
sourcePalette = palette[:768]
source_palette = palette[:768]
else:
sourcePalette = im.im.getpalette("RGB")[:768]
source_palette = im.im.getpalette("RGB")[:768]
else: # L-mode
if palette and isinstance(palette, bytes):
sourcePalette = palette[:768]
source_palette = palette[:768]
else:
sourcePalette = bytearray([i//3 for i in range(768)])
source_palette = bytearray([i//3 for i in range(768)])
usedPaletteColors = paletteBytes = None
used_palette_colors = palette_bytes = None
if optimize:
usedPaletteColors = []
# check which colors are used
i = 0
for count in im.histogram():
if count:
usedPaletteColors.append(i)
i += 1
if _get_optimize(im, info):
used_palette_colors = _get_used_palette_colors(im)
# create the new palette if not every color is used
if len(usedPaletteColors) < 256:
paletteBytes = b""
newPositions = {}
if len(used_palette_colors) < 256:
palette_bytes = b""
new_positions = {}
i = 0
# pick only the used colors from the palette
for oldPosition in usedPaletteColors:
paletteBytes += sourcePalette[oldPosition*3:oldPosition*3+3]
newPositions[oldPosition] = i
for oldPosition in used_palette_colors:
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
new_positions[oldPosition] = i
i += 1
# replace the palette color id of all pixel with the new id
imageBytes = bytearray(im.tobytes())
for i in range(len(imageBytes)):
imageBytes[i] = newPositions[imageBytes[i]]
im.frombytes(bytes(imageBytes))
newPaletteBytes = (paletteBytes +
(768 - len(paletteBytes)) * b'\x00')
im.putpalette(newPaletteBytes)
im.palette = ImagePalette.ImagePalette("RGB", palette=paletteBytes,
size=len(paletteBytes))
image_bytes = bytearray(im.tobytes())
for i in range(len(image_bytes)):
image_bytes[i] = new_positions[image_bytes[i]]
im.frombytes(bytes(image_bytes))
new_palette_bytes = (palette_bytes +
(768 - len(palette_bytes)) * b'\x00')
im.putpalette(new_palette_bytes)
im.palette = ImagePalette.ImagePalette("RGB",
palette=palette_bytes,
size=len(palette_bytes))
if not paletteBytes:
paletteBytes = sourcePalette
if not palette_bytes:
palette_bytes = source_palette
# Logical Screen Descriptor
# calculate the palette size for the header
import math
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1
if colorTableSize < 0:
colorTableSize = 0
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
if color_table_size < 0:
color_table_size = 0
# size of global color table + global color table flag
header.append(o8(colorTableSize + 128))
header.append(o8(color_table_size + 128))
# background + reserved/aspect
header.append(o8(0) + o8(0))
# end of Logical Screen Descriptor
# add the missing amount of bytes
# the palette has to be 2<<n in size
actualTargetSizeDiff = (2 << colorTableSize) - len(paletteBytes)//3
if actualTargetSizeDiff > 0:
paletteBytes += o8(0) * 3 * actualTargetSizeDiff
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
if actual_target_size_diff > 0:
palette_bytes += o8(0) * 3 * actual_target_size_diff
# Header + Logical Screen Descriptor + Global Color Table
header.append(paletteBytes)
return header, usedPaletteColors
header.append(palette_bytes)
return header, used_palette_colors
def getdata(im, offset=(0, 0), **params):
@ -497,7 +557,7 @@ def getdata(im, offset=(0, 0), **params):
The first string is a local image header, the rest contains
encoded image data."""
class collector:
class Collector(object):
data = []
def write(self, data):
@ -505,19 +565,13 @@ def getdata(im, offset=(0, 0), **params):
im.load() # make sure raster data is available
fp = collector()
fp = Collector()
try:
im.encoderinfo = params
# local image header
fp.write(b"," +
o16(offset[0]) + # offset
o16(offset[1]) +
o16(im.size[0]) + # size
o16(im.size[1]) +
o8(0) + # flags
o8(8)) # bits
get_local_header(fp, im, offset)
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])

View File

@ -58,7 +58,7 @@ def sphere_decreasing(middle, pos):
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
class GradientFile:
class GradientFile(object):
gradient = None

View File

@ -21,7 +21,7 @@ from PIL._binary import o8
##
# File handler for GIMP's palette format.
class GimpPaletteFile:
class GimpPaletteFile(object):
rawmode = "RGB"

View File

@ -17,7 +17,11 @@
from PIL import Image, ImageFile, PngImagePlugin, _binary
import io
import os
import shutil
import struct
import sys
import tempfile
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
if enable_jpeg2k:
@ -90,7 +94,7 @@ def read_32(fobj, start_length, size):
def read_mk(fobj, start_length, size):
# Alpha masks seem to be uncompressed
(start, length) = start_length
start = start_length[0]
fobj.seek(start)
pixel_size = (size[0] * size[2], size[1] * size[2])
sizesq = pixel_size[0] * pixel_size[1]
@ -126,7 +130,7 @@ def read_png_or_jpeg2000(fobj, start_length, size):
raise ValueError('Unsupported icon subimage format')
class IcnsFile:
class IcnsFile(object):
SIZES = {
(512, 512, 2): [
@ -247,7 +251,7 @@ class IcnsFile:
class IcnsImageFile(ImageFile.ImageFile):
"""
PIL read-only image support for Mac OS .icns files.
PIL image support for Mac OS .icns files.
Chooses the best resolution, but will possibly load
a different size image if you mutate the size attribute
before calling 'load'.
@ -293,12 +297,64 @@ class IcnsImageFile(ImageFile.ImageFile):
self.tile = ()
self.load_end()
def _save(im, fp, filename):
"""
Saves the image as a series of PNG files,
that are then converted to a .icns file
using the OS X command line utility 'iconutil'.
OS X only.
"""
try:
fp.flush()
except:
pass
# create the temporary set of pngs
iconset = tempfile.mkdtemp('.iconset')
last_w = None
last_im = None
for w in [16, 32, 128, 256, 512]:
prefix = 'icon_{}x{}'.format(w, w)
if last_w == w:
im_scaled = last_im
else:
im_scaled = im.resize((w, w), Image.LANCZOS)
im_scaled.save(os.path.join(iconset, prefix+'.png'))
im_scaled = im.resize((w*2, w*2), Image.LANCZOS)
im_scaled.save(os.path.join(iconset, prefix+'@2x.png'))
last_im = im_scaled
# iconutil -c icns -o {} {}
from subprocess import Popen, PIPE, CalledProcessError
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
stderr = tempfile.TemporaryFile()
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr)
convert_proc.stdout.close()
retcode = convert_proc.wait()
# remove the temporary files
shutil.rmtree(iconset)
if retcode:
raise CalledProcessError(retcode, convert_cmd)
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
Image.register_extension("ICNS", '.icns')
if sys.platform == 'darwin':
Image.register_save("ICNS", _save)
Image.register_mime("ICNS", "image/icns")
if __name__ == '__main__':
import os
import sys
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
for size in imf.info['sizes']:
imf.size = size

View File

@ -24,6 +24,9 @@
__version__ = "0.1"
import struct
from io import BytesIO
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
from math import log, ceil
@ -37,11 +40,46 @@ i32 = _binary.i32le
_MAGIC = b"\0\0\1\0"
def _save(im, fp, filename):
fp.write(_MAGIC) # (2+2)
sizes = im.encoderinfo.get("sizes",
[(16, 16), (24, 24), (32, 32), (48, 48),
(64, 64), (128, 128), (255, 255)])
width, height = im.size
filter(lambda x: False if (x[0] > width or x[1] > height or
x[0] > 255 or x[1] > 255) else True, sizes)
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
offset = fp.tell() + len(sizes)*16
for size in sizes:
width, height = size
fp.write(struct.pack("B", width)) # bWidth(1)
fp.write(struct.pack("B", height)) # bHeight(1)
fp.write(b"\0") # bColorCount(1)
fp.write(b"\0") # bReserved(1)
fp.write(b"\0\0") # wPlanes(2)
fp.write(struct.pack("<H", 32)) # wBitCount(2)
image_io = BytesIO()
tmp = im.copy()
tmp.thumbnail(size, Image.LANCZOS)
tmp.save(image_io, "png")
image_io.seek(0)
image_bytes = image_io.read()
bytes_len = len(image_bytes)
fp.write(struct.pack("<I", bytes_len)) # dwBytesInRes(4)
fp.write(struct.pack("<I", offset)) # dwImageOffset(4)
current = fp.tell()
fp.seek(offset)
fp.write(image_bytes)
offset = offset + bytes_len
fp.seek(current)
def _accept(prefix):
return prefix[:4] == _MAGIC
class IcoFile:
class IcoFile(object):
def __init__(self, buf):
"""
Parse image from file-like object containing ico file data
@ -234,11 +272,12 @@ class IcoImageFile(ImageFile.ImageFile):
self.size = im.size
def load_seek(self):
# Flage the ImageFile.Parser so that it
# Flag the ImageFile.Parser so that it
# just does all the decode at the end.
pass
#
# --------------------------------------------------------------------
Image.register_open("ICO", IcoImageFile, _accept)
Image.register_save("ICO", _save)
Image.register_extension("ICO", ".ico")

View File

@ -260,6 +260,10 @@ class ImImageFile(ImageFile.ImageFile):
self.tile = [("raw", (0, 0)+self.size, offs,
(self.rawmode, 0, -1))]
@property
def n_frames(self):
return self.info[FRAMES]
def seek(self, frame):
if frame < 0 or frame >= self.info[FRAMES]:
@ -313,7 +317,7 @@ SAVE = {
def _save(im, fp, filename, check=0):
try:
type, rawmode = SAVE[im.mode]
image_type, rawmode = SAVE[im.mode]
except KeyError:
raise ValueError("Cannot save %s images as IM" % im.mode)
@ -325,7 +329,7 @@ def _save(im, fp, filename, check=0):
if check:
return check
fp.write(("Image type: %s image\r\n" % type).encode('ascii'))
fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
if filename:
fp.write(("Name: %s\r\n" % filename).encode('ascii'))
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))

View File

@ -35,7 +35,7 @@ class DecompressionBombWarning(RuntimeWarning):
pass
class _imaging_not_installed:
class _imaging_not_installed(object):
# module placeholder
def __getattr__(self, id):
raise ImportError("The _imaging C module is not installed")
@ -55,10 +55,11 @@ except ImportError:
pass
try:
# If the _imaging C module is not present, you can still use
# the "open" function to identify files, but you cannot load
# them. Note that other modules should not refer to _imaging
# directly; import Image and use the Image.core variable instead.
# If the _imaging C module is not present, Pillow will not load.
# Note that other modules should not refer to _imaging directly;
# import Image and use the Image.core variable instead.
# Also note that Image.core is not a publicly documented interface,
# and should be considered private and subject to change.
from PIL import _imaging as core
if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
raise ImportError("The _imaging extension was built for another "
@ -91,6 +92,7 @@ except ImportError as v:
RuntimeWarning
)
# Fail here anyway. Don't let people run with a mostly broken Pillow.
# see docs/porting-pil-to-pillow.rst
raise
try:
@ -107,6 +109,8 @@ from PIL._util import deferred_error
import os
import sys
import io
import struct
# type stuff
import collections
@ -150,6 +154,7 @@ FLIP_TOP_BOTTOM = 1
ROTATE_90 = 2
ROTATE_180 = 3
ROTATE_270 = 4
TRANSPOSE = 5
# transforms
AFFINE = 0
@ -159,11 +164,10 @@ QUAD = 3
MESH = 4
# resampling filters
NONE = 0
NEAREST = 0
ANTIALIAS = 1 # 3-lobed lanczos
LINEAR = BILINEAR = 2
CUBIC = BICUBIC = 3
NEAREST = NONE = 0
LANCZOS = ANTIALIAS = 1
BILINEAR = LINEAR = 2
BICUBIC = CUBIC = 3
# dithers
NONE = 0
@ -383,7 +387,7 @@ def init():
for plugin in _plugins:
try:
if DEBUG:
print ("Importing %s" % plugin)
print("Importing %s" % plugin)
__import__("PIL.%s" % plugin, globals(), locals(), [])
except ImportError:
if DEBUG:
@ -439,7 +443,7 @@ def coerce_e(value):
return value if isinstance(value, _E) else _E(value)
class _E:
class _E(object):
def __init__(self, data):
self.data = data
@ -474,7 +478,7 @@ def _getscaleoffset(expr):
# --------------------------------------------------------------------
# Implementation wrapper
class Image:
class Image(object):
"""
This class represents an image object. To create
:py:class:`~PIL.Image.Image` objects, use the appropriate factory
@ -542,7 +546,7 @@ class Image:
self.fp.close()
except Exception as msg:
if DEBUG:
print ("Error closing: %s" % msg)
print("Error closing: %s" % msg)
# Instead of simply setting to None, we're setting up a
# deferred error that will better explain that the core image
@ -596,6 +600,16 @@ class Image:
id(self)
)
def _repr_png_(self):
""" iPython display hook support
:returns: png version of the image as bytes
"""
from io import BytesIO
b = BytesIO()
self.save(b, 'PNG')
return b.getvalue()
def __getattr__(self, name):
if name == "__array_interface__":
# numpy array interface support
@ -623,7 +637,7 @@ class Image:
self.mode = mode
self.size = size
self.im = core.new(mode, size)
if mode in ("L", "P"):
if mode in ("L", "P") and palette:
self.putpalette(palette)
self.frombytes(data)
@ -742,6 +756,7 @@ class Image:
associated with the image.
:returns: An image access object.
:rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
"""
if self.im and self.palette and self.palette.dirty:
# realize palette
@ -801,7 +816,7 @@ class Image:
use other thresholds, use the :py:meth:`~PIL.Image.Image.point`
method.
:param mode: The requested mode.
:param mode: The requested mode. See: :ref:`concept-modes`.
:param matrix: An optional conversion matrix. If given, this
should be 4- or 16-tuple containing floating point values.
:param dither: Dithering method, used when converting from
@ -875,16 +890,15 @@ class Image:
elif self.mode == 'P' and mode == 'RGBA':
t = self.info['transparency']
delete_trns = True
if isinstance(t, bytes):
self.im.putpalettealphas(t)
elif isinstance(t, int):
self.im.putpalettealpha(t,0)
self.im.putpalettealpha(t, 0)
else:
raise ValueError("Transparency for P mode should" +
" be bytes or int")
if mode == "P" and palette == ADAPTIVE:
im = self.im.quantize(colors)
new = self._new(im)
@ -936,14 +950,19 @@ class Image:
return new_im
def quantize(self, colors=256, method=None, kmeans=0, palette=None):
"""
Convert the image to 'P' mode with the specified number
of colors.
# methods:
# 0 = median cut
# 1 = maximum coverage
# 2 = fast octree
:param colors: The desired number of colors, <= 256
:param method: 0 = median cut
1 = maximum coverage
2 = fast octree
:param kmeans: Integer
:param palette: Quantize to the :py:class:`PIL.ImagingPalette` palette.
:returns: A new image
# NOTE: this functionality will be moved to the extended
# quantizer interface in a later version of PIL.
"""
self.load()
@ -1269,11 +1288,11 @@ class Image:
images (in the latter case, the alpha band is used as mask).
Where the mask is 255, the given image is copied as is. Where
the mask is 0, the current value is preserved. Intermediate
values can be used for transparency effects.
values will mix the two images together, including their alpha
channels if they have them.
Note that if you paste an "RGBA" image, the alpha band is
ignored. You can work around this by using the same image as
both source image and mask.
See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
combine images with respect to their alpha channels.
:param im: Source image or pixel value (integer or tuple).
:param box: An optional 4-tuple giving the region to paste into.
@ -1514,21 +1533,20 @@ class Image:
(width, height).
: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), :py:attr:`PIL.Image.BICUBIC` (cubic spline
interpolation in a 4x4 environment), or
:py:attr:`PIL.Image.ANTIALIAS` (a high-quality downsampling filter).
:py:attr:`PIL.Image.BILINEAR` (linear interpolation),
:py:attr:`PIL.Image.BICUBIC` (cubic spline interpolation), or
:py:attr:`PIL.Image.LANCZOS` (a high-quality downsampling filter).
If omitted, or if the image has mode "1" or "P", it is
set :py:attr:`PIL.Image.NEAREST`.
:returns: An :py:class:`~PIL.Image.Image` object.
"""
if resample not in (NEAREST, BILINEAR, BICUBIC, ANTIALIAS):
if resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS):
raise ValueError("unknown resampling filter")
self.load()
size=tuple(size)
size = tuple(size)
if self.size == size:
return self._new(self.im)
@ -1538,16 +1556,7 @@ class Image:
if self.mode == 'RGBA':
return self.convert('RGBa').resize(size, resample).convert('RGBA')
if resample == ANTIALIAS:
# requires stretch support (imToolkit & PIL 1.1.3)
try:
im = self.im.stretch(size, resample)
except AttributeError:
raise ValueError("unsupported resampling filter")
else:
im = self.im.resize(size, resample)
return self._new(im)
return self._new(self.im.resize(size, resample))
def rotate(self, angle, resample=NEAREST, expand=0):
"""
@ -1618,15 +1627,16 @@ class Image:
Keyword options can be used to provide additional instructions
to the writer. If a writer doesn't recognise an option, it is
silently ignored. The available options are described later in
this handbook.
silently ignored. The available options are described in the
:doc:`image format documentation
<../handbook/image-file-formats>` for each writer.
You can use a file object instead of a filename. In this case,
you must always specify the format. The file object must
implement the **seek**, **tell**, and **write**
implement the ``seek``, ``tell``, and ``write``
methods, and be opened in binary mode.
:param file: File name or file object.
:param fp: File name or file object.
:param format: Optional format override. If omitted, the
format to use is determined from the filename extension.
If a file object was used instead of a filename, this
@ -1753,7 +1763,7 @@ class Image:
"""
return 0
def thumbnail(self, size, resample=ANTIALIAS):
def thumbnail(self, size, resample=BICUBIC):
"""
Make this image into a thumbnail. This method modifies the
image to contain a thumbnail version of itself, no larger than
@ -1762,12 +1772,7 @@ class Image:
:py:meth:`~PIL.Image.Image.draft` method to configure the file reader
(where applicable), and finally resizes the image.
Note that the bilinear and bicubic filters in the current
version of PIL are not well-suited for thumbnail generation.
You should use :py:attr:`PIL.Image.ANTIALIAS` unless speed is much more
important than quality.
Also note that this function modifies the :py:class:`~PIL.Image.Image`
Note that this function modifies the :py:class:`~PIL.Image.Image`
object in place. If you need to use the full resolution image as well,
apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
image.
@ -1775,10 +1780,9 @@ class Image:
:param size: Requested size.
:param resample: Optional resampling filter. This can be one
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS`
(best quality). If omitted, it defaults to
:py:attr:`PIL.Image.ANTIALIAS`. (was :py:attr:`PIL.Image.NEAREST`
prior to version 2.5.0)
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.LANCZOS`.
If omitted, it defaults to :py:attr:`PIL.Image.BICUBIC`.
(was :py:attr:`PIL.Image.NEAREST` prior to version 2.5.0)
:returns: None
"""
@ -1797,14 +1801,7 @@ class Image:
self.draft(None, size)
self.load()
try:
im = self.resize(size, resample)
except ValueError:
if resample != ANTIALIAS:
raise
im = self.resize(size, NEAREST) # fallback
im = self.resize(size, resample)
self.im = im.im
self.mode = im.mode
@ -1813,7 +1810,7 @@ class Image:
self.readonly = 0
self.pyaccess = None
# FIXME: the different tranform methods need further explanation
# FIXME: the different transform methods need further explanation
# instead of bloating the method docs, add a separate chapter.
def transform(self, size, method, data=None, resample=NEAREST, fill=1):
"""
@ -1920,13 +1917,13 @@ class Image:
:param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`,
:py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`,
:py:attr:`PIL.Image.ROTATE_180`, or :py:attr:`PIL.Image.ROTATE_270`.
:py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270` or
:py:attr:`PIL.Image.TRANSPOSE`.
:returns: Returns a flipped or rotated copy of this image.
"""
self.load()
im = self.im.transpose(method)
return self._new(im)
return self._new(self.im.transpose(method))
def effect_spread(self, distance):
"""
@ -1978,12 +1975,12 @@ class _ImageCrop(Image):
# --------------------------------------------------------------------
# Abstract handlers.
class ImagePointHandler:
class ImagePointHandler(object):
# used as a mixin by point transforms (for use with im.point)
pass
class ImageTransformHandler:
class ImageTransformHandler(object):
# used as a mixin by geometry transforms (for use with im.transform)
pass
@ -2004,7 +2001,8 @@ def new(mode, size, color=0):
"""
Creates a new image with the given mode and size.
:param mode: The mode to use for the new image.
:param mode: The mode to use for the new image. See:
:ref:`concept-modes`.
:param size: A 2-tuple, containing (width, height) in pixels.
:param color: What color to use for the image. Default is black.
If given, this should be a single integer or floating point value
@ -2037,14 +2035,14 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
You can also use any pixel decoder supported by PIL. For more
information on available decoders, see the section
**Writing Your Own File Decoder**.
:ref:`Writing Your Own File Decoder <file-decoders>`.
Note that this function decodes pixel data only, not entire images.
If you have an entire image in a string, wrap it in a
:py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load
it.
:param mode: The image mode.
:param mode: The image mode. See: :ref:`concept-modes`.
:param size: The image size.
:param data: A byte buffer containing raw data for the given mode.
:param decoder_name: What decoder to use.
@ -2096,7 +2094,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
issues a warning if you do this; to disable the warning, you should provide
the full set of parameters. See below for details.
:param mode: The image mode.
:param mode: The image mode. See: :ref:`concept-modes`.
:param size: The image size.
:param data: A bytes or other buffer object containing raw
data for the given mode.
@ -2147,7 +2145,8 @@ def fromarray(obj, mode=None):
:param obj: Object with array interface
:param mode: Mode to use (will be determined from type if None)
:returns: An image memory.
See: :ref:`concept-modes`.
:returns: An image object.
.. versionadded:: 1.1.6
"""
@ -2250,6 +2249,11 @@ def open(fp, mode="r"):
else:
filename = ""
try:
fp.seek(0)
except (AttributeError, io.UnsupportedOperation):
fp = io.BytesIO(fp.read())
prefix = fp.read(16)
preinit()
@ -2262,7 +2266,7 @@ def open(fp, mode="r"):
im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError):
except (SyntaxError, IndexError, TypeError, struct.error):
# import traceback
# traceback.print_exc()
pass
@ -2277,7 +2281,7 @@ def open(fp, mode="r"):
im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError):
except (SyntaxError, IndexError, TypeError, struct.error):
# import traceback
# traceback.print_exc()
pass
@ -2364,7 +2368,8 @@ def merge(mode, bands):
"""
Merge a set of single band images into a new multiband image.
:param mode: The mode to use for the output image.
:param mode: The mode to use for the output image. See:
:ref:`concept-modes`.
:param bands: A sequence containing one single-band image for
each band in the output image. All bands must have the
same size.

View File

@ -64,7 +64,7 @@ pyCMS
0.0.2 alpha Jan 6, 2002
Added try/except statements arount type() checks of
Added try/except statements around type() checks of
potential CObjects... Python won't let you use type()
on them, and raises a TypeError (stupid, if you ask
me!)
@ -123,8 +123,8 @@ FLAGS = {
"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
"HIGHRESPRECALC": 1024, # Use more memory to give better accuracy
"LOWRESPRECALC": 2048, # Use less memory to minimize resources
"WHITEBLACKCOMPENSATION": 8192,
"BLACKPOINTCOMPENSATION": 8192,
"GAMUTCHECK": 4096, # Out of Gamut alarm
@ -147,7 +147,7 @@ for flag in FLAGS.values():
##
# Profile.
class ImageCmsProfile:
class ImageCmsProfile(object):
def __init__(self, profile):
"""
@ -573,7 +573,7 @@ def applyTransform(im, transform, inPlace=0):
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
considerable calcuation time if doing the same conversion multiple times.
considerable calculation time if doing the same conversion multiple times.
If you want to modify im in-place instead of receiving a new image as
the return value, set inPlace to TRUE. This can only be done if
@ -858,7 +858,7 @@ def getDefaultIntent(profile):
If an error occurs while trying to obtain the default intent, a
PyCMSError is raised.
Use this function to determine the default (and usually best optomized)
Use this function to determine the default (and usually best optimized)
rendering intent for this profile. Most profiles support multiple
rendering intents, but are intended mostly for one type of conversion.
If you wish to use a different intent than returned, use
@ -914,7 +914,7 @@ def isIntentSupported(profile, intent, direction):
see the pyCMS documentation for details on rendering intents and what
they do.
:param direction: Integer specifing if the profile is to be used for input,
:param direction: Integer specifying if the profile is to be used for input,
output, or proof
INPUT = 0 (or use ImageCms.DIRECTION_INPUT)

View File

@ -47,7 +47,7 @@ except ImportError:
# Application code should use the <b>Draw</b> factory, instead of
# directly.
class ImageDraw:
class ImageDraw(object):
##
# Create a drawing instance.

View File

@ -19,25 +19,25 @@
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
class Pen:
class Pen(object):
def __init__(self, color, width=1, opacity=255):
self.color = ImageColor.getrgb(color)
self.width = width
class Brush:
class Brush(object):
def __init__(self, color, opacity=255):
self.color = ImageColor.getrgb(color)
class Font:
class Font(object):
def __init__(self, color, file, size=12):
# FIXME: add support for bitmap fonts
self.color = ImageColor.getrgb(color)
self.font = ImageFont.truetype(file, size)
class Draw:
class Draw(object):
def __init__(self, image, size=None, color=None):
if not hasattr(image, "im"):

View File

@ -21,7 +21,7 @@
from PIL import Image, ImageFilter, ImageStat
class _Enhance:
class _Enhance(object):
def enhance(self, factor):
"""
@ -53,6 +53,7 @@ class Color(_Enhance):
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
class Contrast(_Enhance):
"""Adjust image contrast.
@ -72,7 +73,7 @@ class Contrast(_Enhance):
class Brightness(_Enhance):
"""Adjust image brightness.
This class can be used to control the brighntess of an image. An
This class can be used to control the brightness of an image. An
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
original image.
"""

View File

@ -196,6 +196,9 @@ class ImageFile(Image.Image):
except AttributeError:
prefix = b""
# Buffer length read; assign a default value
t = 0
for d, e, o, a in self.tile:
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
seek(o)
@ -308,7 +311,7 @@ class StubImageFile(ImageFile):
)
class Parser:
class Parser(object):
"""
Incremental image parser. This class implements the standard
feed/close consumer interface.
@ -319,6 +322,7 @@ class Parser:
image = None
data = None
decoder = None
offset = 0
finished = 0
def reset(self):

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ _modes = {}
##
# Wrapper for mode strings.
class ModeDescriptor:
class ModeDescriptor(object):
def __init__(self, mode, bands, basemode, basetype):
self.mode = mode

View File

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

View File

@ -20,7 +20,7 @@
from PIL import Image
from PIL._util import isStringType
import operator
from functools import reduce
import functools
#
@ -213,7 +213,7 @@ def equalize(image, mask=None):
if len(histo) <= 1:
lut.extend(list(range(256)))
else:
step = (reduce(operator.add, histo) - histo[-1]) // 255
step = (functools.reduce(operator.add, histo) - histo[-1]) // 255
if not step:
lut.extend(list(range(256)))
else:
@ -233,7 +233,6 @@ def expand(image, border=0, fill=0):
:param fill: Pixel fill value (a color value). Default is 0 (black).
:return: An image.
"""
"Add border to image"
left, top, right, bottom = _border(border)
width = left + image.size[0] + right
height = top + image.size[1] + bottom
@ -441,3 +440,22 @@ def unsharp_mask(im, radius=None, percent=None, threshold=None):
return im.im.unsharp_mask(radius, percent, threshold)
usm = unsharp_mask
def box_blur(image, radius):
"""
Blur the image by setting each pixel to the average value of the pixels
in a square box extending radius pixels in each direction.
Supports float radius of arbitrary size. Uses an optimized implementation
which runs in linear time relative to the size of the image
for any radius value.
:param image: The image to blur.
:param radius: Size of the box in one direction. Radius 0 does not blur,
returns an identical image. Radius 1 takes 1 pixel
in each direction, i.e. 9 pixels in total.
:return: An image.
"""
image.load()
return image._new(image.im.box_blur(radius))

View File

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

View File

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

View File

@ -18,16 +18,24 @@
from PIL import Image
from PIL._util import isPath
import sys
try:
from PyQt5.QtGui import QImage, qRgba
except:
if 'PyQt4.QtGui' not in sys.modules:
try:
from PyQt5.QtGui import QImage, qRgba
except:
try:
from PyQt4.QtGui import QImage, qRgba
except:
from PySide.QtGui import QImage, qRgba
else: #PyQt4 is used
from PyQt4.QtGui import QImage, qRgba
##
# (Internal) Turns an RGB color into a Qt compatible color integer.
def rgb(r, g, b, a=255):
# use qRgb to pack the colors, and then turn the resulting long
# into a negative integer with the same bitpattern.

View File

@ -16,7 +16,7 @@
##
class Iterator:
class Iterator(object):
"""
This class implements an iterator object that can be used to loop
over an image sequence.

View File

@ -56,7 +56,7 @@ def show(image, title=None, **options):
##
# Base class for viewers.
class Viewer:
class Viewer(object):
# main api

View File

@ -23,10 +23,10 @@
import math
import operator
from functools import reduce
import functools
class Stat:
class Stat(object):
def __init__(self, image_or_list, mask=None):
try:
@ -71,7 +71,7 @@ class Stat:
v = []
for i in range(0, len(self.h), 256):
v.append(reduce(operator.add, self.h[i:i+256]))
v.append(functools.reduce(operator.add, self.h[i:i+256]))
return v
def _getsum(self):
@ -79,10 +79,10 @@ class Stat:
v = []
for i in range(0, len(self.h), 256):
sum = 0.0
layerSum = 0.0
for j in range(256):
sum += j * self.h[i + j]
v.append(sum)
layerSum += j * self.h[i + j]
v.append(layerSum)
return v
def _getsum2(self):

View File

@ -56,7 +56,7 @@ def _pilbitmap_check():
# --------------------------------------------------------------------
# PhotoImage
class PhotoImage:
class PhotoImage(object):
"""
A Tkinter-compatible photo image. This can be used
everywhere Tkinter expects an image object. If the image is an RGBA
@ -190,7 +190,7 @@ class PhotoImage:
# BitmapImage
class BitmapImage:
class BitmapImage(object):
"""
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter

View File

@ -21,7 +21,7 @@ import warnings
from PIL import Image
class HDC:
class HDC(object):
"""
Wraps an HDC integer. The resulting object can be passed to the
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
@ -34,7 +34,7 @@ class HDC:
return self.dc
class HWND:
class HWND(object):
"""
Wraps an HWND integer. The resulting object can be passed to the
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
@ -47,7 +47,7 @@ class HWND:
return self.wnd
class Dib:
class Dib(object):
"""
A Windows bitmap with the given mode and size. The mode can be one of "1",
"L", "P", or "RGB".
@ -206,7 +206,7 @@ class Dib:
##
# Create a Window with the given title size.
class Window:
class Window(object):
def __init__(self, title="PIL", width=None, height=None):
self.hwnd = Image.core.createwindow(

View File

@ -222,7 +222,7 @@ def getiptcinfo(im):
offset += 2
# resource name (usually empty)
name_len = i8(app[offset])
name = app[offset+1:offset+1+name_len]
# name = app[offset+1:offset+1+name_len]
offset = 1 + offset + name_len
if offset & 1:
offset += 1
@ -251,7 +251,7 @@ def getiptcinfo(im):
return None # no properties
# create an IptcImagePlugin object without initializing it
class FakeImage:
class FakeImage(object):
pass
im = FakeImage()
im.__class__ = IptcImageFile

View File

@ -12,14 +12,13 @@
#
# See the README file for information on usage and redistribution.
#
__version__ = "0.1"
from PIL import Image, ImageFile
import struct
import os
import io
__version__ = "0.1"
def _parse_codestream(fp):
"""Parse the JPEG 2000 codestream to extract the size and component
@ -208,8 +207,8 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
def _accept(prefix):
return (prefix[:4] == b'\xff\x4f\xff\x51'
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
return (prefix[:4] == b'\xff\x4f\xff\x51' or
prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
# ------------------------------------------------------------

View File

@ -4,7 +4,7 @@
#
# JPEG (JFIF) file handling
#
# See "Digital Compression and Coding of Continous-Tone Still Images,
# See "Digital Compression and Coding of Continuous-Tone Still Images,
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
#
# History:
@ -355,7 +355,7 @@ class JpegImageFile(ImageFile.ImageFile):
scale = s
self.tile = [(d, e, o, a)]
self.decoderconfig = (scale, 1)
self.decoderconfig = (scale, 0)
return self
@ -454,13 +454,13 @@ def _getmp(self):
data = self.info["mp"]
except KeyError:
return None
file = io.BytesIO(data)
head = file.read(8)
file_contents = io.BytesIO(data)
head = file_contents.read(8)
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
mp = {}
# process dictionary
info = TiffImagePlugin.ImageFileDirectory(head)
info.load(file)
info.load(file_contents)
for key, value in info.items():
mp[key] = _fixup(value)
# it's an error not to have a number of images
@ -684,7 +684,8 @@ def _save(im, fp, filename):
# https://github.com/jdriscoll/django-imagekit/issues/50
bufsize = 0
if "optimize" in info or "progressive" in info or "progression" in info:
if quality >= 95:
# keep sets quality to 0, but the actual value may be high.
if quality >= 95 or quality == 0:
bufsize = 2 * im.size[0] * im.size[1]
else:
bufsize = im.size[0] * im.size[1]
@ -703,7 +704,7 @@ def _save_cjpeg(im, fp, filename):
tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
try:
os.unlink(file)
os.unlink(tempfile)
except:
pass

View File

@ -67,7 +67,7 @@ Libjpeg ref.: http://www.jpegcameras.com/libjpeg/libjpeg-3.html
"""
presets = {
'web_low': {'subsampling': 2, # "4:1:1"
'web_low': {'subsampling': 2, # "4:1:1"
'quantization': [
[20, 16, 25, 39, 50, 46, 62, 68,
16, 18, 23, 38, 38, 53, 65, 68,
@ -86,7 +86,7 @@ presets = {
68, 68, 68, 68, 68, 68, 68, 68,
68, 68, 68, 68, 68, 68, 68, 68]
]},
'web_medium': {'subsampling': 2, # "4:1:1"
'web_medium': {'subsampling': 2, # "4:1:1"
'quantization': [
[16, 11, 11, 16, 23, 27, 31, 30,
11, 12, 12, 15, 20, 23, 23, 30,
@ -105,7 +105,7 @@ presets = {
38, 35, 46, 53, 64, 64, 64, 64,
48, 43, 53, 64, 64, 64, 64, 64]
]},
'web_high': {'subsampling': 0, # "4:4:4"
'web_high': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 6, 4, 4, 6, 9, 11, 12, 16,
4, 5, 5, 6, 8, 10, 12, 12,
@ -124,7 +124,7 @@ presets = {
31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31]
]},
'web_very_high': {'subsampling': 0, # "4:4:4"
'web_very_high': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 2, 2, 2, 2, 3, 4, 5, 6,
2, 2, 2, 2, 3, 4, 5, 6,
@ -143,7 +143,7 @@ presets = {
15, 12, 12, 12, 12, 12, 12, 12,
15, 12, 12, 12, 12, 12, 12, 12]
]},
'web_maximum': {'subsampling': 0, # "4:4:4"
'web_maximum': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
@ -162,7 +162,7 @@ presets = {
3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3]
]},
'low': {'subsampling': 2, # "4:1:1"
'low': {'subsampling': 2, # "4:1:1"
'quantization': [
[18, 14, 14, 21, 30, 35, 34, 17,
14, 16, 16, 19, 26, 23, 12, 12,
@ -181,7 +181,7 @@ presets = {
17, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12]
]},
'medium': {'subsampling': 2, # "4:1:1"
'medium': {'subsampling': 2, # "4:1:1"
'quantization': [
[12, 8, 8, 12, 17, 21, 24, 17,
8, 9, 9, 11, 15, 19, 12, 12,
@ -200,7 +200,7 @@ presets = {
17, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12]
]},
'high': {'subsampling': 0, # "4:4:4"
'high': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 6, 4, 4, 6, 9, 11, 12, 16,
4, 5, 5, 6, 8, 10, 12, 12,
@ -219,7 +219,7 @@ presets = {
17, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12]
]},
'maximum': {'subsampling': 0, # "4:4:4"
'maximum': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 2, 2, 2, 2, 3, 4, 5, 6,
2, 2, 2, 2, 3, 4, 5, 6,

View File

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

View File

@ -22,7 +22,7 @@ from PIL._binary import i8
#
# Bitstream parser
class BitStream:
class BitStream(object):
def __init__(self, fp):
self.fp = fp

View File

@ -62,6 +62,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
def load_seek(self, pos):
self.__fp.seek(pos)
@property
def n_frames(self):
return self.__framecount
def seek(self, frame):
if frame < 0 or frame >= self.__framecount:
raise EOFError("no more images in MPO file")

View File

@ -49,10 +49,10 @@ class MspImageFile(ImageFile.ImageFile):
raise SyntaxError("not an MSP file")
# Header checksum
sum = 0
checksum = 0
for i in range(0, 32, 2):
sum = sum ^ i16(s[i:i+2])
if sum != 0:
checksum = checksum ^ i16(s[i:i+2])
if checksum != 0:
raise SyntaxError("bad MSP checksum")
self.mode = "1"
@ -83,10 +83,10 @@ def _save(im, fp, filename):
header[6], header[7] = 1, 1
header[8], header[9] = im.size
sum = 0
checksum = 0
for h in header:
sum = sum ^ h
header[12] = sum # FIXME: is this the right field?
checksum = checksum ^ h
header[12] = checksum # FIXME: is this the right field?
# header
for h in header:

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -19,7 +19,7 @@ from PIL._binary import o8
##
# File handler for Teragon-style palette files.
class PaletteFile:
class PaletteFile(object):
rawmode = "RGB"

View File

@ -52,25 +52,6 @@ class PcdImageFile(ImageFile.ImageFile):
self.size = 768, 512 # FIXME: not correct for rotated images!
self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)]
def draft(self, mode, size):
if len(self.tile) != 1:
return
d, e, o, a = self.tile[0]
if size:
scale = max(self.size[0] / size[0], self.size[1] / size[1])
for s, o in [(4, 0*2048), (2, 0*2048), (1, 96*2048)]:
if scale >= s:
break
# e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1]
# self.size = ((self.size[0]+s-1)/s, (self.size[1]+s-1)/s)
self.tile = [(d, e, o, a)]
return self
#
# registry

View File

@ -204,7 +204,7 @@ class PcfFontFile(FontFile.FontFile):
for i in range(4):
bitmapSizes.append(i32(fp.read(4)))
byteorder = format & 4 # non-zero => MSB
# byteorder = format & 4 # non-zero => MSB
bitorder = format & 8 # non-zero => MSB
padindex = format & 3

View File

@ -25,7 +25,7 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.6"
from __future__ import print_function
from PIL import Image, ImageFile, ImagePalette, _binary
@ -33,6 +33,8 @@ i8 = _binary.i8
i16 = _binary.i16le
o8 = _binary.o8
__version__ = "0.6"
def _accept(prefix):
return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
@ -58,7 +60,7 @@ class PcxImageFile(ImageFile.ImageFile):
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
raise SyntaxError("bad PCX image size")
if Image.DEBUG:
print ("BBox: %s %s %s %s" % bbox)
print("BBox: %s %s %s %s" % bbox)
# format
version = i8(s[1])
@ -66,8 +68,8 @@ class PcxImageFile(ImageFile.ImageFile):
planes = i8(s[65])
stride = i16(s, 66)
if Image.DEBUG:
print ("PCX version %s, bits %s, planes %s, stride %s" %
(version, bits, planes, stride))
print("PCX version %s, bits %s, planes %s, stride %s" %
(version, bits, planes, stride))
self.info["dpi"] = i16(s, 12), i16(s, 14)
@ -106,7 +108,7 @@ class PcxImageFile(ImageFile.ImageFile):
bbox = (0, 0) + self.size
if Image.DEBUG:
print ("size: %sx%s" % self.size)
print("size: %sx%s" % self.size)
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
@ -143,7 +145,7 @@ def _save(im, fp, filename, check=0):
# gets overwritten.
if Image.DEBUG:
print ("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
print("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
im.size[0], bits, stride))
# under windows, we could determine the current screen size with

View File

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

View File

@ -71,12 +71,27 @@ _MODES = {
_simple_palette = re.compile(b'^\xff+\x00\xff*$')
_null_palette = re.compile(b'^\x00*$')
# Maximum decompressed size for a iTXt or zTXt chunk.
# Eliminates decompression bombs where compressed chunks can expand 1000x
MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK
# Set the maximum total text chunk size.
MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK
def _safe_zlib_decompress(s):
dobj = zlib.decompressobj()
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
if dobj.unconsumed_tail:
raise ValueError("Decompressed Data Too Large")
return plaintext
# --------------------------------------------------------------------
# Support classes. Suitable for PNG and related formats like MNG etc.
class ChunkStream:
class ChunkStream(object):
def __init__(self, fp):
@ -149,31 +164,56 @@ class ChunkStream:
return cids
# --------------------------------------------------------------------
# Subclass of string to allow iTXt chunks to look like strings while
# keeping their extra information
class iTXt(str):
"""
Subclass of string to allow iTXt chunks to look like strings while
keeping their extra information
"""
@staticmethod
def __new__(cls, text, lang, tkey):
"""
:param value: value for this key
:param lang: language code
:param tkey: UTF-8 version of the key name
"""
self = str.__new__(cls, text)
self.lang = lang
self.tkey = tkey
return self
# --------------------------------------------------------------------
# PNG chunk container (for use with save(pnginfo=))
class PngInfo(object):
"""
PNG chunk container (for use with save(pnginfo=))
class PngInfo:
"""
def __init__(self):
self.chunks = []
def add(self, cid, data):
"""Appends an arbitrary chunk. Use with caution.
:param cid: a byte string, 4 bytes long.
:param data: a byte string of the encoded data
"""
self.chunks.append((cid, data))
def add_itxt(self, key, value, lang="", tkey="", zip=False):
"""Appends an iTXt chunk.
:param key: latin-1 encodable text key name
:param value: value for this key
:param lang: language code
:param tkey: UTF-8 version of the key name
:param zip: compression flag
"""
if not isinstance(key, bytes):
key = key.encode("latin-1", "strict")
if not isinstance(value, bytes):
@ -184,7 +224,6 @@ class PngInfo:
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:
@ -192,6 +231,14 @@ class PngInfo:
value)
def add_text(self, key, value, zip=0):
"""Appends a text chunk.
:param key: latin-1 encodable text key name
:param value: value for this key, text or an
:py:class:`PIL.PngImagePlugin.iTXt` instance
:param zip: compression flag
"""
if isinstance(value, iTXt):
return self.add_itxt(key, value, value.lang, value.tkey, bool(zip))
@ -206,7 +253,6 @@ class PngInfo:
key = key.encode('latin-1', 'strict')
if zip:
import zlib
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
else:
self.add(b"tEXt", key + b"\0" + value)
@ -229,6 +275,14 @@ class PngStream(ChunkStream):
self.im_tile = None
self.im_palette = None
self.text_memory = 0
def check_text_memory(self, chunklen):
self.text_memory += chunklen
if self.text_memory > MAX_TEXT_MEMORY:
raise ValueError("Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" %
self.text_memory)
def chunk_iCCP(self, pos, length):
# ICC profile
@ -247,7 +301,7 @@ class PngStream(ChunkStream):
raise SyntaxError("Unknown compression method %s in iCCP chunk" %
comp_method)
try:
icc_profile = zlib.decompress(s[i+2:])
icc_profile = _safe_zlib_decompress(s[i+2:])
except zlib.error:
icc_profile = None # FIXME
self.im_info["icc_profile"] = icc_profile
@ -297,6 +351,8 @@ class PngStream(ChunkStream):
i = s.find(b"\0")
if i >= 0:
self.im_info["transparency"] = i
elif _null_palette.match(s):
self.im_info["transparency"] = 0
else:
self.im_info["transparency"] = s
elif self.im_mode == "L":
@ -341,6 +397,8 @@ class PngStream(ChunkStream):
v = v.decode('latin-1', 'replace')
self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))
return s
def chunk_zTXt(self, pos, length):
@ -359,9 +417,8 @@ class PngStream(ChunkStream):
if comp_method != 0:
raise SyntaxError("Unknown compression method %s in zTXt chunk" %
comp_method)
import zlib
try:
v = zlib.decompress(v[1:])
v = _safe_zlib_decompress(v[1:])
except zlib.error:
v = b""
@ -371,6 +428,8 @@ class PngStream(ChunkStream):
v = v.decode('latin-1', 'replace')
self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))
return s
def chunk_iTXt(self, pos, length):
@ -390,9 +449,8 @@ class PngStream(ChunkStream):
return s
if cf != 0:
if cm == 0:
import zlib
try:
v = zlib.decompress(v)
v = _safe_zlib_decompress(v)
except zlib.error:
return s
else:
@ -407,6 +465,7 @@ class PngStream(ChunkStream):
return s
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
self.check_text_memory(len(v))
return s
@ -564,7 +623,7 @@ def putchunk(fp, cid, *data):
fp.write(o16(hi) + o16(lo))
class _idat:
class _idat(object):
# wrap output from the encoder in IDAT chunks
def __init__(self, fp, chunk):
@ -674,10 +733,6 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
alpha_bytes = 2**bits
chunk(fp, b"tRNS", alpha[:alpha_bytes])
if 0:
# FIXME: to be supported some day
chunk(fp, b"gAMA", o32(int(gamma * 100000.0)))
dpi = im.encoderinfo.get("dpi")
if dpi:
chunk(fp, b"pHYs",
@ -719,7 +774,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
def getchunks(im, **params):
"""Return a list of PNG chunks representing this image."""
class collector:
class collector(object):
data = []
def write(self, data):

View File

@ -132,6 +132,10 @@ class PsdImageFile(ImageFile.ImageFile):
self._fp = self.fp
self.frame = 0
@property
def n_frames(self):
return len(self.layers)
def seek(self, layer):
# seek to given layer (1..max)
if layer == self.frame:

View File

@ -51,10 +51,10 @@ class PyAccess(object):
self.ysize = vals['ysize']
if DEBUG:
print (vals)
print(vals)
self._post_init()
def _post_init():
def _post_init(self):
pass
def __setitem__(self, xy, color):
@ -78,6 +78,8 @@ class PyAccess(object):
images
:param xy: The pixel coordinate, given as (x, y).
:returns: a pixel value for single band images, a tuple of
pixel values for multiband images.
"""
(x, y) = self.check_xy(xy)
@ -192,7 +194,7 @@ class _PyAccessI16_L(PyAccess):
pixel = self.pixels[y][x]
try:
color = min(color, 65535)
except:
except TypeError:
color = min(color[0], 65535)
pixel.l = color & 0xFF

View File

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

View File

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

View File

@ -37,7 +37,7 @@ def register_handler(handler):
if hasattr(Image.core, "drawwmf"):
# install default handler (windows only)
class WmfHandler:
class WmfHandler(object):
def open(self, im):
im.mode = "RGB"

View File

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

View File

@ -11,6 +11,8 @@
# See the README file for information on usage and redistribution.
#
from struct import unpack, pack
if bytes is str:
def i8(c):
return ord(c)
@ -34,7 +36,7 @@ def i16le(c, o=0):
c: string containing bytes to convert
o: offset of bytes to convert in string
"""
return i8(c[o]) | (i8(c[o+1]) << 8)
return unpack("<H", c[o:o+2])[0]
def i32le(c, o=0):
@ -44,33 +46,31 @@ def i32le(c, o=0):
c: string containing bytes to convert
o: offset of bytes to convert in string
"""
return (i8(c[o]) | (i8(c[o+1]) << 8) | (i8(c[o+2]) << 16) |
(i8(c[o+3]) << 24))
return unpack("<I", c[o:o+4])[0]
def i16be(c, o=0):
return (i8(c[o]) << 8) | i8(c[o+1])
return unpack(">H", c[o:o+2])[0]
def i32be(c, o=0):
return ((i8(c[o]) << 24) | (i8(c[o+1]) << 16) |
(i8(c[o+2]) << 8) | i8(c[o+3]))
return unpack(">I", c[o:o+4])[0]
# Output, le = little endian, be = big endian
def o16le(i):
return o8(i) + o8(i >> 8)
return pack("<H", i)
def o32le(i):
return o8(i) + o8(i >> 8) + o8(i >> 16) + o8(i >> 24)
return pack("<I", i)
def o16be(i):
return o8(i >> 8) + o8(i)
return pack(">H", i)
def o32be(i):
return o8(i >> 24) + o8(i >> 16) + o8(i >> 8) + o8(i)
return pack(">I", i)
# End of file

67
PIL/features.py Normal file
View File

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

View File

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

View File

@ -2,29 +2,62 @@
## Main Release
Released quarterly.
Released quarterly on the first day of January, April, July, October.
* [ ] Get master to the appropriate code release state. [Travis CI](https://travis-ci.org/python-pillow/Pillow) should be running cleanly for all merges to master.
* [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c`, Update date in `CHANGES.rst`.
* [ ] Run pre-release check via `make pre`
* [ ] Tag and push to release branch in python-pillow repo.
* [ ] Upload binaries.
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174
* [ ] Develop and prepare release in ``master`` branch.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
```
PIL/__init__.py setup.py _imaging.c
```
* [ ] Update `CHANGES.rst`.
* [ ] Run pre-release check via `make pre`.
* [ ] Create branch and tag for release e.g.:
```
$ git branch 2.9.x
$ git tag 2.9.0
$ git push --all
$ git push --tags
```
* [ ] Create and upload source distributions e.g.:
```
$ make sdistup
```
* [ ] Create and upload [binary distributions](#binary-distributions)
* [ ] Manually hide old versions on PyPI as needed, such that only the latest main release is visible when viewing https://pypi.python.org/pypi/Pillow
## Point Release
Released as required for security or installation fixes.
Released as needed for security, installation or critical bug fixes.
* [ ] Make necessary changes in master.
* [ ] Cherry pick individual commits. Touch up `CHANGES.rst` to reflect reality.
* [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c`
* [ ] Run pre-release check via `make pre`
* [ ] Push to release branch in personal repo. Let Travis run cleanly.
* [ ] Tag and push to release branch in python-pillow repo.
* [ ] Upload binaries.
* [ ] Make necessary changes in ``master`` branch.
* [ ] Update `CHANGES.rst`.
* [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``2.9.x``.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``2.9.x``.
* [ ] Checkout release branch e.g.:
```
git checkout -t remotes/origin/2.9.x
```
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
```
PIL/__init__.py setup.py _imaging.c
```
* [ ] Run pre-release check via `make pre`.
* [ ] Create tag for release e.g.:
```
$ git tag 2.9.1
$ git push --tags
```
* [ ] Create and upload source distributions e.g.:
```
$ make sdistup
```
* [ ] Create and upload [binary distributions](#binary-distributions)
## Embargoed Release
Security fixes that need to be pushed to the distros prior to public release.
Released as needed privately to individual vendors for critical security-related bug fixes.
* [ ] Prepare patch for all versions that will get a fix. Test against local installations.
* [ ] Commit against master, cherry pick to affected release branches.
@ -34,26 +67,39 @@ Security fixes that need to be pushed to the distros prior to public release.
* [ ] Amend any commits with the CVE #
* [ ] On release date, tag and push to GitHub.
```
git checkout 2.5.x
git tag 2.5.3
git push origin 2.5.x
git push origin --tags
git checkout 2.5.x
git tag 2.5.3
git push origin 2.5.x
git push origin --tags
```
* [ ] Upload binaries
## Binary Upload Process
* [ ] Ping cgohlke for Windows binaries
* [ ] From a clean source directory with no extra temp files:
* [ ] Create and upload source distributions e.g.:
```
python setup.py register
python setup.py sdist --format=zip upload
python setup.py sdist upload
$ make sdistup
```
(Debian requests a tarball, everyone else would just prefer that we choose one and stick to it. So both it is)
* [ ] Push a commit to https://github.com/python-pillow/pillow-wheels to build OSX versions (UNDONE latest tag or specific release???)
* [ ] Retrieve the OS X Wheels from Rackspace files, upload to PyPi (Twine?)
* [ ] Grab Windows binaries, `twine upload dist/*.[whl|egg]`. Manually upload .exe installers.
* [ ] Announce release availability. [Twitter](https://twitter.com/pythonpillow), web.
* [ ] Create and upload [binary distributions](#binary-distributions)
## Binary Distributions
### Windows
* [ ] Contact @cgohlke for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174.
* [ ] Download and extract tarball from @cgohlke and ``twine upload *``.
### OS X
* [ ] Use the [Pillow OS X Wheel Builder](https://github.com/python-pillow/pillow-wheels):
```
$ git checkout https://github.com/python-pillow/pillow-wheels
$ cd pillow-wheels
$ git submodule init
$ git submodule update
$ cd Pillow
$ git fetch --all
$ git commit -a -m "Pillow -> 2.9.0"
$ git push
```
* [ ] Download distributions from the [Pillow OS X Wheel Builder container](http://cdf58691c5cf45771290-6a3b6a0f5f6ab91aadc447b2a897dd9a.r50.cf2.rackcdn.com/) and ``twine upload *``.
### Linux
## Publicize Release
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328.

View File

@ -63,20 +63,3 @@ 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
Word documents, FlashPix images, etc.
Note that datetime fields currently show the number of seconds since
January 1st, 1601.

View File

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

View File

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

View File

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

View File

@ -49,20 +49,23 @@ from PIL.GifImagePlugin import getheader, getdata
# --------------------------------------------------------------------
# sequence iterator
class image_sequence:
class image_sequence(object):
def __init__(self, im):
self.im = im
def __getitem__(self, ix):
try:
if ix:
self.im.seek(ix)
return self.im
except EOFError:
raise IndexError # end of sequence
raise IndexError # end of sequence
# --------------------------------------------------------------------
# straightforward delta encoding
def makedelta(fp, sequence):
"""Convert list of image frames to a GIF animation file"""
@ -72,13 +75,13 @@ def makedelta(fp, sequence):
for im in sequence:
#
# FIXME: write graphics control block before each frame
# To specify duration, add the time in milliseconds to getdata(),
# e.g. getdata(im, duration=1000)
if not previous:
# global header
for s in getheader(im) + getdata(im):
for s in getheader(im)[0] + getdata(im):
fp.write(s)
else:
@ -91,7 +94,7 @@ def makedelta(fp, sequence):
if bbox:
# compress difference
for s in getdata(im.crop(bbox), offset = bbox[:2]):
for s in getdata(im.crop(bbox), offset=bbox[:2]):
fp.write(s)
else:
@ -109,6 +112,7 @@ def makedelta(fp, sequence):
# --------------------------------------------------------------------
# main hack
def compress(infile, outfile):
# open input image, and force loading of first frame

View File

@ -10,9 +10,9 @@
#
try:
from tkinter import *
from tkinter import Tk, Canvas, NW
except ImportError:
from Tkinter import *
from Tkinter import Tk, Canvas, NW
from PIL import Image, ImageTk
import sys
@ -20,6 +20,7 @@ import sys
#
# painter widget
class PaintCanvas(Canvas):
def __init__(self, master, image):
Canvas.__init__(self, master, width=image.size[0], height=image.size[1])
@ -33,7 +34,7 @@ class PaintCanvas(Canvas):
box = x, y, min(xsize, x+tilesize), min(ysize, y+tilesize)
tile = ImageTk.PhotoImage(image.crop(box))
self.create_image(x, y, image=tile, anchor=NW)
self.tile[(x,y)] = box, tile
self.tile[(x, y)] = box, tile
self.image = image
@ -59,7 +60,7 @@ class PaintCanvas(Canvas):
xy, tile = self.tile[(x, y)]
tile.paste(self.image.crop(xy))
except KeyError:
pass # outside the image
pass # outside the image
self.update_idletasks()
#

View File

@ -15,11 +15,13 @@
from __future__ import print_function
import site
import getopt, string, sys
import getopt
import string
import sys
from PIL import Image
def usage():
print("PIL Convert 0.5/1998-12-30 -- convert image files")
print("Usage: pilconvert [option] infile outfile")
@ -47,10 +49,10 @@ except getopt.error as v:
print(v)
sys.exit(1)
format = None
output_format = None
convert = None
options = { }
options = {}
for o, a in opt:
@ -66,7 +68,7 @@ for o, a in opt:
sys.exit(1)
elif o == "-c":
format = a
output_format = a
if o == "-g":
convert = "L"
@ -88,8 +90,8 @@ try:
if convert and im.mode != convert:
im.draft(convert, im.size)
im = im.convert(convert)
if format:
im.save(argv[1], format, **options)
if output_format:
im.save(argv[1], output_format, **options)
else:
im.save(argv[1], **options)
except:

View File

@ -52,7 +52,8 @@ from __future__ import print_function
from PIL import Image
class PILDriver:
class PILDriver(object):
verbose = 0
@ -497,10 +498,6 @@ class PILDriver:
if __name__ == '__main__':
import sys
try:
import readline
except ImportError:
pass # not available on all platforms
# If we see command-line arguments, interpret them as a stack state
# and execute. Otherwise go interactive.

View File

@ -19,8 +19,9 @@
from __future__ import print_function
import site
import getopt, glob, sys
import getopt
import glob
import sys
from PIL import Image
@ -59,6 +60,7 @@ for o, a in opt:
elif o == "-D":
Image.DEBUG += 1
def globfix(files):
# expand wildcards where necessary
if sys.platform == "win32":

View File

@ -14,7 +14,8 @@ from __future__ import print_function
VERSION = "0.4"
import glob, sys
import glob
import sys
# drivers
from PIL import BdfFontFile

View File

@ -12,24 +12,25 @@
#
from __future__ import print_function
import getopt
import os
import sys
VERSION = "pilprint 0.3/2003-05-05"
from PIL import Image
from PIL import PSDraw
letter = ( 1.0*72, 1.0*72, 7.5*72, 10.0*72 )
letter = (1.0*72, 1.0*72, 7.5*72, 10.0*72)
def description(file, image):
import os
title = os.path.splitext(os.path.split(file)[1])[0]
def description(filepath, image):
title = os.path.splitext(os.path.split(filepath)[1])[0]
format = " (%dx%d "
if image.format:
format = " (" + image.format + " %dx%d "
return title + format % image.size + image.mode + ")"
import getopt, os, sys
if len(sys.argv) == 1:
print("PIL Print 0.2a1/96-10-04 -- print image files")
print("Usage: pilprint files...")
@ -45,8 +46,8 @@ except getopt.error as v:
print(v)
sys.exit(1)
printer = None # print to stdout
monochrome = 1 # reduce file size for most common case
printer = None # print to stdout
monochrome = 1 # reduce file size for most common case
for o, a in opt:
if o == "-d":
@ -64,12 +65,12 @@ for o, a in opt:
# printer channel
printer = "lpr -P%s" % a
for file in argv:
for filepath in argv:
try:
im = Image.open(file)
im = Image.open(filepath)
title = description(file, im)
title = description(filepath, im)
if monochrome and im.mode not in ["1", "L"]:
im.draft("L", im.size)

View File

@ -56,7 +56,7 @@ class UI(Label):
del self.im[0]
self.image.paste(im)
except IndexError:
return # end of list
return # end of list
else:
@ -65,7 +65,7 @@ class UI(Label):
im.seek(im.tell() + 1)
self.image.paste(im)
except EOFError:
return # end of file
return # end of file
try:
duration = im.info["duration"]

View File

@ -18,8 +18,9 @@ import sys
#
# an image viewer
class UI(Frame):
def __init__(self, master, im, value = 128):
def __init__(self, master, im, value=128):
Frame.__init__(self, master)
self.image = im
@ -31,7 +32,7 @@ class UI(Frame):
self.canvas.pack()
scale = Scale(self, orient=HORIZONTAL, from_=0, to=255,
resolution=1, command=self.update, length=256)
resolution=1, command=self.update_scale, length=256)
scale.set(value)
scale.bind("<ButtonRelease-1>", self.redraw)
scale.pack()
@ -40,21 +41,21 @@ class UI(Frame):
# be too slow on some platforms)
# self.redraw()
def update(self, value):
def update_scale(self, value):
self.value = eval(value)
self.redraw()
def redraw(self, event = None):
def redraw(self, event=None):
# create overlay (note the explicit conversion to mode "1")
im = self.image.point(lambda v,t=self.value: v>=t, "1")
im = self.image.point(lambda v, t=self.value: v >= t, "1")
self.overlay = ImageTk.BitmapImage(im, foreground="green")
# update canvas
self.canvas.delete("overlay")
self.canvas.create_image(0, 0, image=self.overlay, anchor=NW,
tags="overlay")
tags="overlay")
# --------------------------------------------------------------------
# main

View File

@ -7,15 +7,16 @@
from __future__ import print_function
try:
from tkinter import *
from tkinter import Tk, Label
except ImportError:
from Tkinter import *
from Tkinter import Tk, Label
from PIL import Image, ImageTk
#
# an image viewer
class UI(Label):
def __init__(self, master, im):

View File

@ -3,7 +3,7 @@ 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
Dependencies
-----------
Install::

View File

@ -47,7 +47,7 @@ class BenchCffiAccess(PillowTestCase):
self.assertEqual(caccess[(0, 0)], access[(0, 0)])
print ("Size: %sx%s" % im.size)
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)

View File

@ -10,7 +10,7 @@ def bench(mode):
get = im.im.getpixel
xy = 50, 50 # position shouldn't really matter
t0 = timeit.default_timer()
for i in range(1000000):
for _ in range(1000000):
get(xy)
print(mode, timeit.default_timer() - t0, "us")

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python
from __future__ import division
from helper import unittest, PillowTestCase
import sys
from PIL import Image, ImageFilter
min_iterations = 100
max_iterations = 10000
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
class TestImagingLeaks(PillowTestCase):
def _get_mem_usage(self):
from resource import getpagesize, getrusage, RUSAGE_SELF
mem = getrusage(RUSAGE_SELF).ru_maxrss
return mem * getpagesize() / 1024 / 1024
def _test_leak(self, min_iterations, max_iterations, fn, *args, **kwargs):
mem_limit = None
for i in range(max_iterations):
fn(*args, **kwargs)
mem = self._get_mem_usage()
if i < min_iterations:
mem_limit = mem + 1
continue
self.assertLessEqual(mem, mem_limit,
msg='memory usage limit exceeded after %d iterations'
% (i + 1))
def test_leak_putdata(self):
im = Image.new('RGB', (25, 25))
self._test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
def test_leak_getlist(self):
im = Image.new('P', (25, 25))
self._test_leak(min_iterations, max_iterations,
# Pass a new list at each iteration.
lambda: im.point(range(256)))
if __name__ == '__main__':
unittest.main()

View File

@ -21,7 +21,7 @@ class TestJpegLeaks(PillowTestCase):
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for count in range(iterations):
for _ in range(iterations):
with Image.open(test_file) as im:
im.load()
@ -29,13 +29,13 @@ class TestJpegLeaks(PillowTestCase):
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for count in range(iterations):
for _ in range(iterations):
with Image.open(test_file) as im:
im.load()
test_output = BytesIO()
im.save(test_output, "JPEG2000")
test_output.seek(0)
output = test_output.read()
test_output.read()
if __name__ == '__main__':

View File

@ -1,5 +1,4 @@
from helper import unittest, PillowTestCase, hopper
from PIL import Image
from io import BytesIO
import sys
@ -71,9 +70,8 @@ post-patch:
| :@:@@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
0 +----------------------------------------------------------------------->Gi
0 8.421
"""
"""
def test_qtables_leak(self):
im = hopper('RGB')
@ -103,8 +101,7 @@ post-patch:
qtables = [standard_l_qtable,
standard_chrominance_qtable]
for count in range(iterations):
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
@ -135,7 +132,7 @@ pre patch:
0 +----------------------------------------------------------------------->Gi
0 11.37
post patch:
MB
@ -168,12 +165,12 @@ post patch:
im = hopper('RGB')
exif = b'12345678'*4096
for count in range(iterations):
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", exif=exif)
"""
def test_base_save(self):
"""
base case:
MB
20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@:::
@ -199,11 +196,9 @@ base case:
0 +----------------------------------------------------------------------->Gi
0 7.882
"""
def test_base_save(self):
im = hopper('RGB')
for count in range(iterations):
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG")

48
Tests/check_png_dos.py Normal file
View File

@ -0,0 +1,48 @@
from helper import unittest, PillowTestCase
from PIL import Image, PngImagePlugin
from io import BytesIO
import zlib
TEST_FILE = "Tests/images/png_decompression_dos.png"
class TestPngDos(PillowTestCase):
def test_dos_text(self):
try:
im = Image.open(TEST_FILE)
im.load()
except ValueError as msg:
self.assertTrue(msg, "Decompressed Data Too Large")
return
for s in im.text.values():
self.assertLess(len(s), 1024*1024, "Text chunk larger than 1M")
def test_dos_total_memory(self):
im = Image.new('L', (1, 1))
compressed_data = zlib.compress('a'*1024*1023)
info = PngImagePlugin.PngInfo()
for x in range(64):
info.add_text('t%s' % x, compressed_data, 1)
info.add_itxt('i%s' % x, compressed_data, zip=True)
b = BytesIO()
im.save(b, 'PNG', pnginfo=info)
b.seek(0)
try:
im2 = Image.open(b)
except ValueError as msg:
self.assertIn("Too much memory", msg)
return
total_len = 0
for txt in im2.text.values():
total_len += len(txt)
self.assertLess(total_len, 64*1024*1024, "Total text chunks greater than 64M")
if __name__ == '__main__':
unittest.main()

38
Tests/check_webp_leaks.py Normal file
View File

@ -0,0 +1,38 @@
from __future__ import division
from helper import unittest, PillowTestCase
import sys
from PIL import Image
from io import BytesIO
# Limits for testing the leak
mem_limit = 16 # max increase in MB
iterations = 5000
test_file = "Tests/images/hopper.webp"
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
class TestWebPLeaks(PillowTestCase):
def setUp(self):
try:
from PIL import _webp
except ImportError:
self.skipTest('WebP support not installed')
def _get_mem_usage(self):
from resource import getpagesize, getrusage, RUSAGE_SELF
mem = getrusage(RUSAGE_SELF).ru_maxrss
return mem * getpagesize() / 1024 / 1024
def test_leak_load(self):
with open(test_file, 'rb') as f:
im_data = f.read()
start_mem = self._get_mem_usage()
for _ in range(iterations):
with Image.open(BytesIO(im_data)) as im:
im.load()
mem = (self._get_mem_usage() - start_mem)
self.assertLess(mem, mem_limit, msg='memory usage limit exceeded')
if __name__ == '__main__':
unittest.main()

Binary file not shown.

BIN
Tests/fonts/DejaVuSans.ttf Normal file

Binary file not shown.

View File

@ -130,7 +130,7 @@ class PillowTestCase(unittest.TestCase):
# 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))
print(os.environ.get('PILLOW_RUN_KNOWN_BAD', False))
return
skip = True
@ -165,7 +165,6 @@ class PillowTestCase(unittest.TestCase):
# helpers
import sys
py3 = (sys.version_info >= (3, 0))
@ -175,31 +174,33 @@ def fromstring(data):
return Image.open(BytesIO(data))
def tostring(im, format, **options):
def tostring(im, string_format, **options):
from io import BytesIO
out = BytesIO()
im.save(out, format, **options)
im.save(out, string_format, **options)
return out.getvalue()
def hopper(mode="RGB", cache={}):
def hopper(mode=None, 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
if mode is None:
# Always return fresh not-yet-loaded version of image.
# Operations on not-yet-loaded images is separate class of errors
# what we should catch.
return Image.open("Tests/images/hopper.ppm")
# Use 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)
im = cache.get(mode)
if im is None:
if mode == "RGB":
im = Image.open("Tests/images/hopper.ppm")
elif mode == "F":
if mode == "F":
im = hopper("L").convert(mode)
elif mode[:4] == "I;16":
im = hopper("I").convert(mode)
else:
im = hopper("RGB").convert(mode)
# cache[mode] = im
return im
im = hopper().convert(mode)
cache[mode] = im
return im.copy()
def command_succeeds(cmd):
@ -207,7 +208,6 @@ 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:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
Tests/images/hopper.im Normal file

Binary file not shown.

BIN
Tests/images/hopper.msp Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

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