mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-05-28 17:53:11 +03:00
Merge
This commit is contained in:
commit
ba817af569
24
.editorconfig
Normal file
24
.editorconfig
Normal 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
|
11
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
11
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
|
@ -1,6 +1,6 @@
|
||||||
# Contributing to Pillow
|
# 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.
|
## 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.
|
- Fork the Pillow repository.
|
||||||
- Create a branch from master.
|
- Create a branch from master.
|
||||||
- Develop bug fixes, features, tests, etc.
|
- 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.
|
- Create a pull request to pull the changes from your branch to the Pillow master.
|
||||||
|
|
||||||
### Guidelines
|
### Guidelines
|
||||||
|
@ -17,6 +17,7 @@ Please send a pull request to the master branch. Please include [documentation](
|
||||||
- Separate code commits from reformatting commits.
|
- Separate code commits from reformatting commits.
|
||||||
- Provide tests for any newly added code.
|
- Provide tests for any newly added code.
|
||||||
- Follow PEP8.
|
- 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
|
## 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 did you expect to happen?
|
||||||
- What actually happened?
|
- What actually happened?
|
||||||
- What versions of Pillow and Python are you using?
|
- 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
13
.github/ISSUE_TEMPLATE.md
vendored
Normal 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
|
||||||
|
```
|
|
@ -1,2 +1,3 @@
|
||||||
strictness: medium
|
strictness: medium
|
||||||
test-warnings: yes
|
test-warnings: yes
|
||||||
|
max-line-length: 80
|
||||||
|
|
35
.travis.yml
35
.travis.yml
|
@ -9,21 +9,34 @@ notifications:
|
||||||
python:
|
python:
|
||||||
- "pypy"
|
- "pypy"
|
||||||
- "pypy3"
|
- "pypy3"
|
||||||
- 3.4
|
- 3.5
|
||||||
- 2.7
|
- 2.7
|
||||||
- 2.6
|
- 2.6
|
||||||
- "2.7_with_system_site_packages" # For PyQt4
|
- "2.7_with_system_site_packages" # For PyQt4
|
||||||
- 3.2
|
- 3.2
|
||||||
- 3.3
|
- 3.3
|
||||||
|
- 3.4
|
||||||
|
- nightly
|
||||||
|
|
||||||
install:
|
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 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 cffi"
|
||||||
- "travis_retry pip install coverage nose"
|
- "travis_retry pip install nose"
|
||||||
- "travis_retry pip install pyroma"
|
- "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
|
- 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
|
# webp
|
||||||
- pushd depends && ./install_webp.sh && popd
|
- pushd depends && ./install_webp.sh && popd
|
||||||
|
|
||||||
|
@ -31,12 +44,16 @@ install:
|
||||||
- pushd depends && ./install_openjpeg.sh && popd
|
- pushd depends && ./install_openjpeg.sh && popd
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- coverage erase
|
- if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage erase; fi
|
||||||
- python setup.py clean
|
- python setup.py clean
|
||||||
- CFLAGS="-coverage" python setup.py build_ext --inplace
|
- CFLAGS="-coverage" python setup.py build_ext --inplace
|
||||||
|
|
||||||
- coverage run --append --include=PIL/* selftest.py
|
- if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage run --append --include=PIL/* selftest.py; fi
|
||||||
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
|
- 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:
|
after_success:
|
||||||
# gather the coverage data
|
# gather the coverage data
|
||||||
|
@ -61,8 +78,8 @@ after_success:
|
||||||
|
|
||||||
# Coverage and quality reports on just the latest diff.
|
# Coverage and quality reports on just the latest diff.
|
||||||
# (Installation is very slow on Py3, so just do it for Py2.)
|
# (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 depends/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-run.sh; fi
|
||||||
|
|
||||||
# after_all
|
# after_all
|
||||||
- |
|
- |
|
||||||
|
@ -104,6 +121,8 @@ after_script:
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
allow_failures:
|
||||||
|
- python: nightly
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
|
|
486
CHANGES.rst
486
CHANGES.rst
|
@ -1,13 +1,480 @@
|
||||||
Changelog (Pillow)
|
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
|
- Provide n_frames attribute to multi-frame formats #1261
|
||||||
[anntzer, radarhere]
|
[anntzer, radarhere]
|
||||||
|
|
||||||
- Add duration and loop set to GifImagePlugin #1172
|
- Add duration and loop set to GifImagePlugin #1172, #1269
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Ico files are little endian #1232
|
- Ico files are little endian #1232
|
||||||
|
@ -861,7 +1328,7 @@ Changelog (Pillow)
|
||||||
|
|
||||||
- Use PyCapsule for py3.1, fixes #237.
|
- 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)
|
2.0.0 (2013-03-15)
|
||||||
------------------
|
------------------
|
||||||
|
@ -884,7 +1351,7 @@ Changelog (Pillow)
|
||||||
- Backport PIL's PNG/Zip improvements.
|
- Backport PIL's PNG/Zip improvements.
|
||||||
[olt]
|
[olt]
|
||||||
|
|
||||||
- Various 64 bit and Windows fixes.
|
- Various 64-bit and Windows fixes.
|
||||||
[cgohlke]
|
[cgohlke]
|
||||||
|
|
||||||
- Add testing suite.
|
- 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>`_
|
- Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
|
||||||
[aclark4life]
|
[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
|
This section may not be fully complete. For changes since this file
|
||||||
was last updated, see the repository revision history:
|
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)
|
(1.1.7 final)
|
||||||
|
|
||||||
|
@ -2028,7 +2496,7 @@ Changelog (Pillow)
|
||||||
+ Added experimental "RGBa" mode support.
|
+ Added experimental "RGBa" mode support.
|
||||||
|
|
||||||
An "RGBa" image is an RGBA image where the colour components
|
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
|
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
|
RGBa images on top of RGB images. Since this saves a bunch
|
||||||
of multiplications and shifts, it is typically about twice
|
of multiplications and shifts, it is typically about twice
|
||||||
|
|
6
LICENSE
6
LICENSE
|
@ -3,6 +3,12 @@ The Python Imaging Library (PIL) is
|
||||||
Copyright © 1997-2011 by Secret Labs AB
|
Copyright © 1997-2011 by Secret Labs AB
|
||||||
Copyright © 1995-2011 by Fredrik Lundh
|
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:
|
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.
|
||||||
|
|
95
MANIFEST.in
95
MANIFEST.in
|
@ -1,80 +1,31 @@
|
||||||
|
|
||||||
include *.c
|
include *.c
|
||||||
include *.h
|
include *.h
|
||||||
|
include *.in
|
||||||
include *.md
|
include *.md
|
||||||
include *.py
|
include *.py
|
||||||
include *.sh
|
|
||||||
include *.rst
|
include *.rst
|
||||||
|
include *.sh
|
||||||
include *.txt
|
include *.txt
|
||||||
include *.yaml
|
|
||||||
include .coveragerc
|
|
||||||
include .gitattributes
|
|
||||||
include .travis.yml
|
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include Makefile
|
include Makefile
|
||||||
include tox.ini
|
graft Scripts
|
||||||
recursive-include PIL *.md
|
graft Tests
|
||||||
recursive-include Scripts *.py
|
graft PIL
|
||||||
recursive-include Scripts *.rst
|
graft Tk
|
||||||
recursive-include Scripts *.sh
|
graft libImaging
|
||||||
recursive-include Scripts README.rst
|
graft depends
|
||||||
recursive-include Tests *.bdf
|
graft winbuild
|
||||||
recursive-include Tests *.bin
|
graft docs
|
||||||
recursive-include Tests *.bmp
|
prune docs/_static
|
||||||
recursive-include Tests *.bw
|
|
||||||
recursive-include Tests *.cur
|
# build/src control detritus
|
||||||
recursive-include Tests *.dcx
|
exclude .coveragerc
|
||||||
recursive-include Tests *.doc
|
exclude .editorconfig
|
||||||
recursive-include Tests *.eps
|
exclude .landscape.yaml
|
||||||
recursive-include Tests *.fli
|
exclude appveyor.yml
|
||||||
recursive-include Tests *.ggr
|
exclude build_children.sh
|
||||||
recursive-include Tests *.gif
|
exclude tox.ini
|
||||||
recursive-include Tests *.gnuplot
|
global-exclude .git*
|
||||||
recursive-include Tests *.html
|
global-exclude *.pyc
|
||||||
recursive-include Tests *.icc
|
global-exclude *.so
|
||||||
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
|
|
||||||
|
|
15
Makefile
15
Makefile
|
@ -1,5 +1,6 @@
|
||||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
# 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
|
.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test
|
||||||
|
.DEFAULT_GOAL := release-test
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
python setup.py clean
|
python setup.py clean
|
||||||
|
@ -7,6 +8,12 @@ clean:
|
||||||
rm -r build || true
|
rm -r build || true
|
||||||
find . -name __pycache__ | xargs rm -r || 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:
|
||||||
coverage erase
|
coverage erase
|
||||||
coverage run --parallel-mode --include=PIL/* selftest.py
|
coverage run --parallel-mode --include=PIL/* selftest.py
|
||||||
|
@ -32,6 +39,7 @@ help:
|
||||||
@echo " inplace make inplace extension"
|
@echo " inplace make inplace extension"
|
||||||
@echo " install make and install"
|
@echo " install make and install"
|
||||||
@echo " install-req install documentation and test dependencies"
|
@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 " release-test run code and package tests before release"
|
||||||
@echo " test run tests on installed pillow"
|
@echo " test run tests on installed pillow"
|
||||||
@echo " upload build and upload sdists to PyPI"
|
@echo " upload build and upload sdists to PyPI"
|
||||||
|
@ -47,6 +55,10 @@ install:
|
||||||
install-req:
|
install-req:
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
install-venv:
|
||||||
|
virtualenv .
|
||||||
|
bin/pip install -r requirements.txt
|
||||||
|
|
||||||
release-test:
|
release-test:
|
||||||
$(MAKE) install-req
|
$(MAKE) install-req
|
||||||
python setup.py develop
|
python setup.py develop
|
||||||
|
@ -74,3 +86,6 @@ upload-test:
|
||||||
|
|
||||||
upload:
|
upload:
|
||||||
python setup.py sdist --format=gztar,zip upload
|
python setup.py sdist --format=gztar,zip upload
|
||||||
|
|
||||||
|
readme:
|
||||||
|
viewdoc
|
||||||
|
|
|
@ -109,10 +109,10 @@ class BdfFontFile(FontFile.FontFile):
|
||||||
if s.find(b"LogicalFontDescription") < 0:
|
if s.find(b"LogicalFontDescription") < 0:
|
||||||
comments.append(s[i+1:-1].decode('ascii'))
|
comments.append(s[i+1:-1].decode('ascii'))
|
||||||
|
|
||||||
font = props["FONT"].split("-")
|
# font = props["FONT"].split("-")
|
||||||
|
|
||||||
font[4] = bdf_slant[font[4].upper()]
|
# font[4] = bdf_slant[font[4].upper()]
|
||||||
font[11] = bdf_spacing[font[11].upper()]
|
# font[11] = bdf_spacing[font[11].upper()]
|
||||||
|
|
||||||
# ascent = int(props["FONT_ASCENT"])
|
# ascent = int(props["FONT_ASCENT"])
|
||||||
# descent = int(props["FONT_DESCENT"])
|
# descent = int(props["FONT_DESCENT"])
|
||||||
|
@ -123,7 +123,6 @@ class BdfFontFile(FontFile.FontFile):
|
||||||
# for i in comments:
|
# for i in comments:
|
||||||
# print "#", i
|
# print "#", i
|
||||||
|
|
||||||
font = []
|
|
||||||
while True:
|
while True:
|
||||||
c = bdf_char(fp)
|
c = bdf_char(fp)
|
||||||
if not c:
|
if not c:
|
||||||
|
|
|
@ -24,12 +24,10 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.7"
|
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
__version__ = "0.7"
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16le
|
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['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))
|
||||||
file_info['colors'] = i32(header_data[28:32])
|
file_info['colors'] = i32(header_data[28:32])
|
||||||
file_info['palette_padding'] = 4
|
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 file_info['compression'] == self.BITFIELDS:
|
||||||
if len(header_data) >= 52:
|
if len(header_data) >= 52:
|
||||||
for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
|
for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
|
||||||
|
@ -132,11 +132,16 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
SUPPORTED = {
|
SUPPORTED = {
|
||||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
|
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
|
||||||
24: [(0xff0000, 0xff00, 0xff)],
|
24: [(0xff0000, 0xff00, 0xff)],
|
||||||
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]}
|
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
|
||||||
|
}
|
||||||
MASK_MODES = {
|
MASK_MODES = {
|
||||||
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
|
||||||
|
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
|
||||||
|
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
||||||
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
||||||
(16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"}
|
(16, (0xf800, 0x7e0, 0x1f)): "BGR;16",
|
||||||
|
(16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"
|
||||||
|
}
|
||||||
if file_info['bits'] in SUPPORTED:
|
if file_info['bits'] in SUPPORTED:
|
||||||
if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
|
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'])]
|
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_save(BmpImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension(BmpImageFile.format, ".bmp")
|
Image.register_extension(BmpImageFile.format, ".bmp")
|
||||||
|
|
||||||
|
Image.register_mime(BmpImageFile.format, "image/bmp")
|
||||||
|
|
|
@ -17,10 +17,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
from PIL import Image, BmpImagePlugin, _binary
|
from PIL import Image, BmpImagePlugin, _binary
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -67,6 +66,8 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
# print "hotspot y", i16(s[6:])
|
# print "hotspot y", i16(s[6:])
|
||||||
# print "bytes", i32(s[8:])
|
# print "bytes", i32(s[8:])
|
||||||
# print "offset", i32(s[12:])
|
# print "offset", i32(s[12:])
|
||||||
|
if not m:
|
||||||
|
raise TypeError("No cursors were found")
|
||||||
|
|
||||||
# load as bitmap
|
# load as bitmap
|
||||||
self._bitmap(i32(m[12:]) + offset)
|
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")
|
||||||
|
|
|
@ -21,19 +21,18 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.2"
|
|
||||||
|
|
||||||
from PIL import Image, _binary
|
from PIL import Image, _binary
|
||||||
|
|
||||||
from PIL.PcxImagePlugin import PcxImageFile
|
from PIL.PcxImagePlugin import PcxImageFile
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
||||||
|
|
||||||
i32 = _binary.i32le
|
i32 = _binary.i32le
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i32(prefix) == MAGIC
|
return len(prefix) >= 4 and i32(prefix) == MAGIC
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -66,6 +65,10 @@ class DcxImageFile(PcxImageFile):
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
return len(self._offset)
|
return len(self._offset)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_animated(self):
|
||||||
|
return len(self._offset) > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame >= len(self._offset):
|
if frame >= len(self._offset):
|
||||||
raise EOFError("attempt to seek outside DCX directory")
|
raise EOFError("attempt to seek outside DCX directory")
|
||||||
|
@ -78,6 +81,6 @@ class DcxImageFile(PcxImageFile):
|
||||||
return self.frame
|
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
268
PIL/DdsImagePlugin.py
Normal 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")
|
|
@ -20,12 +20,13 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.5"
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import io
|
import io
|
||||||
|
import sys
|
||||||
from PIL import Image, ImageFile, _binary
|
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]*$")
|
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
||||||
|
|
||||||
gs_windows_binary = None
|
gs_windows_binary = None
|
||||||
import sys
|
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
import shutil
|
import shutil
|
||||||
if hasattr(shutil, 'which'):
|
if hasattr(shutil, 'which'):
|
||||||
|
@ -123,8 +123,8 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
"-q", # quiet mode
|
"-q", # quiet mode
|
||||||
"-g%dx%d" % size, # set output geometry (pixels)
|
"-g%dx%d" % size, # set output geometry (pixels)
|
||||||
"-r%fx%f" % res, # set input DPI (dots per inch)
|
"-r%fx%f" % res, # set input DPI (dots per inch)
|
||||||
"-dNOPAUSE -dSAFER", # don't pause between pages,
|
"-dNOPAUSE", # don't pause between pages,
|
||||||
# safe mode
|
"-dSAFER", # safe mode
|
||||||
"-sDEVICE=ppmraw", # ppm driver
|
"-sDEVICE=ppmraw", # ppm driver
|
||||||
"-sOutputFile=%s" % outfile, # output file
|
"-sOutputFile=%s" % outfile, # output file
|
||||||
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
|
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
|
||||||
|
@ -151,7 +151,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
os.unlink(outfile)
|
os.unlink(outfile)
|
||||||
if infile_temp:
|
if infile_temp:
|
||||||
os.unlink(infile_temp)
|
os.unlink(infile_temp)
|
||||||
except:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
@ -187,7 +187,8 @@ class PSFile(object):
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
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
|
# Image plugin for Encapsulated Postscript. This plugin supports only
|
||||||
|
@ -294,7 +295,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
self.mode = self.mode_map[int(mo)]
|
self.mode = self.mode_map[int(mo)]
|
||||||
except:
|
except ValueError:
|
||||||
break
|
break
|
||||||
|
|
||||||
self.size = int(x), int(y)
|
self.size = int(x), int(y)
|
||||||
|
@ -376,6 +377,7 @@ def _save(im, fp, filename, eps=1):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
base_fp = fp
|
base_fp = fp
|
||||||
|
if fp != sys.stdout:
|
||||||
fp = NoCloseStream(fp)
|
fp = NoCloseStream(fp)
|
||||||
if sys.version_info[0] > 2:
|
if sys.version_info[0] > 2:
|
||||||
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
||||||
|
@ -403,12 +405,14 @@ 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("[%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("{ currentfile buf readhexstring pop } bind\n")
|
||||||
fp.write(operator[2] + "\n")
|
fp.write(operator[2] + "\n")
|
||||||
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
|
ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
|
||||||
|
|
||||||
fp.write("\n%%%%EndBinary\n")
|
fp.write("\n%%%%EndBinary\n")
|
||||||
fp.write("grestore end\n")
|
fp.write("grestore end\n")
|
||||||
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -27,9 +27,9 @@ TAGS = {
|
||||||
0x0102: "BitsPerSample",
|
0x0102: "BitsPerSample",
|
||||||
0x0103: "Compression",
|
0x0103: "Compression",
|
||||||
0x0106: "PhotometricInterpretation",
|
0x0106: "PhotometricInterpretation",
|
||||||
0x0107: "Threshholding",
|
0x0107: "Thresholding",
|
||||||
0x0108: "CellWidth",
|
0x0108: "CellWidth",
|
||||||
0x0109: "CellLenght",
|
0x0109: "CellLength",
|
||||||
0x010a: "FillOrder",
|
0x010a: "FillOrder",
|
||||||
0x010d: "DocumentName",
|
0x010d: "DocumentName",
|
||||||
0x011d: "PageName",
|
0x011d: "PageName",
|
||||||
|
@ -40,7 +40,7 @@ TAGS = {
|
||||||
0x0112: "Orientation",
|
0x0112: "Orientation",
|
||||||
0x0115: "SamplesPerPixel",
|
0x0115: "SamplesPerPixel",
|
||||||
0x0116: "RowsPerStrip",
|
0x0116: "RowsPerStrip",
|
||||||
0x0117: "StripByteConunts",
|
0x0117: "StripByteCounts",
|
||||||
0x0118: "MinSampleValue",
|
0x0118: "MinSampleValue",
|
||||||
0x0119: "MaxSampleValue",
|
0x0119: "MaxSampleValue",
|
||||||
0x011a: "XResolution",
|
0x011a: "XResolution",
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.2"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
i32 = _binary.i32le
|
i32 = _binary.i32le
|
||||||
|
@ -30,7 +30,7 @@ o8 = _binary.o8
|
||||||
# decoder
|
# decoder
|
||||||
|
|
||||||
def _accept(prefix):
|
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.__fp = self.fp
|
||||||
self.__rewind = self.fp.tell()
|
self.__rewind = self.fp.tell()
|
||||||
self._n_frames = None
|
self._n_frames = None
|
||||||
|
self._is_animated = None
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
def _palette(self, palette, shift):
|
def _palette(self, palette, shift):
|
||||||
|
@ -122,13 +123,33 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
self.seek(current)
|
self.seek(current)
|
||||||
return self._n_frames
|
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):
|
def seek(self, frame):
|
||||||
if frame == self.__frame:
|
if frame == self.__frame:
|
||||||
return
|
return
|
||||||
if frame < self.__frame:
|
if frame < self.__frame:
|
||||||
self._seek(0)
|
self._seek(0)
|
||||||
|
|
||||||
|
last_frame = self.__frame
|
||||||
for f in range(self.__frame + 1, frame + 1):
|
for f in range(self.__frame + 1, frame + 1):
|
||||||
|
try:
|
||||||
self._seek(f)
|
self._seek(f)
|
||||||
|
except EOFError:
|
||||||
|
self.seek(last_frame)
|
||||||
|
raise EOFError("no more images in FLI file")
|
||||||
|
|
||||||
def _seek(self, frame):
|
def _seek(self, frame):
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
|
@ -161,7 +182,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
Image.register_open("FLI", FliImageFile, _accept)
|
Image.register_open(FliImageFile.format, FliImageFile, _accept)
|
||||||
|
|
||||||
Image.register_extension("FLI", ".fli")
|
Image.register_extension(FliImageFile.format, ".fli")
|
||||||
Image.register_extension("FLI", ".flc")
|
Image.register_extension(FliImageFile.format, ".flc")
|
||||||
|
|
|
@ -16,12 +16,11 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
|
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||||
MODES = {
|
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
96
PIL/FtexImagePlugin.py
Normal 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")
|
|
@ -1,17 +1,28 @@
|
||||||
#
|
#
|
||||||
# The Python Imaging Library
|
# The Python Imaging Library
|
||||||
# $Id$
|
|
||||||
#
|
#
|
||||||
# load a GIMP brush file
|
# load a GIMP brush file
|
||||||
#
|
#
|
||||||
# History:
|
# History:
|
||||||
# 96-03-14 fl Created
|
# 96-03-14 fl Created
|
||||||
|
# 16-01-08 es Version 2
|
||||||
#
|
#
|
||||||
# Copyright (c) Secret Labs AB 1997.
|
# Copyright (c) Secret Labs AB 1997.
|
||||||
# Copyright (c) Fredrik Lundh 1996.
|
# Copyright (c) Fredrik Lundh 1996.
|
||||||
|
# Copyright (c) Eric Soroos 2016.
|
||||||
#
|
#
|
||||||
# See the README file for information on usage and redistribution.
|
# 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
|
from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
|
@ -19,7 +30,7 @@ i32 = _binary.i32be
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
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"
|
format_description = "GIMP brush file"
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
|
||||||
header_size = i32(self.fp.read(4))
|
header_size = i32(self.fp.read(4))
|
||||||
version = 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")
|
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))
|
width = i32(self.fp.read(4))
|
||||||
height = i32(self.fp.read(4))
|
height = i32(self.fp.read(4))
|
||||||
color_depth = 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")
|
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"
|
self.mode = "L"
|
||||||
|
else:
|
||||||
|
self.mode = 'RGBA'
|
||||||
|
|
||||||
self.size = width, height
|
self.size = width, height
|
||||||
|
|
||||||
self.info["comment"] = comment
|
self.info["comment"] = comment
|
||||||
|
|
||||||
# Since the brush is so small, we read the data immediately
|
# Image might not be small
|
||||||
self.data = self.fp.read(width * height)
|
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):
|
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 = Image.core.new(self.mode, self.size)
|
||||||
self.im.frombytes(self.data)
|
self.frombytes(self.fp.read(self._data_size))
|
||||||
self.data = b""
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
Image.register_open("GBR", GbrImageFile, _accept)
|
Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
|
||||||
|
Image.register_extension(GbrImageFile.format, ".gbr")
|
||||||
Image.register_extension("GBR", ".gbr")
|
|
||||||
|
|
|
@ -23,11 +23,11 @@
|
||||||
# purposes only.
|
# purposes only.
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
from PIL import ImageFile, ImagePalette, _binary
|
from PIL import ImageFile, ImagePalette, _binary
|
||||||
from PIL._util import isPath
|
from PIL._util import isPath
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import builtins
|
import builtins
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# 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"
|
__version__ = "0.9"
|
||||||
|
|
||||||
|
@ -88,6 +89,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.__fp = self.fp # FIXME: hack
|
self.__fp = self.fp # FIXME: hack
|
||||||
self.__rewind = self.fp.tell()
|
self.__rewind = self.fp.tell()
|
||||||
self._n_frames = None
|
self._n_frames = None
|
||||||
|
self._is_animated = None
|
||||||
self._seek(0) # get ready to read first frame
|
self._seek(0) # get ready to read first frame
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -102,13 +104,33 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.seek(current)
|
self.seek(current)
|
||||||
return self._n_frames
|
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):
|
def seek(self, frame):
|
||||||
if frame == self.__frame:
|
if frame == self.__frame:
|
||||||
return
|
return
|
||||||
if frame < self.__frame:
|
if frame < self.__frame:
|
||||||
self._seek(0)
|
self._seek(0)
|
||||||
|
|
||||||
|
last_frame = self.__frame
|
||||||
for f in range(self.__frame + 1, frame + 1):
|
for f in range(self.__frame + 1, frame + 1):
|
||||||
|
try:
|
||||||
self._seek(f)
|
self._seek(f)
|
||||||
|
except EOFError:
|
||||||
|
self.seek(last_frame)
|
||||||
|
raise EOFError("no more images in GIF file")
|
||||||
|
|
||||||
def _seek(self, frame):
|
def _seek(self, frame):
|
||||||
|
|
||||||
|
@ -241,7 +263,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
if not self.tile:
|
if not self.tile:
|
||||||
# self.__fp = None
|
# self.__fp = None
|
||||||
raise EOFError("no more images in GIF file")
|
raise EOFError
|
||||||
|
|
||||||
self.mode = "L"
|
self.mode = "L"
|
||||||
if self.palette:
|
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:
|
if _imaging_gif:
|
||||||
# call external driver
|
# call external driver
|
||||||
try:
|
try:
|
||||||
|
@ -290,17 +331,9 @@ def _save(im, fp, filename):
|
||||||
pass # write uncompressed file
|
pass # write uncompressed file
|
||||||
|
|
||||||
if im.mode in RAWMODE:
|
if im.mode in RAWMODE:
|
||||||
im_out = im
|
im_out = im.copy()
|
||||||
else:
|
else:
|
||||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
im_out = _convert_mode(im, True)
|
||||||
# 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")
|
|
||||||
|
|
||||||
# header
|
# header
|
||||||
try:
|
try:
|
||||||
|
@ -309,12 +342,52 @@ def _save(im, fp, filename):
|
||||||
palette = None
|
palette = None
|
||||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||||
|
|
||||||
header, used_palette_colors = getheader(im_out, palette, im.encoderinfo)
|
if save_all:
|
||||||
|
previous = None
|
||||||
|
|
||||||
|
first_frame = None
|
||||||
|
for im_frame in ImageSequence.Iterator(im):
|
||||||
|
im_frame = _convert_mode(im_frame)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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:
|
for s in header:
|
||||||
fp.write(s)
|
fp.write(s)
|
||||||
|
|
||||||
|
flags = 0
|
||||||
|
|
||||||
|
if get_interlace(im):
|
||||||
|
flags = flags | 64
|
||||||
|
|
||||||
# local image header
|
# local image header
|
||||||
get_local_header(fp, im)
|
_get_local_header(fp, im, (0, 0), flags)
|
||||||
|
|
||||||
im_out.encoderconfig = (8, get_interlace(im))
|
im_out.encoderconfig = (8, get_interlace(im))
|
||||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||||
|
@ -324,10 +397,8 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
fp.write(b";") # end of file
|
fp.write(b";") # end of file
|
||||||
|
|
||||||
try:
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_interlace(im):
|
def get_interlace(im):
|
||||||
|
@ -343,7 +414,7 @@ def get_interlace(im):
|
||||||
return interlace
|
return interlace
|
||||||
|
|
||||||
|
|
||||||
def get_local_header(fp, im, offset=(0, 0)):
|
def _get_local_header(fp, im, offset, flags):
|
||||||
transparent_color_exists = False
|
transparent_color_exists = False
|
||||||
try:
|
try:
|
||||||
transparency = im.encoderinfo["transparency"]
|
transparency = im.encoderinfo["transparency"]
|
||||||
|
@ -394,12 +465,6 @@ def get_local_header(fp, im, offset=(0, 0)):
|
||||||
o8(1) +
|
o8(1) +
|
||||||
o16(number_of_loops) + # number of loops
|
o16(number_of_loops) + # number of loops
|
||||||
o8(0))
|
o8(0))
|
||||||
|
|
||||||
flags = 0
|
|
||||||
|
|
||||||
if get_interlace(im):
|
|
||||||
flags = flags | 64
|
|
||||||
|
|
||||||
fp.write(b"," +
|
fp.write(b"," +
|
||||||
o16(offset[0]) + # offset
|
o16(offset[0]) + # offset
|
||||||
o16(offset[1]) +
|
o16(offset[1]) +
|
||||||
|
@ -451,7 +516,7 @@ def _save_netpbm(im, fp, filename):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.unlink(file)
|
os.unlink(file)
|
||||||
except:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -480,8 +545,19 @@ def getheader(im, palette=None, info=None):
|
||||||
|
|
||||||
# Header Block
|
# Header Block
|
||||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
# 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 = [
|
header = [
|
||||||
b"GIF87a" + # signature + version
|
b"GIF"+version + # signature + version
|
||||||
o16(im.size[0]) + # canvas width
|
o16(im.size[0]) + # canvas width
|
||||||
o16(im.size[1]) # canvas height
|
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
|
# size of global color table + global color table flag
|
||||||
header.append(o8(color_table_size + 128))
|
header.append(o8(color_table_size + 128))
|
||||||
# background + reserved/aspect
|
# 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
|
# end of Logical Screen Descriptor
|
||||||
|
|
||||||
# add the missing amount of bytes
|
# add the missing amount of bytes
|
||||||
|
@ -571,7 +657,7 @@ def getdata(im, offset=(0, 0), **params):
|
||||||
im.encoderinfo = params
|
im.encoderinfo = params
|
||||||
|
|
||||||
# local image header
|
# 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])])
|
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_open(GifImageFile.format, GifImageFile, _accept)
|
||||||
Image.register_save(GifImageFile.format, _save)
|
Image.register_save(GifImageFile.format, _save)
|
||||||
|
Image.register_save_all(GifImageFile.format, _save_all)
|
||||||
Image.register_extension(GifImageFile.format, ".gif")
|
Image.register_extension(GifImageFile.format, ".gif")
|
||||||
Image.register_mime(GifImageFile.format, "image/gif")
|
Image.register_mime(GifImageFile.format, "image/gif")
|
||||||
|
|
||||||
|
|
|
@ -306,10 +306,8 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
OS X only.
|
OS X only.
|
||||||
"""
|
"""
|
||||||
try:
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# create the temporary set of pngs
|
# create the temporary set of pngs
|
||||||
iconset = tempfile.mkdtemp('.iconset')
|
iconset = tempfile.mkdtemp('.iconset')
|
||||||
|
@ -345,13 +343,14 @@ def _save(im, fp, filename):
|
||||||
if retcode:
|
if retcode:
|
||||||
raise CalledProcessError(retcode, convert_cmd)
|
raise CalledProcessError(retcode, convert_cmd)
|
||||||
|
|
||||||
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
|
Image.register_open(IcnsImageFile.format, IcnsImageFile,
|
||||||
Image.register_extension("ICNS", '.icns')
|
lambda x: x[:4] == b'icns')
|
||||||
|
Image.register_extension(IcnsImageFile.format, '.icns')
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
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__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -15,21 +15,21 @@
|
||||||
|
|
||||||
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||||
# <casadebender@gmail.com>.
|
# <casadebender@gmail.com>.
|
||||||
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||||
#
|
#
|
||||||
# Icon format references:
|
# Icon format references:
|
||||||
# * http://en.wikipedia.org/wiki/ICO_(file_format)
|
# * https://en.wikipedia.org/wiki/ICO_(file_format)
|
||||||
# * http://msdn.microsoft.com/en-us/library/ms997538.aspx
|
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
||||||
from math import log, ceil
|
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
|
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||||
<casadebender@gmail.com>.
|
<casadebender@gmail.com>.
|
||||||
https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||||
"""
|
"""
|
||||||
format = "ICO"
|
format = "ICO"
|
||||||
format_description = "Windows Icon"
|
format_description = "Windows Icon"
|
||||||
|
@ -278,6 +278,6 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
Image.register_open("ICO", IcoImageFile, _accept)
|
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
|
||||||
Image.register_save("ICO", _save)
|
Image.register_save(IcoImageFile.format, _save)
|
||||||
Image.register_extension("ICO", ".ico")
|
Image.register_extension(IcoImageFile.format, ".ico")
|
||||||
|
|
|
@ -26,12 +26,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.7"
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from PIL import Image, ImageFile, ImagePalette
|
from PIL import Image, ImageFile, ImagePalette
|
||||||
from PIL._binary import i8
|
from PIL._binary import i8
|
||||||
|
|
||||||
|
__version__ = "0.7"
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Standard tags
|
# Standard tags
|
||||||
|
@ -264,6 +264,10 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
return self.info[FRAMES]
|
return self.info[FRAMES]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_animated(self):
|
||||||
|
return self.info[FRAMES] > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
|
||||||
if frame < 0 or frame >= self.info[FRAMES]:
|
if frame < 0 or frame >= self.info[FRAMES]:
|
||||||
|
@ -345,7 +349,7 @@ def _save(im, fp, filename, check=0):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Registry
|
# Registry
|
||||||
|
|
||||||
Image.register_open("IM", ImImageFile)
|
Image.register_open(ImImageFile.format, ImImageFile)
|
||||||
Image.register_save("IM", _save)
|
Image.register_save(ImImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension("IM", ".im")
|
Image.register_extension(ImImageFile.format, ".im")
|
||||||
|
|
273
PIL/Image.py
273
PIL/Image.py
|
@ -28,8 +28,11 @@ from __future__ import print_function
|
||||||
|
|
||||||
from PIL import VERSION, PILLOW_VERSION, _plugins
|
from PIL import VERSION, PILLOW_VERSION, _plugins
|
||||||
|
|
||||||
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DecompressionBombWarning(RuntimeWarning):
|
class DecompressionBombWarning(RuntimeWarning):
|
||||||
pass
|
pass
|
||||||
|
@ -79,20 +82,24 @@ except ImportError as v:
|
||||||
)
|
)
|
||||||
elif str(v).startswith("The _imaging extension"):
|
elif str(v).startswith("The _imaging extension"):
|
||||||
warnings.warn(str(v), RuntimeWarning)
|
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(
|
warnings.warn(
|
||||||
"The _imaging extension was built for Python with UCS2 support; "
|
"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
|
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(
|
warnings.warn(
|
||||||
"The _imaging extension was built for Python with UCS4 support; "
|
"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
|
RuntimeWarning
|
||||||
)
|
)
|
||||||
# Fail here anyway. Don't let people run with a mostly broken Pillow.
|
# 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
|
raise
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -121,7 +128,7 @@ USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info')
|
||||||
try:
|
try:
|
||||||
import cffi
|
import cffi
|
||||||
HAS_CFFI = True
|
HAS_CFFI = True
|
||||||
except:
|
except ImportError:
|
||||||
HAS_CFFI = False
|
HAS_CFFI = False
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,11 +145,6 @@ def isImageType(t):
|
||||||
"""
|
"""
|
||||||
return hasattr(t, "im")
|
return hasattr(t, "im")
|
||||||
|
|
||||||
#
|
|
||||||
# Debug level
|
|
||||||
|
|
||||||
DEBUG = 0
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Constants (also defined in _imagingmodule.c!)
|
# Constants (also defined in _imagingmodule.c!)
|
||||||
|
|
||||||
|
@ -204,6 +206,7 @@ ID = []
|
||||||
OPEN = {}
|
OPEN = {}
|
||||||
MIME = {}
|
MIME = {}
|
||||||
SAVE = {}
|
SAVE = {}
|
||||||
|
SAVE_ALL = {}
|
||||||
EXTENSION = {}
|
EXTENSION = {}
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -251,6 +254,7 @@ _MODE_CONV = {
|
||||||
"CMYK": ('|u1', 4),
|
"CMYK": ('|u1', 4),
|
||||||
"YCbCr": ('|u1', 3),
|
"YCbCr": ('|u1', 3),
|
||||||
"LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1
|
"LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1
|
||||||
|
"HSV": ('|u1', 3),
|
||||||
# I;16 == I;16L, and I;32 == I;32L
|
# I;16 == I;16L, and I;32 == I;32L
|
||||||
"I;16": ('<u2', None),
|
"I;16": ('<u2', None),
|
||||||
"I;16B": ('>u2', None),
|
"I;16B": ('>u2', None),
|
||||||
|
@ -386,13 +390,10 @@ def init():
|
||||||
|
|
||||||
for plugin in _plugins:
|
for plugin in _plugins:
|
||||||
try:
|
try:
|
||||||
if DEBUG:
|
logger.debug("Importing %s", plugin)
|
||||||
print("Importing %s" % plugin)
|
|
||||||
__import__("PIL.%s" % plugin, globals(), locals(), [])
|
__import__("PIL.%s" % plugin, globals(), locals(), [])
|
||||||
except ImportError:
|
except ImportError as e:
|
||||||
if DEBUG:
|
logger.debug("Image: failed to import %s: %s", plugin, e)
|
||||||
print("Image: failed to import", end=' ')
|
|
||||||
print(plugin, ":", sys.exc_info()[1])
|
|
||||||
|
|
||||||
if OPEN or SAVE:
|
if OPEN or SAVE:
|
||||||
_initialized = 2
|
_initialized = 2
|
||||||
|
@ -504,12 +505,21 @@ class Image(object):
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self.size[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self.size[1]
|
||||||
|
|
||||||
def _new(self, im):
|
def _new(self, im):
|
||||||
new = Image()
|
new = Image()
|
||||||
new.im = im
|
new.im = im
|
||||||
new.mode = im.mode
|
new.mode = im.mode
|
||||||
new.size = im.size
|
new.size = im.size
|
||||||
new.palette = self.palette
|
if self.palette:
|
||||||
|
new.palette = self.palette.copy()
|
||||||
if im.mode == "P" and not new.palette:
|
if im.mode == "P" and not new.palette:
|
||||||
from PIL import ImagePalette
|
from PIL import ImagePalette
|
||||||
new.palette = ImagePalette.ImagePalette()
|
new.palette = ImagePalette.ImagePalette()
|
||||||
|
@ -545,8 +555,7 @@ class Image(object):
|
||||||
try:
|
try:
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
except Exception as msg:
|
except Exception as msg:
|
||||||
if DEBUG:
|
logger.debug("Error closing: %s", msg)
|
||||||
print("Error closing: %s" % msg)
|
|
||||||
|
|
||||||
# Instead of simply setting to None, we're setting up a
|
# Instead of simply setting to None, we're setting up a
|
||||||
# deferred error that will better explain that the core image
|
# deferred error that will better explain that the core image
|
||||||
|
@ -618,6 +627,7 @@ class Image(object):
|
||||||
new['shape'] = shape
|
new['shape'] = shape
|
||||||
new['typestr'] = typestr
|
new['typestr'] = typestr
|
||||||
new['data'] = self.tobytes()
|
new['data'] = self.tobytes()
|
||||||
|
new['version'] = 3
|
||||||
return new
|
return new
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
|
|
||||||
|
@ -643,7 +653,14 @@ class Image(object):
|
||||||
|
|
||||||
def tobytes(self, encoder_name="raw", *args):
|
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
|
:param encoder_name: What encoder to use. The default is to
|
||||||
use the standard "raw" encoder.
|
use the standard "raw" encoder.
|
||||||
|
@ -677,18 +694,9 @@ class Image(object):
|
||||||
|
|
||||||
return b"".join(data)
|
return b"".join(data)
|
||||||
|
|
||||||
# Declare tostring as alias to tobytes
|
|
||||||
def tostring(self, *args, **kw):
|
def tostring(self, *args, **kw):
|
||||||
"""Deprecated alias to tobytes.
|
raise Exception("tostring() has been removed. " +
|
||||||
|
"Please call tobytes() instead.")
|
||||||
.. deprecated:: 2.0
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
'tostring() is deprecated. Please call tobytes() instead.',
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return self.tobytes(*args, **kw)
|
|
||||||
|
|
||||||
def tobitmap(self, name="image"):
|
def tobitmap(self, name="image"):
|
||||||
"""
|
"""
|
||||||
|
@ -738,14 +746,8 @@ class Image(object):
|
||||||
raise ValueError("cannot decode image data")
|
raise ValueError("cannot decode image data")
|
||||||
|
|
||||||
def fromstring(self, *args, **kw):
|
def fromstring(self, *args, **kw):
|
||||||
"""Deprecated alias to frombytes.
|
raise Exception("fromstring() has been removed. " +
|
||||||
|
"Please call frombytes() instead.")
|
||||||
.. deprecated:: 2.0
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
'fromstring() is deprecated. Please call frombytes() instead.',
|
|
||||||
DeprecationWarning)
|
|
||||||
return self.frombytes(*args, **kw)
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""
|
"""
|
||||||
|
@ -818,7 +820,7 @@ class Image(object):
|
||||||
|
|
||||||
:param mode: The requested mode. See: :ref:`concept-modes`.
|
:param mode: The requested mode. See: :ref:`concept-modes`.
|
||||||
:param matrix: An optional conversion matrix. If given, this
|
: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
|
:param dither: Dithering method, used when converting from
|
||||||
mode "RGB" to "P" or from "RGB" or "L" to "1".
|
mode "RGB" to "P" or from "RGB" or "L" to "1".
|
||||||
Available methods are NONE or FLOYDSTEINBERG (default).
|
Available methods are NONE or FLOYDSTEINBERG (default).
|
||||||
|
@ -877,6 +879,12 @@ class Image(object):
|
||||||
trns_im = Image()._new(core.new(self.mode, (1, 1)))
|
trns_im = Image()._new(core.new(self.mode, (1, 1)))
|
||||||
if self.mode == 'P':
|
if self.mode == 'P':
|
||||||
trns_im.putpalette(self.palette)
|
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)
|
trns_im.putpixel((0, 0), t)
|
||||||
|
|
||||||
if mode in ('L', 'RGB'):
|
if mode in ('L', 'RGB'):
|
||||||
|
@ -1004,6 +1012,8 @@ class Image(object):
|
||||||
im = self.im.copy()
|
im = self.im.copy()
|
||||||
return self._new(im)
|
return self._new(im)
|
||||||
|
|
||||||
|
__copy__ = copy
|
||||||
|
|
||||||
def crop(self, box=None):
|
def crop(self, box=None):
|
||||||
"""
|
"""
|
||||||
Returns a rectangular region from this image. The box is a
|
Returns a rectangular region from this image. The box is a
|
||||||
|
@ -1244,27 +1254,8 @@ class Image(object):
|
||||||
return self.im.histogram()
|
return self.im.histogram()
|
||||||
|
|
||||||
def offset(self, xoffset, yoffset=None):
|
def offset(self, xoffset, yoffset=None):
|
||||||
"""
|
raise Exception("offset() has been removed. " +
|
||||||
.. deprecated:: 2.0
|
"Please call ImageChops.offset() instead.")
|
||||||
|
|
||||||
.. 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)
|
|
||||||
|
|
||||||
def paste(self, im, box=None, mask=None):
|
def paste(self, im, box=None, mask=None):
|
||||||
"""
|
"""
|
||||||
|
@ -1316,7 +1307,7 @@ class Image(object):
|
||||||
box = (0, 0) + self.size
|
box = (0, 0) + self.size
|
||||||
|
|
||||||
if len(box) == 2:
|
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):
|
if isImageType(im):
|
||||||
size = im.size
|
size = im.size
|
||||||
elif isImageType(mask):
|
elif isImageType(mask):
|
||||||
|
@ -1617,7 +1608,7 @@ class Image(object):
|
||||||
if self.mode in ("1", "P"):
|
if self.mode in ("1", "P"):
|
||||||
resample = NEAREST
|
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):
|
def save(self, fp, format=None, **params):
|
||||||
"""
|
"""
|
||||||
|
@ -1636,7 +1627,7 @@ class Image(object):
|
||||||
implement the ``seek``, ``tell``, and ``write``
|
implement the ``seek``, ``tell``, and ``write``
|
||||||
methods, and be opened in binary mode.
|
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
|
:param format: Optional format override. If omitted, the
|
||||||
format to use is determined from the filename extension.
|
format to use is determined from the filename extension.
|
||||||
If a file object was used instead of a filename, this
|
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.
|
may have been created, and may contain partial data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
filename = ""
|
||||||
|
open_fp = False
|
||||||
if isPath(fp):
|
if isPath(fp):
|
||||||
filename = fp
|
filename = fp
|
||||||
else:
|
open_fp = True
|
||||||
if hasattr(fp, "name") and isPath(fp.name):
|
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
|
filename = fp.name
|
||||||
else:
|
|
||||||
filename = ""
|
|
||||||
|
|
||||||
# may mutate self!
|
# may mutate self!
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
|
save_all = False
|
||||||
|
if 'save_all' in params:
|
||||||
|
save_all = params['save_all']
|
||||||
|
del params['save_all']
|
||||||
self.encoderinfo = params
|
self.encoderinfo = params
|
||||||
self.encoderconfig = ()
|
self.encoderconfig = ()
|
||||||
|
|
||||||
|
@ -1668,32 +1669,25 @@ class Image(object):
|
||||||
ext = os.path.splitext(filename)[1].lower()
|
ext = os.path.splitext(filename)[1].lower()
|
||||||
|
|
||||||
if not format:
|
if not format:
|
||||||
try:
|
if ext not in EXTENSION:
|
||||||
format = EXTENSION[ext]
|
|
||||||
except KeyError:
|
|
||||||
init()
|
init()
|
||||||
try:
|
|
||||||
format = EXTENSION[ext]
|
format = EXTENSION[ext]
|
||||||
except KeyError:
|
|
||||||
raise KeyError(ext) # unknown extension
|
|
||||||
|
|
||||||
try:
|
if format.upper() not in SAVE:
|
||||||
save_handler = SAVE[format.upper()]
|
|
||||||
except KeyError:
|
|
||||||
init()
|
init()
|
||||||
save_handler = SAVE[format.upper()] # unknown format
|
if save_all:
|
||||||
|
save_handler = SAVE_ALL[format.upper()]
|
||||||
if isPath(fp):
|
|
||||||
fp = builtins.open(fp, "wb")
|
|
||||||
close = 1
|
|
||||||
else:
|
else:
|
||||||
close = 0
|
save_handler = SAVE[format.upper()]
|
||||||
|
|
||||||
|
if open_fp:
|
||||||
|
fp = builtins.open(filename, "wb")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
save_handler(self, fp, filename)
|
save_handler(self, fp, filename)
|
||||||
finally:
|
finally:
|
||||||
# do what we can to clean up
|
# do what we can to clean up
|
||||||
if close:
|
if open_fp:
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
@ -1935,6 +1929,20 @@ class Image(object):
|
||||||
im = self.im.effect_spread(distance)
|
im = self.im.effect_spread(distance)
|
||||||
return self._new(im)
|
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
|
# Lazy operations
|
||||||
|
@ -1945,7 +1953,9 @@ class _ImageCrop(Image):
|
||||||
|
|
||||||
Image.__init__(self)
|
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:
|
if x1 < x0:
|
||||||
x1 = x0
|
x1 = x0
|
||||||
if y1 < y0:
|
if y1 < y0:
|
||||||
|
@ -2063,16 +2073,8 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
|
||||||
|
|
||||||
|
|
||||||
def fromstring(*args, **kw):
|
def fromstring(*args, **kw):
|
||||||
"""Deprecated alias to frombytes.
|
raise Exception("fromstring() has been removed. " +
|
||||||
|
"Please call frombytes() instead.")
|
||||||
.. deprecated:: 2.0
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
'fromstring() is deprecated. Please call frombytes() instead.',
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2
|
|
||||||
)
|
|
||||||
return frombytes(*args, **kw)
|
|
||||||
|
|
||||||
|
|
||||||
def frombuffer(mode, size, data, decoder_name="raw", *args):
|
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.
|
**BytesIO** object, and use :py:func:`~PIL.Image.open` to load it.
|
||||||
|
|
||||||
In the current version, the default parameters used for the "raw" decoder
|
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
|
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
|
issues a warning if you do this; to disable the warning, you should provide
|
||||||
the full set of parameters. See below for details.
|
the full set of parameters. See below for details.
|
||||||
|
@ -2116,7 +2118,6 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
|
||||||
|
|
||||||
if decoder_name == "raw":
|
if decoder_name == "raw":
|
||||||
if args == ():
|
if args == ():
|
||||||
if warnings:
|
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"the frombuffer defaults may change in a future release; "
|
"the frombuffer defaults may change in a future release; "
|
||||||
"for portability, change the call to read:\n"
|
"for portability, change the call to read:\n"
|
||||||
|
@ -2184,6 +2185,22 @@ def fromarray(obj, mode=None):
|
||||||
|
|
||||||
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
|
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 = {
|
_fromarray_typemap = {
|
||||||
# (shape, typestr) => mode, rawmode
|
# (shape, typestr) => mode, rawmode
|
||||||
# first two members of shape are set to one
|
# 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:meth:`~PIL.Image.Image.load` method). See
|
||||||
:py:func:`~PIL.Image.new`.
|
:py:func:`~PIL.Image.new`.
|
||||||
|
|
||||||
:param file: A filename (string) or a file object. The file object
|
:param fp: A filename (string), pathlib.Path object or a file object.
|
||||||
must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and
|
The file object must implement :py:meth:`~file.read`,
|
||||||
:py:meth:`~file.tell` methods, and be opened in binary mode.
|
: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".
|
:param mode: The mode. If given, this argument must be "r".
|
||||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||||
:exception IOError: If the file cannot be found, or the image cannot be
|
: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":
|
if mode != "r":
|
||||||
raise ValueError("bad mode %r" % mode)
|
raise ValueError("bad mode %r" % mode)
|
||||||
|
|
||||||
|
filename = ""
|
||||||
if isPath(fp):
|
if isPath(fp):
|
||||||
filename = fp
|
filename = fp
|
||||||
fp = builtins.open(fp, "rb")
|
elif sys.version_info >= (3, 4):
|
||||||
else:
|
from pathlib import Path
|
||||||
filename = ""
|
if isinstance(fp, Path):
|
||||||
|
filename = str(fp.resolve())
|
||||||
|
if filename:
|
||||||
|
fp = builtins.open(filename, "rb")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
|
@ -2258,6 +2280,7 @@ def open(fp, mode="r"):
|
||||||
|
|
||||||
preinit()
|
preinit()
|
||||||
|
|
||||||
|
def _open_core(fp, filename, prefix):
|
||||||
for i in ID:
|
for i in ID:
|
||||||
try:
|
try:
|
||||||
factory, accept = OPEN[i]
|
factory, accept = OPEN[i]
|
||||||
|
@ -2267,38 +2290,34 @@ def open(fp, mode="r"):
|
||||||
_decompression_bomb_check(im.size)
|
_decompression_bomb_check(im.size)
|
||||||
return im
|
return im
|
||||||
except (SyntaxError, IndexError, TypeError, struct.error):
|
except (SyntaxError, IndexError, TypeError, struct.error):
|
||||||
# import traceback
|
# Leave disabled by default, spams the logs with image
|
||||||
# traceback.print_exc()
|
# opening failures that are entirely expected.
|
||||||
pass
|
# logger.debug("", exc_info=True)
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
im = _open_core(fp, filename, prefix)
|
||||||
|
|
||||||
|
if im is None:
|
||||||
if init():
|
if init():
|
||||||
|
im = _open_core(fp, filename, prefix)
|
||||||
|
|
||||||
for i in ID:
|
if im:
|
||||||
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
|
return im
|
||||||
except (SyntaxError, IndexError, TypeError, struct.error):
|
|
||||||
# import traceback
|
|
||||||
# traceback.print_exc()
|
|
||||||
pass
|
|
||||||
|
|
||||||
raise IOError("cannot identify image file %r"
|
raise IOError("cannot identify image file %r"
|
||||||
% (filename if filename else fp))
|
% (filename if filename else fp))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Image processing.
|
# Image processing.
|
||||||
|
|
||||||
|
|
||||||
def alpha_composite(im1, im2):
|
def alpha_composite(im1, im2):
|
||||||
"""
|
"""
|
||||||
Alpha composite im2 over im1.
|
Alpha composite im2 over im1.
|
||||||
|
|
||||||
:param im1: The first image.
|
:param im1: The first image. Must have mode RGBA.
|
||||||
:param im2: The second image. Must have the same mode and size as
|
:param im2: The second image. Must have mode RGBA, and the same size as
|
||||||
the first image.
|
the first image.
|
||||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||||
"""
|
"""
|
||||||
|
@ -2430,6 +2449,18 @@ def register_save(id, driver):
|
||||||
SAVE[id.upper()] = 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):
|
def register_extension(id, extension):
|
||||||
"""
|
"""
|
||||||
Registers an image extension. This function should not be
|
Registers an image extension. This function should not be
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
# below for the original description.
|
# below for the original description.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
|
||||||
DESCRIPTION = """
|
DESCRIPTION = """
|
||||||
pyCMS
|
pyCMS
|
||||||
|
@ -240,7 +241,6 @@ def get_display_profile(handle=None):
|
||||||
:returns: None if the profile is not known.
|
:returns: None if the profile is not known.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
from PIL import ImageWin
|
from PIL import ImageWin
|
||||||
if isinstance(handle, ImageWin.HDC):
|
if isinstance(handle, ImageWin.HDC):
|
||||||
|
@ -943,7 +943,6 @@ def versions():
|
||||||
(pyCMS) Fetches versions.
|
(pyCMS) Fetches versions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
return (
|
return (
|
||||||
VERSION, core.littlecms_version,
|
VERSION, core.littlecms_version,
|
||||||
sys.version.split()[0], Image.VERSION
|
sys.version.split()[0], Image.VERSION
|
||||||
|
@ -954,10 +953,9 @@ def versions():
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# create a cheap manual from the __doc__ strings for the functions above
|
# create a cheap manual from the __doc__ strings for the functions above
|
||||||
|
|
||||||
from PIL import ImageCms
|
|
||||||
print(__doc__)
|
print(__doc__)
|
||||||
|
|
||||||
for f in dir(ImageCms):
|
for f in dir(sys.modules[__name__]):
|
||||||
doc = None
|
doc = None
|
||||||
try:
|
try:
|
||||||
exec("doc = %s.__doc__" % (f))
|
exec("doc = %s.__doc__" % (f))
|
||||||
|
|
|
@ -31,22 +31,18 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import numbers
|
import numbers
|
||||||
|
import warnings
|
||||||
|
|
||||||
from PIL import Image, ImageColor
|
from PIL import Image, ImageColor
|
||||||
from PIL._util import isStringType
|
from PIL._util import isStringType
|
||||||
|
|
||||||
try:
|
|
||||||
import warnings
|
|
||||||
except ImportError:
|
|
||||||
warnings = None
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# A simple 2D drawing interface for PIL images.
|
# A simple 2D drawing interface for PIL images.
|
||||||
# <p>
|
# <p>
|
||||||
# Application code should use the <b>Draw</b> factory, instead of
|
# Application code should use the <b>Draw</b> factory, instead of
|
||||||
# directly.
|
# directly.
|
||||||
|
|
||||||
|
|
||||||
class ImageDraw(object):
|
class ImageDraw(object):
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -90,38 +86,17 @@ class ImageDraw(object):
|
||||||
self.fill = 0
|
self.fill = 0
|
||||||
self.font = None
|
self.font = None
|
||||||
|
|
||||||
##
|
|
||||||
# Set the default pen color.
|
|
||||||
|
|
||||||
def setink(self, ink):
|
def setink(self, ink):
|
||||||
# compatibility
|
raise Exception("setink() has been removed. " +
|
||||||
if warnings:
|
"Please use keyword arguments instead.")
|
||||||
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.
|
|
||||||
|
|
||||||
def setfill(self, onoff):
|
def setfill(self, onoff):
|
||||||
# compatibility
|
raise Exception("setfill() has been removed. " +
|
||||||
if warnings:
|
"Please use keyword arguments instead.")
|
||||||
warnings.warn(
|
|
||||||
"'setfill' is deprecated; use keyword arguments instead",
|
|
||||||
DeprecationWarning, stacklevel=2
|
|
||||||
)
|
|
||||||
self.fill = onoff
|
|
||||||
|
|
||||||
##
|
|
||||||
# Set the default font.
|
|
||||||
|
|
||||||
def setfont(self, font):
|
def setfont(self, font):
|
||||||
|
warnings.warn("setfont() is deprecated. " +
|
||||||
|
"Please set the attribute directly instead.")
|
||||||
# compatibility
|
# compatibility
|
||||||
self.font = font
|
self.font = font
|
||||||
|
|
||||||
|
@ -256,7 +231,20 @@ class ImageDraw(object):
|
||||||
##
|
##
|
||||||
# Draw text.
|
# 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)
|
ink, fill = self._getink(fill)
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self.getfont()
|
font = self.getfont()
|
||||||
|
@ -273,14 +261,50 @@ class ImageDraw(object):
|
||||||
mask = font.getmask(text)
|
mask = font.getmask(text)
|
||||||
self.draw.draw_bitmap(xy, mask, ink)
|
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.
|
# 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:
|
if font is None:
|
||||||
font = self.getfont()
|
font = self.getfont()
|
||||||
return font.getsize(text)
|
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.
|
# A simple 2D drawing interface for PIL images.
|
||||||
|
@ -301,7 +325,7 @@ def Draw(im, mode=None):
|
||||||
# experimental access to the outline API
|
# experimental access to the outline API
|
||||||
try:
|
try:
|
||||||
Outline = Image.core.outline
|
Outline = Image.core.outline
|
||||||
except:
|
except AttributeError:
|
||||||
Outline = None
|
Outline = None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ from PIL._util import isPath
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import struct
|
||||||
|
|
||||||
MAXBLOCK = 65536
|
MAXBLOCK = 65536
|
||||||
|
|
||||||
|
@ -95,21 +95,11 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._open()
|
self._open()
|
||||||
except IndexError as v: # end of data
|
except (IndexError, # end of data
|
||||||
if Image.DEBUG > 1:
|
TypeError, # end of data (ord)
|
||||||
traceback.print_exc()
|
KeyError, # unsupported mode
|
||||||
raise SyntaxError(v)
|
EOFError, # got header but not the first frame
|
||||||
except TypeError as v: # end of data (ord)
|
struct.error) as v:
|
||||||
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()
|
|
||||||
raise SyntaxError(v)
|
raise SyntaxError(v)
|
||||||
|
|
||||||
if not self.mode or self.size[0] <= 0:
|
if not self.mode or self.size[0] <= 0:
|
||||||
|
@ -173,10 +163,10 @@ class ImageFile(Image.Image):
|
||||||
else:
|
else:
|
||||||
# use mmap, if possible
|
# use mmap, if possible
|
||||||
import mmap
|
import mmap
|
||||||
file = open(self.filename, "r+")
|
fp = open(self.filename, "r+")
|
||||||
size = os.path.getsize(self.filename)
|
size = os.path.getsize(self.filename)
|
||||||
# FIXME: on Unix, use PROT_READ etc
|
# 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.im = Image.core.map_buffer(
|
||||||
self.map, self.size, d, e, o, a
|
self.map, self.size, d, e, o, a
|
||||||
)
|
)
|
||||||
|
@ -196,9 +186,6 @@ class ImageFile(Image.Image):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
prefix = b""
|
prefix = b""
|
||||||
|
|
||||||
# Buffer length read; assign a default value
|
|
||||||
t = 0
|
|
||||||
|
|
||||||
for d, e, o, a in self.tile:
|
for d, e, o, a in self.tile:
|
||||||
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
|
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
|
||||||
seek(o)
|
seek(o)
|
||||||
|
@ -207,15 +194,14 @@ class ImageFile(Image.Image):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
b = prefix
|
b = prefix
|
||||||
t = len(b)
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
s = read(self.decodermaxblock)
|
s = read(self.decodermaxblock)
|
||||||
except IndexError as ie: # truncated png/gif
|
except (IndexError, struct.error): # truncated png/gif
|
||||||
if LOAD_TRUNCATED_IMAGES:
|
if LOAD_TRUNCATED_IMAGES:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise IndexError(ie)
|
raise IOError("image file is truncated")
|
||||||
|
|
||||||
if not s and not d.handles_eof: # truncated jpeg
|
if not s and not d.handles_eof: # truncated jpeg
|
||||||
self.tile = []
|
self.tile = []
|
||||||
|
@ -236,7 +222,6 @@ class ImageFile(Image.Image):
|
||||||
if n < 0:
|
if n < 0:
|
||||||
break
|
break
|
||||||
b = b[n:]
|
b = b[n:]
|
||||||
t = t + n
|
|
||||||
# Need to cleanup here to prevent leaks in PyPy
|
# Need to cleanup here to prevent leaks in PyPy
|
||||||
d.cleanup()
|
d.cleanup()
|
||||||
|
|
||||||
|
@ -245,7 +230,7 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
self.fp = None # might be shared
|
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
|
# still raised if decoder fails to return anything
|
||||||
raise_ioerror(e)
|
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
|
# But, it would need at least the image size in most cases. RawEncode is
|
||||||
# a tricky case.
|
# a tricky case.
|
||||||
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
|
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
|
||||||
|
if fp == sys.stdout:
|
||||||
|
fp.flush()
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
fh = fp.fileno()
|
fh = fp.fileno()
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
@ -497,10 +485,8 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
if s < 0:
|
if s < 0:
|
||||||
raise IOError("encoder error %d when writing image file" % s)
|
raise IOError("encoder error %d when writing image file" % s)
|
||||||
e.cleanup()
|
e.cleanup()
|
||||||
try:
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _safe_read(fp, size):
|
def _safe_read(fp, size):
|
||||||
|
|
|
@ -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)
|
|
|
@ -30,11 +30,6 @@ from PIL._util import isDirectory, isPath
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
try:
|
|
||||||
import warnings
|
|
||||||
except ImportError:
|
|
||||||
warnings = None
|
|
||||||
|
|
||||||
|
|
||||||
class _imagingft_not_installed(object):
|
class _imagingft_not_installed(object):
|
||||||
# module placeholder
|
# module placeholder
|
||||||
|
@ -67,7 +62,7 @@ class ImageFont(object):
|
||||||
|
|
||||||
def _load_pilfont(self, filename):
|
def _load_pilfont(self, filename):
|
||||||
|
|
||||||
file = open(filename, "rb")
|
fp = open(filename, "rb")
|
||||||
|
|
||||||
for ext in (".png", ".gif", ".pbm"):
|
for ext in (".png", ".gif", ".pbm"):
|
||||||
try:
|
try:
|
||||||
|
@ -83,7 +78,7 @@ class ImageFont(object):
|
||||||
|
|
||||||
self.file = fullname
|
self.file = fullname
|
||||||
|
|
||||||
return self._load_pilfont_data(file, image)
|
return self._load_pilfont_data(fp, image)
|
||||||
|
|
||||||
def _load_pilfont_data(self, file, image):
|
def _load_pilfont_data(self, file, image):
|
||||||
|
|
||||||
|
@ -121,15 +116,8 @@ class ImageFont(object):
|
||||||
class FreeTypeFont(object):
|
class FreeTypeFont(object):
|
||||||
"FreeType font wrapper (requires _imagingft service)"
|
"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
|
# 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.path = font
|
||||||
self.size = size
|
self.size = size
|
||||||
|
@ -171,7 +159,7 @@ class FreeTypeFont(object):
|
||||||
using any specified arguments to override the settings.
|
using any specified arguments to override the settings.
|
||||||
|
|
||||||
Parameters are identical to the parameters used to initialize this
|
Parameters are identical to the parameters used to initialize this
|
||||||
object, minus the deprecated 'file' argument.
|
object.
|
||||||
|
|
||||||
:return: A FreeTypeFont object.
|
:return: A FreeTypeFont object.
|
||||||
"""
|
"""
|
||||||
|
@ -225,7 +213,7 @@ def load(filename):
|
||||||
return f
|
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.
|
Load a TrueType or OpenType font file, and create a font object.
|
||||||
This function loads a font object from the given file, and creates
|
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),
|
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
||||||
and "armn" (Apple Roman). See the FreeType documentation
|
and "armn" (Apple Roman). See the FreeType documentation
|
||||||
for more information.
|
for more information.
|
||||||
:param filename: Deprecated. Please use font instead.
|
|
||||||
:return: A font object.
|
:return: A font object.
|
||||||
:exception IOError: If the file could not be read.
|
: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:
|
try:
|
||||||
return FreeTypeFont(font, size, index, encoding)
|
return FreeTypeFont(font, size, index, encoding)
|
||||||
except IOError:
|
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
|
# According to the freedesktop spec, XDG_DATA_DIRS should
|
||||||
# default to /usr/share
|
# default to /usr/share
|
||||||
lindirs = '/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':
|
elif sys.platform == 'darwin':
|
||||||
dirs += ['/Library/Fonts', '/System/Library/Fonts',
|
dirs += ['/Library/Fonts', '/System/Library/Fonts',
|
||||||
os.path.expanduser('~/Library/Fonts')]
|
os.path.expanduser('~/Library/Fonts')]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# The Python Imaging Library
|
# The Python Imaging Library
|
||||||
# $Id$
|
# $Id$
|
||||||
#
|
#
|
||||||
# screen grabber (windows only)
|
# screen grabber (OS X and Windows only)
|
||||||
#
|
#
|
||||||
# History:
|
# History:
|
||||||
# 2001-04-26 fl created
|
# 2001-04-26 fl created
|
||||||
|
@ -18,19 +18,26 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
if sys.platform != "win32":
|
if sys.platform not in ["win32", "darwin"]:
|
||||||
raise ImportError("ImageGrab is Windows only")
|
raise ImportError("ImageGrab is OS X and Windows only")
|
||||||
|
|
||||||
try:
|
if sys.platform == "win32":
|
||||||
# built-in driver (1.1.3 and later)
|
|
||||||
grabber = Image.core.grabscreen
|
grabber = Image.core.grabscreen
|
||||||
except AttributeError:
|
elif sys.platform == "darwin":
|
||||||
# stand-alone driver (pil plus)
|
import os
|
||||||
import _grabscreen
|
import tempfile
|
||||||
grabber = _grabscreen.grab
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
def grab(bbox=None):
|
def grab(bbox=None):
|
||||||
|
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()
|
size, data = grabber()
|
||||||
im = Image.frombytes(
|
im = Image.frombytes(
|
||||||
"RGB", size, data,
|
"RGB", size, data,
|
||||||
|
@ -43,6 +50,8 @@ def grab(bbox=None):
|
||||||
|
|
||||||
|
|
||||||
def grabclipboard():
|
def grabclipboard():
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
raise NotImplementedError("Method is not implemented on OS X")
|
||||||
debug = 0 # temporary interface
|
debug = 0 # temporary interface
|
||||||
data = Image.core.grabclipboard(debug)
|
data = Image.core.grabclipboard(debug)
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
|
|
|
@ -198,6 +198,8 @@ class MorphOp(object):
|
||||||
if self.lut is None:
|
if self.lut is None:
|
||||||
raise Exception('No operator loaded')
|
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)
|
outimage = Image.new(image.mode, image.size, None)
|
||||||
count = _imagingmorph.apply(
|
count = _imagingmorph.apply(
|
||||||
bytes(self.lut), image.im.id, outimage.im.id)
|
bytes(self.lut), image.im.id, outimage.im.id)
|
||||||
|
@ -212,6 +214,8 @@ class MorphOp(object):
|
||||||
if self.lut is None:
|
if self.lut is None:
|
||||||
raise Exception('No operator loaded')
|
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)
|
return _imagingmorph.match(bytes(self.lut), image.im.id)
|
||||||
|
|
||||||
def get_on_pixels(self, image):
|
def get_on_pixels(self, image):
|
||||||
|
@ -220,6 +224,8 @@ class MorphOp(object):
|
||||||
Returns a list of tuples of (x,y) coordinates
|
Returns a list of tuples of (x,y) coordinates
|
||||||
of all matching pixels."""
|
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)
|
return _imagingmorph.get_on_pixels(image.im.id)
|
||||||
|
|
||||||
def load_lut(self, filename):
|
def load_lut(self, filename):
|
||||||
|
|
|
@ -17,12 +17,23 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import array
|
import array
|
||||||
import warnings
|
|
||||||
from PIL import ImageColor
|
from PIL import ImageColor
|
||||||
|
|
||||||
|
|
||||||
class ImagePalette(object):
|
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):
|
def __init__(self, mode="RGB", palette=None, size=0):
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -34,6 +45,18 @@ class ImagePalette(object):
|
||||||
(size != 0 and size != len(self.palette))):
|
(size != 0 and size != len(self.palette))):
|
||||||
raise ValueError("wrong palette size")
|
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):
|
def getdata(self):
|
||||||
"""
|
"""
|
||||||
Get palette contents in format suitable # for the low-level
|
Get palette contents in format suitable # for the low-level
|
||||||
|
@ -56,7 +79,6 @@ class ImagePalette(object):
|
||||||
return self.palette
|
return self.palette
|
||||||
arr = array.array("B", self.palette)
|
arr = array.array("B", self.palette)
|
||||||
if hasattr(arr, 'tobytes'):
|
if hasattr(arr, 'tobytes'):
|
||||||
# py3k has a tobytes, tostring is deprecated.
|
|
||||||
return arr.tobytes()
|
return arr.tobytes()
|
||||||
return arr.tostring()
|
return arr.tostring()
|
||||||
|
|
||||||
|
@ -125,26 +147,6 @@ def raw(rawmode, data):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Factories
|
# 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):
|
def make_linear_lut(black, white):
|
||||||
lut = []
|
lut = []
|
||||||
if black == 0:
|
if black == 0:
|
||||||
|
|
149
PIL/ImageQt.py
149
PIL/ImageQt.py
|
@ -18,48 +18,114 @@
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL._util import isPath
|
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:
|
try:
|
||||||
from PyQt5.QtGui import QImage, qRgba
|
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
||||||
except:
|
from PyQt5.QtCore import QBuffer, QIODevice
|
||||||
|
qt_version = '5'
|
||||||
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
from PyQt4.QtGui import QImage, qRgba
|
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
||||||
except:
|
from PyQt4.QtCore import QBuffer, QIODevice
|
||||||
from PySide.QtGui import QImage, qRgba
|
qt_version = '4'
|
||||||
|
except ImportError:
|
||||||
else: #PyQt4 is used
|
try:
|
||||||
from PyQt4.QtGui import QImage, qRgba
|
from PySide.QtGui import QImage, qRgba, QPixmap
|
||||||
|
from PySide.QtCore import QBuffer, QIODevice
|
||||||
##
|
qt_version = 'side'
|
||||||
# (Internal) Turns an RGB color into a Qt compatible color integer.
|
except ImportError:
|
||||||
|
qt_is_installed = False
|
||||||
|
|
||||||
|
|
||||||
def rgb(r, g, b, a=255):
|
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
|
# use qRgb to pack the colors, and then turn the resulting long
|
||||||
# into a negative integer with the same bitpattern.
|
# into a negative integer with the same bitpattern.
|
||||||
return (qRgba(r, g, b, a) & 0xffffffff)
|
return (qRgba(r, g, b, a) & 0xffffffff)
|
||||||
|
|
||||||
|
|
||||||
##
|
# :param im A PIL Image object, or a file name
|
||||||
# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage
|
# (given either as Python string or a PyQt string object)
|
||||||
# class.
|
|
||||||
#
|
|
||||||
# @param im A PIL Image object, or a file name (given either as Python
|
|
||||||
# string or a PyQt string object).
|
|
||||||
|
|
||||||
class ImageQt(QImage):
|
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')
|
||||||
|
|
||||||
def __init__(self, im):
|
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
|
data = None
|
||||||
colortable = None
|
colortable = None
|
||||||
|
|
||||||
# handle filename, if given instead of image name
|
# handle filename, if given instead of image name
|
||||||
if hasattr(im, "toUtf8"):
|
if hasattr(im, "toUtf8"):
|
||||||
# FIXME - is this really the best way to do this?
|
# FIXME - is this really the best way to do this?
|
||||||
|
if str is bytes:
|
||||||
im = unicode(im.toUtf8(), "utf-8")
|
im = unicode(im.toUtf8(), "utf-8")
|
||||||
|
else:
|
||||||
|
im = str(im.toUtf8(), "utf-8")
|
||||||
if isPath(im):
|
if isPath(im):
|
||||||
im = Image.open(im)
|
im = Image.open(im)
|
||||||
|
|
||||||
|
@ -91,9 +157,42 @@ class ImageQt(QImage):
|
||||||
raise ValueError("unsupported image mode %r" % im.mode)
|
raise ValueError("unsupported image mode %r" % im.mode)
|
||||||
|
|
||||||
# must keep a reference, or Qt will crash!
|
# must keep a reference, or Qt will crash!
|
||||||
self.__data = data or im.tobytes()
|
__data = data or align8to32(im.tobytes(), im.size[0], im.mode)
|
||||||
|
return {
|
||||||
|
'data': __data, 'im': im, 'format': format, 'colortable': colortable
|
||||||
|
}
|
||||||
|
|
||||||
QImage.__init__(self, self.__data, im.size[0], im.size[1], format)
|
##
|
||||||
|
# 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).
|
||||||
|
|
||||||
if colortable:
|
if qt_is_installed:
|
||||||
self.setColorTable(colortable)
|
class ImageQt(QImage):
|
||||||
|
|
||||||
|
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'])
|
||||||
|
|
||||||
|
|
||||||
|
def toqimage(im):
|
||||||
|
return ImageQt(im)
|
||||||
|
|
||||||
|
|
||||||
|
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 = toqimage(im)
|
||||||
|
return QPixmap.fromImage(qimage)
|
||||||
|
|
|
@ -32,11 +32,25 @@ class Iterator(object):
|
||||||
if not hasattr(im, "seek"):
|
if not hasattr(im, "seek"):
|
||||||
raise AttributeError("im must have seek method")
|
raise AttributeError("im must have seek method")
|
||||||
self.im = im
|
self.im = im
|
||||||
|
self.position = 0
|
||||||
|
|
||||||
def __getitem__(self, ix):
|
def __getitem__(self, ix):
|
||||||
try:
|
try:
|
||||||
if ix:
|
|
||||||
self.im.seek(ix)
|
self.im.seek(ix)
|
||||||
return self.im
|
return self.im
|
||||||
except EOFError:
|
except EOFError:
|
||||||
raise IndexError # end of sequence
|
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__()
|
||||||
|
|
|
@ -76,7 +76,7 @@ class ExtentTransform(Transform):
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Define an quad image transform.
|
# Define a quad image transform.
|
||||||
# <p>
|
# <p>
|
||||||
# Maps a quadrilateral (a region defined by four corners) from the
|
# Maps a quadrilateral (a region defined by four corners) from the
|
||||||
# image to a rectangle of the given size.
|
# 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.
|
# or more individual quad transforms.
|
||||||
#
|
#
|
||||||
# @def MeshTransform(data)
|
# @def MeshTransform(data)
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
import warnings
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
@ -183,24 +182,13 @@ class Dib(object):
|
||||||
"""
|
"""
|
||||||
return self.image.tobytes()
|
return self.image.tobytes()
|
||||||
|
|
||||||
##
|
|
||||||
# Deprecated aliases to frombytes & tobytes.
|
|
||||||
|
|
||||||
def fromstring(self, *args, **kw):
|
def fromstring(self, *args, **kw):
|
||||||
warnings.warn(
|
raise Exception("fromstring() has been removed. " +
|
||||||
'fromstring() is deprecated. Please call frombytes() instead.',
|
"Please use frombytes() instead.")
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2
|
|
||||||
)
|
|
||||||
return self.frombytes(*args, **kw)
|
|
||||||
|
|
||||||
def tostring(self):
|
def tostring(self, *args, **kw):
|
||||||
warnings.warn(
|
raise Exception("tostring() has been removed. " +
|
||||||
'tostring() is deprecated. Please call tobytes() instead.',
|
"Please use tobytes() instead.")
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2
|
|
||||||
)
|
|
||||||
return self.tobytes()
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -15,12 +15,13 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.2"
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
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)
|
# no extension registered (".im" is simply too common)
|
||||||
|
|
|
@ -17,13 +17,12 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
__version__ = "0.3"
|
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile, _binary
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
__version__ = "0.3"
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16be
|
i16 = _binary.i16be
|
||||||
i32 = _binary.i32be
|
i32 = _binary.i32be
|
||||||
|
@ -180,13 +179,13 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(outfile)
|
os.unlink(outfile)
|
||||||
except:
|
except OSError:
|
||||||
pass
|
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":
|
while app[offset:offset+4] == b"8BIM":
|
||||||
offset += 4
|
offset += 4
|
||||||
# resource code
|
# resource code
|
||||||
code = JpegImagePlugin.i16(app, offset)
|
code = i16(app, offset)
|
||||||
offset += 2
|
offset += 2
|
||||||
# resource name (usually empty)
|
# resource name (usually empty)
|
||||||
name_len = i8(app[offset])
|
name_len = i8(app[offset])
|
||||||
|
@ -227,7 +226,7 @@ def getiptcinfo(im):
|
||||||
if offset & 1:
|
if offset & 1:
|
||||||
offset += 1
|
offset += 1
|
||||||
# resource data block
|
# resource data block
|
||||||
size = JpegImagePlugin.i32(app, offset)
|
size = i32(app, offset)
|
||||||
offset += 4
|
offset += 4
|
||||||
if code == 0x0404:
|
if code == 0x0404:
|
||||||
# 0x0404 contains IPTC/NAA data
|
# 0x0404 contains IPTC/NAA data
|
||||||
|
|
|
@ -84,6 +84,7 @@ def _parse_jp2_header(fp):
|
||||||
size = None
|
size = None
|
||||||
mode = None
|
mode = None
|
||||||
bpc = None
|
bpc = None
|
||||||
|
nc = None
|
||||||
|
|
||||||
hio = io.BytesIO(header)
|
hio = io.BytesIO(header)
|
||||||
while True:
|
while True:
|
||||||
|
@ -141,6 +142,9 @@ def _parse_jp2_header(fp):
|
||||||
mode = 'RGBA'
|
mode = 'RGBA'
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if size is None or mode is None:
|
||||||
|
raise SyntaxError("Malformed jp2 header")
|
||||||
|
|
||||||
return (size, mode)
|
return (size, mode)
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -262,15 +266,15 @@ def _save(im, fp, filename):
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Registry stuff
|
# Registry stuff
|
||||||
|
|
||||||
Image.register_open('JPEG2000', Jpeg2KImageFile, _accept)
|
Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
|
||||||
Image.register_save('JPEG2000', _save)
|
Image.register_save(Jpeg2KImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension('JPEG2000', '.jp2')
|
Image.register_extension(Jpeg2KImageFile.format, '.jp2')
|
||||||
Image.register_extension('JPEG2000', '.j2k')
|
Image.register_extension(Jpeg2KImageFile.format, '.j2k')
|
||||||
Image.register_extension('JPEG2000', '.jpc')
|
Image.register_extension(Jpeg2KImageFile.format, '.jpc')
|
||||||
Image.register_extension('JPEG2000', '.jpf')
|
Image.register_extension(Jpeg2KImageFile.format, '.jpf')
|
||||||
Image.register_extension('JPEG2000', '.jpx')
|
Image.register_extension(Jpeg2KImageFile.format, '.jpx')
|
||||||
Image.register_extension('JPEG2000', '.j2c')
|
Image.register_extension(Jpeg2KImageFile.format, '.j2c')
|
||||||
|
|
||||||
Image.register_mime('JPEG2000', 'image/jp2')
|
Image.register_mime(Jpeg2KImageFile.format, 'image/jp2')
|
||||||
Image.register_mime('JPEG2000', 'image/jpx')
|
Image.register_mime(Jpeg2KImageFile.format, 'image/jpx')
|
||||||
|
|
|
@ -32,12 +32,11 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.6"
|
|
||||||
|
|
||||||
import array
|
import array
|
||||||
import struct
|
import struct
|
||||||
import io
|
import io
|
||||||
from struct import unpack
|
import warnings
|
||||||
|
from struct import unpack_from
|
||||||
from PIL import Image, ImageFile, TiffImagePlugin, _binary
|
from PIL import Image, ImageFile, TiffImagePlugin, _binary
|
||||||
from PIL.JpegPresets import presets
|
from PIL.JpegPresets import presets
|
||||||
from PIL._util import isStringType
|
from PIL._util import isStringType
|
||||||
|
@ -47,6 +46,8 @@ o8 = _binary.o8
|
||||||
i16 = _binary.i16be
|
i16 = _binary.i16be
|
||||||
i32 = _binary.i32be
|
i32 = _binary.i32be
|
||||||
|
|
||||||
|
__version__ = "0.6"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Parser
|
# Parser
|
||||||
|
@ -287,7 +288,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
s = self.fp.read(1)
|
s = self.fp.read(1)
|
||||||
|
|
||||||
if i8(s[0]) != 255:
|
if i8(s) != 255:
|
||||||
raise SyntaxError("not a JPEG file")
|
raise SyntaxError("not a JPEG file")
|
||||||
|
|
||||||
# Create attributes
|
# Create attributes
|
||||||
|
@ -310,7 +311,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
i = i16(s)
|
i = i16(s)
|
||||||
else:
|
else:
|
||||||
# Skip non-0xFF junk
|
# Skip non-0xFF junk
|
||||||
s = b"\xff"
|
s = self.fp.read(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if i in MARKER:
|
if i in MARKER:
|
||||||
|
@ -378,7 +379,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
except:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.mode = self.im.mode
|
self.mode = self.im.mode
|
||||||
|
@ -393,12 +394,18 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
return _getmp(self)
|
return _getmp(self)
|
||||||
|
|
||||||
|
|
||||||
|
def _fixup_dict(src_dict):
|
||||||
|
# Helper function for _getexif()
|
||||||
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
def _fixup(value):
|
def _fixup(value):
|
||||||
# Helper function for _getexif() and _getmp()
|
try:
|
||||||
if len(value) == 1:
|
if len(value) == 1 and type(value) != type({}):
|
||||||
return value[0]
|
return value[0]
|
||||||
|
except: pass
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
return dict([(k, _fixup(v)) for k, v in src_dict.items()])
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
# Extract EXIF information. This method is highly experimental,
|
# Extract EXIF information. This method is highly experimental,
|
||||||
|
@ -413,33 +420,35 @@ def _getexif(self):
|
||||||
return None
|
return None
|
||||||
file = io.BytesIO(data[6:])
|
file = io.BytesIO(data[6:])
|
||||||
head = file.read(8)
|
head = file.read(8)
|
||||||
exif = {}
|
|
||||||
# process dictionary
|
# process dictionary
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
exif = dict(_fixup_dict(info))
|
||||||
exif[key] = _fixup(value)
|
|
||||||
# get exif extension
|
# get exif extension
|
||||||
try:
|
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])
|
file.seek(exif[0x8769])
|
||||||
except KeyError:
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
exif.update(_fixup_dict(info))
|
||||||
exif[key] = _fixup(value)
|
|
||||||
# get gpsinfo extension
|
# get gpsinfo extension
|
||||||
try:
|
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])
|
file.seek(exif[0x8825])
|
||||||
except KeyError:
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
exif[0x8825] = gps = {}
|
exif[0x8825] = _fixup_dict(info)
|
||||||
for key, value in info.items():
|
|
||||||
gps[key] = _fixup(value)
|
|
||||||
return exif
|
return exif
|
||||||
|
|
||||||
|
|
||||||
|
@ -457,23 +466,25 @@ def _getmp(self):
|
||||||
file_contents = io.BytesIO(data)
|
file_contents = io.BytesIO(data)
|
||||||
head = file_contents.read(8)
|
head = file_contents.read(8)
|
||||||
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
||||||
mp = {}
|
|
||||||
# process dictionary
|
# process dictionary
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
try:
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||||
info.load(file_contents)
|
info.load(file_contents)
|
||||||
for key, value in info.items():
|
mp = dict(info)
|
||||||
mp[key] = _fixup(value)
|
except:
|
||||||
|
raise SyntaxError("malformed MP Index (unreadable directory)")
|
||||||
# it's an error not to have a number of images
|
# it's an error not to have a number of images
|
||||||
try:
|
try:
|
||||||
quant = mp[0xB001]
|
quant = mp[0xB001]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise SyntaxError("malformed MP Index (no number of images)")
|
raise SyntaxError("malformed MP Index (no number of images)")
|
||||||
# get MP entries
|
# get MP entries
|
||||||
try:
|
|
||||||
mpentries = []
|
mpentries = []
|
||||||
|
try:
|
||||||
|
rawmpentries = mp[0xB002]
|
||||||
for entrynum in range(0, quant):
|
for entrynum in range(0, quant):
|
||||||
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
|
unpackedentry = unpack_from(
|
||||||
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
|
'{0}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
|
||||||
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
||||||
'EntryNo2')
|
'EntryNo2')
|
||||||
mpentry = dict(zip(labels, unpackedentry))
|
mpentry = dict(zip(labels, unpackedentry))
|
||||||
|
@ -681,7 +692,7 @@ def _save(im, fp, filename):
|
||||||
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
|
# 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
|
# 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.
|
# 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
|
bufsize = 0
|
||||||
if "optimize" in info or "progressive" in info or "progression" in info:
|
if "optimize" in info or "progressive" in info or "progression" in info:
|
||||||
# keep sets quality to 0, but the actual value may be high.
|
# 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])
|
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
||||||
try:
|
try:
|
||||||
os.unlink(tempfile)
|
os.unlink(tempfile)
|
||||||
except:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -713,8 +724,8 @@ def _save_cjpeg(im, fp, filename):
|
||||||
# Factory for making JPEG and MPO instances
|
# Factory for making JPEG and MPO instances
|
||||||
def jpeg_factory(fp=None, filename=None):
|
def jpeg_factory(fp=None, filename=None):
|
||||||
im = JpegImageFile(fp, filename)
|
im = JpegImageFile(fp, filename)
|
||||||
mpheader = im._getmp()
|
|
||||||
try:
|
try:
|
||||||
|
mpheader = im._getmp()
|
||||||
if mpheader[45057] > 1:
|
if mpheader[45057] > 1:
|
||||||
# It's actually an MPO
|
# It's actually an MPO
|
||||||
from .MpoImagePlugin import MpoImageFile
|
from .MpoImagePlugin import MpoImageFile
|
||||||
|
@ -722,18 +733,21 @@ def jpeg_factory(fp=None, filename=None):
|
||||||
except (TypeError, IndexError):
|
except (TypeError, IndexError):
|
||||||
# It is really a JPEG
|
# It is really a JPEG
|
||||||
pass
|
pass
|
||||||
|
except SyntaxError:
|
||||||
|
warnings.warn("Image appears to be a malformed MPO file, it will be "
|
||||||
|
"interpreted as a base JPEG file")
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------q-
|
# -------------------------------------------------------------------q-
|
||||||
# Registry stuff
|
# Registry stuff
|
||||||
|
|
||||||
Image.register_open("JPEG", jpeg_factory, _accept)
|
Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
|
||||||
Image.register_save("JPEG", _save)
|
Image.register_save(JpegImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension("JPEG", ".jfif")
|
Image.register_extension(JpegImageFile.format, ".jfif")
|
||||||
Image.register_extension("JPEG", ".jpe")
|
Image.register_extension(JpegImageFile.format, ".jpe")
|
||||||
Image.register_extension("JPEG", ".jpg")
|
Image.register_extension(JpegImageFile.format, ".jpg")
|
||||||
Image.register_extension("JPEG", ".jpeg")
|
Image.register_extension(JpegImageFile.format, ".jpeg")
|
||||||
|
|
||||||
Image.register_mime("JPEG", "image/jpeg")
|
Image.register_mime(JpegImageFile.format, "image/jpeg")
|
||||||
|
|
|
@ -27,7 +27,7 @@ Subsampling
|
||||||
|
|
||||||
Subsampling is the practice of encoding images by implementing less resolution
|
Subsampling is the practice of encoding images by implementing less resolution
|
||||||
for chroma information than for luma information.
|
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
|
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?).
|
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
|
They are values use by the DCT (Discrete cosine transform) to remove
|
||||||
*unnecessary* information from the image (the lossy part of the compression).
|
*unnecessary* information from the image (the lossy part of the compression).
|
||||||
(ref.: http://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
|
(ref.: https://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
|
||||||
http://en.wikipedia.org/wiki/JPEG#Quantization)
|
https://en.wikipedia.org/wiki/JPEG#Quantization)
|
||||||
|
|
||||||
You can get the quantization tables of a JPEG with::
|
You can get the quantization tables of a JPEG with::
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ The tables format between im.quantization and quantization in presets differ in
|
||||||
You can convert the dict format to the preset format with the
|
You can convert the dict format to the preset format with the
|
||||||
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
|
`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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,11 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.2"
|
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
|
||||||
def _accept(s):
|
def _accept(s):
|
||||||
return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04"
|
return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04"
|
||||||
|
@ -69,6 +69,6 @@ class McIdasImageFile(ImageFile.ImageFile):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
Image.register_open("MCIDAS", McIdasImageFile, _accept)
|
Image.register_open(McIdasImageFile.format, McIdasImageFile, _accept)
|
||||||
|
|
||||||
# no default extension
|
# no default extension
|
||||||
|
|
|
@ -17,12 +17,11 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
from PIL import Image, TiffImagePlugin
|
||||||
from PIL.OleFileIO import MAGIC, OleFileIO
|
from PIL.OleFileIO import MAGIC, OleFileIO
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -75,6 +74,10 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
return len(self.images)
|
return len(self.images)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_animated(self):
|
||||||
|
return len(self.images) > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
|
||||||
try:
|
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")
|
||||||
|
|
|
@ -13,11 +13,12 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
from PIL._binary import i8
|
from PIL._binary import i8
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Bitstream parser
|
# Bitstream parser
|
||||||
|
@ -77,9 +78,9 @@ class MpegImageFile(ImageFile.ImageFile):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Registry stuff
|
# Registry stuff
|
||||||
|
|
||||||
Image.register_open("MPEG", MpegImageFile)
|
Image.register_open(MpegImageFile.format, MpegImageFile)
|
||||||
|
|
||||||
Image.register_extension("MPEG", ".mpg")
|
Image.register_extension(MpegImageFile.format, ".mpg")
|
||||||
Image.register_extension("MPEG", ".mpeg")
|
Image.register_extension(MpegImageFile.format, ".mpeg")
|
||||||
|
|
||||||
Image.register_mime("MPEG", "video/mpeg")
|
Image.register_mime(MpegImageFile.format, "video/mpeg")
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
from PIL import Image, JpegImagePlugin
|
from PIL import Image, JpegImagePlugin
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return JpegImagePlugin._accept(prefix)
|
return JpegImagePlugin._accept(prefix)
|
||||||
|
@ -66,6 +66,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
return self.__framecount
|
return self.__framecount
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_animated(self):
|
||||||
|
return self.__framecount > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame < 0 or frame >= self.__framecount:
|
if frame < 0 or frame >= self.__framecount:
|
||||||
raise EOFError("no more images in MPO file")
|
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
|
# Note that since MPO shares a factory with JPEG, we do not need to do a
|
||||||
# separate registration for it here.
|
# separate registration for it here.
|
||||||
# Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
|
# Image.register_open(MpoImageFile.format,
|
||||||
Image.register_save("MPO", _save)
|
# 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")
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# read MSP files
|
# read MSP files
|
||||||
|
@ -98,7 +98,7 @@ def _save(im, fp, filename):
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
Image.register_open("MSP", MspImageFile, _accept)
|
Image.register_open(MspImageFile.format, MspImageFile, _accept)
|
||||||
Image.register_save("MSP", _save)
|
Image.register_save(MspImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension("MSP", ".msp")
|
Image.register_extension(MspImageFile.format, ".msp")
|
||||||
|
|
|
@ -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
|
News
|
||||||
----
|
----
|
||||||
|
|
||||||
Follow all updates and news on Twitter: <https://twitter.com/decalage2>
|
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.
|
- **2015-01-25 v0.42**: improved handling of special characters in stream/storage names on Python 2.x (using UTF-8
|
||||||
- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed parsing of direntry timestamps
|
instead of Latin-1), fixed bug in listdir with empty storages.
|
||||||
- 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)
|
- 2014-11-25 v0.41: OleFileIO.open and isOleFile now support OLE files stored in byte strings, fixed installer for
|
||||||
- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved getproperties to convert timestamps to Python datetime
|
python 3, added support for Jython (Niko Ehrenfeuchter)
|
||||||
- 2012-10-09: published [python-oletools](http://www.decalage.info/python/oletools), a package of analysis tools based on OleFileIO_PL
|
- 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-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)
|
- 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
|
- 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)
|
- 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.
|
- 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
|
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
|
- List all the streams and storages contained in an OLE file
|
||||||
- Open streams as files
|
- Open streams as files
|
||||||
- Parse and read property streams, containing metadata of the file
|
- Parse and read property streams, containing metadata of the file
|
||||||
- Portable, pure Python module, no dependency
|
- 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:
|
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
|
- Can open file-like objects
|
||||||
- Added setup.py and install.bat to ease installation
|
- Added setup.py and install.bat to ease installation
|
||||||
- More convenient slash-based syntax for stream paths
|
- 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 ##
|
## 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/).
|
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
|
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
|
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
|
||||||
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.
|
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.
|
||||||
|
|
|
@ -16,11 +16,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import EpsImagePlugin
|
from PIL import EpsImagePlugin
|
||||||
|
import sys
|
||||||
|
|
||||||
##
|
##
|
||||||
# Simple Postscript graphics interface.
|
# Simple Postscript graphics interface.
|
||||||
|
|
||||||
|
|
||||||
class PSDraw(object):
|
class PSDraw(object):
|
||||||
"""
|
"""
|
||||||
Sets up printing to the given file. If **file** is omitted,
|
Sets up printing to the given file. If **file** is omitted,
|
||||||
|
@ -29,12 +30,11 @@ class PSDraw(object):
|
||||||
|
|
||||||
def __init__(self, fp=None):
|
def __init__(self, fp=None):
|
||||||
if not fp:
|
if not fp:
|
||||||
import sys
|
|
||||||
fp = sys.stdout
|
fp = sys.stdout
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
|
|
||||||
def _fp_write(self, to_write):
|
def _fp_write(self, to_write):
|
||||||
if bytes is str:
|
if bytes is str or self.fp == sys.stdout:
|
||||||
self.fp.write(to_write)
|
self.fp.write(to_write)
|
||||||
else:
|
else:
|
||||||
self.fp.write(bytes(to_write, 'UTF-8'))
|
self.fp.write(bytes(to_write, 'UTF-8'))
|
||||||
|
@ -47,7 +47,7 @@ class PSDraw(object):
|
||||||
"/showpage { } def\n"
|
"/showpage { } def\n"
|
||||||
"%%EndComments\n"
|
"%%EndComments\n"
|
||||||
"%%BeginDocument\n")
|
"%%BeginDocument\n")
|
||||||
# self.fp_write(ERROR_PS) # debugging!
|
# self._fp_write(ERROR_PS) # debugging!
|
||||||
self._fp_write(EDROFF_PS)
|
self._fp_write(EDROFF_PS)
|
||||||
self._fp_write(VDI_PS)
|
self._fp_write(VDI_PS)
|
||||||
self._fp_write("%%EndProlog\n")
|
self._fp_write("%%EndProlog\n")
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
# Image plugin for Palm pixmap images (output only).
|
# Image plugin for Palm pixmap images (output only).
|
||||||
##
|
##
|
||||||
|
|
||||||
__version__ = "1.0"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
|
__version__ = "1.0"
|
||||||
|
|
||||||
_Palm8BitColormapValues = (
|
_Palm8BitColormapValues = (
|
||||||
(255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255),
|
(255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255),
|
||||||
(255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204),
|
(255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204),
|
||||||
|
@ -227,6 +227,7 @@ def _save(im, fp, filename, check=0):
|
||||||
ImageFile._save(
|
ImageFile._save(
|
||||||
im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))])
|
im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))])
|
||||||
|
|
||||||
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,10 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +54,6 @@ class PcdImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
Image.register_open("PCD", PcdImageFile)
|
Image.register_open(PcdImageFile.format, PcdImageFile)
|
||||||
|
|
||||||
Image.register_extension("PCD", ".pcd")
|
Image.register_extension(PcdImageFile.format, ".pcd")
|
||||||
|
|
|
@ -27,8 +27,11 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import logging
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
o8 = _binary.o8
|
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
|
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]:
|
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
|
||||||
raise SyntaxError("bad PCX image size")
|
raise SyntaxError("bad PCX image size")
|
||||||
if Image.DEBUG:
|
logger.debug("BBox: %s %s %s %s", *bbox)
|
||||||
print("BBox: %s %s %s %s" % bbox)
|
|
||||||
|
|
||||||
# format
|
# format
|
||||||
version = i8(s[1])
|
version = i8(s[1])
|
||||||
bits = i8(s[3])
|
bits = i8(s[3])
|
||||||
planes = i8(s[65])
|
planes = i8(s[65])
|
||||||
stride = i16(s, 66)
|
stride = i16(s, 66)
|
||||||
if Image.DEBUG:
|
logger.debug("PCX version %s, bits %s, planes %s, stride %s",
|
||||||
print("PCX version %s, bits %s, planes %s, stride %s" %
|
version, bits, planes, stride)
|
||||||
(version, bits, planes, stride))
|
|
||||||
|
|
||||||
self.info["dpi"] = i16(s, 12), i16(s, 14)
|
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]
|
self.size = bbox[2]-bbox[0], bbox[3]-bbox[1]
|
||||||
|
|
||||||
bbox = (0, 0) + self.size
|
bbox = (0, 0) + self.size
|
||||||
if Image.DEBUG:
|
logger.debug("size: %sx%s", *self.size)
|
||||||
print("size: %sx%s" % self.size)
|
|
||||||
|
|
||||||
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
|
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
|
# Ideally it should be passed in in the state, but the bytes value
|
||||||
# gets overwritten.
|
# gets overwritten.
|
||||||
|
|
||||||
if Image.DEBUG:
|
logger.debug("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
|
||||||
print("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
|
im.size[0], bits, stride)
|
||||||
im.size[0], bits, stride))
|
|
||||||
|
|
||||||
# under windows, we could determine the current screen size with
|
# under windows, we could determine the current screen size with
|
||||||
# "Image.core.display_mode()[1]", but I think that's overkill...
|
# "Image.core.display_mode()[1]", but I think that's overkill...
|
||||||
|
@ -182,7 +181,7 @@ def _save(im, fp, filename, check=0):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
Image.register_open("PCX", PcxImageFile, _accept)
|
Image.register_open(PcxImageFile.format, PcxImageFile, _accept)
|
||||||
Image.register_save("PCX", _save)
|
Image.register_save(PcxImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension("PCX", ".pcx")
|
Image.register_extension(PcxImageFile.format, ".pcx")
|
||||||
|
|
|
@ -20,12 +20,12 @@
|
||||||
# Image plugin for PDF images (output only).
|
# Image plugin for PDF images (output only).
|
||||||
##
|
##
|
||||||
|
|
||||||
__version__ = "0.4"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
from PIL._binary import i8
|
from PIL._binary import i8
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
__version__ = "0.4"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -51,17 +51,21 @@ def _endobj(fp):
|
||||||
fp.write("endobj\n")
|
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.
|
# (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)
|
resolution = im.encoderinfo.get("resolution", 72.0)
|
||||||
|
|
||||||
#
|
#
|
||||||
# make sure image data is available
|
# make sure image data is available
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
xref = [0]*(5+1) # placeholders
|
xref = [0]
|
||||||
|
|
||||||
class TextWriter(object):
|
class TextWriter(object):
|
||||||
def __init__(self, fp):
|
def __init__(self, fp):
|
||||||
|
@ -78,11 +82,6 @@ def _save(im, fp, filename):
|
||||||
fp.write("%PDF-1.2\n")
|
fp.write("%PDF-1.2\n")
|
||||||
fp.write("% created by PIL PDF driver " + __version__ + "\n")
|
fp.write("% created by PIL PDF driver " + __version__ + "\n")
|
||||||
|
|
||||||
#
|
|
||||||
# Get image characteristics
|
|
||||||
|
|
||||||
width, height = im.size
|
|
||||||
|
|
||||||
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)
|
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)
|
||||||
# or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports
|
# or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports
|
||||||
# Flatedecode (zip compression).
|
# Flatedecode (zip compression).
|
||||||
|
@ -125,7 +124,7 @@ def _save(im, fp, filename):
|
||||||
#
|
#
|
||||||
# catalogue
|
# catalogue
|
||||||
|
|
||||||
xref[1] = fp.tell()
|
xref.append(fp.tell())
|
||||||
_obj(
|
_obj(
|
||||||
fp, 1,
|
fp, 1,
|
||||||
Type="/Catalog",
|
Type="/Catalog",
|
||||||
|
@ -134,15 +133,27 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
#
|
#
|
||||||
# pages
|
# 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(
|
_obj(
|
||||||
fp, 2,
|
fp, 2,
|
||||||
Type="/Pages",
|
Type="/Pages",
|
||||||
Count=1,
|
Count=len(pages),
|
||||||
Kids="[4 0 R]")
|
Kids="["+"\n".join(pages)+"]")
|
||||||
_endobj(fp)
|
_endobj(fp)
|
||||||
|
|
||||||
|
for pageNumber in range(0, numberOfPages):
|
||||||
|
im.seek(pageNumber)
|
||||||
|
|
||||||
#
|
#
|
||||||
# image
|
# image
|
||||||
|
|
||||||
|
@ -165,9 +176,14 @@ def _save(im, fp, filename):
|
||||||
else:
|
else:
|
||||||
raise ValueError("unsupported PDF filter (%s)" % filter)
|
raise ValueError("unsupported PDF filter (%s)" % filter)
|
||||||
|
|
||||||
xref[3] = fp.tell()
|
#
|
||||||
|
# Get image characteristics
|
||||||
|
|
||||||
|
width, height = im.size
|
||||||
|
|
||||||
|
xref.append(fp.tell())
|
||||||
_obj(
|
_obj(
|
||||||
fp, 3,
|
fp, pageNumber*3+3,
|
||||||
Type="/XObject",
|
Type="/XObject",
|
||||||
Subtype="/Image",
|
Subtype="/Image",
|
||||||
Width=width, # * 72.0 / resolution,
|
Width=width, # * 72.0 / resolution,
|
||||||
|
@ -187,16 +203,18 @@ def _save(im, fp, filename):
|
||||||
#
|
#
|
||||||
# page
|
# page
|
||||||
|
|
||||||
xref[4] = fp.tell()
|
xref.append(fp.tell())
|
||||||
_obj(fp, 4)
|
_obj(fp, pageNumber*3+4)
|
||||||
fp.write(
|
fp.write(
|
||||||
"<<\n/Type /Page\n/Parent 2 0 R\n"
|
"<<\n/Type /Page\n/Parent 2 0 R\n"
|
||||||
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
|
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
|
||||||
"/XObject << /image 3 0 R >>\n>>\n"
|
"/XObject << /image %d 0 R >>\n>>\n"
|
||||||
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % (
|
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
|
||||||
procset,
|
procset,
|
||||||
|
pageNumber*3+3,
|
||||||
int(width * 72.0 / resolution),
|
int(width * 72.0 / resolution),
|
||||||
int(height * 72.0 / resolution)))
|
int(height * 72.0 / resolution),
|
||||||
|
pageNumber*3+5))
|
||||||
_endobj(fp)
|
_endobj(fp)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -209,8 +227,8 @@ def _save(im, fp, filename):
|
||||||
int(width * 72.0 / resolution),
|
int(width * 72.0 / resolution),
|
||||||
int(height * 72.0 / resolution)))
|
int(height * 72.0 / resolution)))
|
||||||
|
|
||||||
xref[5] = fp.tell()
|
xref.append(fp.tell())
|
||||||
_obj(fp, 5, Length=len(op.fp.getvalue()))
|
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
|
||||||
|
|
||||||
fp.write("stream\n")
|
fp.write("stream\n")
|
||||||
fp.fp.write(op.fp.getvalue())
|
fp.fp.write(op.fp.getvalue())
|
||||||
|
@ -226,12 +244,14 @@ def _save(im, fp, filename):
|
||||||
fp.write("%010d 00000 n \n" % x)
|
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("trailer\n<<\n/Size %d\n/Root 1 0 R\n>>\n" % len(xref))
|
||||||
fp.write("startxref\n%d\n%%%%EOF\n" % startxref)
|
fp.write("startxref\n%d\n%%%%EOF\n" % startxref)
|
||||||
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
Image.register_save("PDF", _save)
|
Image.register_save("PDF", _save)
|
||||||
|
Image.register_save_all("PDF", _save_all)
|
||||||
|
|
||||||
Image.register_extension("PDF", ".pdf")
|
Image.register_extension("PDF", ".pdf")
|
||||||
|
|
||||||
|
|
|
@ -19,15 +19,14 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
#
|
#
|
||||||
# helpers
|
# helpers
|
||||||
|
|
||||||
i16 = _binary.i16le
|
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?
|
# FIXME: what's the standard extension?
|
||||||
|
|
|
@ -33,12 +33,15 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
__version__ = "0.9"
|
import logging
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import zlib
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
import zlib
|
|
||||||
|
__version__ = "0.9"
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16be
|
i16 = _binary.i16be
|
||||||
|
@ -129,8 +132,7 @@ class ChunkStream(object):
|
||||||
def call(self, cid, pos, length):
|
def call(self, cid, pos, length):
|
||||||
"Call the appropriate chunk handler"
|
"Call the appropriate chunk handler"
|
||||||
|
|
||||||
if Image.DEBUG:
|
logger.debug("STREAM %s %s %s", cid, pos, length)
|
||||||
print("STREAM", cid, pos, length)
|
|
||||||
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length)
|
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length)
|
||||||
|
|
||||||
def crc(self, cid, data):
|
def crc(self, cid, data):
|
||||||
|
@ -293,9 +295,8 @@ class PngStream(ChunkStream):
|
||||||
# Compression method 1 byte (0)
|
# Compression method 1 byte (0)
|
||||||
# Compressed profile n bytes (zlib with deflate compression)
|
# Compressed profile n bytes (zlib with deflate compression)
|
||||||
i = s.find(b"\0")
|
i = s.find(b"\0")
|
||||||
if Image.DEBUG:
|
logger.debug("iCCP profile name %s", s[:i])
|
||||||
print("iCCP profile name", s[:i])
|
logger.debug("Compression method %s", i8(s[i]))
|
||||||
print("Compression method", i8(s[i]))
|
|
||||||
comp_method = i8(s[i])
|
comp_method = i8(s[i])
|
||||||
if comp_method != 0:
|
if comp_method != 0:
|
||||||
raise SyntaxError("Unknown compression method %s in iCCP chunk" %
|
raise SyntaxError("Unknown compression method %s in iCCP chunk" %
|
||||||
|
@ -507,8 +508,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
except EOFError:
|
except EOFError:
|
||||||
break
|
break
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if Image.DEBUG:
|
logger.debug("%s %s %s (unknown)", cid, pos, length)
|
||||||
print(cid, pos, length, "(unknown)")
|
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
|
|
||||||
self.png.crc(cid, s)
|
self.png.crc(cid, s)
|
||||||
|
@ -762,10 +762,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
|
|
||||||
chunk(fp, b"IEND", b"")
|
chunk(fp, b"IEND", b"")
|
||||||
|
|
||||||
try:
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -803,9 +801,9 @@ def getchunks(im, **params):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Registry
|
# Registry
|
||||||
|
|
||||||
Image.register_open("PNG", PngImageFile, _accept)
|
Image.register_open(PngImageFile.format, PngImageFile, _accept)
|
||||||
Image.register_save("PNG", _save)
|
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")
|
||||||
|
|
|
@ -15,12 +15,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.2"
|
|
||||||
|
|
||||||
import string
|
import string
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -93,6 +93,8 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
s = self.fp.read(1)
|
s = self.fp.read(1)
|
||||||
if s not in b_whitespace:
|
if s not in b_whitespace:
|
||||||
break
|
break
|
||||||
|
if s == b"":
|
||||||
|
raise ValueError("File does not extend beyond magic number")
|
||||||
if s != b"#":
|
if s != b"#":
|
||||||
break
|
break
|
||||||
s = self.fp.readline()
|
s = self.fp.readline()
|
||||||
|
@ -164,9 +166,9 @@ def _save(im, fp, filename):
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
Image.register_open("PPM", PpmImageFile, _accept)
|
Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
|
||||||
Image.register_save("PPM", _save)
|
Image.register_save(PpmImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension("PPM", ".pbm")
|
Image.register_extension(PpmImageFile.format, ".pbm")
|
||||||
Image.register_extension("PPM", ".pgm")
|
Image.register_extension(PpmImageFile.format, ".pgm")
|
||||||
Image.register_extension("PPM", ".ppm")
|
Image.register_extension(PpmImageFile.format, ".ppm")
|
||||||
|
|
|
@ -136,6 +136,10 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
return len(self.layers)
|
return len(self.layers)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_animated(self):
|
||||||
|
return len(self.layers) > 1
|
||||||
|
|
||||||
def seek(self, layer):
|
def seek(self, layer):
|
||||||
# seek to given layer (1..max)
|
# seek to given layer (1..max)
|
||||||
if layer == self.frame:
|
if layer == self.frame:
|
||||||
|
@ -303,6 +307,6 @@ def _maketile(file, mode, bbox, channels):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# registry
|
# 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")
|
||||||
|
|
|
@ -22,10 +22,14 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from cffi import FFI
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
DEBUG = 0
|
from cffi import FFI
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
defs = """
|
defs = """
|
||||||
struct Pixel_RGBA {
|
struct Pixel_RGBA {
|
||||||
|
@ -50,8 +54,9 @@ class PyAccess(object):
|
||||||
self.xsize = vals['xsize']
|
self.xsize = vals['xsize']
|
||||||
self.ysize = vals['ysize']
|
self.ysize = vals['ysize']
|
||||||
|
|
||||||
if DEBUG:
|
# Debugging is polluting test traces, only useful here
|
||||||
print(vals)
|
# when hacking on PyAccess
|
||||||
|
# logger.debug("%s", vals)
|
||||||
self._post_init()
|
self._post_init()
|
||||||
|
|
||||||
def _post_init(self):
|
def _post_init(self):
|
||||||
|
@ -305,11 +310,8 @@ else:
|
||||||
def new(img, readonly=False):
|
def new(img, readonly=False):
|
||||||
access_type = mode_map.get(img.mode, None)
|
access_type = mode_map.get(img.mode, None)
|
||||||
if not access_type:
|
if not access_type:
|
||||||
if DEBUG:
|
logger.debug("PyAccess Not Implemented: %s", img.mode)
|
||||||
print("PyAccess Not Implemented: %s" % img.mode)
|
|
||||||
return None
|
return None
|
||||||
if DEBUG:
|
|
||||||
print("New PyAccess: %s" % img.mode)
|
|
||||||
return access_type(img, readonly)
|
return access_type(img, readonly)
|
||||||
|
|
||||||
# End of file
|
# End of file
|
||||||
|
|
|
@ -18,18 +18,16 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.2"
|
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16be
|
i16 = _binary.i16be
|
||||||
i32 = _binary.i32be
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i16(prefix) == 474
|
return len(prefix) >= 2 and i16(prefix) == 474
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -81,11 +79,11 @@ class SgiImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
Image.register_open("SGI", SgiImageFile, _accept)
|
Image.register_open(SgiImageFile.format, SgiImageFile, _accept)
|
||||||
|
|
||||||
Image.register_extension("SGI", ".bw")
|
Image.register_extension(SgiImageFile.format, ".bw")
|
||||||
Image.register_extension("SGI", ".rgb")
|
Image.register_extension(SgiImageFile.format, ".rgb")
|
||||||
Image.register_extension("SGI", ".rgba")
|
Image.register_extension(SgiImageFile.format, ".rgba")
|
||||||
Image.register_extension("SGI", ".sgi")
|
Image.register_extension(SgiImageFile.format, ".sgi")
|
||||||
|
|
||||||
# End of file
|
# End of file
|
||||||
|
|
|
@ -27,10 +27,10 @@
|
||||||
# image data from electron microscopy and tomography.
|
# image data from electron microscopy and tomography.
|
||||||
#
|
#
|
||||||
# Spider home page:
|
# 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:
|
# 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
|
from __future__ import print_function
|
||||||
|
@ -50,6 +50,8 @@ def isInt(f):
|
||||||
return 0
|
return 0
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 0
|
return 0
|
||||||
|
except OverflowError:
|
||||||
|
return 0
|
||||||
|
|
||||||
iforms = [1, 3, -11, -12, -21, -22]
|
iforms = [1, 3, -11, -12, -21, -22]
|
||||||
|
|
||||||
|
@ -158,6 +160,10 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
return self._nimages
|
return self._nimages
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_animated(self):
|
||||||
|
return self._nimages > 1
|
||||||
|
|
||||||
# 1st image index is zero (although SPIDER imgnumber starts at 1)
|
# 1st image index is zero (although SPIDER imgnumber starts at 1)
|
||||||
def tell(self):
|
def tell(self):
|
||||||
if self.imgnumber < 1:
|
if self.imgnumber < 1:
|
||||||
|
@ -281,8 +287,8 @@ def _save_spider(im, fp, filename):
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
Image.register_open("SPIDER", SpiderImageFile)
|
Image.register_open(SpiderImageFile.format, SpiderImageFile)
|
||||||
Image.register_save("SPIDER", _save_spider)
|
Image.register_save(SpiderImageFile.format, _save_spider)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
|
|
@ -17,17 +17,15 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.3"
|
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
|
||||||
i16 = _binary.i16be
|
__version__ = "0.3"
|
||||||
|
|
||||||
i32 = _binary.i32be
|
i32 = _binary.i32be
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i32(prefix) == 0x59a66a95
|
return len(prefix) >= 4 and i32(prefix) == 0x59a66a95
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -78,6 +76,6 @@ class SunImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# registry
|
# 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")
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.3"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
|
||||||
|
__version__ = "0.3"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -28,7 +28,6 @@ from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
i32 = _binary.i32le
|
|
||||||
|
|
||||||
|
|
||||||
MODES = {
|
MODES = {
|
||||||
|
@ -193,7 +192,7 @@ def _save(im, fp, filename, check=0):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Registry
|
# Registry
|
||||||
|
|
||||||
Image.register_open("TGA", TgaImageFile)
|
Image.register_open(TgaImageFile.format, TgaImageFile)
|
||||||
Image.register_save("TGA", _save)
|
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
616
PIL/TiffTags.py
616
PIL/TiffTags.py
|
@ -17,291 +17,387 @@
|
||||||
# well-known TIFF tags.
|
# 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",
|
TAGS_V2 = {
|
||||||
255: "SubfileType",
|
|
||||||
256: "ImageWidth",
|
|
||||||
257: "ImageLength",
|
|
||||||
258: "BitsPerSample",
|
|
||||||
|
|
||||||
259: "Compression",
|
254: ("NewSubfileType", LONG, 1),
|
||||||
(259, 1): "Uncompressed",
|
255: ("SubfileType", SHORT, 1),
|
||||||
(259, 2): "CCITT 1d",
|
256: ("ImageWidth", LONG, 1),
|
||||||
(259, 3): "Group 3 Fax",
|
257: ("ImageLength", LONG, 1),
|
||||||
(259, 4): "Group 4 Fax",
|
258: ("BitsPerSample", SHORT, 0),
|
||||||
(259, 5): "LZW",
|
259: ("Compression", SHORT, 1,
|
||||||
(259, 6): "JPEG",
|
{"Uncompressed": 1, "CCITT 1d": 2, "Group 3 Fax": 3, "Group 4 Fax": 4,
|
||||||
(259, 32773): "PackBits",
|
"LZW": 5, "JPEG": 6, "PackBits": 32773}),
|
||||||
|
|
||||||
262: "PhotometricInterpretation",
|
262: ("PhotometricInterpretation", SHORT, 1,
|
||||||
(262, 0): "WhiteIsZero",
|
{"WhiteIsZero": 0, "BlackIsZero": 1, "RGB": 2, "RBG Palette": 3,
|
||||||
(262, 1): "BlackIsZero",
|
"Transparency Mask": 4, "CMYK": 5, "YCbCr": 6, "CieLAB": 8,
|
||||||
(262, 2): "RGB",
|
"CFA": 32803, # TIFF/EP, Adobe DNG
|
||||||
(262, 3): "RGB Palette",
|
"LinearRaw": 32892}), # Adobe DNG
|
||||||
(262, 4): "Transparency Mask",
|
263: ("Threshholding", SHORT, 1),
|
||||||
(262, 5): "CMYK",
|
264: ("CellWidth", SHORT, 1),
|
||||||
(262, 6): "YCbCr",
|
265: ("CellLength", SHORT, 1),
|
||||||
(262, 8): "CieLAB",
|
266: ("FillOrder", SHORT, 1),
|
||||||
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
|
269: ("DocumentName", ASCII, 1),
|
||||||
(262, 32892): "LinearRaw", # Adobe DNG
|
|
||||||
|
|
||||||
263: "Thresholding",
|
270: ("ImageDescription", ASCII, 1),
|
||||||
264: "CellWidth",
|
271: ("Make", ASCII, 1),
|
||||||
265: "CellHeight",
|
272: ("Model", ASCII, 1),
|
||||||
266: "FillOrder",
|
273: ("StripOffsets", LONG, 0),
|
||||||
269: "DocumentName",
|
274: ("Orientation", SHORT, 1),
|
||||||
|
277: ("SamplesPerPixel", SHORT, 1),
|
||||||
|
278: ("RowsPerStrip", LONG, 1),
|
||||||
|
279: ("StripByteCounts", LONG, 0),
|
||||||
|
|
||||||
270: "ImageDescription",
|
280: ("MinSampleValue", LONG, 0),
|
||||||
271: "Make",
|
281: ("MaxSampleValue", SHORT, 0),
|
||||||
272: "Model",
|
282: ("XResolution", RATIONAL, 1),
|
||||||
273: "StripOffsets",
|
283: ("YResolution", RATIONAL, 1),
|
||||||
274: "Orientation",
|
284: ("PlanarConfiguration", SHORT, 1, {"Contigous": 1, "Separate": 2}),
|
||||||
277: "SamplesPerPixel",
|
285: ("PageName", ASCII, 1),
|
||||||
278: "RowsPerStrip",
|
286: ("XPosition", RATIONAL, 1),
|
||||||
279: "StripByteCounts",
|
287: ("YPosition", RATIONAL, 1),
|
||||||
|
288: ("FreeOffsets", LONG, 1),
|
||||||
|
289: ("FreeByteCounts", LONG, 1),
|
||||||
|
|
||||||
280: "MinSampleValue",
|
290: ("GrayResponseUnit", SHORT, 1),
|
||||||
281: "MaxSampleValue",
|
291: ("GrayResponseCurve", SHORT, 0),
|
||||||
282: "XResolution",
|
292: ("T4Options", LONG, 1),
|
||||||
283: "YResolution",
|
293: ("T6Options", LONG, 1),
|
||||||
284: "PlanarConfiguration",
|
296: ("ResolutionUnit", SHORT, 1, {"inch": 1, "cm": 2}),
|
||||||
(284, 1): "Contigous",
|
297: ("PageNumber", SHORT, 2),
|
||||||
(284, 2): "Separate",
|
|
||||||
|
|
||||||
285: "PageName",
|
301: ("TransferFunction", SHORT, 0),
|
||||||
286: "XPosition",
|
305: ("Software", ASCII, 1),
|
||||||
287: "YPosition",
|
306: ("DateTime", ASCII, 1),
|
||||||
288: "FreeOffsets",
|
|
||||||
289: "FreeByteCounts",
|
|
||||||
|
|
||||||
290: "GrayResponseUnit",
|
315: ("Artist", ASCII, 1),
|
||||||
291: "GrayResponseCurve",
|
316: ("HostComputer", ASCII, 1),
|
||||||
292: "T4Options",
|
317: ("Predictor", SHORT, 1),
|
||||||
293: "T6Options",
|
318: ("WhitePoint", RATIONAL, 2),
|
||||||
296: "ResolutionUnit",
|
319: ("PrimaryChromaticities", SHORT, 6),
|
||||||
297: "PageNumber",
|
|
||||||
|
|
||||||
301: "TransferFunction",
|
320: ("ColorMap", SHORT, 0),
|
||||||
305: "Software",
|
321: ("HalftoneHints", SHORT, 2),
|
||||||
306: "DateTime",
|
322: ("TileWidth", LONG, 1),
|
||||||
|
323: ("TileLength", LONG, 1),
|
||||||
|
324: ("TileOffsets", LONG, 0),
|
||||||
|
325: ("TileByteCounts", LONG, 0),
|
||||||
|
|
||||||
315: "Artist",
|
332: ("InkSet", SHORT, 1),
|
||||||
316: "HostComputer",
|
333: ("InkNames", ASCII, 1),
|
||||||
317: "Predictor",
|
334: ("NumberOfInks", SHORT, 1),
|
||||||
318: "WhitePoint",
|
336: ("DotRange", SHORT, 0),
|
||||||
319: "PrimaryChromaticies",
|
337: ("TargetPrinter", ASCII, 1),
|
||||||
|
338: ("ExtraSamples", SHORT, 0),
|
||||||
|
339: ("SampleFormat", SHORT, 0),
|
||||||
|
|
||||||
320: "ColorMap",
|
340: ("SMinSampleValue", 12, 0),
|
||||||
321: "HalftoneHints",
|
341: ("SMaxSampleValue", 12, 0),
|
||||||
322: "TileWidth",
|
342: ("TransferRange", SHORT, 6),
|
||||||
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",
|
|
||||||
|
|
||||||
# obsolete JPEG tags
|
# obsolete JPEG tags
|
||||||
512: "JPEGProc",
|
512: ("JPEGProc", SHORT, 1),
|
||||||
513: "JPEGInterchangeFormat",
|
513: ("JPEGInterchangeFormat", LONG, 1),
|
||||||
514: "JPEGInterchangeFormatLength",
|
514: ("JPEGInterchangeFormatLength", LONG, 1),
|
||||||
515: "JPEGRestartInterval",
|
515: ("JPEGRestartInterval", SHORT, 1),
|
||||||
517: "JPEGLosslessPredictors",
|
517: ("JPEGLosslessPredictors", SHORT, 0),
|
||||||
518: "JPEGPointTransforms",
|
518: ("JPEGPointTransforms", SHORT, 0),
|
||||||
519: "JPEGQTables",
|
519: ("JPEGQTables", LONG, 0),
|
||||||
520: "JPEGDCTables",
|
520: ("JPEGDCTables", LONG, 0),
|
||||||
521: "JPEGACTables",
|
521: ("JPEGACTables", LONG, 0),
|
||||||
|
|
||||||
529: "YCbCrCoefficients",
|
529: ("YCbCrCoefficients", RATIONAL, 3),
|
||||||
530: "YCbCrSubSampling",
|
530: ("YCbCrSubSampling", SHORT, 2),
|
||||||
531: "YCbCrPositioning",
|
531: ("YCbCrPositioning", SHORT, 1),
|
||||||
532: "ReferenceBlackWhite",
|
532: ("ReferenceBlackWhite", LONG, 0),
|
||||||
|
|
||||||
# XMP
|
33432: ("Copyright", ASCII, 1),
|
||||||
700: "XMP",
|
|
||||||
|
|
||||||
33432: "Copyright",
|
# FIXME add more tags here
|
||||||
|
34665: ("ExifIFD", SHORT, 1),
|
||||||
# various extensions (should check specs for "official" names)
|
34675: ('ICCProfile', 7, 0),
|
||||||
33723: "IptcNaaInfo",
|
34853: ('GPSInfoIFD', 1, 1),
|
||||||
34377: "PhotoshopInfo",
|
|
||||||
|
|
||||||
# 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",
|
|
||||||
|
|
||||||
# MPInfo
|
# MPInfo
|
||||||
45056: "MPFVersion",
|
45056: ("MPFVersion", 7, 1),
|
||||||
45057: "NumberOfImages",
|
45057: ("NumberOfImages", LONG, 1),
|
||||||
45058: "MPEntry",
|
45058: ("MPEntry", 7, 1),
|
||||||
45059: "ImageUIDList",
|
45059: ("ImageUIDList", 7, 0),
|
||||||
45060: "TotalFrames",
|
45060: ("TotalFrames", LONG, 1),
|
||||||
45313: "MPIndividualNum",
|
45313: ("MPIndividualNum", LONG, 1),
|
||||||
45569: "PanOrientation",
|
45569: ("PanOrientation", LONG, 1),
|
||||||
45570: "PanOverlap_H",
|
45570: ("PanOverlap_H", RATIONAL, 1),
|
||||||
45571: "PanOverlap_V",
|
45571: ("PanOverlap_V", RATIONAL, 1),
|
||||||
45572: "BaseViewpointNum",
|
45572: ("BaseViewpointNum", LONG, 1),
|
||||||
45573: "ConvergenceAngle",
|
45573: ("ConvergenceAngle", 10, 1),
|
||||||
45574: "BaselineLength",
|
45574: ("BaselineLength", RATIONAL, 1),
|
||||||
45575: "VerticalDivergence",
|
45575: ("VerticalDivergence", 10, 1),
|
||||||
45576: "AxisDistance_X",
|
45576: ("AxisDistance_X", 10, 1),
|
||||||
45577: "AxisDistance_Y",
|
45577: ("AxisDistance_Y", 10, 1),
|
||||||
45578: "AxisDistance_Z",
|
45578: ("AxisDistance_Z", 10, 1),
|
||||||
45579: "YawAngle",
|
45579: ("YawAngle", 10, 1),
|
||||||
45580: "PitchAngle",
|
45580: ("PitchAngle", 10, 1),
|
||||||
45581: "RollAngle",
|
45581: ("RollAngle", 10, 1),
|
||||||
|
|
||||||
|
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
|
# Adobe DNG
|
||||||
50706: "DNGVersion",
|
50706: 'DNGVersion',
|
||||||
50707: "DNGBackwardVersion",
|
50707: 'DNGBackwardVersion',
|
||||||
50708: "UniqueCameraModel",
|
50708: 'UniqueCameraModel',
|
||||||
50709: "LocalizedCameraModel",
|
50709: 'LocalizedCameraModel',
|
||||||
50710: "CFAPlaneColor",
|
50710: 'CFAPlaneColor',
|
||||||
50711: "CFALayout",
|
50711: 'CFALayout',
|
||||||
50712: "LinearizationTable",
|
50712: 'LinearizationTable',
|
||||||
50713: "BlackLevelRepeatDim",
|
50713: 'BlackLevelRepeatDim',
|
||||||
50714: "BlackLevel",
|
50714: 'BlackLevel',
|
||||||
50715: "BlackLevelDeltaH",
|
50715: 'BlackLevelDeltaH',
|
||||||
50716: "BlackLevelDeltaV",
|
50716: 'BlackLevelDeltaV',
|
||||||
50717: "WhiteLevel",
|
50717: 'WhiteLevel',
|
||||||
50718: "DefaultScale",
|
50718: 'DefaultScale',
|
||||||
50719: "DefaultCropOrigin",
|
50719: 'DefaultCropOrigin',
|
||||||
50720: "DefaultCropSize",
|
50720: 'DefaultCropSize',
|
||||||
50778: "CalibrationIlluminant1",
|
50721: 'ColorMatrix1',
|
||||||
50779: "CalibrationIlluminant2",
|
50722: 'ColorMatrix2',
|
||||||
50721: "ColorMatrix1",
|
50723: 'CameraCalibration1',
|
||||||
50722: "ColorMatrix2",
|
50724: 'CameraCalibration2',
|
||||||
50723: "CameraCalibration1",
|
50725: 'ReductionMatrix1',
|
||||||
50724: "CameraCalibration2",
|
50726: 'ReductionMatrix2',
|
||||||
50725: "ReductionMatrix1",
|
50727: 'AnalogBalance',
|
||||||
50726: "ReductionMatrix2",
|
50728: 'AsShotNeutral',
|
||||||
50727: "AnalogBalance",
|
50729: 'AsShotWhiteXY',
|
||||||
50728: "AsShotNeutral",
|
50730: 'BaselineExposure',
|
||||||
50729: "AsShotWhiteXY",
|
50731: 'BaselineNoise',
|
||||||
50730: "BaselineExposure",
|
50732: 'BaselineSharpness',
|
||||||
50731: "BaselineNoise",
|
50733: 'BayerGreenSplit',
|
||||||
50732: "BaselineSharpness",
|
50734: 'LinearResponseLimit',
|
||||||
50733: "BayerGreenSplit",
|
50735: 'CameraSerialNumber',
|
||||||
50734: "LinearResponseLimit",
|
50736: 'LensInfo',
|
||||||
50735: "CameraSerialNumber",
|
50737: 'ChromaBlurRadius',
|
||||||
50736: "LensInfo",
|
50738: 'AntiAliasStrength',
|
||||||
50737: "ChromaBlurRadius",
|
50740: 'DNGPrivateData',
|
||||||
50738: "AntiAliasStrength",
|
50778: 'CalibrationIlluminant1',
|
||||||
50740: "DNGPrivateData",
|
50779: 'CalibrationIlluminant2',
|
||||||
50741: "MakerNoteSafety",
|
|
||||||
50780: "BestQualityScale",
|
|
||||||
|
|
||||||
# ImageJ
|
|
||||||
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
|
|
||||||
50839: "ImageJMetaData", # private tag registered with Adobe
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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",
|
# was:
|
||||||
2: "ascii",
|
# TYPES = {
|
||||||
3: "short",
|
# 1: "byte",
|
||||||
4: "long",
|
# 2: "ascii",
|
||||||
5: "rational",
|
# 3: "short",
|
||||||
6: "signed byte",
|
# 4: "long",
|
||||||
7: "undefined",
|
# 5: "rational",
|
||||||
8: "signed short",
|
# 6: "signed byte",
|
||||||
9: "signed long",
|
# 7: "undefined",
|
||||||
10: "signed rational",
|
# 8: "signed short",
|
||||||
11: "float",
|
# 9: "signed long",
|
||||||
12: "double",
|
# 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.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
#
|
||||||
# The Python Imaging Library.
|
# The Python Imaging Library.
|
||||||
# $Id$
|
# $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\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"
|
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")
|
|
||||||
|
|
|
@ -73,8 +73,8 @@ def _save(im, fp, filename):
|
||||||
fp.write(data)
|
fp.write(data)
|
||||||
|
|
||||||
|
|
||||||
Image.register_open("WEBP", WebPImageFile, _accept)
|
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
|
||||||
Image.register_save("WEBP", _save)
|
Image.register_save(WebPImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension("WEBP", ".webp")
|
Image.register_extension(WebPImageFile.format, ".webp")
|
||||||
Image.register_mime("WEBP", "image/webp")
|
Image.register_mime(WebPImageFile.format, "image/webp")
|
||||||
|
|
|
@ -15,10 +15,10 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.2"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
if str != bytes:
|
if str != bytes:
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
# FIXME: make save work (this requires quantization support)
|
# FIXME: make save work (this requires quantization support)
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
o8 = _binary.o8
|
o8 = _binary.o8
|
||||||
|
|
||||||
# standard color palette for thumbnails (RGB332)
|
# 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)
|
||||||
|
|
|
@ -19,15 +19,15 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.6"
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
|
|
||||||
|
__version__ = "0.6"
|
||||||
|
|
||||||
# XBM header
|
# XBM header
|
||||||
xbm_head = re.compile(
|
xbm_head = re.compile(
|
||||||
b"\s*#define[ \t]+[^_]*_width[ \t]+(?P<width>[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"#define[ \t]+.*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
|
||||||
b"(?P<hotspot>"
|
b"(?P<hotspot>"
|
||||||
b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
|
b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
|
||||||
b"#define[ \t]+[^_]*_y_hot[ \t]+(?P<yhot>[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")
|
fp.write(b"};\n")
|
||||||
|
|
||||||
|
|
||||||
Image.register_open("XBM", XbmImageFile, _accept)
|
Image.register_open(XbmImageFile.format, XbmImageFile, _accept)
|
||||||
Image.register_save("XBM", _save)
|
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")
|
||||||
|
|
|
@ -15,13 +15,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.2"
|
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from PIL import Image, ImageFile, ImagePalette
|
from PIL import Image, ImageFile, ImagePalette
|
||||||
from PIL._binary import i8, o8
|
from PIL._binary import i8, o8
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
# XPM header
|
# XPM header
|
||||||
xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)")
|
xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)")
|
||||||
|
|
||||||
|
@ -124,8 +123,8 @@ class XpmImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# Registry
|
# 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")
|
||||||
|
|
|
@ -12,16 +12,18 @@
|
||||||
# ;-)
|
# ;-)
|
||||||
|
|
||||||
VERSION = '1.1.7' # PIL version
|
VERSION = '1.1.7' # PIL version
|
||||||
PILLOW_VERSION = '2.9.0.dev0' # Pillow
|
PILLOW_VERSION = '3.3.0.dev0' # Pillow
|
||||||
|
|
||||||
_plugins = ['BmpImagePlugin',
|
_plugins = ['BmpImagePlugin',
|
||||||
'BufrStubImagePlugin',
|
'BufrStubImagePlugin',
|
||||||
'CurImagePlugin',
|
'CurImagePlugin',
|
||||||
'DcxImagePlugin',
|
'DcxImagePlugin',
|
||||||
|
'DdsImagePlugin',
|
||||||
'EpsImagePlugin',
|
'EpsImagePlugin',
|
||||||
'FitsStubImagePlugin',
|
'FitsStubImagePlugin',
|
||||||
'FliImagePlugin',
|
'FliImagePlugin',
|
||||||
'FpxImagePlugin',
|
'FpxImagePlugin',
|
||||||
|
'FtexImagePlugin',
|
||||||
'GbrImagePlugin',
|
'GbrImagePlugin',
|
||||||
'GifImagePlugin',
|
'GifImagePlugin',
|
||||||
'GribStubImagePlugin',
|
'GribStubImagePlugin',
|
||||||
|
|
46
README.rst
46
README.rst
|
@ -4,45 +4,57 @@ Pillow
|
||||||
Python Imaging Library (Fork)
|
Python Imaging Library (Fork)
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
Pillow is the "friendly PIL fork" by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
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://zenodo.org/badge/17549/python-pillow/Pillow.svg
|
||||||
image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
|
: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
|
:target: https://travis-ci.org/python-pillow/Pillow
|
||||||
:alt: Travis CI build status (Linux)
|
:alt: Travis CI build status (Linux)
|
||||||
|
|
||||||
..
|
.. image:: https://img.shields.io/travis/python-pillow/pillow-wheels/latest.svg?label=OS%20X%20build
|
||||||
image:: https://pypip.in/v/Pillow/badge.png
|
:target: https://travis-ci.org/python-pillow/pillow-wheels
|
||||||
|
:alt: Travis CI build status (OS X)
|
||||||
|
|
||||||
|
.. 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://img.shields.io/pypi/v/pillow.svg
|
||||||
:target: https://pypi.python.org/pypi/Pillow/
|
:target: https://pypi.python.org/pypi/Pillow/
|
||||||
:alt: Latest PyPI version
|
:alt: Latest PyPI version
|
||||||
|
|
||||||
..
|
.. image:: https://img.shields.io/pypi/dm/pillow.svg
|
||||||
image:: https://pypip.in/d/Pillow/badge.png
|
|
||||||
:target: https://pypi.python.org/pypi/Pillow/
|
:target: https://pypi.python.org/pypi/Pillow/
|
||||||
:alt: Number of PyPI downloads
|
:alt: Number of PyPI downloads
|
||||||
|
|
||||||
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
|
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github
|
||||||
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
|
:target: https://coveralls.io/github/python-pillow/Pillow?branch=master
|
||||||
:alt: Code coverage
|
: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
|
:target: https://landscape.io/github/python-pillow/Pillow/master
|
||||||
:alt: Code health
|
:alt: Code health
|
||||||
|
|
||||||
|
|
||||||
More Information
|
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>`_
|
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_
|
||||||
|
|
||||||
- `Issues <https://github.com/python-pillow/Pillow/issues>`_
|
- `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>`_
|
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork>`_
|
||||||
- `Guides <http://pillow.readthedocs.org/guides.html>`_
|
|
||||||
- `Installation <http://pillow.readthedocs.org/installation.html>`_
|
|
||||||
- `Reference <http://pillow.readthedocs.org/reference/index.html>`_
|
|
||||||
|
|
24
RELEASING.md
24
RELEASING.md
|
@ -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
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174
|
||||||
* [ ] Develop and prepare release in ``master`` branch.
|
* [ ] 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:
|
* [ ] 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`.
|
* [ ] 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.:
|
* [ ] Create branch and tag for release e.g.:
|
||||||
```
|
```
|
||||||
$ git branch 2.9.x
|
$ 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.:
|
* [ ] Create and upload source distributions e.g.:
|
||||||
```
|
```
|
||||||
$ make sdistup
|
$ make sdist
|
||||||
|
$ make upload
|
||||||
```
|
```
|
||||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
* [ ] 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
|
## 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:
|
* [ ] 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.:
|
* [ ] Create tag for release e.g.:
|
||||||
```
|
```
|
||||||
$ git tag 2.9.1
|
$ 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.
|
* [ ] Commit against master, cherry pick to affected release branches.
|
||||||
* [ ] Run local test matrix on each release & Python version.
|
* [ ] Run local test matrix on each release & Python version.
|
||||||
* [ ] Privately send to distros.
|
* [ ] 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 #
|
* [ ] Amend any commits with the CVE #
|
||||||
* [ ] On release date, tag and push to GitHub.
|
* [ ] 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
|
## Publicize Release
|
||||||
|
|
||||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328.
|
* [ ] 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/
|
||||||
|
|
|
@ -12,7 +12,7 @@ pildriver.py (by Eric S. Raymond)
|
||||||
--------------------------------------------------------------------
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
A class implementing an image-processing calculator for scripts.
|
A class implementing an image-processing calculator for scripts.
|
||||||
Parses lists of commnds (or, called interactively, command-line
|
Parses lists of commands (or, called interactively, command-line
|
||||||
arguments) into image loads, transformations, and saves.
|
arguments) into image loads, transformations, and saves.
|
||||||
|
|
||||||
viewer.py
|
viewer.py
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
|
|
@ -14,120 +14,9 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# 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 __future__ import print_function
|
||||||
|
|
||||||
from PIL import Image, ImageChops
|
from PIL import Image
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
@ -138,4 +27,5 @@ if __name__ == "__main__":
|
||||||
print("Usage: gifmaker infile outfile")
|
print("Usage: gifmaker infile outfile")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
compress(sys.argv[1], sys.argv[2])
|
im = Image.open(sys.argv[1])
|
||||||
|
im.save(sys.argv[2], save_all=True)
|
||||||
|
|
|
@ -68,6 +68,10 @@ class PaintCanvas(Canvas):
|
||||||
|
|
||||||
root = Tk()
|
root = Tk()
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: painter file")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
im = Image.open(sys.argv[1])
|
im = Image.open(sys.argv[1])
|
||||||
|
|
||||||
if im.mode != "RGB":
|
if im.mode != "RGB":
|
||||||
|
|
|
@ -70,7 +70,7 @@ class PILDriver(object):
|
||||||
|
|
||||||
def push(self, item):
|
def push(self, item):
|
||||||
"Push an argument onto the evaluation stack."
|
"Push an argument onto the evaluation stack."
|
||||||
self.stack = [item] + self.stack
|
self.stack.insert(0, item)
|
||||||
|
|
||||||
def top(self):
|
def top(self):
|
||||||
"Return the top-of-stack element."
|
"Return the top-of-stack element."
|
||||||
|
@ -90,9 +90,7 @@ class PILDriver(object):
|
||||||
|
|
||||||
Discard the top element on the stack.
|
Discard the top element on the stack.
|
||||||
"""
|
"""
|
||||||
top = self.stack[0]
|
return self.stack.pop(0)
|
||||||
self.stack = self.stack[1:]
|
|
||||||
return top
|
|
||||||
|
|
||||||
def do_dup(self):
|
def do_dup(self):
|
||||||
"""usage: dup
|
"""usage: dup
|
||||||
|
@ -103,7 +101,7 @@ class PILDriver(object):
|
||||||
dup = self.stack[0].copy()
|
dup = self.stack[0].copy()
|
||||||
else:
|
else:
|
||||||
dup = self.stack[0]
|
dup = self.stack[0]
|
||||||
self.stack = [dup] + self.stack
|
self.push(dup)
|
||||||
|
|
||||||
def do_swap(self):
|
def do_swap(self):
|
||||||
"""usage: swap
|
"""usage: swap
|
||||||
|
@ -152,7 +150,8 @@ class PILDriver(object):
|
||||||
self.push(Image.composite(image1, image2, mask))
|
self.push(Image.composite(image1, image2, mask))
|
||||||
|
|
||||||
def do_merge(self):
|
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.
|
Merge top-of stack images in a way described by the mode.
|
||||||
"""
|
"""
|
||||||
|
@ -181,7 +180,8 @@ class PILDriver(object):
|
||||||
self.dup()
|
self.dup()
|
||||||
|
|
||||||
def do_crop(self):
|
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.
|
Crop and push a rectangular region from the current image.
|
||||||
"""
|
"""
|
||||||
|
@ -243,7 +243,8 @@ class PILDriver(object):
|
||||||
self.push(image.offset(xoff, yoff))
|
self.push(image.offset(xoff, yoff))
|
||||||
|
|
||||||
def do_paste(self):
|
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.
|
Paste figure image into ground with upper left at given offsets.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -21,6 +21,7 @@ from __future__ import print_function
|
||||||
|
|
||||||
import getopt
|
import getopt
|
||||||
import glob
|
import glob
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -42,6 +43,7 @@ except getopt.error as v:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
verbose = quiet = verify = 0
|
verbose = quiet = verify = 0
|
||||||
|
logging_level = "WARNING"
|
||||||
|
|
||||||
for o, a in opt:
|
for o, a in opt:
|
||||||
if o == "-f":
|
if o == "-f":
|
||||||
|
@ -58,7 +60,9 @@ for o, a in opt:
|
||||||
elif o == "-v":
|
elif o == "-v":
|
||||||
verify = 1
|
verify = 1
|
||||||
elif o == "-D":
|
elif o == "-D":
|
||||||
Image.DEBUG += 1
|
logging_level = "DEBUG"
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging_level)
|
||||||
|
|
||||||
|
|
||||||
def globfix(files):
|
def globfix(files):
|
||||||
|
|
|
@ -12,8 +12,6 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
VERSION = "0.4"
|
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -21,6 +19,8 @@ import sys
|
||||||
from PIL import BdfFontFile
|
from PIL import BdfFontFile
|
||||||
from PIL import PcfFontFile
|
from PIL import PcfFontFile
|
||||||
|
|
||||||
|
VERSION = "0.4"
|
||||||
|
|
||||||
if len(sys.argv) <= 1:
|
if len(sys.argv) <= 1:
|
||||||
print("PILFONT", VERSION, "-- PIL font compiler.")
|
print("PILFONT", VERSION, "-- PIL font compiler.")
|
||||||
print()
|
print()
|
||||||
|
|
18
Scripts/pilprint.py
Normal file → Executable file
18
Scripts/pilprint.py
Normal file → Executable file
|
@ -15,6 +15,7 @@ from __future__ import print_function
|
||||||
import getopt
|
import getopt
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
VERSION = "pilprint 0.3/2003-05-05"
|
VERSION = "pilprint 0.3/2003-05-05"
|
||||||
|
|
||||||
|
@ -32,10 +33,11 @@ def description(filepath, image):
|
||||||
return title + format % image.size + image.mode + ")"
|
return title + format % image.size + image.mode + ")"
|
||||||
|
|
||||||
if len(sys.argv) == 1:
|
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("Usage: pilprint files...")
|
||||||
print("Options:")
|
print("Options:")
|
||||||
print(" -c colour printer (default is monochrome)")
|
print(" -c colour printer (default is monochrome)")
|
||||||
|
print(" -d debug (show available drivers)")
|
||||||
print(" -p print via lpr (default is stdout)")
|
print(" -p print via lpr (default is stdout)")
|
||||||
print(" -P <printer> same as -p but use given printer")
|
print(" -P <printer> same as -p but use given printer")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -46,7 +48,7 @@ except getopt.error as v:
|
||||||
print(v)
|
print(v)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
printer = None # print to stdout
|
printerArgs = [] # print to stdout
|
||||||
monochrome = 1 # reduce file size for most common case
|
monochrome = 1 # reduce file size for most common case
|
||||||
|
|
||||||
for o, a in opt:
|
for o, a in opt:
|
||||||
|
@ -60,10 +62,10 @@ for o, a in opt:
|
||||||
monochrome = 0
|
monochrome = 0
|
||||||
elif o == "-p":
|
elif o == "-p":
|
||||||
# default printer channel
|
# default printer channel
|
||||||
printer = "lpr"
|
printerArgs = ["lpr"]
|
||||||
elif o == "-P":
|
elif o == "-P":
|
||||||
# printer channel
|
# printer channel
|
||||||
printer = "lpr -P%s" % a
|
printerArgs = ["lpr", "-P%s" % a]
|
||||||
|
|
||||||
for filepath in argv:
|
for filepath in argv:
|
||||||
try:
|
try:
|
||||||
|
@ -76,8 +78,9 @@ for filepath in argv:
|
||||||
im.draft("L", im.size)
|
im.draft("L", im.size)
|
||||||
im = im.convert("L")
|
im = im.convert("L")
|
||||||
|
|
||||||
if printer:
|
if printerArgs:
|
||||||
fp = os.popen(printer, "w")
|
p = subprocess.Popen(printerArgs, stdin=subprocess.PIPE)
|
||||||
|
fp = p.stdin
|
||||||
else:
|
else:
|
||||||
fp = sys.stdout
|
fp = sys.stdout
|
||||||
|
|
||||||
|
@ -91,6 +94,9 @@ for filepath in argv:
|
||||||
ps.image(letter, im)
|
ps.image(letter, im)
|
||||||
ps.end_document()
|
ps.end_document()
|
||||||
|
|
||||||
|
if printerArgs:
|
||||||
|
fp.close()
|
||||||
|
|
||||||
except:
|
except:
|
||||||
print("cannot print image", end=' ')
|
print("cannot print image", end=' ')
|
||||||
print("(%s:%s)" % (sys.exc_info()[0], sys.exc_info()[1]))
|
print("(%s:%s)" % (sys.exc_info()[0], sys.exc_info()[1]))
|
||||||
|
|
|
@ -15,9 +15,6 @@ from PIL import Image, ImageTk
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
Image.DEBUG = 0
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# an image animation player
|
# an image animation player
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import sys
|
|
||||||
sys.path.insert(0, ".")
|
|
||||||
|
|
||||||
import helper
|
import helper
|
||||||
import timeit
|
import timeit
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, ".")
|
||||||
|
|
||||||
|
|
||||||
def bench(mode):
|
def bench(mode):
|
||||||
im = helper.hopper(mode)
|
im = helper.hopper(mode)
|
||||||
|
|
16
Tests/check_fli_overflow.py
Normal file
16
Tests/check_fli_overflow.py
Normal 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()
|
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
from helper import unittest, PillowTestCase
|
from helper import unittest, PillowTestCase
|
||||||
import sys
|
import sys
|
||||||
from PIL import Image, ImageFilter
|
from PIL import Image
|
||||||
|
|
||||||
min_iterations = 100
|
min_iterations = 100
|
||||||
max_iterations = 10000
|
max_iterations = 10000
|
||||||
|
@ -31,7 +31,8 @@ class TestImagingLeaks(PillowTestCase):
|
||||||
|
|
||||||
def test_leak_putdata(self):
|
def test_leak_putdata(self):
|
||||||
im = Image.new('RGB', (25, 25))
|
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):
|
def test_leak_getlist(self):
|
||||||
im = Image.new('P', (25, 25))
|
im = Image.new('P', (25, 25))
|
||||||
|
|
|
@ -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 \
|
NOSE_PROCESSES=0 NOSE_TIMEOUT=600 valgrind --tool=massif \
|
||||||
python test-installed.py -s -v Tests/check_jpeg_leaks.py
|
python test-installed.py -s -v Tests/check_jpeg_leaks.py
|
||||||
|
@ -105,6 +106,7 @@ post-patch:
|
||||||
test_output = BytesIO()
|
test_output = BytesIO()
|
||||||
im.save(test_output, "JPEG", qtables=qtables)
|
im.save(test_output, "JPEG", qtables=qtables)
|
||||||
|
|
||||||
|
def test_exif_leak(self):
|
||||||
"""
|
"""
|
||||||
pre patch:
|
pre patch:
|
||||||
|
|
||||||
|
@ -160,8 +162,6 @@ post patch:
|
||||||
0 11.33
|
0 11.33
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_exif_leak(self):
|
|
||||||
im = hopper('RGB')
|
im = hopper('RGB')
|
||||||
exif = b'12345678'*4096
|
exif = b'12345678'*4096
|
||||||
|
|
||||||
|
|
23
Tests/check_libtiff_segfault.py
Normal file
23
Tests/check_libtiff_segfault.py
Normal 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()
|
|
@ -42,7 +42,8 @@ class TestPngDos(PillowTestCase):
|
||||||
total_len = 0
|
total_len = 0
|
||||||
for txt in im2.text.values():
|
for txt in im2.text.values():
|
||||||
total_len += len(txt)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -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()
|
|
|
@ -43,11 +43,6 @@ class PillowTestCase(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
print("=== orphaned temp file: %s" % path)
|
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):
|
def assert_deep_equal(self, a, b, msg=None):
|
||||||
try:
|
try:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -234,6 +229,9 @@ def imagemagick_available():
|
||||||
return IMCONVERT and command_succeeds([IMCONVERT, '-version'])
|
return IMCONVERT and command_succeeds([IMCONVERT, '-version'])
|
||||||
|
|
||||||
|
|
||||||
|
def on_appveyor():
|
||||||
|
return 'APPVEYOR' in os.environ
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
IMCONVERT = os.environ.get('MAGICK_HOME', '')
|
IMCONVERT = os.environ.get('MAGICK_HOME', '')
|
||||||
if IMCONVERT:
|
if IMCONVERT:
|
||||||
|
|
BIN
Tests/images/7x13.png
Normal file
BIN
Tests/images/7x13.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 204 B |
12
Tests/images/bad_palette_entry.gpl
Normal file
12
Tests/images/bad_palette_entry.gpl
Normal 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
|
12
Tests/images/bad_palette_file.gpl
Normal file
12
Tests/images/bad_palette_file.gpl
Normal 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
|
BIN
Tests/images/broken_data_stream.png
Normal file
BIN
Tests/images/broken_data_stream.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
Tests/images/compression.tif
Executable file
BIN
Tests/images/compression.tif
Executable file
Binary file not shown.
BIN
Tests/images/copyleft.tiff
Normal file
BIN
Tests/images/copyleft.tiff
Normal file
Binary file not shown.
12
Tests/images/custom_gimp_palette.gpl
Normal file
12
Tests/images/custom_gimp_palette.gpl
Normal 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
|
BIN
Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds
Executable file
BIN
Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds
Executable file
Binary file not shown.
BIN
Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.png
Normal file
BIN
Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.png
Normal file
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
Loading…
Reference in New Issue
Block a user