This commit is contained in:
hugovk 2016-04-03 23:08:49 +03:00
commit ba817af569
326 changed files with 10153 additions and 3942 deletions

24
.editorconfig Normal file
View File

@ -0,0 +1,24 @@
# Top-most EditorConfig file
root = true
[*]
# Unix-style newlines with a newline ending every file
end_of_line = lf
insert_final_newline = true
charset = utf-8
# Four-space indentation
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.yml]
# Two-space indentation
indent_size = 2
indent_style = space
# Tab indentation (no size specified)
[Makefile]
indent_style = tab

View File

@ -1,6 +1,6 @@
# Contributing to Pillow
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.
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/pulls). All contributions are welcome.
## Bug fixes, feature additions, etc.
@ -9,7 +9,7 @@ Please send a pull request to the master branch. Please include [documentation](
- 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.
- Run the test suite on both Python 2.x and 3.x. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo 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.
### Guidelines
@ -17,6 +17,7 @@ Please send a pull request to the master branch. Please include [documentation](
- Separate code commits from reformatting commits.
- Provide tests for any newly added code.
- Follow PEP8.
- When committing only documentation changes please include [ci skip] in the commit message to avoid running tests on Travis-CI and AppVeyor.
## Reporting Issues
@ -28,3 +29,9 @@ When reporting issues, please include code that reproduces the issue and wheneve
- What did you expect to happen?
- What actually happened?
- What versions of Pillow and Python are you using?
## Security vulnerabilities
To report sensitive vulnerability information, email aclark@aclark.net.
If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method.

13
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,13 @@
### What did you do?
### What did you expect to happen?
### What actually happened?
### What versions of Pillow and Python are you using?
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.
```python
code goes here
```

View File

@ -1,2 +1,3 @@
strictness: medium
test-warnings: yes
max-line-length: 80

View File

@ -9,21 +9,34 @@ notifications:
python:
- "pypy"
- "pypy3"
- 3.4
- 3.5
- 2.7
- 2.6
- "2.7_with_system_site_packages" # For PyQt4
- 3.2
- 3.3
- 3.4
- nightly
install:
- "travis_retry sudo apt-get update"
- "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"
- "travis_retry pip install pyroma"
- "travis_retry pip install nose"
- "travis_retry pip install check-manifest"
- "travis_retry pip install Sphinx"
# Pyroma tests sometimes hang on PyPy and Python 2.6; skip for those
- if [ $TRAVIS_PYTHON_VERSION != "pypy" && $TRAVIS_PYTHON_VERSION != "2.6" ]; then travis_retry pip install pyroma; fi
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
# Coverage 4.0 doesn't support Python 3.2
- if [ "$TRAVIS_PYTHON_VERSION" == "3.2" ]; then travis_retry pip install coverage==3.7.1; fi
- if [ "$TRAVIS_PYTHON_VERSION" != "3.2" ]; then travis_retry pip install coverage; fi
# clean checkout for manifest
- mkdir /tmp/check-manifest && cp -a . /tmp/check-manifest
# webp
- pushd depends && ./install_webp.sh && popd
@ -31,12 +44,16 @@ install:
- pushd depends && ./install_openjpeg.sh && popd
script:
- coverage erase
- if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage erase; fi
- python setup.py clean
- CFLAGS="-coverage" python setup.py build_ext --inplace
- coverage run --append --include=PIL/* selftest.py
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
- if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage run --append --include=PIL/* selftest.py; fi
- if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py; fi
- pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd
# Sphinx
- make install
- pushd docs; make html; make linkcheck; popd
after_success:
# gather the coverage data
@ -54,15 +71,15 @@ after_success:
- travis_retry pip install pep8 pyflakes
- pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py
- pep8 --statistics --count Tests/*.py
- pyflakes *.py | tee >(wc -l)
- pyflakes PIL/*.py | tee >(wc -l)
- pyflakes Tests/*.py | tee >(wc -l)
# Coverage and quality reports on just the latest diff.
# (Installation is very slow on Py3, so just do it for Py2.)
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-install.sh; fi
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-run.sh; fi
# after_all
- |
@ -104,6 +121,8 @@ after_script:
matrix:
fast_finish: true
allow_failures:
- python: nightly
env:
global:

View File

@ -1,13 +1,480 @@
Changelog (Pillow)
==================
2.9.0 (Unreleased)
3.3.0 (unreleased)
------------------
- Added Support for 2/4 bpp Tiff Grayscale Images #1789
[zwhfly]
- Removed unused variable from selftest #1788
[radarhere]
- Added warning for as_dict method (deprecated in 3.0.0) #1799
[radarhere]
- Removed powf support for older Python versions #1784
[radarhere]
- Health fixes #1625
[radarhere]
3.2.0 (2016-04-01)
------------------
- Added install docs for Fedora 23 and FreeBSD #1729, #1739, #1792
[koobs, zandermartin, wiredfool]
- Fixed TIFF multiframe load when the frames have different compression types #1782
[radarhere, geka000]
- Added __copy__ method to Image #1772
[radarhere]
- Updated dates in PIL license in OleFileIO README #1787
[radarhere]
- Corrected Tiff tag names #1786
[radarhere]
- Fixed documented name of JPEG property #1783
[radarhere]
- Fixed UnboundLocalErrorwhen loading a corrupt jpeg2k file #1780
[wiredfool]
- Fixed integer overflow in path.c #1773
[wiredfool, nedwill]
- Added debug to command line help text for pilprint #1766
[radarhere]
- Expose many more fields in ICC Profiles #1756
[lambdafu]
- Documentation changes, URL update, transpose, release checklist
[radarhere]
- Fixed saving to nonexistant files specified by pathlib.Path objects, fixes #1747
[radarhere]
- Round Image.crop arguments to the nearest integer, fixes #1744
[hugovk]
- Fix uninitialized variable warning in _imaging.c:getink, fixes #486
[wiredfool]
- Disable multiprocessing install on cygwin, fixes #1690
[wiredfool]
- Fix the error reported when libz is not found #1764
[wiredfool]
- More general error check to avoid Symbol not found: _PyUnicodeUCS2_AsLatin1String on OS X #1761
[wiredfool]
- Added py35 to tox envlist #1724
[radarhere]
- Fix EXIF tag name typos #1736
[zarlant, radarhere]
- Updated freetype to 2.6.3, Tk/Tcl to 8.6.5 and 8.5.19
[radarhere]
- Add a loader for the FTEX format from Independence War 2: Edge of Chaos #1688
[jleclanche]
- Improved alpha_composite documentation #1698
[radarhere]
- Extend ImageDraw.text method to pass on multiline_text method specific arguments #1647
[radarhere]
- Allow ImageSequence to seek to zero #1686
[radarhere]
- ImageSequence Iterator is now an iterator #1649
[radarhere]
- Updated windows test builds to jpeg9b
[radarhere]
- Fixed support for .gbr version 1 images, added support for version 2 in GbrImagePlugin #1653
[wiredfool]
- Clarified which YCbCr format is used #1677
[radarhere]
- Added TiffTags documentation, Moved windows build documentation to winbuild/ #1667
[wiredfool]
- Add tests for OLE file based formats #1678
[radarhere]
- Add TIFF IFD test #1671
[radarhere]
- Add a basic DDS image plugin with more tests #1654
[jleclanche, hugovk, wiredfool]
- Fix incorrect conditional in encode.c #1638
[manisandro]
3.1.2 (2016-04-01)
------------------
- Fixed an integer overflow in Jpeg2KEncode.c causing a buffer overflow. CVE-2016-3076
[wiredfool]
3.1.1 (2016-02-04)
------------------
- Fixed an integer overflow in Resample.c causing writes in the Python heap.
[nedwill]
- Fixed a buffer overflow in PcdDecode.c causing a segfault when opening PhotoCD files. CVE-2016-2533
[wiredfool]
- Fixed a buffer overflow in FliDecode.c causing a segfault when opening FLI files. CVE-2016-0775
[wiredfool]
- Fixed a buffer overflow in TiffDecode.c causing an arbitrary amount of memory to be overwritten when opening a specially crafted invalid TIFF file. CVE-2016-0740
[wiredfool]
3.1.0 (2016-01-04)
------------------
- Fixing test failures on Python 2.6/Windows #1633
[wiredfool]
- Limit metadata tags when writing using libtiff #1620
[wiredfool]
- Rolling back exif support to pre-3.0 format #1627
[wiredfool]
- Fix Divide by zero in Exif, add IFDRational class #1531
[wiredfool]
- Catch the IFD error near the source #1622
[wiredfool]
- Added release notes for 3.1.0 #1623
[radarhere]
- Updated spacing to be consistent between multiline methods #1624
[radarhere]
- Let EditorConfig take care of some basic formatting #1489
[hugovk]
- Restore gpsexif data to the v1 form
[wiredfool]
- Add /usr/local include and library directories for freebsd #1613
[leforestier]
- Updated installation docs for new versions of dependencies #1611
[radarhere]
- Removed unrunnable test file #1610
[radarhere]
- Changed register calls to use format property #1608
[radarhere]
- Added field type constants to TiffTags #1596
[radarhere]
- Allow saving RowsPerStrip with libtiff #1594
[wiredfool]
- Enabled conversion to numpy array for HSV images #1578
[cartisan]
- Changed some urls in the docs to use https #1580
[hugovk]
- Removed logger.exception from ImageFile.py #1590
[radarhere]
- Removed warnings module check #1587
[radarhere]
- Changed arcs, chords and pie slices to use floats #1577
[radarhere]
- Update unit test asserts #1584, #1598
[radarhere]
- Fix command to invoke ghostscript for eps files #1478
[baumatron, radarhere]
- Consistent multiline text spacing #1574
[wiredfool, hugovk]
- Removed unused lines in BDFFontFile #1530
[radarhere]
- Changed ImageQt import of Image #1560
[radarhere, ericfrederich]
- Throw TypeError if no cursors were found in .cur file #1556
[radarhere]
- Fix crash in ImageTk.PhotoImage on win-amd64 #1553
[cgohlke]
- ExtraSamples tag should be a SHORT, not a BYTE #1555
[Nexuapex]
- Docs and code health fixes #1565 #1566 #1581 #1586 #1591 #1621
[radarhere]
- Updated freetype to 2.6.2 #1564
[radarhere]
- Updated WebP to 0.5.0 for Travis #1515 #1609
[radarhere]
- Fix missing 'version' key value in __array_interface__ #1519
[mattip]
- Replaced os.popen with subprocess.Popen to pilprint script #1523
[radarhere]
- Catch OverflowError in SpiderImagePlugin #1545
[radarhere, MrShark]
- Fix the definition of icc_profile in TiffTags #1539
[wiredfool]
- Remove old _imagingtiff.c and pilplus stuff #1499
[hugovk]
- Fix Exception when requiring jpeg #1501
[hansmosh]
- Dependency scripts for Debian and Ubuntu #1486
[wiredfool]
- Added Usage message to painter script #1482
[radarhere]
- Add tag info for iccprofile, fixes #1462. #1465
[wiredfool]
- Added some requirements for make release-test #1451
[wiredfool]
- Flatten tiff metadata value SAMPLEFORMAT to initial value, fixes #1466
[wiredfool]
- Fix handling of pathlib in Image.save. Fixes #1460
[wiredfool]
- Make tests more robust #1469
[hugovk]
- Use correctly sized pointers for windows handle types. #1458
[nu744]
3.0.0 (2015-10-01)
------------------
- Check flush method existence for file-like object #1398
[mrTable, radarhere]
- Added PDF multipage saving #1445
[radarhere]
- Removed deprecated code, Image.tostring, Image.fromstring, Image.offset, ImageDraw.setink, ImageDraw.setfill, ImageFileIO, ImageFont.FreeTypeFont and ImageFont.truetype `file` kwarg, ImagePalette private _make functions, ImageWin.fromstring and ImageWin.tostring #1343
[radarhere]
- Load more broken images #1428
[homm]
- Require zlib and libjpeg #1439
[wiredfool]
- Preserve alpha when converting from a QImage to a Pillow Image by using png instead of ppm #1429
[ericfrederich]
- Qt needs 32 bit aligned image data #1430
[ericfrederich]
- Tiff ImageFileDirectory rewrite #1419
[anntzer, wiredfool, homm]
- Removed spammy debug logging #1423
[wiredfool]
- Save as GiF89a with support for animation parameters #1384
[radarhere]
- Correct convert matrix docs #1426
[wiredfool]
- Catch TypeError in _getexif #1414
[radarhere, wiredfool]
- Fix for UnicodeDecodeError in TiffImagePlugin #1416
[bogdan199, wiredfool]
- Dedup code in image.open #1415
[wiredfool]
- Skip any number extraneous chars at the end of JPEG chunks #1337
[homm]
- Single threaded build for pypy3, refactor #1413
[wiredfool]
- Fix loading of truncated images with LOAD_TRUNCATED_IMAGES enabled #1366
[homm]
- Documentation update for concepts: bands
[merriam]
- Add Solaris/SmartOS include and library directories #1356
[njones11]
- Improved handling of getink color #1387
[radarhere]
- Disable compiler optimizations for topalette and tobilevel functions for all msvc versions, fixes #1357
[cgohlke]
- Skip ImageFont_bitmap test if _imagingft C module is not installed #1409
[homm]
- Add param documentation to ImagePalette #1381
[bwrsandman]
- Corrected scripts path #1407
[radarhere]
- Updated libtiff to 4.0.6 #1405, #1421
[radarhere]
- Updated Platform Support for Yosemite #1403
[radarhere]
- Fixed infinite loop on truncated file #1401
[radarhere]
- Check that images are L mode in ImageMorph methods #1400
[radarhere]
- In tutorial of pasting images, add to mask text #1389
[merriam]
- Style/health fixes #1391, #1397, #1417, #1418
[radarhere]
- Test on Python 3.5 dev and 3.6 nightly #1361
[hugovk]
- Fix fast rotate operations #1373
[radarhere]
- Added support for pathlib Path objects to open and save #1372
[radarhere]
- Changed register calls to use format property #1333
[radarhere]
- Added support for ImageGrab.grab to OS X #1367, #1443
[radarhere, hugovk]
- Fixed PSDraw stdout Python 3 compatibility #1365
[radarhere]
- Added Python 3.3 to AppVeyor #1363
[radarhere]
- Treat MPO with unknown header as base JPEG file #1350
[hugovk, radarhere]
- Added various tests #1330, #1344
[radarhere]
- More ImageFont tests #1327
[hugovk]
- Use logging instead of print #1207
[anntzer]
2.9.0 (2015-07-01)
------------------
- Added test for GimpPaletteFile #1324
[radarhere]
- Merged gifmaker script to allow saving of multi-frame GIF images #1320
[radarhere]
- Added is_animated property to multi-frame formats #1319
[radarhere]
- Fixed ValueError in Python 2.6 #1315 #1316
[cgohlke, radarhere]
- Fixed tox test script path #1308
[radarhere]
- Added width and height properties #1304
[radarhere]
- Update tiff and tk tcl 8.5 versions #1303
[radarhere, wiredfool]
- Add functions to convert: Image <-> QImage; Image <-> QPixmap #1217
[radarhere, rominf]
- Remove duplicate code in gifmaker script #1294
[radarhere]
- Multiline text in ImageDraw #1177
[allo-, radarhere]
- Automated Windows CI/build support #1278
[wiredfool]
- Removed support for Tk versions earlier than 8.4 #1288
[radarhere]
- Fixed polygon edge drawing #1255 (fixes #1252)
[radarhere]
- Check prefix length in _accept methods #1267
[radarhere]
- Register MIME type for BMP #1277
[coldmind]
- Adjusted ImageQt use of unicode() for 2/3 compatibility #1218
[radarhere]
- Identify XBM file created with filename including underscore #1230 (fixes #1229)
[hugovk]
- Copy image when saving in GifImagePlugin #1231 (fixes #718)
[radarhere]
- Removed support for FreeType 2.0 #1247
[radarhere]
- Added background saving to GifImagePlugin #1273
[radarhere]
- Provide n_frames attribute to multi-frame formats #1261
[anntzer, radarhere]
- Add duration and loop set to GifImagePlugin #1172
- Add duration and loop set to GifImagePlugin #1172, #1269
[radarhere]
- Ico files are little endian #1232
@ -121,7 +588,7 @@ Changelog (Pillow)
- Split Sane into a separate repo: https://github.com/python-pillow/Sane
[hugovk]
- Look for OSX and Linux fonts in common places. #1054
- Look for OS X and Linux fonts in common places. #1054
[charleslaw]
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
@ -729,7 +1196,7 @@ Changelog (Pillow)
- Fix #328: _imagingcms.c: include windef.h to fix build issue on MSVC
[nu774]
- Automatically discover homebrew include/ and lib/ paths on OSX
- Automatically discover homebrew include/ and lib/ paths on OS X
[donspaulding]
- Fix bytes which should be bytearray
@ -861,7 +1328,7 @@ Changelog (Pillow)
- Use PyCapsule for py3.1, fixes #237.
- Workaround for: http://bugs.python.org/16754 in 3.2.x < 3.2.4 and 3.3.0.
- Workaround for: http://bugs.python.org/issue16754 in 3.2.x < 3.2.4 and 3.3.0.
2.0.0 (2013-03-15)
------------------
@ -884,7 +1351,7 @@ Changelog (Pillow)
- Backport PIL's PNG/Zip improvements.
[olt]
- Various 64 bit and Windows fixes.
- Various 64-bit and Windows fixes.
[cgohlke]
- Add testing suite.
@ -1013,10 +1480,11 @@ Changelog (Pillow)
- Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
[aclark4life]
.. Note:: What follows is the original PIL 1.1.7 CHANGES
Pre-fork
--------
0.2b5 - 1.1.7 (1995-2010)
-------------------------
0.2b5-1.1.7
+++++++++++
::
@ -1055,7 +1523,7 @@ Changelog (Pillow)
This section may not be fully complete. For changes since this file
was last updated, see the repository revision history:
http://bitbucket.org/effbot/pil-2009-raclette/changesets/
https://bitbucket.org/effbot/pil-2009-raclette/commits/all
(1.1.7 final)
@ -2028,7 +2496,7 @@ Changelog (Pillow)
+ Added experimental "RGBa" mode support.
An "RGBa" image is an RGBA image where the colour components
have have been premultipled with the alpha value. PIL allows
have have been premultiplied with the alpha value. PIL allows
you to convert an RGBA image to an RGBa image, and to paste
RGBa images on top of RGB images. Since this saves a bunch
of multiplications and shifts, it is typically about twice

View File

@ -3,6 +3,12 @@ The Python Imaging Library (PIL) is
Copyright © 1997-2011 by Secret Labs AB
Copyright © 1995-2011 by Fredrik Lundh
Pillow is the friendly PIL fork. It is
Copyright © 2016 by Alex Clark and contributors
Like PIL, Pillow is licensed under the MIT-like open source PIL Software License:
By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission.

View File

@ -1,80 +1,31 @@
include *.c
include *.h
include *.in
include *.md
include *.py
include *.sh
include *.rst
include *.sh
include *.txt
include *.yaml
include .coveragerc
include .gitattributes
include .travis.yml
include LICENSE
include Makefile
include tox.ini
recursive-include PIL *.md
recursive-include Scripts *.py
recursive-include Scripts *.rst
recursive-include Scripts *.sh
recursive-include Scripts README.rst
recursive-include Tests *.bdf
recursive-include Tests *.bin
recursive-include Tests *.bmp
recursive-include Tests *.bw
recursive-include Tests *.cur
recursive-include Tests *.dcx
recursive-include Tests *.doc
recursive-include Tests *.eps
recursive-include Tests *.fli
recursive-include Tests *.ggr
recursive-include Tests *.gif
recursive-include Tests *.gnuplot
recursive-include Tests *.html
recursive-include Tests *.icc
recursive-include Tests *.icns
recursive-include Tests *.ico
recursive-include Tests *.j2k
recursive-include Tests *.jp2
recursive-include Tests *.jpg
recursive-include Tests *.lut
recursive-include Tests *.mpo
recursive-include Tests *.pbm
recursive-include Tests *.pcf
recursive-include Tests *.pcx
recursive-include Tests *.pgm
recursive-include Tests *.pil
recursive-include Tests *.png
recursive-include Tests *.ppm
recursive-include Tests *.psd
recursive-include Tests *.py
recursive-include Tests *.ras
recursive-include Tests *.rgb
recursive-include Tests *.rst
recursive-include Tests *.sgi
recursive-include Tests *.spider
recursive-include Tests *.tar
recursive-include Tests *.tga
recursive-include Tests *.tif
recursive-include Tests *.tiff
recursive-include Tests *.ttf
recursive-include Tests *.txt
recursive-include Tests *.webp
recursive-include Tests *.xpm
recursive-include Tests *.msp
recursive-include Tk *.c
recursive-include Tk *.rst
recursive-include depends *.rst
recursive-include depends *.sh
recursive-include docs *.bat
recursive-include docs *.gitignore
recursive-include docs *.html
recursive-include docs *.py
recursive-include docs *.rst
recursive-include docs *.txt
recursive-include docs BUILDME
recursive-include docs COPYING
recursive-include docs Guardfile
recursive-include docs LICENSE
recursive-include docs Makefile
recursive-include libImaging *.c
recursive-include libImaging *.h
graft Scripts
graft Tests
graft PIL
graft Tk
graft libImaging
graft depends
graft winbuild
graft docs
prune docs/_static
# build/src control detritus
exclude .coveragerc
exclude .editorconfig
exclude .landscape.yaml
exclude appveyor.yml
exclude build_children.sh
exclude tox.ini
global-exclude .git*
global-exclude *.pyc
global-exclude *.so

View File

@ -1,5 +1,6 @@
# 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
.DEFAULT_GOAL := release-test
clean:
python setup.py clean
@ -7,6 +8,12 @@ clean:
rm -r build || true
find . -name __pycache__ | xargs rm -r || true
BRANCHES=`git branch -a | grep -v HEAD | grep -v master | grep remote`
co:
-for i in $(BRANCHES) ; do \
git checkout -t $$i ; \
done
coverage:
coverage erase
coverage run --parallel-mode --include=PIL/* selftest.py
@ -32,6 +39,7 @@ help:
@echo " inplace make inplace extension"
@echo " install make and install"
@echo " install-req install documentation and test dependencies"
@echo " install-venv install in virtualenv"
@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"
@ -47,6 +55,10 @@ install:
install-req:
pip install -r requirements.txt
install-venv:
virtualenv .
bin/pip install -r requirements.txt
release-test:
$(MAKE) install-req
python setup.py develop
@ -74,3 +86,6 @@ upload-test:
upload:
python setup.py sdist --format=gztar,zip upload
readme:
viewdoc

View File

@ -109,10 +109,10 @@ class BdfFontFile(FontFile.FontFile):
if s.find(b"LogicalFontDescription") < 0:
comments.append(s[i+1:-1].decode('ascii'))
font = props["FONT"].split("-")
# font = props["FONT"].split("-")
font[4] = bdf_slant[font[4].upper()]
font[11] = bdf_spacing[font[11].upper()]
# font[4] = bdf_slant[font[4].upper()]
# font[11] = bdf_spacing[font[11].upper()]
# ascent = int(props["FONT_ASCENT"])
# descent = int(props["FONT_DESCENT"])
@ -123,7 +123,6 @@ class BdfFontFile(FontFile.FontFile):
# for i in comments:
# print "#", i
font = []
while True:
c = bdf_char(fp)
if not c:

View File

@ -24,12 +24,10 @@
#
__version__ = "0.7"
from PIL import Image, ImageFile, ImagePalette, _binary
import math
__version__ = "0.7"
i8 = _binary.i8
i16 = _binary.i16le
@ -103,7 +101,9 @@ class BmpImageFile(ImageFile.ImageFile):
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']))
self.info["dpi"] = tuple(
map(lambda x: int(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']):
@ -130,13 +130,18 @@ class BmpImageFile(ImageFile.ImageFile):
# ----------------- 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)]}
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"}
(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'])]
@ -279,3 +284,5 @@ Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
Image.register_save(BmpImageFile.format, _save)
Image.register_extension(BmpImageFile.format, ".bmp")
Image.register_mime(BmpImageFile.format, "image/bmp")

View File

@ -17,10 +17,9 @@
#
__version__ = "0.1"
from PIL import Image, BmpImagePlugin, _binary
__version__ = "0.1"
#
# --------------------------------------------------------------------
@ -67,6 +66,8 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
# print "hotspot y", i16(s[6:])
# print "bytes", i32(s[8:])
# print "offset", i32(s[12:])
if not m:
raise TypeError("No cursors were found")
# load as bitmap
self._bitmap(i32(m[12:]) + offset)
@ -82,6 +83,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
#
# --------------------------------------------------------------------
Image.register_open("CUR", CurImageFile, _accept)
Image.register_open(CurImageFile.format, CurImageFile, _accept)
Image.register_extension("CUR", ".cur")
Image.register_extension(CurImageFile.format, ".cur")

View File

@ -21,19 +21,18 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.2"
from PIL import Image, _binary
from PIL.PcxImagePlugin import PcxImageFile
__version__ = "0.2"
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
i32 = _binary.i32le
def _accept(prefix):
return i32(prefix) == MAGIC
return len(prefix) >= 4 and i32(prefix) == MAGIC
##
@ -66,6 +65,10 @@ class DcxImageFile(PcxImageFile):
def n_frames(self):
return len(self._offset)
@property
def is_animated(self):
return len(self._offset) > 1
def seek(self, frame):
if frame >= len(self._offset):
raise EOFError("attempt to seek outside DCX directory")
@ -78,6 +81,6 @@ class DcxImageFile(PcxImageFile):
return self.frame
Image.register_open("DCX", DcxImageFile, _accept)
Image.register_open(DcxImageFile.format, DcxImageFile, _accept)
Image.register_extension("DCX", ".dcx")
Image.register_extension(DcxImageFile.format, ".dcx")

268
PIL/DdsImagePlugin.py Normal file
View File

@ -0,0 +1,268 @@
"""
A Pillow loader for .dds files (S3TC-compressed aka DXTC)
Jerome Leclanche <jerome@leclan.ch>
Documentation:
http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
The contents of this file are hereby released in the public domain (CC0)
Full text of the CC0 license:
https://creativecommons.org/publicdomain/zero/1.0/
"""
import struct
from io import BytesIO
from PIL import Image, ImageFile
# Magic ("DDS ")
DDS_MAGIC = 0x20534444
# DDS flags
DDSD_CAPS = 0x1
DDSD_HEIGHT = 0x2
DDSD_WIDTH = 0x4
DDSD_PITCH = 0x8
DDSD_PIXELFORMAT = 0x1000
DDSD_MIPMAPCOUNT = 0x20000
DDSD_LINEARSIZE = 0x80000
DDSD_DEPTH = 0x800000
# DDS caps
DDSCAPS_COMPLEX = 0x8
DDSCAPS_TEXTURE = 0x1000
DDSCAPS_MIPMAP = 0x400000
DDSCAPS2_CUBEMAP = 0x200
DDSCAPS2_CUBEMAP_POSITIVEX = 0x400
DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800
DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000
DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000
DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000
DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000
DDSCAPS2_VOLUME = 0x200000
# Pixel Format
DDPF_ALPHAPIXELS = 0x1
DDPF_ALPHA = 0x2
DDPF_FOURCC = 0x4
DDPF_PALETTEINDEXED8 = 0x20
DDPF_RGB = 0x40
DDPF_LUMINANCE = 0x20000
# dds.h
DDS_FOURCC = DDPF_FOURCC
DDS_RGB = DDPF_RGB
DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS
DDS_LUMINANCE = DDPF_LUMINANCE
DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
DDS_ALPHA = DDPF_ALPHA
DDS_PAL8 = DDPF_PALETTEINDEXED8
DDS_HEADER_FLAGS_TEXTURE = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH |
DDSD_PIXELFORMAT)
DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE
DDS_HEIGHT = DDSD_HEIGHT
DDS_WIDTH = DDSD_WIDTH
DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE
DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX
DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
# DXT1
DXT1_FOURCC = 0x31545844
# DXT3
DXT3_FOURCC = 0x33545844
# DXT5
DXT5_FOURCC = 0x35545844
def _decode565(bits):
a = ((bits >> 11) & 0x1f) << 3
b = ((bits >> 5) & 0x3f) << 2
c = (bits & 0x1f) << 3
return a, b, c
def _c2a(a, b):
return (2 * a + b) // 3
def _c2b(a, b):
return (a + b) // 2
def _c3(a, b):
return (2 * b + a) // 3
def _dxt1(data, width, height):
# TODO implement this function as pixel format in decode.c
ret = bytearray(4 * width * height)
for y in range(0, height, 4):
for x in range(0, width, 4):
color0, color1, bits = struct.unpack("<HHI", data.read(8))
r0, g0, b0 = _decode565(color0)
r1, g1, b1 = _decode565(color1)
# Decode this block into 4x4 pixels
for j in range(4):
for i in range(4):
# get next control op and generate a pixel
control = bits & 3
bits = bits >> 2
if control == 0:
r, g, b = r0, g0, b0
elif control == 1:
r, g, b = r1, g1, b1
elif control == 2:
if color0 > color1:
r, g, b = _c2a(r0, r1), _c2a(g0, g1), _c2a(b0, b1)
else:
r, g, b = _c2b(r0, r1), _c2b(g0, g1), _c2b(b0, b1)
elif control == 3:
if color0 > color1:
r, g, b = _c3(r0, r1), _c3(g0, g1), _c3(b0, b1)
else:
r, g, b = 0, 0, 0
idx = 4 * ((y + j) * width + (x + i))
ret[idx:idx+4] = struct.pack('4B', r, g, b, 255)
return bytes(ret)
def _dxtc_alpha(a0, a1, ac0, ac1, ai):
if ai <= 12:
ac = (ac0 >> ai) & 7
elif ai == 15:
ac = (ac0 >> 15) | ((ac1 << 1) & 6)
else:
ac = (ac1 >> (ai - 16)) & 7
if ac == 0:
alpha = a0
elif ac == 1:
alpha = a1
elif a0 > a1:
alpha = ((8 - ac) * a0 + (ac - 1) * a1) // 7
elif ac == 6:
alpha = 0
elif ac == 7:
alpha = 0xff
else:
alpha = ((6 - ac) * a0 + (ac - 1) * a1) // 5
return alpha
def _dxt5(data, width, height):
# TODO implement this function as pixel format in decode.c
ret = bytearray(4 * width * height)
for y in range(0, height, 4):
for x in range(0, width, 4):
a0, a1, ac0, ac1, c0, c1, code = struct.unpack("<2BHI2HI",
data.read(16))
r0, g0, b0 = _decode565(c0)
r1, g1, b1 = _decode565(c1)
for j in range(4):
for i in range(4):
ai = 3 * (4 * j + i)
alpha = _dxtc_alpha(a0, a1, ac0, ac1, ai)
cc = (code >> 2 * (4 * j + i)) & 3
if cc == 0:
r, g, b = r0, g0, b0
elif cc == 1:
r, g, b = r1, g1, b1
elif cc == 2:
r, g, b = _c2a(r0, r1), _c2a(g0, g1), _c2a(b0, b1)
elif cc == 3:
r, g, b = _c3(r0, r1), _c3(g0, g1), _c3(b0, b1)
idx = 4 * ((y + j) * width + (x + i))
ret[idx:idx+4] = struct.pack('4B', r, g, b, alpha)
return bytes(ret)
class DdsImageFile(ImageFile.ImageFile):
format = "DDS"
format_description = "DirectDraw Surface"
def _open(self):
magic, header_size = struct.unpack("<II", self.fp.read(8))
if header_size != 124:
raise IOError("Unsupported header size %r" % (header_size))
header_bytes = self.fp.read(header_size - 4)
if len(header_bytes) != 120:
raise IOError("Incomplete header: %s bytes" % len(header_bytes))
header = BytesIO(header_bytes)
flags, height, width = struct.unpack("<3I", header.read(12))
self.size = (width, height)
self.mode = "RGBA"
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
reserved = struct.unpack("<11I", header.read(44))
# pixel format
pfsize, pfflags = struct.unpack("<2I", header.read(8))
fourcc = header.read(4)
bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I",
header.read(20))
self.tile = [
("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))
]
if fourcc == b"DXT1":
self.pixel_format = "DXT1"
codec = _dxt1
elif fourcc == b"DXT5":
self.pixel_format = "DXT5"
codec = _dxt5
else:
raise NotImplementedError("Unimplemented pixel format %r" %
(fourcc))
try:
decoded_data = codec(self.fp, self.width, self.height)
except struct.error:
raise IOError("Truncated DDS file")
finally:
self.fp.close()
self.fp = BytesIO(decoded_data)
def load_seek(self, pos):
pass
def _validate(prefix):
return prefix[:4] == b"DDS "
Image.register_open(DdsImageFile.format, DdsImageFile, _validate)
Image.register_extension(DdsImageFile.format, ".dds")

View File

@ -20,12 +20,13 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.5"
import re
import io
import sys
from PIL import Image, ImageFile, _binary
__version__ = "0.5"
#
# --------------------------------------------------------------------
@ -36,7 +37,6 @@ split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
gs_windows_binary = None
import sys
if sys.platform.startswith('win'):
import shutil
if hasattr(shutil, 'which'):
@ -123,8 +123,8 @@ def Ghostscript(tile, size, fp, scale=1):
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages,
# safe mode
"-dNOPAUSE", # don't pause between pages,
"-dSAFER", # safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
@ -151,7 +151,7 @@ def Ghostscript(tile, size, fp, scale=1):
os.unlink(outfile)
if infile_temp:
os.unlink(infile_temp)
except:
except OSError:
pass
return im
@ -187,7 +187,8 @@ class PSFile(object):
def _accept(prefix):
return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5
return prefix[:4] == b"%!PS" or \
(len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
##
# Image plugin for Encapsulated Postscript. This plugin supports only
@ -294,7 +295,7 @@ class EpsImageFile(ImageFile.ImageFile):
break
try:
self.mode = self.mode_map[int(mo)]
except:
except ValueError:
break
self.size = int(x), int(y)
@ -376,9 +377,10 @@ def _save(im, fp, filename, eps=1):
pass
base_fp = fp
fp = NoCloseStream(fp)
if sys.version_info[0] > 2:
fp = io.TextIOWrapper(fp, encoding='latin-1')
if fp != sys.stdout:
fp = NoCloseStream(fp)
if sys.version_info[0] > 2:
fp = io.TextIOWrapper(fp, encoding='latin-1')
if eps:
#
@ -403,13 +405,15 @@ def _save(im, fp, filename, eps=1):
fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
fp.write("{ currentfile buf readhexstring pop } bind\n")
fp.write(operator[2] + "\n")
fp.flush()
if hasattr(fp, "flush"):
fp.flush()
ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
fp.write("\n%%%%EndBinary\n")
fp.write("grestore end\n")
fp.flush()
if hasattr(fp, "flush"):
fp.flush()
#
# --------------------------------------------------------------------

View File

@ -27,9 +27,9 @@ TAGS = {
0x0102: "BitsPerSample",
0x0103: "Compression",
0x0106: "PhotometricInterpretation",
0x0107: "Threshholding",
0x0107: "Thresholding",
0x0108: "CellWidth",
0x0109: "CellLenght",
0x0109: "CellLength",
0x010a: "FillOrder",
0x010d: "DocumentName",
0x011d: "PageName",
@ -40,7 +40,7 @@ TAGS = {
0x0112: "Orientation",
0x0115: "SamplesPerPixel",
0x0116: "RowsPerStrip",
0x0117: "StripByteConunts",
0x0117: "StripByteCounts",
0x0118: "MinSampleValue",
0x0119: "MaxSampleValue",
0x011a: "XResolution",

View File

@ -16,10 +16,10 @@
#
__version__ = "0.2"
from PIL import Image, ImageFile, ImagePalette, _binary
__version__ = "0.2"
i8 = _binary.i8
i16 = _binary.i16le
i32 = _binary.i32le
@ -30,7 +30,7 @@ o8 = _binary.o8
# decoder
def _accept(prefix):
return i16(prefix[4:6]) in [0xAF11, 0xAF12]
return len(prefix) >= 6 and i16(prefix[4:6]) in [0xAF11, 0xAF12]
##
@ -90,6 +90,7 @@ class FliImageFile(ImageFile.ImageFile):
self.__fp = self.fp
self.__rewind = self.fp.tell()
self._n_frames = None
self._is_animated = None
self.seek(0)
def _palette(self, palette, shift):
@ -122,13 +123,33 @@ class FliImageFile(ImageFile.ImageFile):
self.seek(current)
return self._n_frames
@property
def is_animated(self):
if self._is_animated is None:
current = self.tell()
try:
self.seek(1)
self._is_animated = True
except EOFError:
self._is_animated = False
self.seek(current)
return self._is_animated
def seek(self, frame):
if frame == self.__frame:
return
if frame < self.__frame:
self._seek(0)
last_frame = self.__frame
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
try:
self._seek(f)
except EOFError:
self.seek(last_frame)
raise EOFError("no more images in FLI file")
def _seek(self, frame):
if frame == 0:
@ -161,7 +182,7 @@ class FliImageFile(ImageFile.ImageFile):
#
# registry
Image.register_open("FLI", FliImageFile, _accept)
Image.register_open(FliImageFile.format, FliImageFile, _accept)
Image.register_extension("FLI", ".fli")
Image.register_extension("FLI", ".flc")
Image.register_extension(FliImageFile.format, ".fli")
Image.register_extension(FliImageFile.format, ".flc")

View File

@ -16,12 +16,11 @@
#
__version__ = "0.1"
from PIL import Image, ImageFile
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
__version__ = "0.1"
# we map from colour field tuples to (mode, rawmode) descriptors
MODES = {
@ -222,6 +221,6 @@ class FpxImageFile(ImageFile.ImageFile):
#
# --------------------------------------------------------------------
Image.register_open("FPX", FpxImageFile, _accept)
Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
Image.register_extension("FPX", ".fpx")
Image.register_extension(FpxImageFile.format, ".fpx")

96
PIL/FtexImagePlugin.py Normal file
View File

@ -0,0 +1,96 @@
"""
A Pillow loader for .ftc and .ftu files (FTEX)
Jerome Leclanche <jerome@leclan.ch>
The contents of this file are hereby released in the public domain (CC0)
Full text of the CC0 license:
https://creativecommons.org/publicdomain/zero/1.0/
Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
packed custom format called FTEX. This file format uses file extensions FTC and FTU.
* FTC files are compressed textures (using standard texture compression).
* FTU files are not compressed.
Texture File Format
The FTC and FTU texture files both use the same format, called. This
has the following structure:
{header}
{format_directory}
{data}
Where:
{header} = { u32:magic, u32:version, u32:width, u32:height, u32:mipmap_count, u32:format_count }
* The "magic" number is "FTEX".
* "width" and "height" are the dimensions of the texture.
* "mipmap_count" is the number of mipmaps in the texture.
* "format_count" is the number of texture formats (different versions of the same texture) in this file.
{format_directory} = format_count * { u32:format, u32:where }
The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB uncompressed textures.
The texture data for a format starts at the position "where" in the file.
Each set of texture data in the file has the following structure:
{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
* "mipmap_size" is the number of bytes in that mip level. For compressed textures this is the
size of the texture data compressed with DXT1. For 24 bit uncompressed textures, this is 3 * width * height.
Following this are the image bytes for that mipmap level.
Note: All data is stored in little-Endian (Intel) byte order.
"""
import struct
from io import BytesIO
from PIL import Image, ImageFile
from PIL.DdsImagePlugin import _dxt1
MAGIC = b"FTEX"
FORMAT_DXT1 = 0
FORMAT_UNCOMPRESSED = 1
class FtexImageFile(ImageFile.ImageFile):
format = "FTEX"
format_description = "Texture File Format (IW2:EOC)"
def _open(self):
magic = struct.unpack("<I", self.fp.read(4))
version = struct.unpack("<i", self.fp.read(4))
self.size = struct.unpack("<2i", self.fp.read(8))
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
self.mode = "RGB"
# Only support single-format files. I don't know of any multi-format file.
assert format_count == 1
format, where = struct.unpack("<2i", self.fp.read(8))
self.fp.seek(where)
mipmap_size, = struct.unpack("<i", self.fp.read(4))
data = self.fp.read(mipmap_size)
if format == FORMAT_DXT1:
data = _dxt1(BytesIO(data), self.width, self.height)
self.tile = [("raw", (0, 0) + self.size, 0, ('RGBX', 0, 1))]
elif format == FORMAT_UNCOMPRESSED:
self.tile = [("raw", (0, 0) + self.size, 0, ('RGB', 0, 1))]
else:
raise ValueError("Invalid texture compression format: %r" % (format))
self.fp.close()
self.fp = BytesIO(data)
def load_seek(self, pos):
pass
def _validate(prefix):
return prefix[:4] == MAGIC
Image.register_open(FtexImageFile.format, FtexImageFile, _validate)
Image.register_extension(FtexImageFile.format, ".ftc")
Image.register_extension(FtexImageFile.format, ".ftu")

View File

@ -1,17 +1,28 @@
#
# The Python Imaging Library
# $Id$
#
# load a GIMP brush file
#
# History:
# 96-03-14 fl Created
# 16-01-08 es Version 2
#
# Copyright (c) Secret Labs AB 1997.
# Copyright (c) Fredrik Lundh 1996.
# Copyright (c) Eric Soroos 2016.
#
# See the README file for information on usage and redistribution.
#
#
# See https://github.com/GNOME/gimp/blob/master/devel-docs/gbr.txt for
# format documentation.
#
# This code Interprets version 1 and 2 .gbr files.
# Version 1 files are obsolete, and should not be used for new
# brushes.
# Version 2 files are saved by GIMP v2.8 (at least)
# Version 3 files have a format specifier of 18 for 16bit floats in
# the color depth field. This is currently unsupported by Pillow.
from PIL import Image, ImageFile, _binary
@ -19,7 +30,7 @@ i32 = _binary.i32be
def _accept(prefix):
return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
##
@ -31,41 +42,53 @@ class GbrImageFile(ImageFile.ImageFile):
format_description = "GIMP brush file"
def _open(self):
header_size = i32(self.fp.read(4))
version = i32(self.fp.read(4))
if header_size < 20 or version != 1:
if header_size < 20:
raise SyntaxError("not a GIMP brush")
if version not in (1, 2):
raise SyntaxError("Unsupported GIMP brush version: %s" % version)
width = i32(self.fp.read(4))
height = i32(self.fp.read(4))
color_depth = i32(self.fp.read(4))
if width <= 0 or height <= 0 or color_depth != 1:
if width <= 0 or height <= 0:
raise SyntaxError("not a GIMP brush")
if color_depth not in (1, 4):
raise SyntaxError("Unsupported GMP brush color depth: %s" % color_depth)
comment = self.fp.read(header_size - 20)[:-1]
if version == 1:
comment_length = header_size-20
else:
comment_length = header_size-28
magic_number = self.fp.read(4)
if magic_number != b'GIMP':
raise SyntaxError("not a GIMP brush, bad magic number")
self.info['spacing'] = i32(self.fp.read(4))
comment = self.fp.read(comment_length)[:-1]
if color_depth == 1:
self.mode = "L"
else:
self.mode = 'RGBA'
self.mode = "L"
self.size = width, height
self.info["comment"] = comment
# Since the brush is so small, we read the data immediately
self.data = self.fp.read(width * height)
# Image might not be small
Image._decompression_bomb_check(self.size)
# Data is an uncompressed block of w * h * bytes/pixel
self._data_size = width * height * color_depth
def load(self):
if not self.data:
return
# create an image out of the brush data block
self.im = Image.core.new(self.mode, self.size)
self.im.frombytes(self.data)
self.data = b""
self.frombytes(self.fp.read(self._data_size))
#
# registry
Image.register_open("GBR", GbrImageFile, _accept)
Image.register_extension("GBR", ".gbr")
Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
Image.register_extension(GbrImageFile.format, ".gbr")

View File

@ -23,11 +23,11 @@
# purposes only.
__version__ = "0.1"
from PIL import ImageFile, ImagePalette, _binary
from PIL._util import isPath
__version__ = "0.1"
try:
import builtins
except ImportError:

View File

@ -24,7 +24,8 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image, ImageFile, ImagePalette, _binary
from PIL import Image, ImageFile, ImagePalette, \
ImageChops, ImageSequence, _binary
__version__ = "0.9"
@ -88,6 +89,7 @@ class GifImageFile(ImageFile.ImageFile):
self.__fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell()
self._n_frames = None
self._is_animated = None
self._seek(0) # get ready to read first frame
@property
@ -102,13 +104,33 @@ class GifImageFile(ImageFile.ImageFile):
self.seek(current)
return self._n_frames
@property
def is_animated(self):
if self._is_animated is None:
current = self.tell()
try:
self.seek(1)
self._is_animated = True
except EOFError:
self._is_animated = False
self.seek(current)
return self._is_animated
def seek(self, frame):
if frame == self.__frame:
return
if frame < self.__frame:
self._seek(0)
last_frame = self.__frame
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
try:
self._seek(f)
except EOFError:
self.seek(last_frame)
raise EOFError("no more images in GIF file")
def _seek(self, frame):
@ -241,7 +263,7 @@ class GifImageFile(ImageFile.ImageFile):
if not self.tile:
# self.__fp = None
raise EOFError("no more images in GIF file")
raise EOFError
self.mode = "L"
if self.palette:
@ -279,8 +301,27 @@ RAWMODE = {
}
def _save(im, fp, filename):
def _convert_mode(im, initial_call=False):
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
# should automatically convert images on save...)
if Image.getmodebase(im.mode) == "RGB":
if initial_call:
palette_size = 256
if im.palette:
palette_size = len(im.palette.getdata()[1]) // 3
return im.convert("P", palette=1, colors=palette_size)
else:
return im.convert("P")
return im.convert("L")
def _save_all(im, fp, filename):
_save(im, fp, filename, save_all=True)
def _save(im, fp, filename, save_all=False):
im.encoderinfo.update(im.info)
if _imaging_gif:
# call external driver
try:
@ -290,17 +331,9 @@ def _save(im, fp, filename):
pass # write uncompressed file
if im.mode in RAWMODE:
im_out = im
im_out = im.copy()
else:
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
# should automatically convert images on save...)
if Image.getmodebase(im.mode) == "RGB":
palette_size = 256
if im.palette:
palette_size = len(im.palette.getdata()[1]) // 3
im_out = im.convert("P", palette=1, colors=palette_size)
else:
im_out = im.convert("L")
im_out = _convert_mode(im, True)
# header
try:
@ -309,25 +342,63 @@ def _save(im, fp, filename):
palette = None
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
header, used_palette_colors = getheader(im_out, palette, im.encoderinfo)
for s in header:
fp.write(s)
if save_all:
previous = None
# local image header
get_local_header(fp, im)
first_frame = None
for im_frame in ImageSequence.Iterator(im):
im_frame = _convert_mode(im_frame)
im_out.encoderconfig = (8, get_interlace(im))
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
RAWMODE[im_out.mode])])
# To specify duration, add the time in milliseconds to getdata(),
# e.g. getdata(im_frame, duration=1000)
if not previous:
# global header
first_frame = getheader(im_frame, palette, im.encoderinfo)[0]
first_frame += getdata(im_frame, (0, 0), **im.encoderinfo)
else:
if first_frame:
for s in first_frame:
fp.write(s)
first_frame = None
fp.write(b"\0") # end of image data
# delta frame
delta = ImageChops.subtract_modulo(im_frame, previous.copy())
bbox = delta.getbbox()
if bbox:
# compress difference
for s in getdata(im_frame.crop(bbox),
bbox[:2], **im.encoderinfo):
fp.write(s)
else:
# FIXME: what should we do in this case?
pass
previous = im_frame
if first_frame:
save_all = False
if not save_all:
header = getheader(im_out, palette, im.encoderinfo)[0]
for s in header:
fp.write(s)
flags = 0
if get_interlace(im):
flags = flags | 64
# local image header
_get_local_header(fp, im, (0, 0), flags)
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
fp.write(b";") # end of file
try:
if hasattr(fp, "flush"):
fp.flush()
except:
pass
def get_interlace(im):
@ -343,7 +414,7 @@ def get_interlace(im):
return interlace
def get_local_header(fp, im, offset=(0, 0)):
def _get_local_header(fp, im, offset, flags):
transparent_color_exists = False
try:
transparency = im.encoderinfo["transparency"]
@ -394,12 +465,6 @@ def get_local_header(fp, im, offset=(0, 0)):
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]) +
@ -451,7 +516,7 @@ def _save_netpbm(im, fp, filename):
try:
os.unlink(file)
except:
except OSError:
pass
@ -480,8 +545,19 @@ def getheader(im, palette=None, info=None):
# Header Block
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
version = b"87a"
for extensionKey in ["transparency", "duration", "loop"]:
if info and extensionKey in info and \
not (extensionKey == "duration" and info[extensionKey] == 0):
version = b"89a"
break
else:
if im.info.get("version") == "89a":
version = b"89a"
header = [
b"GIF87a" + # signature + version
b"GIF"+version + # signature + version
o16(im.size[0]) + # canvas width
o16(im.size[1]) # canvas height
]
@ -538,7 +614,17 @@ def getheader(im, palette=None, info=None):
# size of global color table + global color table flag
header.append(o8(color_table_size + 128))
# background + reserved/aspect
header.append(o8(0) + o8(0))
if info and "background" in info:
background = info["background"]
elif "background" in im.info:
# This elif is redundant within GifImagePlugin
# since im.info parameters are bundled into the info dictionary
# However, external scripts may call getheader directly
# So this maintains earlier behaviour
background = im.info["background"]
else:
background = 0
header.append(o8(background) + o8(0))
# end of Logical Screen Descriptor
# add the missing amount of bytes
@ -571,7 +657,7 @@ def getdata(im, offset=(0, 0), **params):
im.encoderinfo = params
# local image header
get_local_header(fp, im, offset)
_get_local_header(fp, im, offset, 0)
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
@ -588,6 +674,7 @@ def getdata(im, offset=(0, 0), **params):
Image.register_open(GifImageFile.format, GifImageFile, _accept)
Image.register_save(GifImageFile.format, _save)
Image.register_save_all(GifImageFile.format, _save_all)
Image.register_extension(GifImageFile.format, ".gif")
Image.register_mime(GifImageFile.format, "image/gif")

View File

@ -306,10 +306,8 @@ def _save(im, fp, filename):
OS X only.
"""
try:
if hasattr(fp, "flush"):
fp.flush()
except:
pass
# create the temporary set of pngs
iconset = tempfile.mkdtemp('.iconset')
@ -345,13 +343,14 @@ def _save(im, fp, filename):
if retcode:
raise CalledProcessError(retcode, convert_cmd)
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
Image.register_extension("ICNS", '.icns')
Image.register_open(IcnsImageFile.format, IcnsImageFile,
lambda x: x[:4] == b'icns')
Image.register_extension(IcnsImageFile.format, '.icns')
if sys.platform == 'darwin':
Image.register_save("ICNS", _save)
Image.register_save(IcnsImageFile.format, _save)
Image.register_mime("ICNS", "image/icns")
Image.register_mime(IcnsImageFile.format, "image/icns")
if __name__ == '__main__':

View File

@ -15,21 +15,21 @@
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
# <casadebender@gmail.com>.
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
#
# Icon format references:
# * http://en.wikipedia.org/wiki/ICO_(file_format)
# * http://msdn.microsoft.com/en-us/library/ms997538.aspx
# * https://en.wikipedia.org/wiki/ICO_(file_format)
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
__version__ = "0.1"
import struct
from io import BytesIO
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
from math import log, ceil
__version__ = "0.1"
#
# --------------------------------------------------------------------
@ -252,7 +252,7 @@ class IcoImageFile(ImageFile.ImageFile):
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
<casadebender@gmail.com>.
https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
"""
format = "ICO"
format_description = "Windows Icon"
@ -278,6 +278,6 @@ class IcoImageFile(ImageFile.ImageFile):
#
# --------------------------------------------------------------------
Image.register_open("ICO", IcoImageFile, _accept)
Image.register_save("ICO", _save)
Image.register_extension("ICO", ".ico")
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
Image.register_save(IcoImageFile.format, _save)
Image.register_extension(IcoImageFile.format, ".ico")

View File

@ -26,12 +26,12 @@
#
__version__ = "0.7"
import re
from PIL import Image, ImageFile, ImagePalette
from PIL._binary import i8
__version__ = "0.7"
# --------------------------------------------------------------------
# Standard tags
@ -264,6 +264,10 @@ class ImImageFile(ImageFile.ImageFile):
def n_frames(self):
return self.info[FRAMES]
@property
def is_animated(self):
return self.info[FRAMES] > 1
def seek(self, frame):
if frame < 0 or frame >= self.info[FRAMES]:
@ -345,7 +349,7 @@ def _save(im, fp, filename, check=0):
# --------------------------------------------------------------------
# Registry
Image.register_open("IM", ImImageFile)
Image.register_save("IM", _save)
Image.register_open(ImImageFile.format, ImImageFile)
Image.register_save(ImImageFile.format, _save)
Image.register_extension("IM", ".im")
Image.register_extension(ImImageFile.format, ".im")

View File

@ -28,8 +28,11 @@ from __future__ import print_function
from PIL import VERSION, PILLOW_VERSION, _plugins
import logging
import warnings
logger = logging.getLogger(__name__)
class DecompressionBombWarning(RuntimeWarning):
pass
@ -79,20 +82,24 @@ except ImportError as v:
)
elif str(v).startswith("The _imaging extension"):
warnings.warn(str(v), RuntimeWarning)
elif "Symbol not found: _PyUnicodeUCS2_FromString" in str(v):
elif "Symbol not found: _PyUnicodeUCS2_" in str(v):
# should match _PyUnicodeUCS2_FromString and
# _PyUnicodeUCS2_AsLatin1String
warnings.warn(
"The _imaging extension was built for Python with UCS2 support; "
"recompile PIL or build Python --without-wide-unicode. ",
"recompile Pillow or build Python --without-wide-unicode. ",
RuntimeWarning
)
elif "Symbol not found: _PyUnicodeUCS4_FromString" in str(v):
elif "Symbol not found: _PyUnicodeUCS4_" in str(v):
# should match _PyUnicodeUCS4_FromString and
# _PyUnicodeUCS4_AsLatin1String
warnings.warn(
"The _imaging extension was built for Python with UCS4 support; "
"recompile PIL or build Python --with-wide-unicode. ",
"recompile Pillow or build Python --with-wide-unicode. ",
RuntimeWarning
)
# Fail here anyway. Don't let people run with a mostly broken Pillow.
# see docs/porting-pil-to-pillow.rst
# see docs/porting.rst
raise
try:
@ -121,7 +128,7 @@ USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info')
try:
import cffi
HAS_CFFI = True
except:
except ImportError:
HAS_CFFI = False
@ -138,11 +145,6 @@ def isImageType(t):
"""
return hasattr(t, "im")
#
# Debug level
DEBUG = 0
#
# Constants (also defined in _imagingmodule.c!)
@ -204,6 +206,7 @@ ID = []
OPEN = {}
MIME = {}
SAVE = {}
SAVE_ALL = {}
EXTENSION = {}
# --------------------------------------------------------------------
@ -251,6 +254,7 @@ _MODE_CONV = {
"CMYK": ('|u1', 4),
"YCbCr": ('|u1', 3),
"LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1
"HSV": ('|u1', 3),
# I;16 == I;16L, and I;32 == I;32L
"I;16": ('<u2', None),
"I;16B": ('>u2', None),
@ -386,13 +390,10 @@ def init():
for plugin in _plugins:
try:
if DEBUG:
print("Importing %s" % plugin)
logger.debug("Importing %s", plugin)
__import__("PIL.%s" % plugin, globals(), locals(), [])
except ImportError:
if DEBUG:
print("Image: failed to import", end=' ')
print(plugin, ":", sys.exc_info()[1])
except ImportError as e:
logger.debug("Image: failed to import %s: %s", plugin, e)
if OPEN or SAVE:
_initialized = 2
@ -504,12 +505,21 @@ class Image(object):
self.readonly = 0
self.pyaccess = None
@property
def width(self):
return self.size[0]
@property
def height(self):
return self.size[1]
def _new(self, im):
new = Image()
new.im = im
new.mode = im.mode
new.size = im.size
new.palette = self.palette
if self.palette:
new.palette = self.palette.copy()
if im.mode == "P" and not new.palette:
from PIL import ImagePalette
new.palette = ImagePalette.ImagePalette()
@ -545,8 +555,7 @@ class Image(object):
try:
self.fp.close()
except Exception as msg:
if DEBUG:
print("Error closing: %s" % msg)
logger.debug("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
@ -618,6 +627,7 @@ class Image(object):
new['shape'] = shape
new['typestr'] = typestr
new['data'] = self.tobytes()
new['version'] = 3
return new
raise AttributeError(name)
@ -643,7 +653,14 @@ class Image(object):
def tobytes(self, encoder_name="raw", *args):
"""
Return image as a bytes object
Return image as a bytes object.
.. warning::
This method returns the raw image data from the internal
storage. For compressed image data (e.g. PNG, JPEG) use
:meth:`~.save`, with a BytesIO parameter for in-memory
data.
:param encoder_name: What encoder to use. The default is to
use the standard "raw" encoder.
@ -677,18 +694,9 @@ class Image(object):
return b"".join(data)
# Declare tostring as alias to tobytes
def tostring(self, *args, **kw):
"""Deprecated alias to tobytes.
.. deprecated:: 2.0
"""
warnings.warn(
'tostring() is deprecated. Please call tobytes() instead.',
DeprecationWarning,
stacklevel=2,
)
return self.tobytes(*args, **kw)
raise Exception("tostring() has been removed. " +
"Please call tobytes() instead.")
def tobitmap(self, name="image"):
"""
@ -738,14 +746,8 @@ class Image(object):
raise ValueError("cannot decode image data")
def fromstring(self, *args, **kw):
"""Deprecated alias to frombytes.
.. deprecated:: 2.0
"""
warnings.warn(
'fromstring() is deprecated. Please call frombytes() instead.',
DeprecationWarning)
return self.frombytes(*args, **kw)
raise Exception("fromstring() has been removed. " +
"Please call frombytes() instead.")
def load(self):
"""
@ -818,7 +820,7 @@ class Image(object):
: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.
should be 4- or 12-tuple containing floating point values.
:param dither: Dithering method, used when converting from
mode "RGB" to "P" or from "RGB" or "L" to "1".
Available methods are NONE or FLOYDSTEINBERG (default).
@ -877,6 +879,12 @@ class Image(object):
trns_im = Image()._new(core.new(self.mode, (1, 1)))
if self.mode == 'P':
trns_im.putpalette(self.palette)
if type(t) == tuple:
try:
t = trns_im.palette.getcolor(t)
except:
raise ValueError("Couldn't allocate a palette "+
"color for transparency")
trns_im.putpixel((0, 0), t)
if mode in ('L', 'RGB'):
@ -1004,6 +1012,8 @@ class Image(object):
im = self.im.copy()
return self._new(im)
__copy__ = copy
def crop(self, box=None):
"""
Returns a rectangular region from this image. The box is a
@ -1244,27 +1254,8 @@ class Image(object):
return self.im.histogram()
def offset(self, xoffset, yoffset=None):
"""
.. deprecated:: 2.0
.. note:: New code should use :py:func:`PIL.ImageChops.offset`.
Returns a copy of the image where the data has been offset by the given
distances. Data wraps around the edges. If **yoffset** is omitted, it
is assumed to be equal to **xoffset**.
:param xoffset: The horizontal distance.
:param yoffset: The vertical distance. If omitted, both
distances are set to the same value.
:returns: An :py:class:`~PIL.Image.Image` object.
"""
if warnings:
warnings.warn(
"'offset' is deprecated; use 'ImageChops.offset' instead",
DeprecationWarning, stacklevel=2
)
from PIL import ImageChops
return ImageChops.offset(self, xoffset, yoffset)
raise Exception("offset() has been removed. " +
"Please call ImageChops.offset() instead.")
def paste(self, im, box=None, mask=None):
"""
@ -1316,7 +1307,7 @@ class Image(object):
box = (0, 0) + self.size
if len(box) == 2:
# lower left corner given; get size from image or mask
# upper left corner given; get size from image or mask
if isImageType(im):
size = im.size
elif isImageType(mask):
@ -1617,7 +1608,7 @@ class Image(object):
if self.mode in ("1", "P"):
resample = NEAREST
return self._new(self.im.rotate(angle, resample))
return self._new(self.im.rotate(angle, resample, expand))
def save(self, fp, format=None, **params):
"""
@ -1636,7 +1627,7 @@ class Image(object):
implement the ``seek``, ``tell``, and ``write``
methods, and be opened in binary mode.
:param fp: File name or file object.
:param fp: A filename (string), pathlib.Path object 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
@ -1649,17 +1640,27 @@ class Image(object):
may have been created, and may contain partial data.
"""
filename = ""
open_fp = False
if isPath(fp):
filename = fp
else:
if hasattr(fp, "name") and isPath(fp.name):
filename = fp.name
else:
filename = ""
open_fp = True
elif sys.version_info >= (3, 4):
from pathlib import Path
if isinstance(fp, Path):
filename = str(fp)
open_fp = True
elif hasattr(fp, "name") and isPath(fp.name):
# only set the name for metadata purposes
filename = fp.name
# may mutate self!
self.load()
save_all = False
if 'save_all' in params:
save_all = params['save_all']
del params['save_all']
self.encoderinfo = params
self.encoderconfig = ()
@ -1668,32 +1669,25 @@ class Image(object):
ext = os.path.splitext(filename)[1].lower()
if not format:
try:
format = EXTENSION[ext]
except KeyError:
if ext not in EXTENSION:
init()
try:
format = EXTENSION[ext]
except KeyError:
raise KeyError(ext) # unknown extension
format = EXTENSION[ext]
try:
save_handler = SAVE[format.upper()]
except KeyError:
if format.upper() not in SAVE:
init()
save_handler = SAVE[format.upper()] # unknown format
if isPath(fp):
fp = builtins.open(fp, "wb")
close = 1
if save_all:
save_handler = SAVE_ALL[format.upper()]
else:
close = 0
save_handler = SAVE[format.upper()]
if open_fp:
fp = builtins.open(filename, "wb")
try:
save_handler(self, fp, filename)
finally:
# do what we can to clean up
if close:
if open_fp:
fp.close()
def seek(self, frame):
@ -1935,6 +1929,20 @@ class Image(object):
im = self.im.effect_spread(distance)
return self._new(im)
def toqimage(self):
"""Returns a QImage copy of this image"""
from PIL import ImageQt
if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed")
return ImageQt.toqimage(self)
def toqpixmap(self):
"""Returns a QPixmap copy of this image"""
from PIL import ImageQt
if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed")
return ImageQt.toqpixmap(self)
# --------------------------------------------------------------------
# Lazy operations
@ -1945,7 +1953,9 @@ class _ImageCrop(Image):
Image.__init__(self)
x0, y0, x1, y1 = box
# Round to nearest integer, runs int(round(x)) when unpacking
x0, y0, x1, y1 = map(int, map(round, box))
if x1 < x0:
x1 = x0
if y1 < y0:
@ -2063,16 +2073,8 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
def fromstring(*args, **kw):
"""Deprecated alias to frombytes.
.. deprecated:: 2.0
"""
warnings.warn(
'fromstring() is deprecated. Please call frombytes() instead.',
DeprecationWarning,
stacklevel=2
)
return frombytes(*args, **kw)
raise Exception("fromstring() has been removed. " +
"Please call frombytes() instead.")
def frombuffer(mode, size, data, decoder_name="raw", *args):
@ -2089,7 +2091,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
**BytesIO** object, and use :py:func:`~PIL.Image.open` to load it.
In the current version, the default parameters used for the "raw" decoder
differs from that used for :py:func:`~PIL.Image.fromstring`. This is a
differs from that used for :py:func:`~PIL.Image.frombytes`. This is a
bug, and will probably be fixed in a future release. The current release
issues a warning if you do this; to disable the warning, you should provide
the full set of parameters. See below for details.
@ -2116,13 +2118,12 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
if decoder_name == "raw":
if args == ():
if warnings:
warnings.warn(
"the frombuffer defaults may change in a future release; "
"for portability, change the call to read:\n"
" frombuffer(mode, size, data, 'raw', mode, 0, 1)",
RuntimeWarning, stacklevel=2
)
warnings.warn(
"the frombuffer defaults may change in a future release; "
"for portability, change the call to read:\n"
" frombuffer(mode, size, data, 'raw', mode, 0, 1)",
RuntimeWarning, stacklevel=2
)
args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6
if args[0] in _MAPMODES:
im = new(mode, (1, 1))
@ -2184,6 +2185,22 @@ def fromarray(obj, mode=None):
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
def fromqimage(im):
"""Creates an image instance from a QImage image"""
from PIL import ImageQt
if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed")
return ImageQt.fromqimage(im)
def fromqpixmap(im):
"""Creates an image instance from a QPixmap image"""
from PIL import ImageQt
if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed")
return ImageQt.fromqpixmap(im)
_fromarray_typemap = {
# (shape, typestr) => mode, rawmode
# first two members of shape are set to one
@ -2231,9 +2248,10 @@ def open(fp, mode="r"):
:py:meth:`~PIL.Image.Image.load` method). See
:py:func:`~PIL.Image.new`.
:param file: A filename (string) or a file object. The file object
must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and
:py:meth:`~file.tell` methods, and be opened in binary mode.
:param fp: A filename (string), pathlib.Path object or a file object.
The file object must implement :py:meth:`~file.read`,
:py:meth:`~file.seek`, and :py:meth:`~file.tell` methods,
and be opened in binary mode.
:param mode: The mode. If given, this argument must be "r".
:returns: An :py:class:`~PIL.Image.Image` object.
:exception IOError: If the file cannot be found, or the image cannot be
@ -2243,11 +2261,15 @@ def open(fp, mode="r"):
if mode != "r":
raise ValueError("bad mode %r" % mode)
filename = ""
if isPath(fp):
filename = fp
fp = builtins.open(fp, "rb")
else:
filename = ""
elif sys.version_info >= (3, 4):
from pathlib import Path
if isinstance(fp, Path):
filename = str(fp.resolve())
if filename:
fp = builtins.open(filename, "rb")
try:
fp.seek(0)
@ -2258,21 +2280,7 @@ def open(fp, mode="r"):
preinit()
for i in ID:
try:
factory, accept = OPEN[i]
if not accept or accept(prefix):
fp.seek(0)
im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError, struct.error):
# import traceback
# traceback.print_exc()
pass
if init():
def _open_core(fp, filename, prefix):
for i in ID:
try:
factory, accept = OPEN[i]
@ -2282,23 +2290,34 @@ def open(fp, mode="r"):
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError, struct.error):
# import traceback
# traceback.print_exc()
pass
# Leave disabled by default, spams the logs with image
# opening failures that are entirely expected.
# logger.debug("", exc_info=True)
continue
return None
im = _open_core(fp, filename, prefix)
if im is None:
if init():
im = _open_core(fp, filename, prefix)
if im:
return im
raise IOError("cannot identify image file %r"
% (filename if filename else fp))
#
# Image processing.
def alpha_composite(im1, im2):
"""
Alpha composite im2 over im1.
:param im1: The first image.
:param im2: The second image. Must have the same mode and size as
:param im1: The first image. Must have mode RGBA.
:param im2: The second image. Must have mode RGBA, and the same size as
the first image.
:returns: An :py:class:`~PIL.Image.Image` object.
"""
@ -2430,6 +2449,18 @@ def register_save(id, driver):
SAVE[id.upper()] = driver
def register_save_all(id, driver):
"""
Registers an image function to save all the frames
of a multiframe format. This function should not be
used in application code.
:param id: An image format identifier.
:param driver: A function to save images in this format.
"""
SAVE_ALL[id.upper()] = driver
def register_extension(id, extension):
"""
Registers an image extension. This function should not be

View File

@ -16,6 +16,7 @@
# below for the original description.
from __future__ import print_function
import sys
DESCRIPTION = """
pyCMS
@ -240,7 +241,6 @@ def get_display_profile(handle=None):
:returns: None if the profile is not known.
"""
import sys
if sys.platform == "win32":
from PIL import ImageWin
if isinstance(handle, ImageWin.HDC):
@ -943,7 +943,6 @@ def versions():
(pyCMS) Fetches versions.
"""
import sys
return (
VERSION, core.littlecms_version,
sys.version.split()[0], Image.VERSION
@ -954,10 +953,9 @@ def versions():
if __name__ == "__main__":
# create a cheap manual from the __doc__ strings for the functions above
from PIL import ImageCms
print(__doc__)
for f in dir(ImageCms):
for f in dir(sys.modules[__name__]):
doc = None
try:
exec("doc = %s.__doc__" % (f))

View File

@ -31,22 +31,18 @@
#
import numbers
import warnings
from PIL import Image, ImageColor
from PIL._util import isStringType
try:
import warnings
except ImportError:
warnings = None
##
# A simple 2D drawing interface for PIL images.
# <p>
# Application code should use the <b>Draw</b> factory, instead of
# directly.
class ImageDraw(object):
##
@ -90,38 +86,17 @@ class ImageDraw(object):
self.fill = 0
self.font = None
##
# Set the default pen color.
def setink(self, ink):
# compatibility
if warnings:
warnings.warn(
"'setink' is deprecated; use keyword arguments instead",
DeprecationWarning, stacklevel=2
)
if isStringType(ink):
ink = ImageColor.getcolor(ink, self.mode)
if self.palette and not isinstance(ink, numbers.Number):
ink = self.palette.getcolor(ink)
self.ink = self.draw.draw_ink(ink, self.mode)
##
# Set the default background color.
raise Exception("setink() has been removed. " +
"Please use keyword arguments instead.")
def setfill(self, onoff):
# compatibility
if warnings:
warnings.warn(
"'setfill' is deprecated; use keyword arguments instead",
DeprecationWarning, stacklevel=2
)
self.fill = onoff
##
# Set the default font.
raise Exception("setfill() has been removed. " +
"Please use keyword arguments instead.")
def setfont(self, font):
warnings.warn("setfont() is deprecated. " +
"Please set the attribute directly instead.")
# compatibility
self.font = font
@ -256,7 +231,20 @@ class ImageDraw(object):
##
# Draw text.
def text(self, xy, text, fill=None, font=None, anchor=None):
def _multiline_check(self, text):
split_character = "\n" if isinstance(text, type("")) else b"\n"
return split_character in text
def _multiline_split(self, text):
split_character = "\n" if isinstance(text, type("")) else b"\n"
return text.split(split_character)
def text(self, xy, text, fill=None, font=None, anchor=None, *args, **kwargs):
if self._multiline_check(text):
return self.multiline_text(xy, text, fill, font, anchor, *args, **kwargs)
ink, fill = self._getink(fill)
if font is None:
font = self.getfont()
@ -273,14 +261,50 @@ class ImageDraw(object):
mask = font.getmask(text)
self.draw.draw_bitmap(xy, mask, ink)
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
spacing=4, align="left"):
widths = []
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines:
line_width, line_height = self.textsize(line, font)
widths.append(line_width)
max_width = max(max_width, line_width)
left, top = xy
for idx, line in enumerate(lines):
if align == "left":
pass # left = x
elif align == "center":
left += (max_width - widths[idx]) / 2.0
elif align == "right":
left += (max_width - widths[idx])
else:
assert False, 'align must be "left", "center" or "right"'
self.text((left, top), line, fill, font, anchor)
top += line_spacing
left = xy[0]
##
# Get the size of a given string, in pixels.
def textsize(self, text, font=None):
def textsize(self, text, font=None, *args, **kwargs):
if self._multiline_check(text):
return self.multiline_textsize(text, font, *args, **kwargs)
if font is None:
font = self.getfont()
return font.getsize(text)
def multiline_textsize(self, text, font=None, spacing=4):
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines:
line_width, line_height = self.textsize(line, font)
max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing
##
# A simple 2D drawing interface for PIL images.
@ -301,7 +325,7 @@ def Draw(im, mode=None):
# experimental access to the outline API
try:
Outline = Image.core.outline
except:
except AttributeError:
Outline = None

View File

@ -32,7 +32,7 @@ from PIL._util import isPath
import io
import os
import sys
import traceback
import struct
MAXBLOCK = 65536
@ -95,21 +95,11 @@ class ImageFile(Image.Image):
try:
self._open()
except IndexError as v: # end of data
if Image.DEBUG > 1:
traceback.print_exc()
raise SyntaxError(v)
except TypeError as v: # end of data (ord)
if Image.DEBUG > 1:
traceback.print_exc()
raise SyntaxError(v)
except KeyError as v: # unsupported mode
if Image.DEBUG > 1:
traceback.print_exc()
raise SyntaxError(v)
except EOFError as v: # got header but not the first frame
if Image.DEBUG > 1:
traceback.print_exc()
except (IndexError, # end of data
TypeError, # end of data (ord)
KeyError, # unsupported mode
EOFError, # got header but not the first frame
struct.error) as v:
raise SyntaxError(v)
if not self.mode or self.size[0] <= 0:
@ -173,10 +163,10 @@ class ImageFile(Image.Image):
else:
# use mmap, if possible
import mmap
file = open(self.filename, "r+")
fp = open(self.filename, "r+")
size = os.path.getsize(self.filename)
# FIXME: on Unix, use PROT_READ etc
self.map = mmap.mmap(file.fileno(), size)
self.map = mmap.mmap(fp.fileno(), size)
self.im = Image.core.map_buffer(
self.map, self.size, d, e, o, a
)
@ -196,9 +186,6 @@ 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)
@ -207,15 +194,14 @@ class ImageFile(Image.Image):
except ValueError:
continue
b = prefix
t = len(b)
while True:
try:
s = read(self.decodermaxblock)
except IndexError as ie: # truncated png/gif
except (IndexError, struct.error): # truncated png/gif
if LOAD_TRUNCATED_IMAGES:
break
else:
raise IndexError(ie)
raise IOError("image file is truncated")
if not s and not d.handles_eof: # truncated jpeg
self.tile = []
@ -236,7 +222,6 @@ class ImageFile(Image.Image):
if n < 0:
break
b = b[n:]
t = t + n
# Need to cleanup here to prevent leaks in PyPy
d.cleanup()
@ -245,7 +230,7 @@ class ImageFile(Image.Image):
self.fp = None # might be shared
if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0:
if not self.map and not LOAD_TRUNCATED_IMAGES and e < 0:
# still raised if decoder fails to return anything
raise_ioerror(e)
@ -468,6 +453,9 @@ def _save(im, fp, tile, bufsize=0):
# But, it would need at least the image size in most cases. RawEncode is
# a tricky case.
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
if fp == sys.stdout:
fp.flush()
return
try:
fh = fp.fileno()
fp.flush()
@ -497,10 +485,8 @@ def _save(im, fp, tile, bufsize=0):
if s < 0:
raise IOError("encoder error %d when writing image file" % s)
e.cleanup()
try:
if hasattr(fp, "flush"):
fp.flush()
except:
pass
def _safe_read(fp, size):

View File

@ -1,40 +0,0 @@
#
# The Python Imaging Library.
# $Id$
#
# kludge to get basic ImageFileIO functionality
#
# History:
# 1998-08-06 fl Recreated
#
# Copyright (c) Secret Labs AB 1998-2002.
#
# See the README file for information on usage and redistribution.
#
"""
The **ImageFileIO** module can be used to read an image from a
socket, or any other stream device.
Deprecated. New code should use the :class:`PIL.ImageFile.Parser`
class in the :mod:`PIL.ImageFile` module instead.
.. seealso:: modules :class:`PIL.ImageFile.Parser`
"""
from io import BytesIO
class ImageFileIO(BytesIO):
def __init__(self, fp):
"""
Adds buffering to a stream file object, in order to
provide **seek** and **tell** methods required
by the :func:`PIL.Image.Image.open` method. The stream object must
implement **read** and **close** methods.
:param fp: Stream file handle.
.. seealso:: modules :func:`PIL.Image.open`
"""
data = fp.read()
BytesIO.__init__(self, data)

View File

@ -30,11 +30,6 @@ from PIL._util import isDirectory, isPath
import os
import sys
try:
import warnings
except ImportError:
warnings = None
class _imagingft_not_installed(object):
# module placeholder
@ -67,7 +62,7 @@ class ImageFont(object):
def _load_pilfont(self, filename):
file = open(filename, "rb")
fp = open(filename, "rb")
for ext in (".png", ".gif", ".pbm"):
try:
@ -83,7 +78,7 @@ class ImageFont(object):
self.file = fullname
return self._load_pilfont_data(file, image)
return self._load_pilfont_data(fp, image)
def _load_pilfont_data(self, file, image):
@ -121,15 +116,8 @@ class ImageFont(object):
class FreeTypeFont(object):
"FreeType font wrapper (requires _imagingft service)"
def __init__(self, font=None, size=10, index=0, encoding="", file=None):
def __init__(self, font=None, size=10, index=0, encoding=""):
# FIXME: use service provider instead
if file:
if warnings:
warnings.warn(
'file parameter deprecated, '
'please use font parameter instead.',
DeprecationWarning)
font = file
self.path = font
self.size = size
@ -171,7 +159,7 @@ class 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.
object.
:return: A FreeTypeFont object.
"""
@ -225,7 +213,7 @@ def load(filename):
return f
def truetype(font=None, size=10, index=0, encoding="", filename=None):
def truetype(font=None, size=10, index=0, encoding=""):
"""
Load a TrueType or OpenType font file, and create a font object.
This function loads a font object from the given file, and creates
@ -243,19 +231,10 @@ 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.
"""
if filename:
if warnings:
warnings.warn(
'filename parameter deprecated, '
'please use font parameter instead.',
DeprecationWarning)
font = filename
try:
return FreeTypeFont(font, size, index, encoding)
except IOError:
@ -275,7 +254,8 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
# 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(":")]
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')]

View File

@ -2,7 +2,7 @@
# The Python Imaging Library
# $Id$
#
# screen grabber (windows only)
# screen grabber (OS X and Windows only)
#
# History:
# 2001-04-26 fl created
@ -18,31 +18,40 @@
from PIL import Image
import sys
if sys.platform != "win32":
raise ImportError("ImageGrab is Windows only")
if sys.platform not in ["win32", "darwin"]:
raise ImportError("ImageGrab is OS X and Windows only")
try:
# built-in driver (1.1.3 and later)
if sys.platform == "win32":
grabber = Image.core.grabscreen
except AttributeError:
# stand-alone driver (pil plus)
import _grabscreen
grabber = _grabscreen.grab
elif sys.platform == "darwin":
import os
import tempfile
import subprocess
def grab(bbox=None):
size, data = grabber()
im = Image.frombytes(
"RGB", size, data,
# RGB, 32-bit line padding, origo in lower left corner
"raw", "BGR", (size[0]*3 + 3) & -4, -1
)
if sys.platform == "darwin":
f, file = tempfile.mkstemp('.png')
os.close(f)
subprocess.call(['screencapture', '-x', file])
im = Image.open(file)
im.load()
os.unlink(file)
else:
size, data = grabber()
im = Image.frombytes(
"RGB", size, data,
# RGB, 32-bit line padding, origo in lower left corner
"raw", "BGR", (size[0]*3 + 3) & -4, -1
)
if bbox:
im = im.crop(bbox)
return im
def grabclipboard():
if sys.platform == "darwin":
raise NotImplementedError("Method is not implemented on OS X")
debug = 0 # temporary interface
data = Image.core.grabclipboard(debug)
if isinstance(data, bytes):

View File

@ -198,6 +198,8 @@ class MorphOp(object):
if self.lut is None:
raise Exception('No operator loaded')
if image.mode != 'L':
raise Exception('Image must be binary, meaning it must use mode L')
outimage = Image.new(image.mode, image.size, None)
count = _imagingmorph.apply(
bytes(self.lut), image.im.id, outimage.im.id)
@ -212,6 +214,8 @@ class MorphOp(object):
if self.lut is None:
raise Exception('No operator loaded')
if image.mode != 'L':
raise Exception('Image must be binary, meaning it must use mode L')
return _imagingmorph.match(bytes(self.lut), image.im.id)
def get_on_pixels(self, image):
@ -220,6 +224,8 @@ class MorphOp(object):
Returns a list of tuples of (x,y) coordinates
of all matching pixels."""
if image.mode != 'L':
raise Exception('Image must be binary, meaning it must use mode L')
return _imagingmorph.get_on_pixels(image.im.id)
def load_lut(self, filename):

View File

@ -17,12 +17,23 @@
#
import array
import warnings
from PIL import ImageColor
class ImagePalette(object):
"Color palette for palette mapped images"
"""
Color palette for palette mapped images
:param mode: The mode to use for the Palette. See:
:ref:`concept-modes`. Defaults to "RGB"
:param palette: An optional palette. If given, it must be a bytearray,
an array or a list of ints between 0-255 and of length ``size``
times the number of colors in ``mode``. The list must be aligned
by channel (All R values must be contiguous in the list before G
and B values.) Defaults to 0 through 255 per channel.
:param size: An optional palette size. If given, it cannot be equal to
or greater than 256. Defaults to 0.
"""
def __init__(self, mode="RGB", palette=None, size=0):
self.mode = mode
@ -34,6 +45,18 @@ class ImagePalette(object):
(size != 0 and size != len(self.palette))):
raise ValueError("wrong palette size")
def copy(self):
new = ImagePalette()
new.mode = self.mode
new.rawmode = self.rawmode
if self.palette is not None:
new.palette = self.palette[:]
new.colors = self.colors.copy()
new.dirty = self.dirty
return new
def getdata(self):
"""
Get palette contents in format suitable # for the low-level
@ -56,7 +79,6 @@ class ImagePalette(object):
return self.palette
arr = array.array("B", self.palette)
if hasattr(arr, 'tobytes'):
# py3k has a tobytes, tostring is deprecated.
return arr.tobytes()
return arr.tostring()
@ -125,26 +147,6 @@ def raw(rawmode, data):
# --------------------------------------------------------------------
# Factories
def _make_linear_lut(black, white):
warnings.warn(
'_make_linear_lut() is deprecated. '
'Please call make_linear_lut() instead.',
DeprecationWarning,
stacklevel=2
)
return make_linear_lut(black, white)
def _make_gamma_lut(exp):
warnings.warn(
'_make_gamma_lut() is deprecated. '
'Please call make_gamma_lut() instead.',
DeprecationWarning,
stacklevel=2
)
return make_gamma_lut(exp)
def make_linear_lut(black, white):
lut = []
if black == 0:
@ -225,8 +227,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

@ -18,82 +18,181 @@
from PIL import Image
from PIL._util import isPath
import sys
from io import BytesIO
if 'PyQt4.QtGui' not in sys.modules:
qt_is_installed = True
qt_version = None
try:
from PyQt5.QtGui import QImage, qRgba, QPixmap
from PyQt5.QtCore import QBuffer, QIODevice
qt_version = '5'
except ImportError:
try:
from PyQt5.QtGui import QImage, qRgba
except:
from PyQt4.QtGui import QImage, qRgba, QPixmap
from PyQt4.QtCore import QBuffer, QIODevice
qt_version = '4'
except ImportError:
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.
from PySide.QtGui import QImage, qRgba, QPixmap
from PySide.QtCore import QBuffer, QIODevice
qt_version = 'side'
except ImportError:
qt_is_installed = False
def rgb(r, g, b, a=255):
"""(Internal) Turns an RGB color into a Qt compatible color integer."""
# use qRgb to pack the colors, and then turn the resulting long
# into a negative integer with the same bitpattern.
return (qRgba(r, g, b, a) & 0xffffffff)
# :param im A PIL Image object, or a file name
# (given either as Python string or a PyQt string object)
def fromqimage(im):
buffer = QBuffer()
buffer.open(QIODevice.ReadWrite)
# preserve alha channel with png
# otherwise ppm is more friendly with Image.open
if im.hasAlphaChannel():
im.save(buffer, 'png')
else:
im.save(buffer, 'ppm')
b = BytesIO()
try:
b.write(buffer.data())
except TypeError:
# workaround for Python 2
b.write(str(buffer.data()))
buffer.close()
b.seek(0)
return Image.open(b)
def fromqpixmap(im):
return fromqimage(im)
# buffer = QBuffer()
# buffer.open(QIODevice.ReadWrite)
# # im.save(buffer)
# # What if png doesn't support some image features like animation?
# im.save(buffer, 'ppm')
# bytes_io = BytesIO()
# bytes_io.write(buffer.data())
# buffer.close()
# bytes_io.seek(0)
# return Image.open(bytes_io)
def align8to32(bytes, width, mode):
"""
converts each scanline of data from 8 bit to 32 bit aligned
"""
bits_per_pixel = {
'1': 1,
'L': 8,
'P': 8,
}[mode]
# calculate bytes per line and the extra padding if needed
bits_per_line = bits_per_pixel * width
full_bytes_per_line, remaining_bits_per_line = divmod(bits_per_line, 8)
bytes_per_line = full_bytes_per_line + (1 if remaining_bits_per_line else 0)
extra_padding = -bytes_per_line % 4
# already 32 bit aligned by luck
if not extra_padding:
return bytes
new_data = []
for i in range(len(bytes) // bytes_per_line):
new_data.append(bytes[i*bytes_per_line:(i+1)*bytes_per_line] + b'\x00' * extra_padding)
return b''.join(new_data)
def _toqclass_helper(im):
data = None
colortable = None
# handle filename, if given instead of image name
if hasattr(im, "toUtf8"):
# FIXME - is this really the best way to do this?
if str is bytes:
im = unicode(im.toUtf8(), "utf-8")
else:
im = str(im.toUtf8(), "utf-8")
if isPath(im):
im = Image.open(im)
if im.mode == "1":
format = QImage.Format_Mono
elif im.mode == "L":
format = QImage.Format_Indexed8
colortable = []
for i in range(256):
colortable.append(rgb(i, i, i))
elif im.mode == "P":
format = QImage.Format_Indexed8
colortable = []
palette = im.getpalette()
for i in range(0, len(palette), 3):
colortable.append(rgb(*palette[i:i+3]))
elif im.mode == "RGB":
data = im.tobytes("raw", "BGRX")
format = QImage.Format_RGB32
elif im.mode == "RGBA":
try:
data = im.tobytes("raw", "BGRA")
except SystemError:
# workaround for earlier versions
r, g, b, a = im.split()
im = Image.merge("RGBA", (b, g, r, a))
format = QImage.Format_ARGB32
else:
raise ValueError("unsupported image mode %r" % im.mode)
# must keep a reference, or Qt will crash!
__data = data or align8to32(im.tobytes(), im.size[0], im.mode)
return {
'data': __data, 'im': im, 'format': format, 'colortable': colortable
}
##
# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage
# An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
# class.
#
# @param im A PIL Image object, or a file name (given either as Python
# string or a PyQt string object).
class ImageQt(QImage):
if qt_is_installed:
class ImageQt(QImage):
def __init__(self, im):
def __init__(self, im):
im_data = _toqclass_helper(im)
QImage.__init__(self,
im_data['data'], im_data['im'].size[0],
im_data['im'].size[1], im_data['format'])
if im_data['colortable']:
self.setColorTable(im_data['colortable'])
data = None
colortable = None
# handle filename, if given instead of image name
if hasattr(im, "toUtf8"):
# FIXME - is this really the best way to do this?
im = unicode(im.toUtf8(), "utf-8")
if isPath(im):
im = Image.open(im)
def toqimage(im):
return ImageQt(im)
if im.mode == "1":
format = QImage.Format_Mono
elif im.mode == "L":
format = QImage.Format_Indexed8
colortable = []
for i in range(256):
colortable.append(rgb(i, i, i))
elif im.mode == "P":
format = QImage.Format_Indexed8
colortable = []
palette = im.getpalette()
for i in range(0, len(palette), 3):
colortable.append(rgb(*palette[i:i+3]))
elif im.mode == "RGB":
data = im.tobytes("raw", "BGRX")
format = QImage.Format_RGB32
elif im.mode == "RGBA":
try:
data = im.tobytes("raw", "BGRA")
except SystemError:
# workaround for earlier versions
r, g, b, a = im.split()
im = Image.merge("RGBA", (b, g, r, a))
format = QImage.Format_ARGB32
else:
raise ValueError("unsupported image mode %r" % im.mode)
# must keep a reference, or Qt will crash!
self.__data = data or im.tobytes()
def toqpixmap(im):
# # This doesn't work. For now using a dumb approach.
# im_data = _toqclass_helper(im)
# result = QPixmap(im_data['im'].size[0], im_data['im'].size[1])
# result.loadFromData(im_data['data'])
# Fix some strange bug that causes
if im.mode == 'RGB':
im = im.convert('RGBA')
QImage.__init__(self, self.__data, im.size[0], im.size[1], format)
if colortable:
self.setColorTable(colortable)
qimage = toqimage(im)
return QPixmap.fromImage(qimage)

View File

@ -32,11 +32,25 @@ class Iterator(object):
if not hasattr(im, "seek"):
raise AttributeError("im must have seek method")
self.im = im
self.position = 0
def __getitem__(self, ix):
try:
if ix:
self.im.seek(ix)
self.im.seek(ix)
return self.im
except EOFError:
raise IndexError # end of sequence
def __iter__(self):
return self
def __next__(self):
try:
self.im.seek(self.position)
self.position += 1
return self.im
except EOFError:
raise StopIteration
def next(self):
return self.__next__()

View File

@ -76,7 +76,7 @@ class ExtentTransform(Transform):
##
# Define an quad image transform.
# Define a quad image transform.
# <p>
# Maps a quadrilateral (a region defined by four corners) from the
# image to a rectangle of the given size.
@ -92,7 +92,7 @@ class QuadTransform(Transform):
##
# Define an mesh image transform. A mesh transform consists of one
# Define a mesh image transform. A mesh transform consists of one
# or more individual quad transforms.
#
# @def MeshTransform(data)

View File

@ -17,7 +17,6 @@
# See the README file for information on usage and redistribution.
#
import warnings
from PIL import Image
@ -183,24 +182,13 @@ class Dib(object):
"""
return self.image.tobytes()
##
# Deprecated aliases to frombytes & tobytes.
def fromstring(self, *args, **kw):
warnings.warn(
'fromstring() is deprecated. Please call frombytes() instead.',
DeprecationWarning,
stacklevel=2
)
return self.frombytes(*args, **kw)
raise Exception("fromstring() has been removed. " +
"Please use frombytes() instead.")
def tostring(self):
warnings.warn(
'tostring() is deprecated. Please call tobytes() instead.',
DeprecationWarning,
stacklevel=2
)
return self.tobytes()
def tostring(self, *args, **kw):
raise Exception("tostring() has been removed. " +
"Please use tobytes() instead.")
##

View File

@ -15,12 +15,13 @@
#
__version__ = "0.2"
import re
from PIL import Image, ImageFile
__version__ = "0.2"
#
# --------------------------------------------------------------------
@ -88,7 +89,7 @@ class ImtImageFile(ImageFile.ImageFile):
#
# --------------------------------------------------------------------
Image.register_open("IMT", ImtImageFile)
Image.register_open(ImtImageFile.format, ImtImageFile)
#
# no extension registered (".im" is simply too common)

View File

@ -17,13 +17,12 @@
from __future__ import print_function
__version__ = "0.3"
from PIL import Image, ImageFile, _binary
import os
import tempfile
__version__ = "0.3"
i8 = _binary.i8
i16 = _binary.i16be
i32 = _binary.i32be
@ -180,13 +179,13 @@ class IptcImageFile(ImageFile.ImageFile):
finally:
try:
os.unlink(outfile)
except:
except OSError:
pass
Image.register_open("IPTC", IptcImageFile)
Image.register_open(IptcImageFile.format, IptcImageFile)
Image.register_extension("IPTC", ".iim")
Image.register_extension(IptcImageFile.format, ".iim")
##
@ -218,7 +217,7 @@ def getiptcinfo(im):
while app[offset:offset+4] == b"8BIM":
offset += 4
# resource code
code = JpegImagePlugin.i16(app, offset)
code = i16(app, offset)
offset += 2
# resource name (usually empty)
name_len = i8(app[offset])
@ -227,7 +226,7 @@ def getiptcinfo(im):
if offset & 1:
offset += 1
# resource data block
size = JpegImagePlugin.i32(app, offset)
size = i32(app, offset)
offset += 4
if code == 0x0404:
# 0x0404 contains IPTC/NAA data

View File

@ -84,6 +84,7 @@ def _parse_jp2_header(fp):
size = None
mode = None
bpc = None
nc = None
hio = io.BytesIO(header)
while True:
@ -141,6 +142,9 @@ def _parse_jp2_header(fp):
mode = 'RGBA'
break
if size is None or mode is None:
raise SyntaxError("Malformed jp2 header")
return (size, mode)
##
@ -262,15 +266,15 @@ def _save(im, fp, filename):
# ------------------------------------------------------------
# Registry stuff
Image.register_open('JPEG2000', Jpeg2KImageFile, _accept)
Image.register_save('JPEG2000', _save)
Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
Image.register_save(Jpeg2KImageFile.format, _save)
Image.register_extension('JPEG2000', '.jp2')
Image.register_extension('JPEG2000', '.j2k')
Image.register_extension('JPEG2000', '.jpc')
Image.register_extension('JPEG2000', '.jpf')
Image.register_extension('JPEG2000', '.jpx')
Image.register_extension('JPEG2000', '.j2c')
Image.register_extension(Jpeg2KImageFile.format, '.jp2')
Image.register_extension(Jpeg2KImageFile.format, '.j2k')
Image.register_extension(Jpeg2KImageFile.format, '.jpc')
Image.register_extension(Jpeg2KImageFile.format, '.jpf')
Image.register_extension(Jpeg2KImageFile.format, '.jpx')
Image.register_extension(Jpeg2KImageFile.format, '.j2c')
Image.register_mime('JPEG2000', 'image/jp2')
Image.register_mime('JPEG2000', 'image/jpx')
Image.register_mime(Jpeg2KImageFile.format, 'image/jp2')
Image.register_mime(Jpeg2KImageFile.format, 'image/jpx')

View File

@ -32,12 +32,11 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.6"
import array
import struct
import io
from struct import unpack
import warnings
from struct import unpack_from
from PIL import Image, ImageFile, TiffImagePlugin, _binary
from PIL.JpegPresets import presets
from PIL._util import isStringType
@ -47,6 +46,8 @@ o8 = _binary.o8
i16 = _binary.i16be
i32 = _binary.i32be
__version__ = "0.6"
#
# Parser
@ -287,7 +288,7 @@ class JpegImageFile(ImageFile.ImageFile):
s = self.fp.read(1)
if i8(s[0]) != 255:
if i8(s) != 255:
raise SyntaxError("not a JPEG file")
# Create attributes
@ -310,7 +311,7 @@ class JpegImageFile(ImageFile.ImageFile):
i = i16(s)
else:
# Skip non-0xFF junk
s = b"\xff"
s = self.fp.read(1)
continue
if i in MARKER:
@ -378,7 +379,7 @@ class JpegImageFile(ImageFile.ImageFile):
finally:
try:
os.unlink(path)
except:
except OSError:
pass
self.mode = self.im.mode
@ -393,11 +394,17 @@ class JpegImageFile(ImageFile.ImageFile):
return _getmp(self)
def _fixup(value):
# Helper function for _getexif() and _getmp()
if len(value) == 1:
return value[0]
return value
def _fixup_dict(src_dict):
# Helper function for _getexif()
# returns a dict with any single item tuples/lists as individual values
def _fixup(value):
try:
if len(value) == 1 and type(value) != type({}):
return value[0]
except: pass
return value
return dict([(k, _fixup(v)) for k, v in src_dict.items()])
def _getexif(self):
@ -413,33 +420,35 @@ def _getexif(self):
return None
file = io.BytesIO(data[6:])
head = file.read(8)
exif = {}
# process dictionary
info = TiffImagePlugin.ImageFileDirectory(head)
info = TiffImagePlugin.ImageFileDirectory_v1(head)
info.load(file)
for key, value in info.items():
exif[key] = _fixup(value)
exif = dict(_fixup_dict(info))
# get exif extension
try:
# exif field 0x8769 is an offset pointer to the location
# of the nested embedded exif ifd.
# It should be a long, but may be corrupted.
file.seek(exif[0x8769])
except KeyError:
except (KeyError, TypeError):
pass
else:
info = TiffImagePlugin.ImageFileDirectory(head)
info = TiffImagePlugin.ImageFileDirectory_v1(head)
info.load(file)
for key, value in info.items():
exif[key] = _fixup(value)
exif.update(_fixup_dict(info))
# get gpsinfo extension
try:
# exif field 0x8825 is an offset pointer to the location
# of the nested embedded gps exif ifd.
# It should be a long, but may be corrupted.
file.seek(exif[0x8825])
except KeyError:
except (KeyError, TypeError):
pass
else:
info = TiffImagePlugin.ImageFileDirectory(head)
info = TiffImagePlugin.ImageFileDirectory_v1(head)
info.load(file)
exif[0x8825] = gps = {}
for key, value in info.items():
gps[key] = _fixup(value)
exif[0x8825] = _fixup_dict(info)
return exif
@ -457,23 +466,25 @@ def _getmp(self):
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_contents)
for key, value in info.items():
mp[key] = _fixup(value)
try:
info = TiffImagePlugin.ImageFileDirectory_v2(head)
info.load(file_contents)
mp = dict(info)
except:
raise SyntaxError("malformed MP Index (unreadable directory)")
# it's an error not to have a number of images
try:
quant = mp[0xB001]
except KeyError:
raise SyntaxError("malformed MP Index (no number of images)")
# get MP entries
mpentries = []
try:
mpentries = []
rawmpentries = mp[0xB002]
for entrynum in range(0, quant):
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
unpackedentry = unpack_from(
'{0}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
'EntryNo2')
mpentry = dict(zip(labels, unpackedentry))
@ -527,14 +538,14 @@ RAWMODE = {
"YCbCr": "YCbCr",
}
zigzag_index = ( 0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63)
zigzag_index = (0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63)
samplings = {(1, 1, 1, 1, 1, 1): 0,
(2, 1, 1, 1, 1, 1): 1,
@ -681,7 +692,7 @@ def _save(im, fp, filename):
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
# in a shot. Guessing on the size, at im.size bytes. (raw pizel size is
# channels*size, this is a value that's been used in a django patch.
# https://github.com/jdriscoll/django-imagekit/issues/50
# https://github.com/matthewwithanm/django-imagekit/issues/50
bufsize = 0
if "optimize" in info or "progressive" in info or "progression" in info:
# keep sets quality to 0, but the actual value may be high.
@ -705,7 +716,7 @@ def _save_cjpeg(im, fp, filename):
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
try:
os.unlink(tempfile)
except:
except OSError:
pass
@ -713,8 +724,8 @@ def _save_cjpeg(im, fp, filename):
# Factory for making JPEG and MPO instances
def jpeg_factory(fp=None, filename=None):
im = JpegImageFile(fp, filename)
mpheader = im._getmp()
try:
mpheader = im._getmp()
if mpheader[45057] > 1:
# It's actually an MPO
from .MpoImagePlugin import MpoImageFile
@ -722,18 +733,21 @@ def jpeg_factory(fp=None, filename=None):
except (TypeError, IndexError):
# It is really a JPEG
pass
except SyntaxError:
warnings.warn("Image appears to be a malformed MPO file, it will be "
"interpreted as a base JPEG file")
return im
# -------------------------------------------------------------------q-
# Registry stuff
Image.register_open("JPEG", jpeg_factory, _accept)
Image.register_save("JPEG", _save)
Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
Image.register_save(JpegImageFile.format, _save)
Image.register_extension("JPEG", ".jfif")
Image.register_extension("JPEG", ".jpe")
Image.register_extension("JPEG", ".jpg")
Image.register_extension("JPEG", ".jpeg")
Image.register_extension(JpegImageFile.format, ".jfif")
Image.register_extension(JpegImageFile.format, ".jpe")
Image.register_extension(JpegImageFile.format, ".jpg")
Image.register_extension(JpegImageFile.format, ".jpeg")
Image.register_mime("JPEG", "image/jpeg")
Image.register_mime(JpegImageFile.format, "image/jpeg")

View File

@ -27,7 +27,7 @@ Subsampling
Subsampling is the practice of encoding images by implementing less resolution
for chroma information than for luma information.
(ref.: http://en.wikipedia.org/wiki/Chroma_subsampling)
(ref.: https://en.wikipedia.org/wiki/Chroma_subsampling)
Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and
4:1:1 (or 4:2:0?).
@ -41,8 +41,8 @@ Quantization tables
They are values use by the DCT (Discrete cosine transform) to remove
*unnecessary* information from the image (the lossy part of the compression).
(ref.: http://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
http://en.wikipedia.org/wiki/JPEG#Quantization)
(ref.: https://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
https://en.wikipedia.org/wiki/JPEG#Quantization)
You can get the quantization tables of a JPEG with::
@ -62,13 +62,13 @@ The tables format between im.quantization and quantization in presets differ in
You can convert the dict format to the preset format with the
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
Libjpeg ref.: http://www.jpegcameras.com/libjpeg/libjpeg-3.html
Libjpeg ref.: http://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
"""
presets = {
'web_low': {'subsampling': 2, # "4:1:1"
'quantization': [
'quantization': [
[20, 16, 25, 39, 50, 46, 62, 68,
16, 18, 23, 38, 38, 53, 65, 68,
25, 23, 31, 38, 53, 65, 68, 68,
@ -85,9 +85,9 @@ presets = {
68, 68, 68, 68, 68, 68, 68, 68,
68, 68, 68, 68, 68, 68, 68, 68,
68, 68, 68, 68, 68, 68, 68, 68]
]},
]},
'web_medium': {'subsampling': 2, # "4:1:1"
'quantization': [
'quantization': [
[16, 11, 11, 16, 23, 27, 31, 30,
11, 12, 12, 15, 20, 23, 23, 30,
11, 12, 13, 16, 23, 26, 35, 47,
@ -104,10 +104,10 @@ presets = {
26, 26, 30, 39, 48, 63, 64, 64,
38, 35, 46, 53, 64, 64, 64, 64,
48, 43, 53, 64, 64, 64, 64, 64]
]},
]},
'web_high': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 6, 4, 4, 6, 9, 11, 12, 16,
'quantization': [
[6, 4, 4, 6, 9, 11, 12, 16,
4, 5, 5, 6, 8, 10, 12, 12,
4, 5, 5, 6, 10, 12, 14, 19,
6, 6, 6, 11, 12, 15, 19, 28,
@ -115,7 +115,7 @@ presets = {
11, 10, 12, 15, 20, 27, 31, 31,
12, 12, 14, 19, 27, 31, 31, 31,
16, 12, 19, 28, 31, 31, 31, 31],
[ 7, 7, 13, 24, 26, 31, 31, 31,
[7, 7, 13, 24, 26, 31, 31, 31,
7, 12, 16, 21, 31, 31, 31, 31,
13, 16, 17, 31, 31, 31, 31, 31,
24, 21, 31, 31, 31, 31, 31, 31,
@ -123,10 +123,10 @@ presets = {
31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31]
]},
]},
'web_very_high': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 2, 2, 2, 2, 3, 4, 5, 6,
'quantization': [
[2, 2, 2, 2, 3, 4, 5, 6,
2, 2, 2, 2, 3, 4, 5, 6,
2, 2, 2, 2, 4, 5, 7, 9,
2, 2, 2, 4, 5, 7, 9, 12,
@ -134,7 +134,7 @@ presets = {
4, 4, 5, 7, 10, 12, 12, 12,
5, 5, 7, 9, 12, 12, 12, 12,
6, 6, 9, 12, 12, 12, 12, 12],
[ 3, 3, 5, 9, 13, 15, 15, 15,
[3, 3, 5, 9, 13, 15, 15, 15,
3, 4, 6, 11, 14, 12, 12, 12,
5, 6, 9, 14, 12, 12, 12, 12,
9, 11, 14, 12, 12, 12, 12, 12,
@ -142,10 +142,10 @@ presets = {
15, 12, 12, 12, 12, 12, 12, 12,
15, 12, 12, 12, 12, 12, 12, 12,
15, 12, 12, 12, 12, 12, 12, 12]
]},
]},
'web_maximum': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 1, 1, 1, 1, 1, 1, 1, 1,
'quantization': [
[1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 2,
1, 1, 1, 1, 1, 1, 2, 2,
@ -153,7 +153,7 @@ presets = {
1, 1, 1, 1, 2, 2, 3, 3,
1, 1, 1, 2, 2, 3, 3, 3,
1, 1, 2, 2, 3, 3, 3, 3],
[ 1, 1, 1, 2, 2, 3, 3, 3,
[1, 1, 1, 2, 2, 3, 3, 3,
1, 1, 1, 2, 3, 3, 3, 3,
1, 1, 1, 3, 3, 3, 3, 3,
2, 2, 3, 3, 3, 3, 3, 3,
@ -161,9 +161,9 @@ presets = {
3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3]
]},
]},
'low': {'subsampling': 2, # "4:1:1"
'quantization': [
'quantization': [
[18, 14, 14, 21, 30, 35, 34, 17,
14, 16, 16, 19, 26, 23, 12, 12,
14, 16, 17, 21, 23, 12, 12, 12,
@ -180,9 +180,9 @@ presets = {
20, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12]
]},
]},
'medium': {'subsampling': 2, # "4:1:1"
'quantization': [
'quantization': [
[12, 8, 8, 12, 17, 21, 24, 17,
8, 9, 9, 11, 15, 19, 12, 12,
8, 9, 10, 12, 19, 12, 12, 12,
@ -199,10 +199,10 @@ presets = {
20, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12]
]},
]},
'high': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 6, 4, 4, 6, 9, 11, 12, 16,
'quantization': [
[6, 4, 4, 6, 9, 11, 12, 16,
4, 5, 5, 6, 8, 10, 12, 12,
4, 5, 5, 6, 10, 12, 12, 12,
6, 6, 6, 11, 12, 12, 12, 12,
@ -210,7 +210,7 @@ presets = {
11, 10, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12,
16, 12, 12, 12, 12, 12, 12, 12],
[ 7, 7, 13, 24, 20, 20, 17, 17,
[7, 7, 13, 24, 20, 20, 17, 17,
7, 12, 16, 14, 14, 12, 12, 12,
13, 16, 14, 14, 12, 12, 12, 12,
24, 14, 14, 12, 12, 12, 12, 12,
@ -218,10 +218,10 @@ presets = {
20, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12,
17, 12, 12, 12, 12, 12, 12, 12]
]},
]},
'maximum': {'subsampling': 0, # "4:4:4"
'quantization': [
[ 2, 2, 2, 2, 3, 4, 5, 6,
'quantization': [
[2, 2, 2, 2, 3, 4, 5, 6,
2, 2, 2, 2, 3, 4, 5, 6,
2, 2, 2, 2, 4, 5, 7, 9,
2, 2, 2, 4, 5, 7, 9, 12,
@ -229,7 +229,7 @@ presets = {
4, 4, 5, 7, 10, 12, 12, 12,
5, 5, 7, 9, 12, 12, 12, 12,
6, 6, 9, 12, 12, 12, 12, 12],
[ 3, 3, 5, 9, 13, 15, 15, 15,
[3, 3, 5, 9, 13, 15, 15, 15,
3, 4, 6, 10, 14, 12, 12, 12,
5, 6, 9, 14, 12, 12, 12, 12,
9, 10, 14, 12, 12, 12, 12, 12,
@ -237,5 +237,5 @@ presets = {
15, 12, 12, 12, 12, 12, 12, 12,
15, 12, 12, 12, 12, 12, 12, 12,
15, 12, 12, 12, 12, 12, 12, 12]
]},
]},
}

View File

@ -16,11 +16,11 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.2"
import struct
from PIL import Image, ImageFile
__version__ = "0.2"
def _accept(s):
return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04"
@ -69,6 +69,6 @@ class McIdasImageFile(ImageFile.ImageFile):
# --------------------------------------------------------------------
# registry
Image.register_open("MCIDAS", McIdasImageFile, _accept)
Image.register_open(McIdasImageFile.format, McIdasImageFile, _accept)
# no default extension

View File

@ -17,12 +17,11 @@
#
__version__ = "0.1"
from PIL import Image, TiffImagePlugin
from PIL.OleFileIO import MAGIC, OleFileIO
__version__ = "0.1"
#
# --------------------------------------------------------------------
@ -75,6 +74,10 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
def n_frames(self):
return len(self.images)
@property
def is_animated(self):
return len(self.images) > 1
def seek(self, frame):
try:
@ -95,6 +98,6 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
#
# --------------------------------------------------------------------
Image.register_open("MIC", MicImageFile, _accept)
Image.register_open(MicImageFile.format, MicImageFile, _accept)
Image.register_extension("MIC", ".mic")
Image.register_extension(MicImageFile.format, ".mic")

View File

@ -13,11 +13,12 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.1"
from PIL import Image, ImageFile
from PIL._binary import i8
__version__ = "0.1"
#
# Bitstream parser
@ -77,9 +78,9 @@ class MpegImageFile(ImageFile.ImageFile):
# --------------------------------------------------------------------
# Registry stuff
Image.register_open("MPEG", MpegImageFile)
Image.register_open(MpegImageFile.format, MpegImageFile)
Image.register_extension("MPEG", ".mpg")
Image.register_extension("MPEG", ".mpeg")
Image.register_extension(MpegImageFile.format, ".mpg")
Image.register_extension(MpegImageFile.format, ".mpeg")
Image.register_mime("MPEG", "video/mpeg")
Image.register_mime(MpegImageFile.format, "video/mpeg")

View File

@ -18,10 +18,10 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.1"
from PIL import Image, JpegImagePlugin
__version__ = "0.1"
def _accept(prefix):
return JpegImagePlugin._accept(prefix)
@ -66,6 +66,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
def n_frames(self):
return self.__framecount
@property
def is_animated(self):
return self.__framecount > 1
def seek(self, frame):
if frame < 0 or frame >= self.__framecount:
raise EOFError("no more images in MPO file")
@ -86,9 +90,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
# Note that since MPO shares a factory with JPEG, we do not need to do a
# separate registration for it here.
# Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
Image.register_save("MPO", _save)
# Image.register_open(MpoImageFile.format,
# JpegImagePlugin.jpeg_factory, _accept)
Image.register_save(MpoImageFile.format, _save)
Image.register_extension("MPO", ".mpo")
Image.register_extension(MpoImageFile.format, ".mpo")
Image.register_mime("MPO", "image/mpo")
Image.register_mime(MpoImageFile.format, "image/mpo")

View File

@ -17,10 +17,10 @@
#
__version__ = "0.1"
from PIL import Image, ImageFile, _binary
__version__ = "0.1"
#
# read MSP files
@ -98,7 +98,7 @@ def _save(im, fp, filename):
#
# registry
Image.register_open("MSP", MspImageFile, _accept)
Image.register_save("MSP", _save)
Image.register_open(MspImageFile.format, MspImageFile, _accept)
Image.register_save(MspImageFile.format, _save)
Image.register_extension("MSP", ".msp")
Image.register_extension(MspImageFile.format, ".msp")

View File

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

View File

@ -16,11 +16,12 @@
#
from PIL import EpsImagePlugin
import sys
##
# Simple Postscript graphics interface.
class PSDraw(object):
"""
Sets up printing to the given file. If **file** is omitted,
@ -29,12 +30,11 @@ class PSDraw(object):
def __init__(self, fp=None):
if not fp:
import sys
fp = sys.stdout
self.fp = fp
def _fp_write(self, to_write):
if bytes is str:
if bytes is str or self.fp == sys.stdout:
self.fp.write(to_write)
else:
self.fp.write(bytes(to_write, 'UTF-8'))
@ -47,7 +47,7 @@ class PSDraw(object):
"/showpage { } def\n"
"%%EndComments\n"
"%%BeginDocument\n")
# self.fp_write(ERROR_PS) # debugging!
# self._fp_write(ERROR_PS) # debugging!
self._fp_write(EDROFF_PS)
self._fp_write(VDI_PS)
self._fp_write("%%EndProlog\n")

View File

@ -7,10 +7,10 @@
# Image plugin for Palm pixmap images (output only).
##
__version__ = "1.0"
from PIL import Image, ImageFile, _binary
__version__ = "1.0"
_Palm8BitColormapValues = (
(255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255),
(255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204),
@ -30,15 +30,15 @@ _Palm8BitColormapValues = (
(102, 255, 204), (102, 204, 204), (102, 153, 204), (102, 102, 204),
(102, 51, 204), (102, 0, 204), (102, 255, 153), (102, 204, 153),
(102, 153, 153), (102, 102, 153), (102, 51, 153), (102, 0, 153),
( 51, 255, 255), ( 51, 204, 255), ( 51, 153, 255), ( 51, 102, 255),
( 51, 51, 255), ( 51, 0, 255), ( 51, 255, 204), ( 51, 204, 204),
( 51, 153, 204), ( 51, 102, 204), ( 51, 51, 204), ( 51, 0, 204),
( 51, 255, 153), ( 51, 204, 153), ( 51, 153, 153), ( 51, 102, 153),
( 51, 51, 153), ( 51, 0, 153), ( 0, 255, 255), ( 0, 204, 255),
( 0, 153, 255), ( 0, 102, 255), ( 0, 51, 255), ( 0, 0, 255),
( 0, 255, 204), ( 0, 204, 204), ( 0, 153, 204), ( 0, 102, 204),
( 0, 51, 204), ( 0, 0, 204), ( 0, 255, 153), ( 0, 204, 153),
( 0, 153, 153), ( 0, 102, 153), ( 0, 51, 153), ( 0, 0, 153),
(51, 255, 255), (51, 204, 255), (51, 153, 255), (51, 102, 255),
(51, 51, 255), (51, 0, 255), (51, 255, 204), (51, 204, 204),
(51, 153, 204), (51, 102, 204), (51, 51, 204), (51, 0, 204),
(51, 255, 153), (51, 204, 153), (51, 153, 153), (51, 102, 153),
(51, 51, 153), (51, 0, 153), (0, 255, 255), (0, 204, 255),
(0, 153, 255), (0, 102, 255), (0, 51, 255), (0, 0, 255),
(0, 255, 204), (0, 204, 204), (0, 153, 204), (0, 102, 204),
(0, 51, 204), (0, 0, 204), (0, 255, 153), (0, 204, 153),
(0, 153, 153), (0, 102, 153), (0, 51, 153), (0, 0, 153),
(255, 255, 102), (255, 204, 102), (255, 153, 102), (255, 102, 102),
(255, 51, 102), (255, 0, 102), (255, 255, 51), (255, 204, 51),
(255, 153, 51), (255, 102, 51), (255, 51, 51), (255, 0, 51),
@ -57,25 +57,25 @@ _Palm8BitColormapValues = (
(102, 255, 51), (102, 204, 51), (102, 153, 51), (102, 102, 51),
(102, 51, 51), (102, 0, 51), (102, 255, 0), (102, 204, 0),
(102, 153, 0), (102, 102, 0), (102, 51, 0), (102, 0, 0),
( 51, 255, 102), ( 51, 204, 102), ( 51, 153, 102), ( 51, 102, 102),
( 51, 51, 102), ( 51, 0, 102), ( 51, 255, 51), ( 51, 204, 51),
( 51, 153, 51), ( 51, 102, 51), ( 51, 51, 51), ( 51, 0, 51),
( 51, 255, 0), ( 51, 204, 0), ( 51, 153, 0), ( 51, 102, 0),
( 51, 51, 0), ( 51, 0, 0), ( 0, 255, 102), ( 0, 204, 102),
( 0, 153, 102), ( 0, 102, 102), ( 0, 51, 102), ( 0, 0, 102),
( 0, 255, 51), ( 0, 204, 51), ( 0, 153, 51), ( 0, 102, 51),
( 0, 51, 51), ( 0, 0, 51), ( 0, 255, 0), ( 0, 204, 0),
( 0, 153, 0), ( 0, 102, 0), ( 0, 51, 0), ( 17, 17, 17),
( 34, 34, 34), ( 68, 68, 68), ( 85, 85, 85), (119, 119, 119),
(51, 255, 102), (51, 204, 102), (51, 153, 102), (51, 102, 102),
(51, 51, 102), (51, 0, 102), (51, 255, 51), (51, 204, 51),
(51, 153, 51), (51, 102, 51), (51, 51, 51), (51, 0, 51),
(51, 255, 0), (51, 204, 0), (51, 153, 0), (51, 102, 0),
(51, 51, 0), (51, 0, 0), (0, 255, 102), (0, 204, 102),
(0, 153, 102), (0, 102, 102), (0, 51, 102), (0, 0, 102),
(0, 255, 51), (0, 204, 51), (0, 153, 51), (0, 102, 51),
(0, 51, 51), (0, 0, 51), (0, 255, 0), (0, 204, 0),
(0, 153, 0), (0, 102, 0), (0, 51, 0), (17, 17, 17),
(34, 34, 34), (68, 68, 68), (85, 85, 85), (119, 119, 119),
(136, 136, 136), (170, 170, 170), (187, 187, 187), (221, 221, 221),
(238, 238, 238), (192, 192, 192), (128, 0, 0), (128, 0, 128),
( 0, 128, 0), ( 0, 128, 128), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0))
(0, 128, 0), (0, 128, 128), (0, 0, 0), (0, 0, 0),
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0))
# so build a prototype image to be used for palette resampling
@ -227,7 +227,8 @@ def _save(im, fp, filename, check=0):
ImageFile._save(
im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))])
fp.flush()
if hasattr(fp, "flush"):
fp.flush()
#

View File

@ -15,11 +15,10 @@
#
__version__ = "0.1"
from PIL import Image, ImageFile, _binary
__version__ = "0.1"
i8 = _binary.i8
@ -55,6 +54,6 @@ class PcdImageFile(ImageFile.ImageFile):
#
# registry
Image.register_open("PCD", PcdImageFile)
Image.register_open(PcdImageFile.format, PcdImageFile)
Image.register_extension("PCD", ".pcd")
Image.register_extension(PcdImageFile.format, ".pcd")

View File

@ -27,8 +27,11 @@
from __future__ import print_function
import logging
from PIL import Image, ImageFile, ImagePalette, _binary
logger = logging.getLogger(__name__)
i8 = _binary.i8
i16 = _binary.i16le
o8 = _binary.o8
@ -59,17 +62,15 @@ class PcxImageFile(ImageFile.ImageFile):
bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
raise SyntaxError("bad PCX image size")
if Image.DEBUG:
print("BBox: %s %s %s %s" % bbox)
logger.debug("BBox: %s %s %s %s", *bbox)
# format
version = i8(s[1])
bits = i8(s[3])
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))
logger.debug("PCX version %s, bits %s, planes %s, stride %s",
version, bits, planes, stride)
self.info["dpi"] = i16(s, 12), i16(s, 14)
@ -107,8 +108,7 @@ class PcxImageFile(ImageFile.ImageFile):
self.size = bbox[2]-bbox[0], bbox[3]-bbox[1]
bbox = (0, 0) + self.size
if Image.DEBUG:
print("size: %sx%s" % self.size)
logger.debug("size: %sx%s", *self.size)
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
@ -144,9 +144,8 @@ def _save(im, fp, filename, check=0):
# Ideally it should be passed in in the state, but the bytes value
# gets overwritten.
if Image.DEBUG:
print("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
im.size[0], bits, stride))
logger.debug("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
im.size[0], bits, stride)
# under windows, we could determine the current screen size with
# "Image.core.display_mode()[1]", but I think that's overkill...
@ -182,7 +181,7 @@ def _save(im, fp, filename, check=0):
# --------------------------------------------------------------------
# registry
Image.register_open("PCX", PcxImageFile, _accept)
Image.register_save("PCX", _save)
Image.register_open(PcxImageFile.format, PcxImageFile, _accept)
Image.register_save(PcxImageFile.format, _save)
Image.register_extension("PCX", ".pcx")
Image.register_extension(PcxImageFile.format, ".pcx")

View File

@ -20,12 +20,12 @@
# Image plugin for PDF images (output only).
##
__version__ = "0.4"
from PIL import Image, ImageFile
from PIL._binary import i8
import io
__version__ = "0.4"
#
# --------------------------------------------------------------------
@ -51,17 +51,21 @@ def _endobj(fp):
fp.write("endobj\n")
def _save_all(im, fp, filename):
_save(im, fp, filename, save_all=True)
##
# (Internal) Image save plugin for the PDF format.
def _save(im, fp, filename):
def _save(im, fp, filename, save_all=False):
resolution = im.encoderinfo.get("resolution", 72.0)
#
# make sure image data is available
im.load()
xref = [0]*(5+1) # placeholders
xref = [0]
class TextWriter(object):
def __init__(self, fp):
@ -78,11 +82,6 @@ def _save(im, fp, filename):
fp.write("%PDF-1.2\n")
fp.write("% created by PIL PDF driver " + __version__ + "\n")
#
# Get image characteristics
width, height = im.size
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)
# or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports
# Flatedecode (zip compression).
@ -125,7 +124,7 @@ def _save(im, fp, filename):
#
# catalogue
xref[1] = fp.tell()
xref.append(fp.tell())
_obj(
fp, 1,
Type="/Catalog",
@ -134,89 +133,108 @@ def _save(im, fp, filename):
#
# pages
numberOfPages = 1
if save_all:
try:
numberOfPages = im.n_frames
except AttributeError:
# Image format does not have n_frames. It is a single frame image
pass
pages = [str(pageNumber*3+4)+" 0 R"
for pageNumber in range(0, numberOfPages)]
xref[2] = fp.tell()
xref.append(fp.tell())
_obj(
fp, 2,
Type="/Pages",
Count=1,
Kids="[4 0 R]")
Count=len(pages),
Kids="["+"\n".join(pages)+"]")
_endobj(fp)
#
# image
for pageNumber in range(0, numberOfPages):
im.seek(pageNumber)
op = io.BytesIO()
#
# image
if filter == "/ASCIIHexDecode":
if bits == 1:
# FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way...
data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None)
im.putdata(data)
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else:
raise ValueError("unsupported PDF filter (%s)" % filter)
op = io.BytesIO()
xref[3] = fp.tell()
_obj(
fp, 3,
Type="/XObject",
Subtype="/Image",
Width=width, # * 72.0 / resolution,
Height=height, # * 72.0 / resolution,
Length=len(op.getvalue()),
Filter=filter,
BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
if filter == "/ASCIIHexDecode":
if bits == 1:
# FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way...
data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None)
im.putdata(data)
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else:
raise ValueError("unsupported PDF filter (%s)" % filter)
fp.write("stream\n")
fp.fp.write(op.getvalue())
fp.write("\nendstream\n")
#
# Get image characteristics
_endobj(fp)
width, height = im.size
#
# page
xref.append(fp.tell())
_obj(
fp, pageNumber*3+3,
Type="/XObject",
Subtype="/Image",
Width=width, # * 72.0 / resolution,
Height=height, # * 72.0 / resolution,
Length=len(op.getvalue()),
Filter=filter,
BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
xref[4] = fp.tell()
_obj(fp, 4)
fp.write(
"<<\n/Type /Page\n/Parent 2 0 R\n"
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/XObject << /image 3 0 R >>\n>>\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % (
procset,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
_endobj(fp)
fp.write("stream\n")
fp.fp.write(op.getvalue())
fp.write("\nendstream\n")
#
# page contents
_endobj(fp)
op = TextWriter(io.BytesIO())
#
# page
op.write(
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
xref.append(fp.tell())
_obj(fp, pageNumber*3+4)
fp.write(
"<<\n/Type /Page\n/Parent 2 0 R\n"
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/XObject << /image %d 0 R >>\n>>\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
procset,
pageNumber*3+3,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution),
pageNumber*3+5))
_endobj(fp)
xref[5] = fp.tell()
_obj(fp, 5, Length=len(op.fp.getvalue()))
#
# page contents
fp.write("stream\n")
fp.fp.write(op.fp.getvalue())
fp.write("\nendstream\n")
op = TextWriter(io.BytesIO())
_endobj(fp)
op.write(
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
xref.append(fp.tell())
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
fp.write("stream\n")
fp.fp.write(op.fp.getvalue())
fp.write("\nendstream\n")
_endobj(fp)
#
# trailer
@ -226,12 +244,14 @@ def _save(im, fp, filename):
fp.write("%010d 00000 n \n" % x)
fp.write("trailer\n<<\n/Size %d\n/Root 1 0 R\n>>\n" % len(xref))
fp.write("startxref\n%d\n%%%%EOF\n" % startxref)
fp.flush()
if hasattr(fp, "flush"):
fp.flush()
#
# --------------------------------------------------------------------
Image.register_save("PDF", _save)
Image.register_save_all("PDF", _save_all)
Image.register_extension("PDF", ".pdf")

View File

@ -19,15 +19,14 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.1"
from PIL import Image, ImageFile, _binary
__version__ = "0.1"
#
# helpers
i16 = _binary.i16le
i32 = _binary.i32le
##
@ -63,7 +62,7 @@ class PixarImageFile(ImageFile.ImageFile):
#
# --------------------------------------------------------------------
Image.register_open("PIXAR", PixarImageFile)
Image.register_open(PixarImageFile.format, PixarImageFile)
#
# FIXME: what's the standard extension?

View File

@ -33,12 +33,15 @@
from __future__ import print_function
__version__ = "0.9"
import logging
import re
import zlib
from PIL import Image, ImageFile, ImagePalette, _binary
import zlib
__version__ = "0.9"
logger = logging.getLogger(__name__)
i8 = _binary.i8
i16 = _binary.i16be
@ -129,8 +132,7 @@ class ChunkStream(object):
def call(self, cid, pos, length):
"Call the appropriate chunk handler"
if Image.DEBUG:
print("STREAM", cid, pos, length)
logger.debug("STREAM %s %s %s", cid, pos, length)
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length)
def crc(self, cid, data):
@ -293,9 +295,8 @@ class PngStream(ChunkStream):
# Compression method 1 byte (0)
# Compressed profile n bytes (zlib with deflate compression)
i = s.find(b"\0")
if Image.DEBUG:
print("iCCP profile name", s[:i])
print("Compression method", i8(s[i]))
logger.debug("iCCP profile name %s", s[:i])
logger.debug("Compression method %s", i8(s[i]))
comp_method = i8(s[i])
if comp_method != 0:
raise SyntaxError("Unknown compression method %s in iCCP chunk" %
@ -507,8 +508,7 @@ class PngImageFile(ImageFile.ImageFile):
except EOFError:
break
except AttributeError:
if Image.DEBUG:
print(cid, pos, length, "(unknown)")
logger.debug("%s %s %s (unknown)", cid, pos, length)
s = ImageFile._safe_read(self.fp, length)
self.png.crc(cid, s)
@ -762,10 +762,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
chunk(fp, b"IEND", b"")
try:
if hasattr(fp, "flush"):
fp.flush()
except:
pass
# --------------------------------------------------------------------
@ -803,9 +801,9 @@ def getchunks(im, **params):
# --------------------------------------------------------------------
# Registry
Image.register_open("PNG", PngImageFile, _accept)
Image.register_save("PNG", _save)
Image.register_open(PngImageFile.format, PngImageFile, _accept)
Image.register_save(PngImageFile.format, _save)
Image.register_extension("PNG", ".png")
Image.register_extension(PngImageFile.format, ".png")
Image.register_mime("PNG", "image/png")
Image.register_mime(PngImageFile.format, "image/png")

View File

@ -15,12 +15,12 @@
#
__version__ = "0.2"
import string
from PIL import Image, ImageFile
__version__ = "0.2"
#
# --------------------------------------------------------------------
@ -93,6 +93,8 @@ class PpmImageFile(ImageFile.ImageFile):
s = self.fp.read(1)
if s not in b_whitespace:
break
if s == b"":
raise ValueError("File does not extend beyond magic number")
if s != b"#":
break
s = self.fp.readline()
@ -164,9 +166,9 @@ def _save(im, fp, filename):
#
# --------------------------------------------------------------------
Image.register_open("PPM", PpmImageFile, _accept)
Image.register_save("PPM", _save)
Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
Image.register_save(PpmImageFile.format, _save)
Image.register_extension("PPM", ".pbm")
Image.register_extension("PPM", ".pgm")
Image.register_extension("PPM", ".ppm")
Image.register_extension(PpmImageFile.format, ".pbm")
Image.register_extension(PpmImageFile.format, ".pgm")
Image.register_extension(PpmImageFile.format, ".ppm")

View File

@ -136,6 +136,10 @@ class PsdImageFile(ImageFile.ImageFile):
def n_frames(self):
return len(self.layers)
@property
def is_animated(self):
return len(self.layers) > 1
def seek(self, layer):
# seek to given layer (1..max)
if layer == self.frame:
@ -303,6 +307,6 @@ def _maketile(file, mode, bbox, channels):
# --------------------------------------------------------------------
# registry
Image.register_open("PSD", PsdImageFile, _accept)
Image.register_open(PsdImageFile.format, PsdImageFile, _accept)
Image.register_extension("PSD", ".psd")
Image.register_extension(PsdImageFile.format, ".psd")

View File

@ -22,10 +22,14 @@
from __future__ import print_function
from cffi import FFI
import logging
import sys
DEBUG = 0
from cffi import FFI
logger = logging.getLogger(__name__)
defs = """
struct Pixel_RGBA {
@ -50,8 +54,9 @@ class PyAccess(object):
self.xsize = vals['xsize']
self.ysize = vals['ysize']
if DEBUG:
print(vals)
# Debugging is polluting test traces, only useful here
# when hacking on PyAccess
# logger.debug("%s", vals)
self._post_init()
def _post_init(self):
@ -305,11 +310,8 @@ else:
def new(img, readonly=False):
access_type = mode_map.get(img.mode, None)
if not access_type:
if DEBUG:
print("PyAccess Not Implemented: %s" % img.mode)
logger.debug("PyAccess Not Implemented: %s", img.mode)
return None
if DEBUG:
print("New PyAccess: %s" % img.mode)
return access_type(img, readonly)
# End of file

View File

@ -18,18 +18,16 @@
#
__version__ = "0.2"
from PIL import Image, ImageFile, _binary
__version__ = "0.2"
i8 = _binary.i8
i16 = _binary.i16be
i32 = _binary.i32be
def _accept(prefix):
return i16(prefix) == 474
return len(prefix) >= 2 and i16(prefix) == 474
##
@ -81,11 +79,11 @@ class SgiImageFile(ImageFile.ImageFile):
#
# registry
Image.register_open("SGI", SgiImageFile, _accept)
Image.register_open(SgiImageFile.format, SgiImageFile, _accept)
Image.register_extension("SGI", ".bw")
Image.register_extension("SGI", ".rgb")
Image.register_extension("SGI", ".rgba")
Image.register_extension("SGI", ".sgi")
Image.register_extension(SgiImageFile.format, ".bw")
Image.register_extension(SgiImageFile.format, ".rgb")
Image.register_extension(SgiImageFile.format, ".rgba")
Image.register_extension(SgiImageFile.format, ".sgi")
# End of file

View File

@ -27,10 +27,10 @@
# image data from electron microscopy and tomography.
#
# Spider home page:
# http://www.wadsworth.org/spider_doc/spider/docs/spider.html
# http://spider.wadsworth.org/spider_doc/spider/docs/spider.html
#
# Details about the Spider image format:
# http://www.wadsworth.org/spider_doc/spider/docs/image_doc.html
# http://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html
#
from __future__ import print_function
@ -50,6 +50,8 @@ def isInt(f):
return 0
except ValueError:
return 0
except OverflowError:
return 0
iforms = [1, 3, -11, -12, -21, -22]
@ -158,6 +160,10 @@ class SpiderImageFile(ImageFile.ImageFile):
def n_frames(self):
return self._nimages
@property
def is_animated(self):
return self._nimages > 1
# 1st image index is zero (although SPIDER imgnumber starts at 1)
def tell(self):
if self.imgnumber < 1:
@ -281,8 +287,8 @@ def _save_spider(im, fp, filename):
# --------------------------------------------------------------------
Image.register_open("SPIDER", SpiderImageFile)
Image.register_save("SPIDER", _save_spider)
Image.register_open(SpiderImageFile.format, SpiderImageFile)
Image.register_save(SpiderImageFile.format, _save_spider)
if __name__ == "__main__":

View File

@ -17,17 +17,15 @@
#
__version__ = "0.3"
from PIL import Image, ImageFile, ImagePalette, _binary
i16 = _binary.i16be
__version__ = "0.3"
i32 = _binary.i32be
def _accept(prefix):
return i32(prefix) == 0x59a66a95
return len(prefix) >= 4 and i32(prefix) == 0x59a66a95
##
@ -78,6 +76,6 @@ class SunImageFile(ImageFile.ImageFile):
#
# registry
Image.register_open("SUN", SunImageFile, _accept)
Image.register_open(SunImageFile.format, SunImageFile, _accept)
Image.register_extension("SUN", ".ras")
Image.register_extension(SunImageFile.format, ".ras")

View File

@ -17,10 +17,10 @@
#
__version__ = "0.3"
from PIL import Image, ImageFile, ImagePalette, _binary
__version__ = "0.3"
#
# --------------------------------------------------------------------
@ -28,7 +28,6 @@ from PIL import Image, ImageFile, ImagePalette, _binary
i8 = _binary.i8
i16 = _binary.i16le
i32 = _binary.i32le
MODES = {
@ -193,7 +192,7 @@ def _save(im, fp, filename, check=0):
# --------------------------------------------------------------------
# Registry
Image.register_open("TGA", TgaImageFile)
Image.register_save("TGA", _save)
Image.register_open(TgaImageFile.format, TgaImageFile)
Image.register_save(TgaImageFile.format, _save)
Image.register_extension("TGA", ".tga")
Image.register_extension(TgaImageFile.format, ".tga")

File diff suppressed because it is too large Load Diff

View File

@ -17,291 +17,387 @@
# well-known TIFF tags.
##
from collections import namedtuple
class TagInfo(namedtuple("_TagInfo", "value name type length enum")):
__slots__ = []
def __new__(cls, value=None, name="unknown", type=None, length=0, enum=None):
return super(TagInfo, cls).__new__(
cls, value, name, type, length, enum or {})
def cvt_enum(self, value):
return self.enum.get(value, value)
def lookup(tag):
"""
:param tag: Integer tag number
:returns: Taginfo namedtuple, From the TAGS_V2 info if possible,
otherwise just populating the value and name from TAGS.
If the tag is not recognized, "unknown" is returned for the name
"""
return TAGS_V2.get(tag, TagInfo(tag, TAGS.get(tag, 'unknown')))
##
# Map tag numbers (or tag number, tag value tuples) to tag names.
# Map tag numbers to tag info.
#
# id: (Name, Type, Length, enum_values)
#
TAGS = {
ASCII = 2
SHORT = 3
LONG = 4
RATIONAL = 5
254: "NewSubfileType",
255: "SubfileType",
256: "ImageWidth",
257: "ImageLength",
258: "BitsPerSample",
TAGS_V2 = {
259: "Compression",
(259, 1): "Uncompressed",
(259, 2): "CCITT 1d",
(259, 3): "Group 3 Fax",
(259, 4): "Group 4 Fax",
(259, 5): "LZW",
(259, 6): "JPEG",
(259, 32773): "PackBits",
254: ("NewSubfileType", LONG, 1),
255: ("SubfileType", SHORT, 1),
256: ("ImageWidth", LONG, 1),
257: ("ImageLength", LONG, 1),
258: ("BitsPerSample", SHORT, 0),
259: ("Compression", SHORT, 1,
{"Uncompressed": 1, "CCITT 1d": 2, "Group 3 Fax": 3, "Group 4 Fax": 4,
"LZW": 5, "JPEG": 6, "PackBits": 32773}),
262: "PhotometricInterpretation",
(262, 0): "WhiteIsZero",
(262, 1): "BlackIsZero",
(262, 2): "RGB",
(262, 3): "RGB Palette",
(262, 4): "Transparency Mask",
(262, 5): "CMYK",
(262, 6): "YCbCr",
(262, 8): "CieLAB",
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
(262, 32892): "LinearRaw", # Adobe DNG
262: ("PhotometricInterpretation", SHORT, 1,
{"WhiteIsZero": 0, "BlackIsZero": 1, "RGB": 2, "RBG Palette": 3,
"Transparency Mask": 4, "CMYK": 5, "YCbCr": 6, "CieLAB": 8,
"CFA": 32803, # TIFF/EP, Adobe DNG
"LinearRaw": 32892}), # Adobe DNG
263: ("Threshholding", SHORT, 1),
264: ("CellWidth", SHORT, 1),
265: ("CellLength", SHORT, 1),
266: ("FillOrder", SHORT, 1),
269: ("DocumentName", ASCII, 1),
263: "Thresholding",
264: "CellWidth",
265: "CellHeight",
266: "FillOrder",
269: "DocumentName",
270: ("ImageDescription", ASCII, 1),
271: ("Make", ASCII, 1),
272: ("Model", ASCII, 1),
273: ("StripOffsets", LONG, 0),
274: ("Orientation", SHORT, 1),
277: ("SamplesPerPixel", SHORT, 1),
278: ("RowsPerStrip", LONG, 1),
279: ("StripByteCounts", LONG, 0),
270: "ImageDescription",
271: "Make",
272: "Model",
273: "StripOffsets",
274: "Orientation",
277: "SamplesPerPixel",
278: "RowsPerStrip",
279: "StripByteCounts",
280: ("MinSampleValue", LONG, 0),
281: ("MaxSampleValue", SHORT, 0),
282: ("XResolution", RATIONAL, 1),
283: ("YResolution", RATIONAL, 1),
284: ("PlanarConfiguration", SHORT, 1, {"Contigous": 1, "Separate": 2}),
285: ("PageName", ASCII, 1),
286: ("XPosition", RATIONAL, 1),
287: ("YPosition", RATIONAL, 1),
288: ("FreeOffsets", LONG, 1),
289: ("FreeByteCounts", LONG, 1),
280: "MinSampleValue",
281: "MaxSampleValue",
282: "XResolution",
283: "YResolution",
284: "PlanarConfiguration",
(284, 1): "Contigous",
(284, 2): "Separate",
290: ("GrayResponseUnit", SHORT, 1),
291: ("GrayResponseCurve", SHORT, 0),
292: ("T4Options", LONG, 1),
293: ("T6Options", LONG, 1),
296: ("ResolutionUnit", SHORT, 1, {"inch": 1, "cm": 2}),
297: ("PageNumber", SHORT, 2),
285: "PageName",
286: "XPosition",
287: "YPosition",
288: "FreeOffsets",
289: "FreeByteCounts",
301: ("TransferFunction", SHORT, 0),
305: ("Software", ASCII, 1),
306: ("DateTime", ASCII, 1),
290: "GrayResponseUnit",
291: "GrayResponseCurve",
292: "T4Options",
293: "T6Options",
296: "ResolutionUnit",
297: "PageNumber",
315: ("Artist", ASCII, 1),
316: ("HostComputer", ASCII, 1),
317: ("Predictor", SHORT, 1),
318: ("WhitePoint", RATIONAL, 2),
319: ("PrimaryChromaticities", SHORT, 6),
301: "TransferFunction",
305: "Software",
306: "DateTime",
320: ("ColorMap", SHORT, 0),
321: ("HalftoneHints", SHORT, 2),
322: ("TileWidth", LONG, 1),
323: ("TileLength", LONG, 1),
324: ("TileOffsets", LONG, 0),
325: ("TileByteCounts", LONG, 0),
315: "Artist",
316: "HostComputer",
317: "Predictor",
318: "WhitePoint",
319: "PrimaryChromaticies",
332: ("InkSet", SHORT, 1),
333: ("InkNames", ASCII, 1),
334: ("NumberOfInks", SHORT, 1),
336: ("DotRange", SHORT, 0),
337: ("TargetPrinter", ASCII, 1),
338: ("ExtraSamples", SHORT, 0),
339: ("SampleFormat", SHORT, 0),
320: "ColorMap",
321: "HalftoneHints",
322: "TileWidth",
323: "TileLength",
324: "TileOffsets",
325: "TileByteCounts",
332: "InkSet",
333: "InkNames",
334: "NumberOfInks",
336: "DotRange",
337: "TargetPrinter",
338: "ExtraSamples",
339: "SampleFormat",
340: "SMinSampleValue",
341: "SMaxSampleValue",
342: "TransferRange",
347: "JPEGTables",
340: ("SMinSampleValue", 12, 0),
341: ("SMaxSampleValue", 12, 0),
342: ("TransferRange", SHORT, 6),
# obsolete JPEG tags
512: "JPEGProc",
513: "JPEGInterchangeFormat",
514: "JPEGInterchangeFormatLength",
515: "JPEGRestartInterval",
517: "JPEGLosslessPredictors",
518: "JPEGPointTransforms",
519: "JPEGQTables",
520: "JPEGDCTables",
521: "JPEGACTables",
512: ("JPEGProc", SHORT, 1),
513: ("JPEGInterchangeFormat", LONG, 1),
514: ("JPEGInterchangeFormatLength", LONG, 1),
515: ("JPEGRestartInterval", SHORT, 1),
517: ("JPEGLosslessPredictors", SHORT, 0),
518: ("JPEGPointTransforms", SHORT, 0),
519: ("JPEGQTables", LONG, 0),
520: ("JPEGDCTables", LONG, 0),
521: ("JPEGACTables", LONG, 0),
529: "YCbCrCoefficients",
530: "YCbCrSubSampling",
531: "YCbCrPositioning",
532: "ReferenceBlackWhite",
529: ("YCbCrCoefficients", RATIONAL, 3),
530: ("YCbCrSubSampling", SHORT, 2),
531: ("YCbCrPositioning", SHORT, 1),
532: ("ReferenceBlackWhite", LONG, 0),
# XMP
700: "XMP",
33432: ("Copyright", ASCII, 1),
33432: "Copyright",
# FIXME add more tags here
34665: ("ExifIFD", SHORT, 1),
34675: ('ICCProfile', 7, 0),
34853: ('GPSInfoIFD', 1, 1),
# various extensions (should check specs for "official" names)
33723: "IptcNaaInfo",
34377: "PhotoshopInfo",
# MPInfo
45056: ("MPFVersion", 7, 1),
45057: ("NumberOfImages", LONG, 1),
45058: ("MPEntry", 7, 1),
45059: ("ImageUIDList", 7, 0),
45060: ("TotalFrames", LONG, 1),
45313: ("MPIndividualNum", LONG, 1),
45569: ("PanOrientation", LONG, 1),
45570: ("PanOverlap_H", RATIONAL, 1),
45571: ("PanOverlap_V", RATIONAL, 1),
45572: ("BaseViewpointNum", LONG, 1),
45573: ("ConvergenceAngle", 10, 1),
45574: ("BaselineLength", RATIONAL, 1),
45575: ("VerticalDivergence", 10, 1),
45576: ("AxisDistance_X", 10, 1),
45577: ("AxisDistance_Y", 10, 1),
45578: ("AxisDistance_Z", 10, 1),
45579: ("YawAngle", 10, 1),
45580: ("PitchAngle", 10, 1),
45581: ("RollAngle", 10, 1),
# Exif IFD
34665: "ExifIFD",
# ICC Profile
34675: "ICCProfile",
# Additional Exif Info
33434: "ExposureTime",
33437: "FNumber",
34850: "ExposureProgram",
34852: "SpectralSensitivity",
34853: "GPSInfoIFD",
34855: "ISOSpeedRatings",
34856: "OECF",
34864: "SensitivityType",
34865: "StandardOutputSensitivity",
34866: "RecommendedExposureIndex",
34867: "ISOSpeed",
34868: "ISOSpeedLatitudeyyy",
34869: "ISOSpeedLatitudezzz",
36864: "ExifVersion",
36867: "DateTimeOriginal",
36868: "DateTImeDigitized",
37121: "ComponentsConfiguration",
37122: "CompressedBitsPerPixel",
37377: "ShutterSpeedValue",
37378: "ApertureValue",
37379: "BrightnessValue",
37380: "ExposureBiasValue",
37381: "MaxApertureValue",
37382: "SubjectDistance",
37383: "MeteringMode",
37384: "LightSource",
37385: "Flash",
37386: "FocalLength",
37396: "SubjectArea",
37500: "MakerNote",
37510: "UserComment",
37520: "SubSec",
37521: "SubSecTimeOriginal",
37522: "SubsecTimeDigitized",
40960: "FlashPixVersion",
40961: "ColorSpace",
40962: "PixelXDimension",
40963: "PixelYDimension",
40964: "RelatedSoundFile",
40965: "InteroperabilityIFD",
41483: "FlashEnergy",
41484: "SpatialFrequencyResponse",
41486: "FocalPlaneXResolution",
41487: "FocalPlaneYResolution",
41488: "FocalPlaneResolutionUnit",
41492: "SubjectLocation",
41493: "ExposureIndex",
41495: "SensingMethod",
41728: "FileSource",
41729: "SceneType",
41730: "CFAPattern",
41985: "CustomRendered",
41986: "ExposureMode",
41987: "WhiteBalance",
41988: "DigitalZoomRatio",
41989: "FocalLengthIn35mmFilm",
41990: "SceneCaptureType",
41991: "GainControl",
41992: "Contrast",
41993: "Saturation",
41994: "Sharpness",
41995: "DeviceSettingDescription",
41996: "SubjectDistanceRange",
42016: "ImageUniqueID",
42032: "CameraOwnerName",
42033: "BodySerialNumber",
42034: "LensSpecification",
42035: "LensMake",
42036: "LensModel",
42037: "LensSerialNumber",
42240: "Gamma",
# MP Info
45056: "MPFVersion",
45057: "NumberOfImages",
45058: "MPEntry",
45059: "ImageUIDList",
45060: "TotalFrames",
45313: "MPIndividualNum",
45569: "PanOrientation",
45570: "PanOverlap_H",
45571: "PanOverlap_V",
45572: "BaseViewpointNum",
45573: "ConvergenceAngle",
45574: "BaselineLength",
45575: "VerticalDivergence",
45576: "AxisDistance_X",
45577: "AxisDistance_Y",
45578: "AxisDistance_Z",
45579: "YawAngle",
45580: "PitchAngle",
45581: "RollAngle",
# Adobe DNG
50706: "DNGVersion",
50707: "DNGBackwardVersion",
50708: "UniqueCameraModel",
50709: "LocalizedCameraModel",
50710: "CFAPlaneColor",
50711: "CFALayout",
50712: "LinearizationTable",
50713: "BlackLevelRepeatDim",
50714: "BlackLevel",
50715: "BlackLevelDeltaH",
50716: "BlackLevelDeltaV",
50717: "WhiteLevel",
50718: "DefaultScale",
50719: "DefaultCropOrigin",
50720: "DefaultCropSize",
50778: "CalibrationIlluminant1",
50779: "CalibrationIlluminant2",
50721: "ColorMatrix1",
50722: "ColorMatrix2",
50723: "CameraCalibration1",
50724: "CameraCalibration2",
50725: "ReductionMatrix1",
50726: "ReductionMatrix2",
50727: "AnalogBalance",
50728: "AsShotNeutral",
50729: "AsShotWhiteXY",
50730: "BaselineExposure",
50731: "BaselineNoise",
50732: "BaselineSharpness",
50733: "BayerGreenSplit",
50734: "LinearResponseLimit",
50735: "CameraSerialNumber",
50736: "LensInfo",
50737: "ChromaBlurRadius",
50738: "AntiAliasStrength",
50740: "DNGPrivateData",
50741: "MakerNoteSafety",
50780: "BestQualityScale",
# ImageJ
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
50839: "ImageJMetaData", # private tag registered with Adobe
50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}),
50780: ("BestQualityScale", RATIONAL, 1),
50838: ("ImageJMetaDataByteCounts", LONG, 1),
50839: ("ImageJMetaData", 7, 1)
}
# Legacy Tags structure
# these tags aren't included above, but were in the previous versions
TAGS = {347: 'JPEGTables',
700: 'XMP',
# Additional Exif Info
33434: 'ExposureTime',
33437: 'FNumber',
33723: 'IptcNaaInfo',
34377: 'PhotoshopInfo',
34850: 'ExposureProgram',
34852: 'SpectralSensitivity',
34855: 'ISOSpeedRatings',
34856: 'OECF',
34864: 'SensitivityType',
34865: 'StandardOutputSensitivity',
34866: 'RecommendedExposureIndex',
34867: 'ISOSpeed',
34868: 'ISOSpeedLatitudeyyy',
34869: 'ISOSpeedLatitudezzz',
36864: 'ExifVersion',
36867: 'DateTimeOriginal',
36868: 'DateTImeDigitized',
37121: 'ComponentsConfiguration',
37122: 'CompressedBitsPerPixel',
37377: 'ShutterSpeedValue',
37378: 'ApertureValue',
37379: 'BrightnessValue',
37380: 'ExposureBiasValue',
37381: 'MaxApertureValue',
37382: 'SubjectDistance',
37383: 'MeteringMode',
37384: 'LightSource',
37385: 'Flash',
37386: 'FocalLength',
37396: 'SubjectArea',
37500: 'MakerNote',
37510: 'UserComment',
37520: 'SubSec',
37521: 'SubSecTimeOriginal',
37522: 'SubsecTimeDigitized',
40960: 'FlashPixVersion',
40961: 'ColorSpace',
40962: 'PixelXDimension',
40963: 'PixelYDimension',
40964: 'RelatedSoundFile',
40965: 'InteroperabilityIFD',
41483: 'FlashEnergy',
41484: 'SpatialFrequencyResponse',
41486: 'FocalPlaneXResolution',
41487: 'FocalPlaneYResolution',
41488: 'FocalPlaneResolutionUnit',
41492: 'SubjectLocation',
41493: 'ExposureIndex',
41495: 'SensingMethod',
41728: 'FileSource',
41729: 'SceneType',
41730: 'CFAPattern',
41985: 'CustomRendered',
41986: 'ExposureMode',
41987: 'WhiteBalance',
41988: 'DigitalZoomRatio',
41989: 'FocalLengthIn35mmFilm',
41990: 'SceneCaptureType',
41991: 'GainControl',
41992: 'Contrast',
41993: 'Saturation',
41994: 'Sharpness',
41995: 'DeviceSettingDescription',
41996: 'SubjectDistanceRange',
42016: 'ImageUniqueID',
42032: 'CameraOwnerName',
42033: 'BodySerialNumber',
42034: 'LensSpecification',
42035: 'LensMake',
42036: 'LensModel',
42037: 'LensSerialNumber',
42240: 'Gamma',
# Adobe DNG
50706: 'DNGVersion',
50707: 'DNGBackwardVersion',
50708: 'UniqueCameraModel',
50709: 'LocalizedCameraModel',
50710: 'CFAPlaneColor',
50711: 'CFALayout',
50712: 'LinearizationTable',
50713: 'BlackLevelRepeatDim',
50714: 'BlackLevel',
50715: 'BlackLevelDeltaH',
50716: 'BlackLevelDeltaV',
50717: 'WhiteLevel',
50718: 'DefaultScale',
50719: 'DefaultCropOrigin',
50720: 'DefaultCropSize',
50721: 'ColorMatrix1',
50722: 'ColorMatrix2',
50723: 'CameraCalibration1',
50724: 'CameraCalibration2',
50725: 'ReductionMatrix1',
50726: 'ReductionMatrix2',
50727: 'AnalogBalance',
50728: 'AsShotNeutral',
50729: 'AsShotWhiteXY',
50730: 'BaselineExposure',
50731: 'BaselineNoise',
50732: 'BaselineSharpness',
50733: 'BayerGreenSplit',
50734: 'LinearResponseLimit',
50735: 'CameraSerialNumber',
50736: 'LensInfo',
50737: 'ChromaBlurRadius',
50738: 'AntiAliasStrength',
50740: 'DNGPrivateData',
50778: 'CalibrationIlluminant1',
50779: 'CalibrationIlluminant2',
}
def _populate():
for k, v in TAGS_V2.items():
# Populate legacy structure.
TAGS[k] = v[0]
if len(v) == 4:
for sk, sv in v[3].items():
TAGS[(k, sv)] = sk
TAGS_V2[k] = TagInfo(k, *v)
_populate()
##
# Map type numbers to type names.
# Map type numbers to type names -- defined in ImageFileDirectory.
TYPES = {
TYPES = {}
1: "byte",
2: "ascii",
3: "short",
4: "long",
5: "rational",
6: "signed byte",
7: "undefined",
8: "signed short",
9: "signed long",
10: "signed rational",
11: "float",
12: "double",
# was:
# TYPES = {
# 1: "byte",
# 2: "ascii",
# 3: "short",
# 4: "long",
# 5: "rational",
# 6: "signed byte",
# 7: "undefined",
# 8: "signed short",
# 9: "signed long",
# 10: "signed rational",
# 11: "float",
# 12: "double",
# }
}
#
# These tags are handled by default in libtiff, without
# adding to the custom dictionary. From tif_dir.c, searching for
# case TIFFTAG in the _TIFFVSetField function:
# Line: item.
# 148: case TIFFTAG_SUBFILETYPE:
# 151: case TIFFTAG_IMAGEWIDTH:
# 154: case TIFFTAG_IMAGELENGTH:
# 157: case TIFFTAG_BITSPERSAMPLE:
# 181: case TIFFTAG_COMPRESSION:
# 202: case TIFFTAG_PHOTOMETRIC:
# 205: case TIFFTAG_THRESHHOLDING:
# 208: case TIFFTAG_FILLORDER:
# 214: case TIFFTAG_ORIENTATION:
# 221: case TIFFTAG_SAMPLESPERPIXEL:
# 228: case TIFFTAG_ROWSPERSTRIP:
# 238: case TIFFTAG_MINSAMPLEVALUE:
# 241: case TIFFTAG_MAXSAMPLEVALUE:
# 244: case TIFFTAG_SMINSAMPLEVALUE:
# 247: case TIFFTAG_SMAXSAMPLEVALUE:
# 250: case TIFFTAG_XRESOLUTION:
# 256: case TIFFTAG_YRESOLUTION:
# 262: case TIFFTAG_PLANARCONFIG:
# 268: case TIFFTAG_XPOSITION:
# 271: case TIFFTAG_YPOSITION:
# 274: case TIFFTAG_RESOLUTIONUNIT:
# 280: case TIFFTAG_PAGENUMBER:
# 284: case TIFFTAG_HALFTONEHINTS:
# 288: case TIFFTAG_COLORMAP:
# 294: case TIFFTAG_EXTRASAMPLES:
# 298: case TIFFTAG_MATTEING:
# 305: case TIFFTAG_TILEWIDTH:
# 316: case TIFFTAG_TILELENGTH:
# 327: case TIFFTAG_TILEDEPTH:
# 333: case TIFFTAG_DATATYPE:
# 344: case TIFFTAG_SAMPLEFORMAT:
# 361: case TIFFTAG_IMAGEDEPTH:
# 364: case TIFFTAG_SUBIFD:
# 376: case TIFFTAG_YCBCRPOSITIONING:
# 379: case TIFFTAG_YCBCRSUBSAMPLING:
# 383: case TIFFTAG_TRANSFERFUNCTION:
# 389: case TIFFTAG_REFERENCEBLACKWHITE:
# 393: case TIFFTAG_INKNAMES:
# some of these are not in our TAGS_V2 dict and were included from tiff.h
LIBTIFF_CORE = set([255, 256, 257, 258, 259, 262, 263, 266, 274, 277,
278, 280, 281, 340, 341, 282, 283, 284, 286, 287,
296, 297, 321, 320, 338, 32995, 322, 323, 32998,
32996, 339, 32997, 330, 531, 530, 301, 532, 333,
# as above
269 # this has been in our tests forever, and works
])
LIBTIFF_CORE.remove(320) # Array of short, crashes
LIBTIFF_CORE.remove(301) # Array of short, crashes
LIBTIFF_CORE.remove(532) # Array of long, crashes
LIBTIFF_CORE.remove(255) # We don't have support for subfiletypes
LIBTIFF_CORE.remove(322) # We don't have support for tiled images in libtiff
LIBTIFF_CORE.remove(323) # Tiled images
LIBTIFF_CORE.remove(333) # Ink Names either
# Note to advanced users: There may be combinations of these
# parameters and values that when added properly, will work and
# produce valid tiff images that may work in your application.
# It is safe to add and remove tags from this set from Pillow's point
# of view so long as you test against libtiff.

View File

@ -1,3 +1,5 @@
# encoding: utf-8
#
# The Python Imaging Library.
# $Id$
#
@ -124,8 +126,3 @@ quake2palette = (
b"\x10\x0f\x0d\x0d\x0b\x3c\x2e\x2a\x36\x27\x20\x30\x21\x18\x29\x1b"
b"\x10\x3c\x39\x37\x37\x32\x2f\x31\x2c\x28\x2b\x26\x21\x30\x22\x20"
)
if __name__ == "__main__":
im = open("../hacks/sample.wal")
print(im.info, im.mode, im.size)
im.save("../out.png")

View File

@ -73,8 +73,8 @@ def _save(im, fp, filename):
fp.write(data)
Image.register_open("WEBP", WebPImageFile, _accept)
Image.register_save("WEBP", _save)
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
Image.register_save(WebPImageFile.format, _save)
Image.register_extension("WEBP", ".webp")
Image.register_mime("WEBP", "image/webp")
Image.register_extension(WebPImageFile.format, ".webp")
Image.register_mime(WebPImageFile.format, "image/webp")

View File

@ -15,10 +15,10 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.2"
from PIL import Image, ImageFile, _binary
__version__ = "0.2"
_handler = None
if str != bytes:

View File

@ -17,10 +17,10 @@
# FIXME: make save work (this requires quantization support)
#
__version__ = "0.1"
from PIL import Image, ImageFile, ImagePalette, _binary
__version__ = "0.1"
o8 = _binary.o8
# standard color palette for thumbnails (RGB332)
@ -72,4 +72,4 @@ class XVThumbImageFile(ImageFile.ImageFile):
# --------------------------------------------------------------------
Image.register_open("XVThumb", XVThumbImageFile)
Image.register_open(XVThumbImageFile.format, XVThumbImageFile)

View File

@ -19,15 +19,15 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.6"
import re
from PIL import Image, ImageFile
__version__ = "0.6"
# XBM header
xbm_head = re.compile(
b"\s*#define[ \t]+[^_]*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
b"#define[ \t]+[^_]*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
b"\s*#define[ \t]+.*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
b"#define[ \t]+.*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
b"(?P<hotspot>"
b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
b"#define[ \t]+[^_]*_y_hot[ \t]+(?P<yhot>[0-9]+)[\r\n]+"
@ -88,9 +88,9 @@ def _save(im, fp, filename):
fp.write(b"};\n")
Image.register_open("XBM", XbmImageFile, _accept)
Image.register_save("XBM", _save)
Image.register_open(XbmImageFile.format, XbmImageFile, _accept)
Image.register_save(XbmImageFile.format, _save)
Image.register_extension("XBM", ".xbm")
Image.register_extension(XbmImageFile.format, ".xbm")
Image.register_mime("XBM", "image/xbm")
Image.register_mime(XbmImageFile.format, "image/xbm")

View File

@ -15,13 +15,12 @@
#
__version__ = "0.2"
import re
from PIL import Image, ImageFile, ImagePalette
from PIL._binary import i8, o8
__version__ = "0.2"
# XPM header
xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)")
@ -124,8 +123,8 @@ class XpmImageFile(ImageFile.ImageFile):
#
# Registry
Image.register_open("XPM", XpmImageFile, _accept)
Image.register_open(XpmImageFile.format, XpmImageFile, _accept)
Image.register_extension("XPM", ".xpm")
Image.register_extension(XpmImageFile.format, ".xpm")
Image.register_mime("XPM", "image/xpm")
Image.register_mime(XpmImageFile.format, "image/xpm")

View File

@ -12,16 +12,18 @@
# ;-)
VERSION = '1.1.7' # PIL version
PILLOW_VERSION = '2.9.0.dev0' # Pillow
PILLOW_VERSION = '3.3.0.dev0' # Pillow
_plugins = ['BmpImagePlugin',
'BufrStubImagePlugin',
'CurImagePlugin',
'DcxImagePlugin',
'DdsImagePlugin',
'EpsImagePlugin',
'FitsStubImagePlugin',
'FliImagePlugin',
'FpxImagePlugin',
'FtexImagePlugin',
'GbrImagePlugin',
'GifImagePlugin',
'GribStubImagePlugin',

View File

@ -4,45 +4,57 @@ Pillow
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.
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://zenodo.org/badge/17549/python-pillow/Pillow.svg
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
.. image:: https://readthedocs.org/projects/pillow/badge/?version=latest
:target: http://pillow.readthedocs.org/?badge=latest
:alt: Documentation Status
.. image:: https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build
:target: https://travis-ci.org/python-pillow/Pillow
:alt: Travis CI build status (Linux)
..
image:: https://pypip.in/v/Pillow/badge.png
:target: https://pypi.python.org/pypi/Pillow/
:alt: Latest PyPI version
.. image:: https://img.shields.io/travis/python-pillow/pillow-wheels/latest.svg?label=OS%20X%20build
:target: https://travis-ci.org/python-pillow/pillow-wheels
:alt: Travis CI build status (OS X)
..
image:: https://pypip.in/d/Pillow/badge.png
:target: https://pypi.python.org/pypi/Pillow/
:alt: Number of PyPI downloads
.. image:: https://img.shields.io/appveyor/ci/python-pillow/Pillow/master.svg?label=Windows%20build
:target: https://ci.appveyor.com/project/python-pillow/Pillow
:alt: AppVeyor CI build status (Windows)
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
.. image:: https://img.shields.io/pypi/v/pillow.svg
:target: https://pypi.python.org/pypi/Pillow/
:alt: Latest PyPI version
.. image:: https://img.shields.io/pypi/dm/pillow.svg
:target: https://pypi.python.org/pypi/Pillow/
:alt: Number of PyPI downloads
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/python-pillow/Pillow?branch=master
:alt: Code coverage
.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.png
.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.svg
: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>`_
- `Documentation <https://pillow.readthedocs.org/>`_
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#02b5---117-1995-2010>`_
- `Installation <https://pillow.readthedocs.org/en/latest/installation.html>`_
- `Handbook <https://pillow.readthedocs.org/en/latest/handbook/index.html>`_
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_
- `Issues <https://github.com/python-pillow/Pillow/issues>`_
- `Pull requests <https://github.com/python-pillow/Pillow/pulls>`_
- `Documentation <http://pillow.readthedocs.org/>`_
- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
- `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>`_
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork>`_

View File

@ -6,13 +6,13 @@ Released quarterly on the first day of January, April, July, October.
* [ ] 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.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/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
PIL/__init__.py setup.py _imaging.c appveyor.yml
```
* [ ] Update `CHANGES.rst`.
* [ ] Run pre-release check via `make pre`.
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
* [ ] Create branch and tag for release e.g.:
```
$ git branch 2.9.x
@ -22,10 +22,11 @@ Released quarterly on the first day of January, April, July, October.
```
* [ ] Create and upload source distributions e.g.:
```
$ make sdistup
$ make sdist
$ make upload
```
* [ ] 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
* [ ] Manually hide old versions on PyPI such that only the latest major release is visible when viewing https://pypi.python.org/pypi/Pillow (https://pypi.python.org/pypi?:action=pkg_edit&name=Pillow)
## Point Release
@ -41,9 +42,12 @@ Released as needed for security, installation or critical bug fixes.
```
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
```
PIL/__init__.py setup.py _imaging.c
PIL/__init__.py
setup.py
_imaging.c
appveyor.yml
```
* [ ] Run pre-release check via `make pre`.
* [ ] Run pre-release check via `make release-test`.
* [ ] Create tag for release e.g.:
```
$ git tag 2.9.1
@ -63,7 +67,7 @@ Released as needed privately to individual vendors for critical security-related
* [ ] Commit against master, cherry pick to affected release branches.
* [ ] Run local test matrix on each release & Python version.
* [ ] Privately send to distros.
* [ ] Run pre-release check via `make pre`
* [ ] Run pre-release check via `make release-test`
* [ ] Amend any commits with the CVE #
* [ ] On release date, tag and push to GitHub.
```
@ -103,3 +107,7 @@ Released as needed privately to individual vendors for critical security-related
## Publicize Release
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328.
## Documentation
* [ ] Make sure the default version for Read the Docs is the latest release version, e.g. ``3.1.x`` rather than ``latest``: https://readthedocs.org/projects/pillow/versions/

View File

@ -12,7 +12,7 @@ pildriver.py (by Eric S. Raymond)
--------------------------------------------------------------------
A class implementing an image-processing calculator for scripts.
Parses lists of commnds (or, called interactively, command-line
Parses lists of commands (or, called interactively, command-line
arguments) into image loads, transformations, and saves.
viewer.py

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
from __future__ import print_function
import base64
import os

View File

@ -14,120 +14,9 @@
# See the README file for information on usage and redistribution.
#
#
# For special purposes, you can import this module and call
# the makedelta or compress functions yourself. For example,
# if you have an application that generates a sequence of
# images, you can convert it to a GIF animation using some-
# thing like the following code:
#
# import Image
# import gifmaker
#
# sequence = []
#
# # generate sequence
# for i in range(100):
# im = <generate image i>
# sequence.append(im)
#
# # write GIF animation
# fp = open("out.gif", "wb")
# gifmaker.makedelta(fp, sequence)
# fp.close()
#
# Alternatively, use an iterator to generate the sequence, and
# write data directly to a socket. Or something...
#
from __future__ import print_function
from PIL import Image, ImageChops
from PIL.GifImagePlugin import getheader, getdata
# --------------------------------------------------------------------
# sequence iterator
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
# --------------------------------------------------------------------
# straightforward delta encoding
def makedelta(fp, sequence):
"""Convert list of image frames to a GIF animation file"""
frames = 0
previous = None
for im in sequence:
# 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)[0] + getdata(im):
fp.write(s)
else:
# delta frame
delta = ImageChops.subtract_modulo(im, previous)
bbox = delta.getbbox()
if bbox:
# compress difference
for s in getdata(im.crop(bbox), offset=bbox[:2]):
fp.write(s)
else:
# FIXME: what should we do in this case?
pass
previous = im.copy()
frames += 1
fp.write(";")
return frames
# --------------------------------------------------------------------
# main hack
def compress(infile, outfile):
# open input image, and force loading of first frame
im = Image.open(infile)
im.load()
# open output file
fp = open(outfile, "wb")
seq = image_sequence(im)
makedelta(fp, seq)
fp.close()
from PIL import Image
if __name__ == "__main__":
@ -138,4 +27,5 @@ if __name__ == "__main__":
print("Usage: gifmaker infile outfile")
sys.exit(1)
compress(sys.argv[1], sys.argv[2])
im = Image.open(sys.argv[1])
im.save(sys.argv[2], save_all=True)

View File

@ -5,7 +5,7 @@
#
# this demo script illustrates pasting into an already displayed
# photoimage. note that the current version of Tk updates the whole
# image everytime we paste, so to get decent performance, we split
# image every time we paste, so to get decent performance, we split
# the image into a set of tiles.
#
@ -68,6 +68,10 @@ class PaintCanvas(Canvas):
root = Tk()
if len(sys.argv) != 2:
print("Usage: painter file")
sys.exit(1)
im = Image.open(sys.argv[1])
if im.mode != "RGB":

View File

@ -70,7 +70,7 @@ class PILDriver(object):
def push(self, item):
"Push an argument onto the evaluation stack."
self.stack = [item] + self.stack
self.stack.insert(0, item)
def top(self):
"Return the top-of-stack element."
@ -90,9 +90,7 @@ class PILDriver(object):
Discard the top element on the stack.
"""
top = self.stack[0]
self.stack = self.stack[1:]
return top
return self.stack.pop(0)
def do_dup(self):
"""usage: dup
@ -103,7 +101,7 @@ class PILDriver(object):
dup = self.stack[0].copy()
else:
dup = self.stack[0]
self.stack = [dup] + self.stack
self.push(dup)
def do_swap(self):
"""usage: swap
@ -152,7 +150,8 @@ class PILDriver(object):
self.push(Image.composite(image1, image2, mask))
def do_merge(self):
"""usage: merge <string:mode> <image:pic1> [<image:pic2> [<image:pic3> [<image:pic4>]]]
"""usage: merge <string:mode> <image:pic1>
[<image:pic2> [<image:pic3> [<image:pic4>]]]
Merge top-of stack images in a way described by the mode.
"""
@ -181,7 +180,8 @@ class PILDriver(object):
self.dup()
def do_crop(self):
"""usage: crop <int:left> <int:upper> <int:right> <int:lower> <image:pic1>
"""usage: crop <int:left> <int:upper> <int:right> <int:lower>
<image:pic1>
Crop and push a rectangular region from the current image.
"""
@ -243,7 +243,8 @@ class PILDriver(object):
self.push(image.offset(xoff, yoff))
def do_paste(self):
"""usage: paste <image:figure> <int:xoffset> <int:yoffset> <image:ground>
"""usage: paste <image:figure> <int:xoffset> <int:yoffset>
<image:ground>
Paste figure image into ground with upper left at given offsets.
"""

View File

@ -21,6 +21,7 @@ from __future__ import print_function
import getopt
import glob
import logging
import sys
from PIL import Image
@ -42,6 +43,7 @@ except getopt.error as v:
sys.exit(1)
verbose = quiet = verify = 0
logging_level = "WARNING"
for o, a in opt:
if o == "-f":
@ -58,7 +60,9 @@ for o, a in opt:
elif o == "-v":
verify = 1
elif o == "-D":
Image.DEBUG += 1
logging_level = "DEBUG"
logging.basicConfig(level=logging_level)
def globfix(files):

View File

@ -12,8 +12,6 @@
from __future__ import print_function
VERSION = "0.4"
import glob
import sys
@ -21,6 +19,8 @@ import sys
from PIL import BdfFontFile
from PIL import PcfFontFile
VERSION = "0.4"
if len(sys.argv) <= 1:
print("PILFONT", VERSION, "-- PIL font compiler.")
print()

20
Scripts/pilprint.py Normal file → Executable file
View File

@ -15,6 +15,7 @@ from __future__ import print_function
import getopt
import os
import sys
import subprocess
VERSION = "pilprint 0.3/2003-05-05"
@ -32,10 +33,11 @@ def description(filepath, image):
return title + format % image.size + image.mode + ")"
if len(sys.argv) == 1:
print("PIL Print 0.2a1/96-10-04 -- print image files")
print("PIL Print 0.3/2003-05-05 -- print image files")
print("Usage: pilprint files...")
print("Options:")
print(" -c colour printer (default is monochrome)")
print(" -d debug (show available drivers)")
print(" -p print via lpr (default is stdout)")
print(" -P <printer> same as -p but use given printer")
sys.exit(1)
@ -46,8 +48,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
printerArgs = [] # print to stdout
monochrome = 1 # reduce file size for most common case
for o, a in opt:
if o == "-d":
@ -60,10 +62,10 @@ for o, a in opt:
monochrome = 0
elif o == "-p":
# default printer channel
printer = "lpr"
printerArgs = ["lpr"]
elif o == "-P":
# printer channel
printer = "lpr -P%s" % a
printerArgs = ["lpr", "-P%s" % a]
for filepath in argv:
try:
@ -76,8 +78,9 @@ for filepath in argv:
im.draft("L", im.size)
im = im.convert("L")
if printer:
fp = os.popen(printer, "w")
if printerArgs:
p = subprocess.Popen(printerArgs, stdin=subprocess.PIPE)
fp = p.stdin
else:
fp = sys.stdout
@ -91,6 +94,9 @@ for filepath in argv:
ps.image(letter, im)
ps.end_document()
if printerArgs:
fp.close()
except:
print("cannot print image", end=' ')
print("(%s:%s)" % (sys.exc_info()[0], sys.exc_info()[1]))

View File

@ -15,9 +15,6 @@ from PIL import Image, ImageTk
import sys
Image.DEBUG = 0
# --------------------------------------------------------------------
# an image animation player

View File

@ -1,9 +1,9 @@
import sys
sys.path.insert(0, ".")
import helper
import timeit
import sys
sys.path.insert(0, ".")
def bench(mode):
im = helper.hopper(mode)

View File

@ -0,0 +1,16 @@
from helper import unittest, PillowTestCase
from PIL import Image
TEST_FILE = "Tests/images/fli_overflow.fli"
class TestFliOverflow(PillowTestCase):
def test_fli_overflow(self):
# this should not crash with a malloc error or access violation
im = Image.open(TEST_FILE)
im.load()
if __name__ == '__main__':
unittest.main()

View File

@ -3,7 +3,7 @@
from __future__ import division
from helper import unittest, PillowTestCase
import sys
from PIL import Image, ImageFilter
from PIL import Image
min_iterations = 100
max_iterations = 10000
@ -31,7 +31,8 @@ class TestImagingLeaks(PillowTestCase):
def test_leak_putdata(self):
im = Image.new('RGB', (25, 25))
self._test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
self._test_leak(min_iterations, max_iterations,
im.putdata, im.getdata())
def test_leak_getlist(self):
im = Image.new('P', (25, 25))

View File

@ -6,7 +6,8 @@ iterations = 5000
"""
When run on a system without the jpeg leak fixes, the valgrind runs look like this.
When run on a system without the jpeg leak fixes,
the valgrind runs look like this.
NOSE_PROCESSES=0 NOSE_TIMEOUT=600 valgrind --tool=massif \
python test-installed.py -s -v Tests/check_jpeg_leaks.py
@ -105,7 +106,8 @@ post-patch:
test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
"""
def test_exif_leak(self):
"""
pre patch:
MB
@ -160,8 +162,6 @@ post patch:
0 11.33
"""
def test_exif_leak(self):
im = hopper('RGB')
exif = b'12345678'*4096

View File

@ -0,0 +1,23 @@
from helper import unittest, PillowTestCase
from PIL import Image
TEST_FILE = "Tests/images/libtiff_segfault.tif"
class TestLibtiffSegfault(PillowTestCase):
def test_segfault(self):
""" This test should not segfault. It will on Pillow <= 3.1.0 and
libtiff >= 4.0.0
"""
try:
im = Image.open(TEST_FILE)
im.load()
except IOError:
self.assertTrue(True, "Got expected IOError")
except Exception:
self.fail("Should have returned IOError")
if __name__ == '__main__':
unittest.main()

View File

@ -42,7 +42,8 @@ class TestPngDos(PillowTestCase):
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")
self.assertLess(total_len, 64*1024*1024,
"Total text chunks greater than 64M")
if __name__ == '__main__':
unittest.main()

View File

@ -1,14 +0,0 @@
from PIL import Image, ImageFont, ImageDraw
font = "../pil-archive/memory-error-2.ttf"
s = "Test Text"
f = ImageFont.truetype(font, 64, index=0, encoding="unicode")
w, h = f.getsize(s)
i = Image.new("RGB", (500, h), "white")
d = ImageDraw.Draw(i)
# this line causes a MemoryError
d.text((0, 0), s, font=f, fill=0)
i.show()

View File

@ -43,11 +43,6 @@ class PillowTestCase(unittest.TestCase):
else:
print("=== orphaned temp file: %s" % path)
def assert_almost_equal(self, a, b, msg=None, eps=1e-6):
self.assertLess(
abs(a-b), eps,
msg or "got %r, expected %r" % (a, b))
def assert_deep_equal(self, a, b, msg=None):
try:
self.assertEqual(
@ -234,6 +229,9 @@ def imagemagick_available():
return IMCONVERT and command_succeeds([IMCONVERT, '-version'])
def on_appveyor():
return 'APPVEYOR' in os.environ
if sys.platform == 'win32':
IMCONVERT = os.environ.get('MAGICK_HOME', '')
if IMCONVERT:

BIN
Tests/images/7x13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

View File

@ -0,0 +1,12 @@
GIMP Palette
Name: badpaletteentry
Columns: 4
#
0 0 0 Index 3
65 38
103 62 49 Index 6
79 73 72 Index 7
114 101 97 Index 8
208 127 100 Index 9
151 144 142 Index 10
221 207 199 Index 11

View File

@ -0,0 +1,12 @@
GIMP Palette
Name: badpalettefile
Columns: 4
#
0 0 0 Index 3
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
103 62 49 Index 6
79 73 72 Index 7
114 101 97 Index 8
208 127 100 Index 9
151 144 142 Index 10
221 207 199 Index 11

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
Tests/images/compression.tif Executable file

Binary file not shown.

BIN
Tests/images/copyleft.tiff Normal file

Binary file not shown.

View File

@ -0,0 +1,12 @@
GIMP Palette
Name: custompalette
Columns: 4
#
0 0 0 Index 3
65 38 30 Index 4
103 62 49 Index 6
79 73 72 Index 7
114 101 97 Index 8
208 127 100 Index 9
151 144 142 Index 10
221 207 199 Index 11

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

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