mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 18:06:18 +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
|
||||
|
||||
Bug fixes, feature additions, tests, documentation and more can be contributed via [issues](https://github.com/python-pillow/Pillow/issues) and/or [pull requests](https://github.com/python-pillow/Pillow/issues). All contributions are welcome.
|
||||
Bug fixes, feature additions, tests, documentation and more can be contributed via [issues](https://github.com/python-pillow/Pillow/issues) and/or [pull requests](https://github.com/python-pillow/Pillow/pulls). All contributions are welcome.
|
||||
|
||||
## Bug fixes, feature additions, etc.
|
||||
|
||||
|
@ -9,7 +9,7 @@ Please send a pull request to the master branch. Please include [documentation](
|
|||
- Fork the Pillow repository.
|
||||
- Create a branch from master.
|
||||
- Develop bug fixes, features, tests, etc.
|
||||
- Run the test suite on both Python 2.x and 3.x. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
|
||||
- Run the test suite on both Python 2.x and 3.x. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
|
||||
- Create a pull request to pull the changes from your branch to the Pillow master.
|
||||
|
||||
### Guidelines
|
||||
|
@ -17,6 +17,7 @@ Please send a pull request to the master branch. Please include [documentation](
|
|||
- Separate code commits from reformatting commits.
|
||||
- Provide tests for any newly added code.
|
||||
- Follow PEP8.
|
||||
- When committing only documentation changes please include [ci skip] in the commit message to avoid running tests on Travis-CI and AppVeyor.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
|
@ -28,3 +29,9 @@ When reporting issues, please include code that reproduces the issue and wheneve
|
|||
- What did you expect to happen?
|
||||
- What actually happened?
|
||||
- What versions of Pillow and Python are you using?
|
||||
|
||||
## Security vulnerabilities
|
||||
|
||||
To report sensitive vulnerability information, email aclark@aclark.net.
|
||||
|
||||
If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method.
|
13
.github/ISSUE_TEMPLATE.md
vendored
Normal file
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
|
||||
test-warnings: yes
|
||||
max-line-length: 80
|
||||
|
|
37
.travis.yml
37
.travis.yml
|
@ -9,21 +9,34 @@ notifications:
|
|||
python:
|
||||
- "pypy"
|
||||
- "pypy3"
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 2.7
|
||||
- 2.6
|
||||
- "2.7_with_system_site_packages" # For PyQt4
|
||||
- 3.2
|
||||
- 3.3
|
||||
- 3.4
|
||||
- nightly
|
||||
|
||||
install:
|
||||
- "travis_retry sudo apt-get update"
|
||||
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
|
||||
- "travis_retry pip install cffi"
|
||||
- "travis_retry pip install coverage nose"
|
||||
- "travis_retry pip install pyroma"
|
||||
- "travis_retry pip install nose"
|
||||
- "travis_retry pip install check-manifest"
|
||||
- "travis_retry pip install Sphinx"
|
||||
# Pyroma tests sometimes hang on PyPy and Python 2.6; skip for those
|
||||
- if [ $TRAVIS_PYTHON_VERSION != "pypy" && $TRAVIS_PYTHON_VERSION != "2.6" ]; then travis_retry pip install pyroma; fi
|
||||
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
|
||||
|
||||
# Coverage 4.0 doesn't support Python 3.2
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" == "3.2" ]; then travis_retry pip install coverage==3.7.1; fi
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" != "3.2" ]; then travis_retry pip install coverage; fi
|
||||
|
||||
# clean checkout for manifest
|
||||
- mkdir /tmp/check-manifest && cp -a . /tmp/check-manifest
|
||||
|
||||
# webp
|
||||
- pushd depends && ./install_webp.sh && popd
|
||||
|
||||
|
@ -31,12 +44,16 @@ install:
|
|||
- pushd depends && ./install_openjpeg.sh && popd
|
||||
|
||||
script:
|
||||
- coverage erase
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage erase; fi
|
||||
- python setup.py clean
|
||||
- CFLAGS="-coverage" python setup.py build_ext --inplace
|
||||
|
||||
- coverage run --append --include=PIL/* selftest.py
|
||||
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage run --append --include=PIL/* selftest.py; fi
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py; fi
|
||||
- pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd
|
||||
# Sphinx
|
||||
- make install
|
||||
- pushd docs; make html; make linkcheck; popd
|
||||
|
||||
after_success:
|
||||
# gather the coverage data
|
||||
|
@ -54,15 +71,15 @@ after_success:
|
|||
|
||||
- travis_retry pip install pep8 pyflakes
|
||||
- pep8 --statistics --count PIL/*.py
|
||||
- pep8 --statistics --count Tests/*.py
|
||||
- pep8 --statistics --count Tests/*.py
|
||||
- pyflakes *.py | tee >(wc -l)
|
||||
- pyflakes PIL/*.py | tee >(wc -l)
|
||||
- pyflakes Tests/*.py | tee >(wc -l)
|
||||
|
||||
# Coverage and quality reports on just the latest diff.
|
||||
# (Installation is very slow on Py3, so just do it for Py2.)
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-install.sh; fi
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-run.sh; fi
|
||||
|
||||
# after_all
|
||||
- |
|
||||
|
@ -104,6 +121,8 @@ after_script:
|
|||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- python: nightly
|
||||
|
||||
env:
|
||||
global:
|
||||
|
|
490
CHANGES.rst
490
CHANGES.rst
|
@ -1,13 +1,480 @@
|
|||
Changelog (Pillow)
|
||||
==================
|
||||
|
||||
2.9.0 (Unreleased)
|
||||
3.3.0 (unreleased)
|
||||
------------------
|
||||
|
||||
- Added Support for 2/4 bpp Tiff Grayscale Images #1789
|
||||
[zwhfly]
|
||||
|
||||
- Removed unused variable from selftest #1788
|
||||
[radarhere]
|
||||
|
||||
- Added warning for as_dict method (deprecated in 3.0.0) #1799
|
||||
[radarhere]
|
||||
|
||||
- Removed powf support for older Python versions #1784
|
||||
[radarhere]
|
||||
|
||||
- Health fixes #1625
|
||||
[radarhere]
|
||||
|
||||
3.2.0 (2016-04-01)
|
||||
------------------
|
||||
|
||||
- Added install docs for Fedora 23 and FreeBSD #1729, #1739, #1792
|
||||
[koobs, zandermartin, wiredfool]
|
||||
|
||||
- Fixed TIFF multiframe load when the frames have different compression types #1782
|
||||
[radarhere, geka000]
|
||||
|
||||
- Added __copy__ method to Image #1772
|
||||
[radarhere]
|
||||
|
||||
- Updated dates in PIL license in OleFileIO README #1787
|
||||
[radarhere]
|
||||
|
||||
- Corrected Tiff tag names #1786
|
||||
[radarhere]
|
||||
|
||||
- Fixed documented name of JPEG property #1783
|
||||
[radarhere]
|
||||
|
||||
- Fixed UnboundLocalErrorwhen loading a corrupt jpeg2k file #1780
|
||||
[wiredfool]
|
||||
|
||||
- Fixed integer overflow in path.c #1773
|
||||
[wiredfool, nedwill]
|
||||
|
||||
- Added debug to command line help text for pilprint #1766
|
||||
[radarhere]
|
||||
|
||||
- Expose many more fields in ICC Profiles #1756
|
||||
[lambdafu]
|
||||
|
||||
- Documentation changes, URL update, transpose, release checklist
|
||||
[radarhere]
|
||||
|
||||
- Fixed saving to nonexistant files specified by pathlib.Path objects, fixes #1747
|
||||
[radarhere]
|
||||
|
||||
- Round Image.crop arguments to the nearest integer, fixes #1744
|
||||
[hugovk]
|
||||
|
||||
- Fix uninitialized variable warning in _imaging.c:getink, fixes #486
|
||||
[wiredfool]
|
||||
|
||||
- Disable multiprocessing install on cygwin, fixes #1690
|
||||
[wiredfool]
|
||||
|
||||
- Fix the error reported when libz is not found #1764
|
||||
[wiredfool]
|
||||
|
||||
- More general error check to avoid Symbol not found: _PyUnicodeUCS2_AsLatin1String on OS X #1761
|
||||
[wiredfool]
|
||||
|
||||
- Added py35 to tox envlist #1724
|
||||
[radarhere]
|
||||
|
||||
- Fix EXIF tag name typos #1736
|
||||
[zarlant, radarhere]
|
||||
|
||||
- Updated freetype to 2.6.3, Tk/Tcl to 8.6.5 and 8.5.19
|
||||
[radarhere]
|
||||
|
||||
- Add a loader for the FTEX format from Independence War 2: Edge of Chaos #1688
|
||||
[jleclanche]
|
||||
|
||||
- Improved alpha_composite documentation #1698
|
||||
[radarhere]
|
||||
|
||||
- Extend ImageDraw.text method to pass on multiline_text method specific arguments #1647
|
||||
[radarhere]
|
||||
|
||||
- Allow ImageSequence to seek to zero #1686
|
||||
[radarhere]
|
||||
|
||||
- ImageSequence Iterator is now an iterator #1649
|
||||
[radarhere]
|
||||
|
||||
- Updated windows test builds to jpeg9b
|
||||
[radarhere]
|
||||
|
||||
- Fixed support for .gbr version 1 images, added support for version 2 in GbrImagePlugin #1653
|
||||
[wiredfool]
|
||||
|
||||
- Clarified which YCbCr format is used #1677
|
||||
[radarhere]
|
||||
|
||||
- Added TiffTags documentation, Moved windows build documentation to winbuild/ #1667
|
||||
[wiredfool]
|
||||
|
||||
- Add tests for OLE file based formats #1678
|
||||
[radarhere]
|
||||
|
||||
- Add TIFF IFD test #1671
|
||||
[radarhere]
|
||||
|
||||
- Add a basic DDS image plugin with more tests #1654
|
||||
[jleclanche, hugovk, wiredfool]
|
||||
|
||||
- Fix incorrect conditional in encode.c #1638
|
||||
[manisandro]
|
||||
|
||||
|
||||
3.1.2 (2016-04-01)
|
||||
------------------
|
||||
|
||||
- Fixed an integer overflow in Jpeg2KEncode.c causing a buffer overflow. CVE-2016-3076
|
||||
[wiredfool]
|
||||
|
||||
3.1.1 (2016-02-04)
|
||||
------------------
|
||||
|
||||
- Fixed an integer overflow in Resample.c causing writes in the Python heap.
|
||||
[nedwill]
|
||||
|
||||
- Fixed a buffer overflow in PcdDecode.c causing a segfault when opening PhotoCD files. CVE-2016-2533
|
||||
[wiredfool]
|
||||
|
||||
- Fixed a buffer overflow in FliDecode.c causing a segfault when opening FLI files. CVE-2016-0775
|
||||
[wiredfool]
|
||||
|
||||
- Fixed a buffer overflow in TiffDecode.c causing an arbitrary amount of memory to be overwritten when opening a specially crafted invalid TIFF file. CVE-2016-0740
|
||||
[wiredfool]
|
||||
|
||||
|
||||
3.1.0 (2016-01-04)
|
||||
------------------
|
||||
|
||||
- Fixing test failures on Python 2.6/Windows #1633
|
||||
[wiredfool]
|
||||
|
||||
- Limit metadata tags when writing using libtiff #1620
|
||||
[wiredfool]
|
||||
|
||||
- Rolling back exif support to pre-3.0 format #1627
|
||||
[wiredfool]
|
||||
|
||||
- Fix Divide by zero in Exif, add IFDRational class #1531
|
||||
[wiredfool]
|
||||
|
||||
- Catch the IFD error near the source #1622
|
||||
[wiredfool]
|
||||
|
||||
- Added release notes for 3.1.0 #1623
|
||||
[radarhere]
|
||||
|
||||
- Updated spacing to be consistent between multiline methods #1624
|
||||
[radarhere]
|
||||
|
||||
- Let EditorConfig take care of some basic formatting #1489
|
||||
[hugovk]
|
||||
|
||||
- Restore gpsexif data to the v1 form
|
||||
[wiredfool]
|
||||
|
||||
- Add /usr/local include and library directories for freebsd #1613
|
||||
[leforestier]
|
||||
|
||||
- Updated installation docs for new versions of dependencies #1611
|
||||
[radarhere]
|
||||
|
||||
- Removed unrunnable test file #1610
|
||||
[radarhere]
|
||||
|
||||
- Changed register calls to use format property #1608
|
||||
[radarhere]
|
||||
|
||||
- Added field type constants to TiffTags #1596
|
||||
[radarhere]
|
||||
|
||||
- Allow saving RowsPerStrip with libtiff #1594
|
||||
[wiredfool]
|
||||
|
||||
- Enabled conversion to numpy array for HSV images #1578
|
||||
[cartisan]
|
||||
|
||||
- Changed some urls in the docs to use https #1580
|
||||
[hugovk]
|
||||
|
||||
- Removed logger.exception from ImageFile.py #1590
|
||||
[radarhere]
|
||||
|
||||
- Removed warnings module check #1587
|
||||
[radarhere]
|
||||
|
||||
- Changed arcs, chords and pie slices to use floats #1577
|
||||
[radarhere]
|
||||
|
||||
- Update unit test asserts #1584, #1598
|
||||
[radarhere]
|
||||
|
||||
- Fix command to invoke ghostscript for eps files #1478
|
||||
[baumatron, radarhere]
|
||||
|
||||
- Consistent multiline text spacing #1574
|
||||
[wiredfool, hugovk]
|
||||
|
||||
- Removed unused lines in BDFFontFile #1530
|
||||
[radarhere]
|
||||
|
||||
- Changed ImageQt import of Image #1560
|
||||
[radarhere, ericfrederich]
|
||||
|
||||
- Throw TypeError if no cursors were found in .cur file #1556
|
||||
[radarhere]
|
||||
|
||||
- Fix crash in ImageTk.PhotoImage on win-amd64 #1553
|
||||
[cgohlke]
|
||||
|
||||
- ExtraSamples tag should be a SHORT, not a BYTE #1555
|
||||
[Nexuapex]
|
||||
|
||||
- Docs and code health fixes #1565 #1566 #1581 #1586 #1591 #1621
|
||||
[radarhere]
|
||||
|
||||
- Updated freetype to 2.6.2 #1564
|
||||
[radarhere]
|
||||
|
||||
- Updated WebP to 0.5.0 for Travis #1515 #1609
|
||||
[radarhere]
|
||||
|
||||
- Fix missing 'version' key value in __array_interface__ #1519
|
||||
[mattip]
|
||||
|
||||
- Replaced os.popen with subprocess.Popen to pilprint script #1523
|
||||
[radarhere]
|
||||
|
||||
- Catch OverflowError in SpiderImagePlugin #1545
|
||||
[radarhere, MrShark]
|
||||
|
||||
- Fix the definition of icc_profile in TiffTags #1539
|
||||
[wiredfool]
|
||||
|
||||
- Remove old _imagingtiff.c and pilplus stuff #1499
|
||||
[hugovk]
|
||||
|
||||
- Fix Exception when requiring jpeg #1501
|
||||
[hansmosh]
|
||||
|
||||
- Dependency scripts for Debian and Ubuntu #1486
|
||||
[wiredfool]
|
||||
|
||||
- Added Usage message to painter script #1482
|
||||
[radarhere]
|
||||
|
||||
- Add tag info for iccprofile, fixes #1462. #1465
|
||||
[wiredfool]
|
||||
|
||||
- Added some requirements for make release-test #1451
|
||||
[wiredfool]
|
||||
|
||||
- Flatten tiff metadata value SAMPLEFORMAT to initial value, fixes #1466
|
||||
[wiredfool]
|
||||
|
||||
- Fix handling of pathlib in Image.save. Fixes #1460
|
||||
[wiredfool]
|
||||
|
||||
- Make tests more robust #1469
|
||||
[hugovk]
|
||||
|
||||
- Use correctly sized pointers for windows handle types. #1458
|
||||
[nu744]
|
||||
|
||||
3.0.0 (2015-10-01)
|
||||
------------------
|
||||
|
||||
- Check flush method existence for file-like object #1398
|
||||
[mrTable, radarhere]
|
||||
|
||||
- Added PDF multipage saving #1445
|
||||
[radarhere]
|
||||
|
||||
- Removed deprecated code, Image.tostring, Image.fromstring, Image.offset, ImageDraw.setink, ImageDraw.setfill, ImageFileIO, ImageFont.FreeTypeFont and ImageFont.truetype `file` kwarg, ImagePalette private _make functions, ImageWin.fromstring and ImageWin.tostring #1343
|
||||
[radarhere]
|
||||
|
||||
- Load more broken images #1428
|
||||
[homm]
|
||||
|
||||
- Require zlib and libjpeg #1439
|
||||
[wiredfool]
|
||||
|
||||
- Preserve alpha when converting from a QImage to a Pillow Image by using png instead of ppm #1429
|
||||
[ericfrederich]
|
||||
|
||||
- Qt needs 32 bit aligned image data #1430
|
||||
[ericfrederich]
|
||||
|
||||
- Tiff ImageFileDirectory rewrite #1419
|
||||
[anntzer, wiredfool, homm]
|
||||
|
||||
- Removed spammy debug logging #1423
|
||||
[wiredfool]
|
||||
|
||||
- Save as GiF89a with support for animation parameters #1384
|
||||
[radarhere]
|
||||
|
||||
- Correct convert matrix docs #1426
|
||||
[wiredfool]
|
||||
|
||||
- Catch TypeError in _getexif #1414
|
||||
[radarhere, wiredfool]
|
||||
|
||||
- Fix for UnicodeDecodeError in TiffImagePlugin #1416
|
||||
[bogdan199, wiredfool]
|
||||
|
||||
- Dedup code in image.open #1415
|
||||
[wiredfool]
|
||||
|
||||
- Skip any number extraneous chars at the end of JPEG chunks #1337
|
||||
[homm]
|
||||
|
||||
- Single threaded build for pypy3, refactor #1413
|
||||
[wiredfool]
|
||||
|
||||
- Fix loading of truncated images with LOAD_TRUNCATED_IMAGES enabled #1366
|
||||
[homm]
|
||||
|
||||
- Documentation update for concepts: bands
|
||||
[merriam]
|
||||
|
||||
- Add Solaris/SmartOS include and library directories #1356
|
||||
[njones11]
|
||||
|
||||
- Improved handling of getink color #1387
|
||||
[radarhere]
|
||||
|
||||
- Disable compiler optimizations for topalette and tobilevel functions for all msvc versions, fixes #1357
|
||||
[cgohlke]
|
||||
|
||||
- Skip ImageFont_bitmap test if _imagingft C module is not installed #1409
|
||||
[homm]
|
||||
|
||||
- Add param documentation to ImagePalette #1381
|
||||
[bwrsandman]
|
||||
|
||||
- Corrected scripts path #1407
|
||||
[radarhere]
|
||||
|
||||
- Updated libtiff to 4.0.6 #1405, #1421
|
||||
[radarhere]
|
||||
|
||||
- Updated Platform Support for Yosemite #1403
|
||||
[radarhere]
|
||||
|
||||
- Fixed infinite loop on truncated file #1401
|
||||
[radarhere]
|
||||
|
||||
- Check that images are L mode in ImageMorph methods #1400
|
||||
[radarhere]
|
||||
|
||||
- In tutorial of pasting images, add to mask text #1389
|
||||
[merriam]
|
||||
|
||||
- Style/health fixes #1391, #1397, #1417, #1418
|
||||
[radarhere]
|
||||
|
||||
- Test on Python 3.5 dev and 3.6 nightly #1361
|
||||
[hugovk]
|
||||
|
||||
- Fix fast rotate operations #1373
|
||||
[radarhere]
|
||||
|
||||
- Added support for pathlib Path objects to open and save #1372
|
||||
[radarhere]
|
||||
|
||||
- Changed register calls to use format property #1333
|
||||
[radarhere]
|
||||
|
||||
- Added support for ImageGrab.grab to OS X #1367, #1443
|
||||
[radarhere, hugovk]
|
||||
|
||||
- Fixed PSDraw stdout Python 3 compatibility #1365
|
||||
[radarhere]
|
||||
|
||||
- Added Python 3.3 to AppVeyor #1363
|
||||
[radarhere]
|
||||
|
||||
- Treat MPO with unknown header as base JPEG file #1350
|
||||
[hugovk, radarhere]
|
||||
|
||||
- Added various tests #1330, #1344
|
||||
[radarhere]
|
||||
|
||||
- More ImageFont tests #1327
|
||||
[hugovk]
|
||||
|
||||
- Use logging instead of print #1207
|
||||
[anntzer]
|
||||
|
||||
2.9.0 (2015-07-01)
|
||||
------------------
|
||||
|
||||
- Added test for GimpPaletteFile #1324
|
||||
[radarhere]
|
||||
|
||||
- Merged gifmaker script to allow saving of multi-frame GIF images #1320
|
||||
[radarhere]
|
||||
|
||||
- Added is_animated property to multi-frame formats #1319
|
||||
[radarhere]
|
||||
|
||||
- Fixed ValueError in Python 2.6 #1315 #1316
|
||||
[cgohlke, radarhere]
|
||||
|
||||
- Fixed tox test script path #1308
|
||||
[radarhere]
|
||||
|
||||
- Added width and height properties #1304
|
||||
[radarhere]
|
||||
|
||||
- Update tiff and tk tcl 8.5 versions #1303
|
||||
[radarhere, wiredfool]
|
||||
|
||||
- Add functions to convert: Image <-> QImage; Image <-> QPixmap #1217
|
||||
[radarhere, rominf]
|
||||
|
||||
- Remove duplicate code in gifmaker script #1294
|
||||
[radarhere]
|
||||
|
||||
- Multiline text in ImageDraw #1177
|
||||
[allo-, radarhere]
|
||||
|
||||
- Automated Windows CI/build support #1278
|
||||
[wiredfool]
|
||||
|
||||
- Removed support for Tk versions earlier than 8.4 #1288
|
||||
[radarhere]
|
||||
|
||||
- Fixed polygon edge drawing #1255 (fixes #1252)
|
||||
[radarhere]
|
||||
|
||||
- Check prefix length in _accept methods #1267
|
||||
[radarhere]
|
||||
|
||||
- Register MIME type for BMP #1277
|
||||
[coldmind]
|
||||
|
||||
- Adjusted ImageQt use of unicode() for 2/3 compatibility #1218
|
||||
[radarhere]
|
||||
|
||||
- Identify XBM file created with filename including underscore #1230 (fixes #1229)
|
||||
[hugovk]
|
||||
|
||||
- Copy image when saving in GifImagePlugin #1231 (fixes #718)
|
||||
[radarhere]
|
||||
|
||||
- Removed support for FreeType 2.0 #1247
|
||||
[radarhere]
|
||||
|
||||
- Added background saving to GifImagePlugin #1273
|
||||
[radarhere]
|
||||
|
||||
- Provide n_frames attribute to multi-frame formats #1261
|
||||
[anntzer, radarhere]
|
||||
|
||||
- Add duration and loop set to GifImagePlugin #1172
|
||||
- Add duration and loop set to GifImagePlugin #1172, #1269
|
||||
[radarhere]
|
||||
|
||||
- Ico files are little endian #1232
|
||||
|
@ -121,7 +588,7 @@ Changelog (Pillow)
|
|||
- Split Sane into a separate repo: https://github.com/python-pillow/Sane
|
||||
[hugovk]
|
||||
|
||||
- Look for OSX and Linux fonts in common places. #1054
|
||||
- Look for OS X and Linux fonts in common places. #1054
|
||||
[charleslaw]
|
||||
|
||||
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
|
||||
|
@ -729,7 +1196,7 @@ Changelog (Pillow)
|
|||
- Fix #328: _imagingcms.c: include windef.h to fix build issue on MSVC
|
||||
[nu774]
|
||||
|
||||
- Automatically discover homebrew include/ and lib/ paths on OSX
|
||||
- Automatically discover homebrew include/ and lib/ paths on OS X
|
||||
[donspaulding]
|
||||
|
||||
- Fix bytes which should be bytearray
|
||||
|
@ -861,7 +1328,7 @@ Changelog (Pillow)
|
|||
|
||||
- Use PyCapsule for py3.1, fixes #237.
|
||||
|
||||
- Workaround for: http://bugs.python.org/16754 in 3.2.x < 3.2.4 and 3.3.0.
|
||||
- Workaround for: http://bugs.python.org/issue16754 in 3.2.x < 3.2.4 and 3.3.0.
|
||||
|
||||
2.0.0 (2013-03-15)
|
||||
------------------
|
||||
|
@ -884,7 +1351,7 @@ Changelog (Pillow)
|
|||
- Backport PIL's PNG/Zip improvements.
|
||||
[olt]
|
||||
|
||||
- Various 64 bit and Windows fixes.
|
||||
- Various 64-bit and Windows fixes.
|
||||
[cgohlke]
|
||||
|
||||
- Add testing suite.
|
||||
|
@ -1013,10 +1480,11 @@ Changelog (Pillow)
|
|||
- Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
|
||||
[aclark4life]
|
||||
|
||||
.. Note:: What follows is the original PIL 1.1.7 CHANGES
|
||||
Pre-fork
|
||||
--------
|
||||
|
||||
0.2b5 - 1.1.7 (1995-2010)
|
||||
-------------------------
|
||||
0.2b5-1.1.7
|
||||
+++++++++++
|
||||
|
||||
::
|
||||
|
||||
|
@ -1055,7 +1523,7 @@ Changelog (Pillow)
|
|||
This section may not be fully complete. For changes since this file
|
||||
was last updated, see the repository revision history:
|
||||
|
||||
http://bitbucket.org/effbot/pil-2009-raclette/changesets/
|
||||
https://bitbucket.org/effbot/pil-2009-raclette/commits/all
|
||||
|
||||
(1.1.7 final)
|
||||
|
||||
|
@ -2028,7 +2496,7 @@ Changelog (Pillow)
|
|||
+ Added experimental "RGBa" mode support.
|
||||
|
||||
An "RGBa" image is an RGBA image where the colour components
|
||||
have have been premultipled with the alpha value. PIL allows
|
||||
have have been premultiplied with the alpha value. PIL allows
|
||||
you to convert an RGBA image to an RGBa image, and to paste
|
||||
RGBa images on top of RGB images. Since this saves a bunch
|
||||
of multiplications and shifts, it is typically about twice
|
||||
|
|
6
LICENSE
6
LICENSE
|
@ -3,6 +3,12 @@ The Python Imaging Library (PIL) is
|
|||
Copyright © 1997-2011 by Secret Labs AB
|
||||
Copyright © 1995-2011 by Fredrik Lundh
|
||||
|
||||
Pillow is the friendly PIL fork. It is
|
||||
|
||||
Copyright © 2016 by Alex Clark and contributors
|
||||
|
||||
Like PIL, Pillow is licensed under the MIT-like open source PIL Software License:
|
||||
|
||||
By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions:
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission.
|
||||
|
|
95
MANIFEST.in
95
MANIFEST.in
|
@ -1,80 +1,31 @@
|
|||
|
||||
include *.c
|
||||
include *.h
|
||||
include *.in
|
||||
include *.md
|
||||
include *.py
|
||||
include *.sh
|
||||
include *.rst
|
||||
include *.sh
|
||||
include *.txt
|
||||
include *.yaml
|
||||
include .coveragerc
|
||||
include .gitattributes
|
||||
include .travis.yml
|
||||
include LICENSE
|
||||
include Makefile
|
||||
include tox.ini
|
||||
recursive-include PIL *.md
|
||||
recursive-include Scripts *.py
|
||||
recursive-include Scripts *.rst
|
||||
recursive-include Scripts *.sh
|
||||
recursive-include Scripts README.rst
|
||||
recursive-include Tests *.bdf
|
||||
recursive-include Tests *.bin
|
||||
recursive-include Tests *.bmp
|
||||
recursive-include Tests *.bw
|
||||
recursive-include Tests *.cur
|
||||
recursive-include Tests *.dcx
|
||||
recursive-include Tests *.doc
|
||||
recursive-include Tests *.eps
|
||||
recursive-include Tests *.fli
|
||||
recursive-include Tests *.ggr
|
||||
recursive-include Tests *.gif
|
||||
recursive-include Tests *.gnuplot
|
||||
recursive-include Tests *.html
|
||||
recursive-include Tests *.icc
|
||||
recursive-include Tests *.icns
|
||||
recursive-include Tests *.ico
|
||||
recursive-include Tests *.j2k
|
||||
recursive-include Tests *.jp2
|
||||
recursive-include Tests *.jpg
|
||||
recursive-include Tests *.lut
|
||||
recursive-include Tests *.mpo
|
||||
recursive-include Tests *.pbm
|
||||
recursive-include Tests *.pcf
|
||||
recursive-include Tests *.pcx
|
||||
recursive-include Tests *.pgm
|
||||
recursive-include Tests *.pil
|
||||
recursive-include Tests *.png
|
||||
recursive-include Tests *.ppm
|
||||
recursive-include Tests *.psd
|
||||
recursive-include Tests *.py
|
||||
recursive-include Tests *.ras
|
||||
recursive-include Tests *.rgb
|
||||
recursive-include Tests *.rst
|
||||
recursive-include Tests *.sgi
|
||||
recursive-include Tests *.spider
|
||||
recursive-include Tests *.tar
|
||||
recursive-include Tests *.tga
|
||||
recursive-include Tests *.tif
|
||||
recursive-include Tests *.tiff
|
||||
recursive-include Tests *.ttf
|
||||
recursive-include Tests *.txt
|
||||
recursive-include Tests *.webp
|
||||
recursive-include Tests *.xpm
|
||||
recursive-include Tests *.msp
|
||||
recursive-include Tk *.c
|
||||
recursive-include Tk *.rst
|
||||
recursive-include depends *.rst
|
||||
recursive-include depends *.sh
|
||||
recursive-include docs *.bat
|
||||
recursive-include docs *.gitignore
|
||||
recursive-include docs *.html
|
||||
recursive-include docs *.py
|
||||
recursive-include docs *.rst
|
||||
recursive-include docs *.txt
|
||||
recursive-include docs BUILDME
|
||||
recursive-include docs COPYING
|
||||
recursive-include docs Guardfile
|
||||
recursive-include docs LICENSE
|
||||
recursive-include docs Makefile
|
||||
recursive-include libImaging *.c
|
||||
recursive-include libImaging *.h
|
||||
graft Scripts
|
||||
graft Tests
|
||||
graft PIL
|
||||
graft Tk
|
||||
graft libImaging
|
||||
graft depends
|
||||
graft winbuild
|
||||
graft docs
|
||||
prune docs/_static
|
||||
|
||||
# build/src control detritus
|
||||
exclude .coveragerc
|
||||
exclude .editorconfig
|
||||
exclude .landscape.yaml
|
||||
exclude appveyor.yml
|
||||
exclude build_children.sh
|
||||
exclude tox.ini
|
||||
global-exclude .git*
|
||||
global-exclude *.pyc
|
||||
global-exclude *.so
|
||||
|
|
15
Makefile
15
Makefile
|
@ -1,5 +1,6 @@
|
|||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||
.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test
|
||||
.DEFAULT_GOAL := release-test
|
||||
|
||||
clean:
|
||||
python setup.py clean
|
||||
|
@ -7,6 +8,12 @@ clean:
|
|||
rm -r build || true
|
||||
find . -name __pycache__ | xargs rm -r || true
|
||||
|
||||
BRANCHES=`git branch -a | grep -v HEAD | grep -v master | grep remote`
|
||||
co:
|
||||
-for i in $(BRANCHES) ; do \
|
||||
git checkout -t $$i ; \
|
||||
done
|
||||
|
||||
coverage:
|
||||
coverage erase
|
||||
coverage run --parallel-mode --include=PIL/* selftest.py
|
||||
|
@ -32,6 +39,7 @@ help:
|
|||
@echo " inplace make inplace extension"
|
||||
@echo " install make and install"
|
||||
@echo " install-req install documentation and test dependencies"
|
||||
@echo " install-venv install in virtualenv"
|
||||
@echo " release-test run code and package tests before release"
|
||||
@echo " test run tests on installed pillow"
|
||||
@echo " upload build and upload sdists to PyPI"
|
||||
|
@ -47,6 +55,10 @@ install:
|
|||
install-req:
|
||||
pip install -r requirements.txt
|
||||
|
||||
install-venv:
|
||||
virtualenv .
|
||||
bin/pip install -r requirements.txt
|
||||
|
||||
release-test:
|
||||
$(MAKE) install-req
|
||||
python setup.py develop
|
||||
|
@ -74,3 +86,6 @@ upload-test:
|
|||
|
||||
upload:
|
||||
python setup.py sdist --format=gztar,zip upload
|
||||
|
||||
readme:
|
||||
viewdoc
|
||||
|
|
|
@ -109,10 +109,10 @@ class BdfFontFile(FontFile.FontFile):
|
|||
if s.find(b"LogicalFontDescription") < 0:
|
||||
comments.append(s[i+1:-1].decode('ascii'))
|
||||
|
||||
font = props["FONT"].split("-")
|
||||
# font = props["FONT"].split("-")
|
||||
|
||||
font[4] = bdf_slant[font[4].upper()]
|
||||
font[11] = bdf_spacing[font[11].upper()]
|
||||
# font[4] = bdf_slant[font[4].upper()]
|
||||
# font[11] = bdf_spacing[font[11].upper()]
|
||||
|
||||
# ascent = int(props["FONT_ASCENT"])
|
||||
# descent = int(props["FONT_DESCENT"])
|
||||
|
@ -123,7 +123,6 @@ class BdfFontFile(FontFile.FontFile):
|
|||
# for i in comments:
|
||||
# print "#", i
|
||||
|
||||
font = []
|
||||
while True:
|
||||
c = bdf_char(fp)
|
||||
if not c:
|
||||
|
|
|
@ -24,12 +24,10 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
import math
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
|
@ -103,7 +101,9 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))
|
||||
file_info['colors'] = i32(header_data[28:32])
|
||||
file_info['palette_padding'] = 4
|
||||
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), file_info['pixels_per_meter']))
|
||||
self.info["dpi"] = tuple(
|
||||
map(lambda x: int(math.ceil(x / 39.3701)),
|
||||
file_info['pixels_per_meter']))
|
||||
if file_info['compression'] == self.BITFIELDS:
|
||||
if len(header_data) >= 52:
|
||||
for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
|
||||
|
@ -130,13 +130,18 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
# ----------------- Process BMP with Bitfields compression (not palette)
|
||||
if file_info['compression'] == self.BITFIELDS:
|
||||
SUPPORTED = {
|
||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
|
||||
24: [(0xff0000, 0xff00, 0xff)],
|
||||
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]}
|
||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
|
||||
24: [(0xff0000, 0xff00, 0xff)],
|
||||
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
|
||||
}
|
||||
MASK_MODES = {
|
||||
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
||||
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
||||
(16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"}
|
||||
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
|
||||
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
|
||||
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
||||
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
||||
(16, (0xf800, 0x7e0, 0x1f)): "BGR;16",
|
||||
(16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"
|
||||
}
|
||||
if file_info['bits'] in SUPPORTED:
|
||||
if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
|
||||
raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])]
|
||||
|
@ -279,3 +284,5 @@ Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
|
|||
Image.register_save(BmpImageFile.format, _save)
|
||||
|
||||
Image.register_extension(BmpImageFile.format, ".bmp")
|
||||
|
||||
Image.register_mime(BmpImageFile.format, "image/bmp")
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
from PIL import Image, BmpImagePlugin, _binary
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -67,6 +66,8 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
|||
# print "hotspot y", i16(s[6:])
|
||||
# print "bytes", i32(s[8:])
|
||||
# print "offset", i32(s[12:])
|
||||
if not m:
|
||||
raise TypeError("No cursors were found")
|
||||
|
||||
# load as bitmap
|
||||
self._bitmap(i32(m[12:]) + offset)
|
||||
|
@ -82,6 +83,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("CUR", CurImageFile, _accept)
|
||||
Image.register_open(CurImageFile.format, CurImageFile, _accept)
|
||||
|
||||
Image.register_extension("CUR", ".cur")
|
||||
Image.register_extension(CurImageFile.format, ".cur")
|
||||
|
|
|
@ -21,19 +21,18 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
from PIL import Image, _binary
|
||||
|
||||
from PIL.PcxImagePlugin import PcxImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
||||
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return i32(prefix) == MAGIC
|
||||
return len(prefix) >= 4 and i32(prefix) == MAGIC
|
||||
|
||||
|
||||
##
|
||||
|
@ -66,6 +65,10 @@ class DcxImageFile(PcxImageFile):
|
|||
def n_frames(self):
|
||||
return len(self._offset)
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
return len(self._offset) > 1
|
||||
|
||||
def seek(self, frame):
|
||||
if frame >= len(self._offset):
|
||||
raise EOFError("attempt to seek outside DCX directory")
|
||||
|
@ -78,6 +81,6 @@ class DcxImageFile(PcxImageFile):
|
|||
return self.frame
|
||||
|
||||
|
||||
Image.register_open("DCX", DcxImageFile, _accept)
|
||||
Image.register_open(DcxImageFile.format, DcxImageFile, _accept)
|
||||
|
||||
Image.register_extension("DCX", ".dcx")
|
||||
Image.register_extension(DcxImageFile.format, ".dcx")
|
||||
|
|
268
PIL/DdsImagePlugin.py
Normal file
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.
|
||||
#
|
||||
|
||||
__version__ = "0.5"
|
||||
|
||||
import re
|
||||
import io
|
||||
import sys
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
__version__ = "0.5"
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
@ -36,7 +37,6 @@ split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
|||
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
||||
|
||||
gs_windows_binary = None
|
||||
import sys
|
||||
if sys.platform.startswith('win'):
|
||||
import shutil
|
||||
if hasattr(shutil, 'which'):
|
||||
|
@ -123,8 +123,8 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
"-q", # quiet mode
|
||||
"-g%dx%d" % size, # set output geometry (pixels)
|
||||
"-r%fx%f" % res, # set input DPI (dots per inch)
|
||||
"-dNOPAUSE -dSAFER", # don't pause between pages,
|
||||
# safe mode
|
||||
"-dNOPAUSE", # don't pause between pages,
|
||||
"-dSAFER", # safe mode
|
||||
"-sDEVICE=ppmraw", # ppm driver
|
||||
"-sOutputFile=%s" % outfile, # output file
|
||||
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
|
||||
|
@ -151,7 +151,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
os.unlink(outfile)
|
||||
if infile_temp:
|
||||
os.unlink(infile_temp)
|
||||
except:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return im
|
||||
|
@ -187,7 +187,8 @@ class PSFile(object):
|
|||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5
|
||||
return prefix[:4] == b"%!PS" or \
|
||||
(len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
|
||||
|
||||
##
|
||||
# Image plugin for Encapsulated Postscript. This plugin supports only
|
||||
|
@ -294,7 +295,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
break
|
||||
try:
|
||||
self.mode = self.mode_map[int(mo)]
|
||||
except:
|
||||
except ValueError:
|
||||
break
|
||||
|
||||
self.size = int(x), int(y)
|
||||
|
@ -376,9 +377,10 @@ def _save(im, fp, filename, eps=1):
|
|||
pass
|
||||
|
||||
base_fp = fp
|
||||
fp = NoCloseStream(fp)
|
||||
if sys.version_info[0] > 2:
|
||||
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
||||
if fp != sys.stdout:
|
||||
fp = NoCloseStream(fp)
|
||||
if sys.version_info[0] > 2:
|
||||
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
||||
|
||||
if eps:
|
||||
#
|
||||
|
@ -403,13 +405,15 @@ def _save(im, fp, filename, eps=1):
|
|||
fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
|
||||
fp.write("{ currentfile buf readhexstring pop } bind\n")
|
||||
fp.write(operator[2] + "\n")
|
||||
fp.flush()
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
|
||||
ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
|
||||
|
||||
fp.write("\n%%%%EndBinary\n")
|
||||
fp.write("grestore end\n")
|
||||
fp.flush()
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -27,9 +27,9 @@ TAGS = {
|
|||
0x0102: "BitsPerSample",
|
||||
0x0103: "Compression",
|
||||
0x0106: "PhotometricInterpretation",
|
||||
0x0107: "Threshholding",
|
||||
0x0107: "Thresholding",
|
||||
0x0108: "CellWidth",
|
||||
0x0109: "CellLenght",
|
||||
0x0109: "CellLength",
|
||||
0x010a: "FillOrder",
|
||||
0x010d: "DocumentName",
|
||||
0x011d: "PageName",
|
||||
|
@ -40,7 +40,7 @@ TAGS = {
|
|||
0x0112: "Orientation",
|
||||
0x0115: "SamplesPerPixel",
|
||||
0x0116: "RowsPerStrip",
|
||||
0x0117: "StripByteConunts",
|
||||
0x0117: "StripByteCounts",
|
||||
0x0118: "MinSampleValue",
|
||||
0x0119: "MaxSampleValue",
|
||||
0x011a: "XResolution",
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
@ -30,7 +30,7 @@ o8 = _binary.o8
|
|||
# decoder
|
||||
|
||||
def _accept(prefix):
|
||||
return i16(prefix[4:6]) in [0xAF11, 0xAF12]
|
||||
return len(prefix) >= 6 and i16(prefix[4:6]) in [0xAF11, 0xAF12]
|
||||
|
||||
|
||||
##
|
||||
|
@ -90,6 +90,7 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
self.__fp = self.fp
|
||||
self.__rewind = self.fp.tell()
|
||||
self._n_frames = None
|
||||
self._is_animated = None
|
||||
self.seek(0)
|
||||
|
||||
def _palette(self, palette, shift):
|
||||
|
@ -122,13 +123,33 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
self.seek(current)
|
||||
return self._n_frames
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
if self._is_animated is None:
|
||||
current = self.tell()
|
||||
|
||||
try:
|
||||
self.seek(1)
|
||||
self._is_animated = True
|
||||
except EOFError:
|
||||
self._is_animated = False
|
||||
|
||||
self.seek(current)
|
||||
return self._is_animated
|
||||
|
||||
def seek(self, frame):
|
||||
if frame == self.__frame:
|
||||
return
|
||||
if frame < self.__frame:
|
||||
self._seek(0)
|
||||
|
||||
last_frame = self.__frame
|
||||
for f in range(self.__frame + 1, frame + 1):
|
||||
self._seek(f)
|
||||
try:
|
||||
self._seek(f)
|
||||
except EOFError:
|
||||
self.seek(last_frame)
|
||||
raise EOFError("no more images in FLI file")
|
||||
|
||||
def _seek(self, frame):
|
||||
if frame == 0:
|
||||
|
@ -161,7 +182,7 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# registry
|
||||
|
||||
Image.register_open("FLI", FliImageFile, _accept)
|
||||
Image.register_open(FliImageFile.format, FliImageFile, _accept)
|
||||
|
||||
Image.register_extension("FLI", ".fli")
|
||||
Image.register_extension("FLI", ".flc")
|
||||
Image.register_extension(FliImageFile.format, ".fli")
|
||||
Image.register_extension(FliImageFile.format, ".flc")
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||
MODES = {
|
||||
|
@ -222,6 +221,6 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("FPX", FpxImageFile, _accept)
|
||||
Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
|
||||
|
||||
Image.register_extension("FPX", ".fpx")
|
||||
Image.register_extension(FpxImageFile.format, ".fpx")
|
||||
|
|
96
PIL/FtexImagePlugin.py
Normal file
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
|
||||
# $Id$
|
||||
#
|
||||
# load a GIMP brush file
|
||||
#
|
||||
# History:
|
||||
# 96-03-14 fl Created
|
||||
# 16-01-08 es Version 2
|
||||
#
|
||||
# Copyright (c) Secret Labs AB 1997.
|
||||
# Copyright (c) Fredrik Lundh 1996.
|
||||
# Copyright (c) Eric Soroos 2016.
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
#
|
||||
# See https://github.com/GNOME/gimp/blob/master/devel-docs/gbr.txt for
|
||||
# format documentation.
|
||||
#
|
||||
# This code Interprets version 1 and 2 .gbr files.
|
||||
# Version 1 files are obsolete, and should not be used for new
|
||||
# brushes.
|
||||
# Version 2 files are saved by GIMP v2.8 (at least)
|
||||
# Version 3 files have a format specifier of 18 for 16bit floats in
|
||||
# the color depth field. This is currently unsupported by Pillow.
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
|
@ -19,7 +30,7 @@ i32 = _binary.i32be
|
|||
|
||||
|
||||
def _accept(prefix):
|
||||
return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
|
||||
return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
|
||||
|
||||
|
||||
##
|
||||
|
@ -31,41 +42,53 @@ class GbrImageFile(ImageFile.ImageFile):
|
|||
format_description = "GIMP brush file"
|
||||
|
||||
def _open(self):
|
||||
|
||||
header_size = i32(self.fp.read(4))
|
||||
version = i32(self.fp.read(4))
|
||||
if header_size < 20 or version != 1:
|
||||
if header_size < 20:
|
||||
raise SyntaxError("not a GIMP brush")
|
||||
if version not in (1, 2):
|
||||
raise SyntaxError("Unsupported GIMP brush version: %s" % version)
|
||||
|
||||
width = i32(self.fp.read(4))
|
||||
height = i32(self.fp.read(4))
|
||||
color_depth = i32(self.fp.read(4))
|
||||
if width <= 0 or height <= 0 or color_depth != 1:
|
||||
if width <= 0 or height <= 0:
|
||||
raise SyntaxError("not a GIMP brush")
|
||||
if color_depth not in (1, 4):
|
||||
raise SyntaxError("Unsupported GMP brush color depth: %s" % color_depth)
|
||||
|
||||
comment = self.fp.read(header_size - 20)[:-1]
|
||||
if version == 1:
|
||||
comment_length = header_size-20
|
||||
else:
|
||||
comment_length = header_size-28
|
||||
magic_number = self.fp.read(4)
|
||||
if magic_number != b'GIMP':
|
||||
raise SyntaxError("not a GIMP brush, bad magic number")
|
||||
self.info['spacing'] = i32(self.fp.read(4))
|
||||
|
||||
comment = self.fp.read(comment_length)[:-1]
|
||||
|
||||
if color_depth == 1:
|
||||
self.mode = "L"
|
||||
else:
|
||||
self.mode = 'RGBA'
|
||||
|
||||
self.mode = "L"
|
||||
self.size = width, height
|
||||
|
||||
self.info["comment"] = comment
|
||||
|
||||
# Since the brush is so small, we read the data immediately
|
||||
self.data = self.fp.read(width * height)
|
||||
# Image might not be small
|
||||
Image._decompression_bomb_check(self.size)
|
||||
|
||||
# Data is an uncompressed block of w * h * bytes/pixel
|
||||
self._data_size = width * height * color_depth
|
||||
|
||||
def load(self):
|
||||
|
||||
if not self.data:
|
||||
return
|
||||
|
||||
# create an image out of the brush data block
|
||||
self.im = Image.core.new(self.mode, self.size)
|
||||
self.im.frombytes(self.data)
|
||||
self.data = b""
|
||||
self.frombytes(self.fp.read(self._data_size))
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
Image.register_open("GBR", GbrImageFile, _accept)
|
||||
|
||||
Image.register_extension("GBR", ".gbr")
|
||||
Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
|
||||
Image.register_extension(GbrImageFile.format, ".gbr")
|
||||
|
|
|
@ -23,11 +23,11 @@
|
|||
# purposes only.
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
from PIL import ImageFile, ImagePalette, _binary
|
||||
from PIL._util import isPath
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
try:
|
||||
import builtins
|
||||
except ImportError:
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from PIL import Image, ImageFile, ImagePalette, \
|
||||
ImageChops, ImageSequence, _binary
|
||||
|
||||
__version__ = "0.9"
|
||||
|
||||
|
@ -88,6 +89,7 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
self.__fp = self.fp # FIXME: hack
|
||||
self.__rewind = self.fp.tell()
|
||||
self._n_frames = None
|
||||
self._is_animated = None
|
||||
self._seek(0) # get ready to read first frame
|
||||
|
||||
@property
|
||||
|
@ -102,13 +104,33 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
self.seek(current)
|
||||
return self._n_frames
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
if self._is_animated is None:
|
||||
current = self.tell()
|
||||
|
||||
try:
|
||||
self.seek(1)
|
||||
self._is_animated = True
|
||||
except EOFError:
|
||||
self._is_animated = False
|
||||
|
||||
self.seek(current)
|
||||
return self._is_animated
|
||||
|
||||
def seek(self, frame):
|
||||
if frame == self.__frame:
|
||||
return
|
||||
if frame < self.__frame:
|
||||
self._seek(0)
|
||||
|
||||
last_frame = self.__frame
|
||||
for f in range(self.__frame + 1, frame + 1):
|
||||
self._seek(f)
|
||||
try:
|
||||
self._seek(f)
|
||||
except EOFError:
|
||||
self.seek(last_frame)
|
||||
raise EOFError("no more images in GIF file")
|
||||
|
||||
def _seek(self, frame):
|
||||
|
||||
|
@ -241,7 +263,7 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
|
||||
if not self.tile:
|
||||
# self.__fp = None
|
||||
raise EOFError("no more images in GIF file")
|
||||
raise EOFError
|
||||
|
||||
self.mode = "L"
|
||||
if self.palette:
|
||||
|
@ -279,8 +301,27 @@ RAWMODE = {
|
|||
}
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
def _convert_mode(im, initial_call=False):
|
||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
||||
# should automatically convert images on save...)
|
||||
if Image.getmodebase(im.mode) == "RGB":
|
||||
if initial_call:
|
||||
palette_size = 256
|
||||
if im.palette:
|
||||
palette_size = len(im.palette.getdata()[1]) // 3
|
||||
return im.convert("P", palette=1, colors=palette_size)
|
||||
else:
|
||||
return im.convert("P")
|
||||
return im.convert("L")
|
||||
|
||||
|
||||
def _save_all(im, fp, filename):
|
||||
_save(im, fp, filename, save_all=True)
|
||||
|
||||
|
||||
def _save(im, fp, filename, save_all=False):
|
||||
|
||||
im.encoderinfo.update(im.info)
|
||||
if _imaging_gif:
|
||||
# call external driver
|
||||
try:
|
||||
|
@ -290,17 +331,9 @@ def _save(im, fp, filename):
|
|||
pass # write uncompressed file
|
||||
|
||||
if im.mode in RAWMODE:
|
||||
im_out = im
|
||||
im_out = im.copy()
|
||||
else:
|
||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
||||
# should automatically convert images on save...)
|
||||
if Image.getmodebase(im.mode) == "RGB":
|
||||
palette_size = 256
|
||||
if im.palette:
|
||||
palette_size = len(im.palette.getdata()[1]) // 3
|
||||
im_out = im.convert("P", palette=1, colors=palette_size)
|
||||
else:
|
||||
im_out = im.convert("L")
|
||||
im_out = _convert_mode(im, True)
|
||||
|
||||
# header
|
||||
try:
|
||||
|
@ -309,25 +342,63 @@ def _save(im, fp, filename):
|
|||
palette = None
|
||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||
|
||||
header, used_palette_colors = getheader(im_out, palette, im.encoderinfo)
|
||||
for s in header:
|
||||
fp.write(s)
|
||||
if save_all:
|
||||
previous = None
|
||||
|
||||
# local image header
|
||||
get_local_header(fp, im)
|
||||
first_frame = None
|
||||
for im_frame in ImageSequence.Iterator(im):
|
||||
im_frame = _convert_mode(im_frame)
|
||||
|
||||
im_out.encoderconfig = (8, get_interlace(im))
|
||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||
RAWMODE[im_out.mode])])
|
||||
# To specify duration, add the time in milliseconds to getdata(),
|
||||
# e.g. getdata(im_frame, duration=1000)
|
||||
if not previous:
|
||||
# global header
|
||||
first_frame = getheader(im_frame, palette, im.encoderinfo)[0]
|
||||
first_frame += getdata(im_frame, (0, 0), **im.encoderinfo)
|
||||
else:
|
||||
if first_frame:
|
||||
for s in first_frame:
|
||||
fp.write(s)
|
||||
first_frame = None
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
# delta frame
|
||||
delta = ImageChops.subtract_modulo(im_frame, previous.copy())
|
||||
bbox = delta.getbbox()
|
||||
|
||||
if bbox:
|
||||
# compress difference
|
||||
for s in getdata(im_frame.crop(bbox),
|
||||
bbox[:2], **im.encoderinfo):
|
||||
fp.write(s)
|
||||
else:
|
||||
# FIXME: what should we do in this case?
|
||||
pass
|
||||
previous = im_frame
|
||||
if first_frame:
|
||||
save_all = False
|
||||
if not save_all:
|
||||
header = getheader(im_out, palette, im.encoderinfo)[0]
|
||||
for s in header:
|
||||
fp.write(s)
|
||||
|
||||
flags = 0
|
||||
|
||||
if get_interlace(im):
|
||||
flags = flags | 64
|
||||
|
||||
# local image header
|
||||
_get_local_header(fp, im, (0, 0), flags)
|
||||
|
||||
im_out.encoderconfig = (8, get_interlace(im))
|
||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||
RAWMODE[im_out.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
|
||||
fp.write(b";") # end of file
|
||||
|
||||
try:
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def get_interlace(im):
|
||||
|
@ -343,7 +414,7 @@ def get_interlace(im):
|
|||
return interlace
|
||||
|
||||
|
||||
def get_local_header(fp, im, offset=(0, 0)):
|
||||
def _get_local_header(fp, im, offset, flags):
|
||||
transparent_color_exists = False
|
||||
try:
|
||||
transparency = im.encoderinfo["transparency"]
|
||||
|
@ -394,12 +465,6 @@ def get_local_header(fp, im, offset=(0, 0)):
|
|||
o8(1) +
|
||||
o16(number_of_loops) + # number of loops
|
||||
o8(0))
|
||||
|
||||
flags = 0
|
||||
|
||||
if get_interlace(im):
|
||||
flags = flags | 64
|
||||
|
||||
fp.write(b"," +
|
||||
o16(offset[0]) + # offset
|
||||
o16(offset[1]) +
|
||||
|
@ -451,7 +516,7 @@ def _save_netpbm(im, fp, filename):
|
|||
|
||||
try:
|
||||
os.unlink(file)
|
||||
except:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
|
@ -480,8 +545,19 @@ def getheader(im, palette=None, info=None):
|
|||
|
||||
# Header Block
|
||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||
|
||||
version = b"87a"
|
||||
for extensionKey in ["transparency", "duration", "loop"]:
|
||||
if info and extensionKey in info and \
|
||||
not (extensionKey == "duration" and info[extensionKey] == 0):
|
||||
version = b"89a"
|
||||
break
|
||||
else:
|
||||
if im.info.get("version") == "89a":
|
||||
version = b"89a"
|
||||
|
||||
header = [
|
||||
b"GIF87a" + # signature + version
|
||||
b"GIF"+version + # signature + version
|
||||
o16(im.size[0]) + # canvas width
|
||||
o16(im.size[1]) # canvas height
|
||||
]
|
||||
|
@ -538,7 +614,17 @@ def getheader(im, palette=None, info=None):
|
|||
# size of global color table + global color table flag
|
||||
header.append(o8(color_table_size + 128))
|
||||
# background + reserved/aspect
|
||||
header.append(o8(0) + o8(0))
|
||||
if info and "background" in info:
|
||||
background = info["background"]
|
||||
elif "background" in im.info:
|
||||
# This elif is redundant within GifImagePlugin
|
||||
# since im.info parameters are bundled into the info dictionary
|
||||
# However, external scripts may call getheader directly
|
||||
# So this maintains earlier behaviour
|
||||
background = im.info["background"]
|
||||
else:
|
||||
background = 0
|
||||
header.append(o8(background) + o8(0))
|
||||
# end of Logical Screen Descriptor
|
||||
|
||||
# add the missing amount of bytes
|
||||
|
@ -571,7 +657,7 @@ def getdata(im, offset=(0, 0), **params):
|
|||
im.encoderinfo = params
|
||||
|
||||
# local image header
|
||||
get_local_header(fp, im, offset)
|
||||
_get_local_header(fp, im, offset, 0)
|
||||
|
||||
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
||||
|
||||
|
@ -588,6 +674,7 @@ def getdata(im, offset=(0, 0), **params):
|
|||
|
||||
Image.register_open(GifImageFile.format, GifImageFile, _accept)
|
||||
Image.register_save(GifImageFile.format, _save)
|
||||
Image.register_save_all(GifImageFile.format, _save_all)
|
||||
Image.register_extension(GifImageFile.format, ".gif")
|
||||
Image.register_mime(GifImageFile.format, "image/gif")
|
||||
|
||||
|
|
|
@ -306,10 +306,8 @@ def _save(im, fp, filename):
|
|||
|
||||
OS X only.
|
||||
"""
|
||||
try:
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
# create the temporary set of pngs
|
||||
iconset = tempfile.mkdtemp('.iconset')
|
||||
|
@ -345,13 +343,14 @@ def _save(im, fp, filename):
|
|||
if retcode:
|
||||
raise CalledProcessError(retcode, convert_cmd)
|
||||
|
||||
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
|
||||
Image.register_extension("ICNS", '.icns')
|
||||
Image.register_open(IcnsImageFile.format, IcnsImageFile,
|
||||
lambda x: x[:4] == b'icns')
|
||||
Image.register_extension(IcnsImageFile.format, '.icns')
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
Image.register_save("ICNS", _save)
|
||||
Image.register_save(IcnsImageFile.format, _save)
|
||||
|
||||
Image.register_mime("ICNS", "image/icns")
|
||||
Image.register_mime(IcnsImageFile.format, "image/icns")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -15,21 +15,21 @@
|
|||
|
||||
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||
# <casadebender@gmail.com>.
|
||||
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
||||
# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||
#
|
||||
# Icon format references:
|
||||
# * http://en.wikipedia.org/wiki/ICO_(file_format)
|
||||
# * http://msdn.microsoft.com/en-us/library/ms997538.aspx
|
||||
# * https://en.wikipedia.org/wiki/ICO_(file_format)
|
||||
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
||||
from math import log, ceil
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
@ -252,7 +252,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
|
||||
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||
<casadebender@gmail.com>.
|
||||
https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
||||
https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||
"""
|
||||
format = "ICO"
|
||||
format_description = "Windows Icon"
|
||||
|
@ -278,6 +278,6 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("ICO", IcoImageFile, _accept)
|
||||
Image.register_save("ICO", _save)
|
||||
Image.register_extension("ICO", ".ico")
|
||||
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
|
||||
Image.register_save(IcoImageFile.format, _save)
|
||||
Image.register_extension(IcoImageFile.format, ".ico")
|
||||
|
|
|
@ -26,12 +26,12 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
import re
|
||||
from PIL import Image, ImageFile, ImagePalette
|
||||
from PIL._binary import i8
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Standard tags
|
||||
|
@ -264,6 +264,10 @@ class ImImageFile(ImageFile.ImageFile):
|
|||
def n_frames(self):
|
||||
return self.info[FRAMES]
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
return self.info[FRAMES] > 1
|
||||
|
||||
def seek(self, frame):
|
||||
|
||||
if frame < 0 or frame >= self.info[FRAMES]:
|
||||
|
@ -345,7 +349,7 @@ def _save(im, fp, filename, check=0):
|
|||
# --------------------------------------------------------------------
|
||||
# Registry
|
||||
|
||||
Image.register_open("IM", ImImageFile)
|
||||
Image.register_save("IM", _save)
|
||||
Image.register_open(ImImageFile.format, ImImageFile)
|
||||
Image.register_save(ImImageFile.format, _save)
|
||||
|
||||
Image.register_extension("IM", ".im")
|
||||
Image.register_extension(ImImageFile.format, ".im")
|
||||
|
|
297
PIL/Image.py
297
PIL/Image.py
|
@ -28,8 +28,11 @@ from __future__ import print_function
|
|||
|
||||
from PIL import VERSION, PILLOW_VERSION, _plugins
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DecompressionBombWarning(RuntimeWarning):
|
||||
pass
|
||||
|
@ -79,20 +82,24 @@ except ImportError as v:
|
|||
)
|
||||
elif str(v).startswith("The _imaging extension"):
|
||||
warnings.warn(str(v), RuntimeWarning)
|
||||
elif "Symbol not found: _PyUnicodeUCS2_FromString" in str(v):
|
||||
elif "Symbol not found: _PyUnicodeUCS2_" in str(v):
|
||||
# should match _PyUnicodeUCS2_FromString and
|
||||
# _PyUnicodeUCS2_AsLatin1String
|
||||
warnings.warn(
|
||||
"The _imaging extension was built for Python with UCS2 support; "
|
||||
"recompile PIL or build Python --without-wide-unicode. ",
|
||||
"recompile Pillow or build Python --without-wide-unicode. ",
|
||||
RuntimeWarning
|
||||
)
|
||||
elif "Symbol not found: _PyUnicodeUCS4_FromString" in str(v):
|
||||
elif "Symbol not found: _PyUnicodeUCS4_" in str(v):
|
||||
# should match _PyUnicodeUCS4_FromString and
|
||||
# _PyUnicodeUCS4_AsLatin1String
|
||||
warnings.warn(
|
||||
"The _imaging extension was built for Python with UCS4 support; "
|
||||
"recompile PIL or build Python --with-wide-unicode. ",
|
||||
"recompile Pillow or build Python --with-wide-unicode. ",
|
||||
RuntimeWarning
|
||||
)
|
||||
# Fail here anyway. Don't let people run with a mostly broken Pillow.
|
||||
# see docs/porting-pil-to-pillow.rst
|
||||
# see docs/porting.rst
|
||||
raise
|
||||
|
||||
try:
|
||||
|
@ -121,7 +128,7 @@ USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info')
|
|||
try:
|
||||
import cffi
|
||||
HAS_CFFI = True
|
||||
except:
|
||||
except ImportError:
|
||||
HAS_CFFI = False
|
||||
|
||||
|
||||
|
@ -138,11 +145,6 @@ def isImageType(t):
|
|||
"""
|
||||
return hasattr(t, "im")
|
||||
|
||||
#
|
||||
# Debug level
|
||||
|
||||
DEBUG = 0
|
||||
|
||||
#
|
||||
# Constants (also defined in _imagingmodule.c!)
|
||||
|
||||
|
@ -204,6 +206,7 @@ ID = []
|
|||
OPEN = {}
|
||||
MIME = {}
|
||||
SAVE = {}
|
||||
SAVE_ALL = {}
|
||||
EXTENSION = {}
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -251,6 +254,7 @@ _MODE_CONV = {
|
|||
"CMYK": ('|u1', 4),
|
||||
"YCbCr": ('|u1', 3),
|
||||
"LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1
|
||||
"HSV": ('|u1', 3),
|
||||
# I;16 == I;16L, and I;32 == I;32L
|
||||
"I;16": ('<u2', None),
|
||||
"I;16B": ('>u2', None),
|
||||
|
@ -386,13 +390,10 @@ def init():
|
|||
|
||||
for plugin in _plugins:
|
||||
try:
|
||||
if DEBUG:
|
||||
print("Importing %s" % plugin)
|
||||
logger.debug("Importing %s", plugin)
|
||||
__import__("PIL.%s" % plugin, globals(), locals(), [])
|
||||
except ImportError:
|
||||
if DEBUG:
|
||||
print("Image: failed to import", end=' ')
|
||||
print(plugin, ":", sys.exc_info()[1])
|
||||
except ImportError as e:
|
||||
logger.debug("Image: failed to import %s: %s", plugin, e)
|
||||
|
||||
if OPEN or SAVE:
|
||||
_initialized = 2
|
||||
|
@ -504,12 +505,21 @@ class Image(object):
|
|||
self.readonly = 0
|
||||
self.pyaccess = None
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self.size[0]
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self.size[1]
|
||||
|
||||
def _new(self, im):
|
||||
new = Image()
|
||||
new.im = im
|
||||
new.mode = im.mode
|
||||
new.size = im.size
|
||||
new.palette = self.palette
|
||||
if self.palette:
|
||||
new.palette = self.palette.copy()
|
||||
if im.mode == "P" and not new.palette:
|
||||
from PIL import ImagePalette
|
||||
new.palette = ImagePalette.ImagePalette()
|
||||
|
@ -545,8 +555,7 @@ class Image(object):
|
|||
try:
|
||||
self.fp.close()
|
||||
except Exception as msg:
|
||||
if DEBUG:
|
||||
print("Error closing: %s" % msg)
|
||||
logger.debug("Error closing: %s", msg)
|
||||
|
||||
# Instead of simply setting to None, we're setting up a
|
||||
# deferred error that will better explain that the core image
|
||||
|
@ -618,6 +627,7 @@ class Image(object):
|
|||
new['shape'] = shape
|
||||
new['typestr'] = typestr
|
||||
new['data'] = self.tobytes()
|
||||
new['version'] = 3
|
||||
return new
|
||||
raise AttributeError(name)
|
||||
|
||||
|
@ -643,7 +653,14 @@ class Image(object):
|
|||
|
||||
def tobytes(self, encoder_name="raw", *args):
|
||||
"""
|
||||
Return image as a bytes object
|
||||
Return image as a bytes object.
|
||||
|
||||
.. warning::
|
||||
|
||||
This method returns the raw image data from the internal
|
||||
storage. For compressed image data (e.g. PNG, JPEG) use
|
||||
:meth:`~.save`, with a BytesIO parameter for in-memory
|
||||
data.
|
||||
|
||||
:param encoder_name: What encoder to use. The default is to
|
||||
use the standard "raw" encoder.
|
||||
|
@ -677,18 +694,9 @@ class Image(object):
|
|||
|
||||
return b"".join(data)
|
||||
|
||||
# Declare tostring as alias to tobytes
|
||||
def tostring(self, *args, **kw):
|
||||
"""Deprecated alias to tobytes.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
"""
|
||||
warnings.warn(
|
||||
'tostring() is deprecated. Please call tobytes() instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.tobytes(*args, **kw)
|
||||
raise Exception("tostring() has been removed. " +
|
||||
"Please call tobytes() instead.")
|
||||
|
||||
def tobitmap(self, name="image"):
|
||||
"""
|
||||
|
@ -738,14 +746,8 @@ class Image(object):
|
|||
raise ValueError("cannot decode image data")
|
||||
|
||||
def fromstring(self, *args, **kw):
|
||||
"""Deprecated alias to frombytes.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
"""
|
||||
warnings.warn(
|
||||
'fromstring() is deprecated. Please call frombytes() instead.',
|
||||
DeprecationWarning)
|
||||
return self.frombytes(*args, **kw)
|
||||
raise Exception("fromstring() has been removed. " +
|
||||
"Please call frombytes() instead.")
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
|
@ -818,7 +820,7 @@ class Image(object):
|
|||
|
||||
:param mode: The requested mode. See: :ref:`concept-modes`.
|
||||
:param matrix: An optional conversion matrix. If given, this
|
||||
should be 4- or 16-tuple containing floating point values.
|
||||
should be 4- or 12-tuple containing floating point values.
|
||||
:param dither: Dithering method, used when converting from
|
||||
mode "RGB" to "P" or from "RGB" or "L" to "1".
|
||||
Available methods are NONE or FLOYDSTEINBERG (default).
|
||||
|
@ -877,6 +879,12 @@ class Image(object):
|
|||
trns_im = Image()._new(core.new(self.mode, (1, 1)))
|
||||
if self.mode == 'P':
|
||||
trns_im.putpalette(self.palette)
|
||||
if type(t) == tuple:
|
||||
try:
|
||||
t = trns_im.palette.getcolor(t)
|
||||
except:
|
||||
raise ValueError("Couldn't allocate a palette "+
|
||||
"color for transparency")
|
||||
trns_im.putpixel((0, 0), t)
|
||||
|
||||
if mode in ('L', 'RGB'):
|
||||
|
@ -1004,6 +1012,8 @@ class Image(object):
|
|||
im = self.im.copy()
|
||||
return self._new(im)
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def crop(self, box=None):
|
||||
"""
|
||||
Returns a rectangular region from this image. The box is a
|
||||
|
@ -1244,27 +1254,8 @@ class Image(object):
|
|||
return self.im.histogram()
|
||||
|
||||
def offset(self, xoffset, yoffset=None):
|
||||
"""
|
||||
.. deprecated:: 2.0
|
||||
|
||||
.. note:: New code should use :py:func:`PIL.ImageChops.offset`.
|
||||
|
||||
Returns a copy of the image where the data has been offset by the given
|
||||
distances. Data wraps around the edges. If **yoffset** is omitted, it
|
||||
is assumed to be equal to **xoffset**.
|
||||
|
||||
:param xoffset: The horizontal distance.
|
||||
:param yoffset: The vertical distance. If omitted, both
|
||||
distances are set to the same value.
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
"""
|
||||
if warnings:
|
||||
warnings.warn(
|
||||
"'offset' is deprecated; use 'ImageChops.offset' instead",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
from PIL import ImageChops
|
||||
return ImageChops.offset(self, xoffset, yoffset)
|
||||
raise Exception("offset() has been removed. " +
|
||||
"Please call ImageChops.offset() instead.")
|
||||
|
||||
def paste(self, im, box=None, mask=None):
|
||||
"""
|
||||
|
@ -1316,7 +1307,7 @@ class Image(object):
|
|||
box = (0, 0) + self.size
|
||||
|
||||
if len(box) == 2:
|
||||
# lower left corner given; get size from image or mask
|
||||
# upper left corner given; get size from image or mask
|
||||
if isImageType(im):
|
||||
size = im.size
|
||||
elif isImageType(mask):
|
||||
|
@ -1617,7 +1608,7 @@ class Image(object):
|
|||
if self.mode in ("1", "P"):
|
||||
resample = NEAREST
|
||||
|
||||
return self._new(self.im.rotate(angle, resample))
|
||||
return self._new(self.im.rotate(angle, resample, expand))
|
||||
|
||||
def save(self, fp, format=None, **params):
|
||||
"""
|
||||
|
@ -1636,7 +1627,7 @@ class Image(object):
|
|||
implement the ``seek``, ``tell``, and ``write``
|
||||
methods, and be opened in binary mode.
|
||||
|
||||
:param fp: File name or file object.
|
||||
:param fp: A filename (string), pathlib.Path object or file object.
|
||||
:param format: Optional format override. If omitted, the
|
||||
format to use is determined from the filename extension.
|
||||
If a file object was used instead of a filename, this
|
||||
|
@ -1649,17 +1640,27 @@ class Image(object):
|
|||
may have been created, and may contain partial data.
|
||||
"""
|
||||
|
||||
filename = ""
|
||||
open_fp = False
|
||||
if isPath(fp):
|
||||
filename = fp
|
||||
else:
|
||||
if hasattr(fp, "name") and isPath(fp.name):
|
||||
filename = fp.name
|
||||
else:
|
||||
filename = ""
|
||||
open_fp = True
|
||||
elif sys.version_info >= (3, 4):
|
||||
from pathlib import Path
|
||||
if isinstance(fp, Path):
|
||||
filename = str(fp)
|
||||
open_fp = True
|
||||
elif hasattr(fp, "name") and isPath(fp.name):
|
||||
# only set the name for metadata purposes
|
||||
filename = fp.name
|
||||
|
||||
# may mutate self!
|
||||
self.load()
|
||||
|
||||
save_all = False
|
||||
if 'save_all' in params:
|
||||
save_all = params['save_all']
|
||||
del params['save_all']
|
||||
self.encoderinfo = params
|
||||
self.encoderconfig = ()
|
||||
|
||||
|
@ -1668,32 +1669,25 @@ class Image(object):
|
|||
ext = os.path.splitext(filename)[1].lower()
|
||||
|
||||
if not format:
|
||||
try:
|
||||
format = EXTENSION[ext]
|
||||
except KeyError:
|
||||
if ext not in EXTENSION:
|
||||
init()
|
||||
try:
|
||||
format = EXTENSION[ext]
|
||||
except KeyError:
|
||||
raise KeyError(ext) # unknown extension
|
||||
format = EXTENSION[ext]
|
||||
|
||||
try:
|
||||
save_handler = SAVE[format.upper()]
|
||||
except KeyError:
|
||||
if format.upper() not in SAVE:
|
||||
init()
|
||||
save_handler = SAVE[format.upper()] # unknown format
|
||||
|
||||
if isPath(fp):
|
||||
fp = builtins.open(fp, "wb")
|
||||
close = 1
|
||||
if save_all:
|
||||
save_handler = SAVE_ALL[format.upper()]
|
||||
else:
|
||||
close = 0
|
||||
save_handler = SAVE[format.upper()]
|
||||
|
||||
if open_fp:
|
||||
fp = builtins.open(filename, "wb")
|
||||
|
||||
try:
|
||||
save_handler(self, fp, filename)
|
||||
finally:
|
||||
# do what we can to clean up
|
||||
if close:
|
||||
if open_fp:
|
||||
fp.close()
|
||||
|
||||
def seek(self, frame):
|
||||
|
@ -1935,6 +1929,20 @@ class Image(object):
|
|||
im = self.im.effect_spread(distance)
|
||||
return self._new(im)
|
||||
|
||||
def toqimage(self):
|
||||
"""Returns a QImage copy of this image"""
|
||||
from PIL import ImageQt
|
||||
if not ImageQt.qt_is_installed:
|
||||
raise ImportError("Qt bindings are not installed")
|
||||
return ImageQt.toqimage(self)
|
||||
|
||||
def toqpixmap(self):
|
||||
"""Returns a QPixmap copy of this image"""
|
||||
from PIL import ImageQt
|
||||
if not ImageQt.qt_is_installed:
|
||||
raise ImportError("Qt bindings are not installed")
|
||||
return ImageQt.toqpixmap(self)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Lazy operations
|
||||
|
@ -1945,7 +1953,9 @@ class _ImageCrop(Image):
|
|||
|
||||
Image.__init__(self)
|
||||
|
||||
x0, y0, x1, y1 = box
|
||||
# Round to nearest integer, runs int(round(x)) when unpacking
|
||||
x0, y0, x1, y1 = map(int, map(round, box))
|
||||
|
||||
if x1 < x0:
|
||||
x1 = x0
|
||||
if y1 < y0:
|
||||
|
@ -2063,16 +2073,8 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
|
|||
|
||||
|
||||
def fromstring(*args, **kw):
|
||||
"""Deprecated alias to frombytes.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
"""
|
||||
warnings.warn(
|
||||
'fromstring() is deprecated. Please call frombytes() instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return frombytes(*args, **kw)
|
||||
raise Exception("fromstring() has been removed. " +
|
||||
"Please call frombytes() instead.")
|
||||
|
||||
|
||||
def frombuffer(mode, size, data, decoder_name="raw", *args):
|
||||
|
@ -2089,7 +2091,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
|
|||
**BytesIO** object, and use :py:func:`~PIL.Image.open` to load it.
|
||||
|
||||
In the current version, the default parameters used for the "raw" decoder
|
||||
differs from that used for :py:func:`~PIL.Image.fromstring`. This is a
|
||||
differs from that used for :py:func:`~PIL.Image.frombytes`. This is a
|
||||
bug, and will probably be fixed in a future release. The current release
|
||||
issues a warning if you do this; to disable the warning, you should provide
|
||||
the full set of parameters. See below for details.
|
||||
|
@ -2116,13 +2118,12 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
|
|||
|
||||
if decoder_name == "raw":
|
||||
if args == ():
|
||||
if warnings:
|
||||
warnings.warn(
|
||||
"the frombuffer defaults may change in a future release; "
|
||||
"for portability, change the call to read:\n"
|
||||
" frombuffer(mode, size, data, 'raw', mode, 0, 1)",
|
||||
RuntimeWarning, stacklevel=2
|
||||
)
|
||||
warnings.warn(
|
||||
"the frombuffer defaults may change in a future release; "
|
||||
"for portability, change the call to read:\n"
|
||||
" frombuffer(mode, size, data, 'raw', mode, 0, 1)",
|
||||
RuntimeWarning, stacklevel=2
|
||||
)
|
||||
args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6
|
||||
if args[0] in _MAPMODES:
|
||||
im = new(mode, (1, 1))
|
||||
|
@ -2184,6 +2185,22 @@ def fromarray(obj, mode=None):
|
|||
|
||||
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
|
||||
|
||||
|
||||
def fromqimage(im):
|
||||
"""Creates an image instance from a QImage image"""
|
||||
from PIL import ImageQt
|
||||
if not ImageQt.qt_is_installed:
|
||||
raise ImportError("Qt bindings are not installed")
|
||||
return ImageQt.fromqimage(im)
|
||||
|
||||
|
||||
def fromqpixmap(im):
|
||||
"""Creates an image instance from a QPixmap image"""
|
||||
from PIL import ImageQt
|
||||
if not ImageQt.qt_is_installed:
|
||||
raise ImportError("Qt bindings are not installed")
|
||||
return ImageQt.fromqpixmap(im)
|
||||
|
||||
_fromarray_typemap = {
|
||||
# (shape, typestr) => mode, rawmode
|
||||
# first two members of shape are set to one
|
||||
|
@ -2231,9 +2248,10 @@ def open(fp, mode="r"):
|
|||
:py:meth:`~PIL.Image.Image.load` method). See
|
||||
:py:func:`~PIL.Image.new`.
|
||||
|
||||
:param file: A filename (string) or a file object. The file object
|
||||
must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and
|
||||
:py:meth:`~file.tell` methods, and be opened in binary mode.
|
||||
:param fp: A filename (string), pathlib.Path object or a file object.
|
||||
The file object must implement :py:meth:`~file.read`,
|
||||
:py:meth:`~file.seek`, and :py:meth:`~file.tell` methods,
|
||||
and be opened in binary mode.
|
||||
:param mode: The mode. If given, this argument must be "r".
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
:exception IOError: If the file cannot be found, or the image cannot be
|
||||
|
@ -2243,11 +2261,15 @@ def open(fp, mode="r"):
|
|||
if mode != "r":
|
||||
raise ValueError("bad mode %r" % mode)
|
||||
|
||||
filename = ""
|
||||
if isPath(fp):
|
||||
filename = fp
|
||||
fp = builtins.open(fp, "rb")
|
||||
else:
|
||||
filename = ""
|
||||
elif sys.version_info >= (3, 4):
|
||||
from pathlib import Path
|
||||
if isinstance(fp, Path):
|
||||
filename = str(fp.resolve())
|
||||
if filename:
|
||||
fp = builtins.open(filename, "rb")
|
||||
|
||||
try:
|
||||
fp.seek(0)
|
||||
|
@ -2258,21 +2280,7 @@ def open(fp, mode="r"):
|
|||
|
||||
preinit()
|
||||
|
||||
for i in ID:
|
||||
try:
|
||||
factory, accept = OPEN[i]
|
||||
if not accept or accept(prefix):
|
||||
fp.seek(0)
|
||||
im = factory(fp, filename)
|
||||
_decompression_bomb_check(im.size)
|
||||
return im
|
||||
except (SyntaxError, IndexError, TypeError, struct.error):
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
|
||||
if init():
|
||||
|
||||
def _open_core(fp, filename, prefix):
|
||||
for i in ID:
|
||||
try:
|
||||
factory, accept = OPEN[i]
|
||||
|
@ -2282,23 +2290,34 @@ def open(fp, mode="r"):
|
|||
_decompression_bomb_check(im.size)
|
||||
return im
|
||||
except (SyntaxError, IndexError, TypeError, struct.error):
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
# Leave disabled by default, spams the logs with image
|
||||
# opening failures that are entirely expected.
|
||||
# logger.debug("", exc_info=True)
|
||||
continue
|
||||
return None
|
||||
|
||||
im = _open_core(fp, filename, prefix)
|
||||
|
||||
if im is None:
|
||||
if init():
|
||||
im = _open_core(fp, filename, prefix)
|
||||
|
||||
if im:
|
||||
return im
|
||||
|
||||
raise IOError("cannot identify image file %r"
|
||||
% (filename if filename else fp))
|
||||
|
||||
|
||||
#
|
||||
# Image processing.
|
||||
|
||||
|
||||
def alpha_composite(im1, im2):
|
||||
"""
|
||||
Alpha composite im2 over im1.
|
||||
|
||||
:param im1: The first image.
|
||||
:param im2: The second image. Must have the same mode and size as
|
||||
:param im1: The first image. Must have mode RGBA.
|
||||
:param im2: The second image. Must have mode RGBA, and the same size as
|
||||
the first image.
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
"""
|
||||
|
@ -2430,6 +2449,18 @@ def register_save(id, driver):
|
|||
SAVE[id.upper()] = driver
|
||||
|
||||
|
||||
def register_save_all(id, driver):
|
||||
"""
|
||||
Registers an image function to save all the frames
|
||||
of a multiframe format. This function should not be
|
||||
used in application code.
|
||||
|
||||
:param id: An image format identifier.
|
||||
:param driver: A function to save images in this format.
|
||||
"""
|
||||
SAVE_ALL[id.upper()] = driver
|
||||
|
||||
|
||||
def register_extension(id, extension):
|
||||
"""
|
||||
Registers an image extension. This function should not be
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
# below for the original description.
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
|
||||
DESCRIPTION = """
|
||||
pyCMS
|
||||
|
@ -240,7 +241,6 @@ def get_display_profile(handle=None):
|
|||
:returns: None if the profile is not known.
|
||||
"""
|
||||
|
||||
import sys
|
||||
if sys.platform == "win32":
|
||||
from PIL import ImageWin
|
||||
if isinstance(handle, ImageWin.HDC):
|
||||
|
@ -943,7 +943,6 @@ def versions():
|
|||
(pyCMS) Fetches versions.
|
||||
"""
|
||||
|
||||
import sys
|
||||
return (
|
||||
VERSION, core.littlecms_version,
|
||||
sys.version.split()[0], Image.VERSION
|
||||
|
@ -954,10 +953,9 @@ def versions():
|
|||
if __name__ == "__main__":
|
||||
# create a cheap manual from the __doc__ strings for the functions above
|
||||
|
||||
from PIL import ImageCms
|
||||
print(__doc__)
|
||||
|
||||
for f in dir(ImageCms):
|
||||
for f in dir(sys.modules[__name__]):
|
||||
doc = None
|
||||
try:
|
||||
exec("doc = %s.__doc__" % (f))
|
||||
|
|
|
@ -31,22 +31,18 @@
|
|||
#
|
||||
|
||||
import numbers
|
||||
import warnings
|
||||
|
||||
from PIL import Image, ImageColor
|
||||
from PIL._util import isStringType
|
||||
|
||||
try:
|
||||
import warnings
|
||||
except ImportError:
|
||||
warnings = None
|
||||
|
||||
|
||||
##
|
||||
# A simple 2D drawing interface for PIL images.
|
||||
# <p>
|
||||
# Application code should use the <b>Draw</b> factory, instead of
|
||||
# directly.
|
||||
|
||||
|
||||
class ImageDraw(object):
|
||||
|
||||
##
|
||||
|
@ -90,38 +86,17 @@ class ImageDraw(object):
|
|||
self.fill = 0
|
||||
self.font = None
|
||||
|
||||
##
|
||||
# Set the default pen color.
|
||||
|
||||
def setink(self, ink):
|
||||
# compatibility
|
||||
if warnings:
|
||||
warnings.warn(
|
||||
"'setink' is deprecated; use keyword arguments instead",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
if isStringType(ink):
|
||||
ink = ImageColor.getcolor(ink, self.mode)
|
||||
if self.palette and not isinstance(ink, numbers.Number):
|
||||
ink = self.palette.getcolor(ink)
|
||||
self.ink = self.draw.draw_ink(ink, self.mode)
|
||||
|
||||
##
|
||||
# Set the default background color.
|
||||
raise Exception("setink() has been removed. " +
|
||||
"Please use keyword arguments instead.")
|
||||
|
||||
def setfill(self, onoff):
|
||||
# compatibility
|
||||
if warnings:
|
||||
warnings.warn(
|
||||
"'setfill' is deprecated; use keyword arguments instead",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
self.fill = onoff
|
||||
|
||||
##
|
||||
# Set the default font.
|
||||
raise Exception("setfill() has been removed. " +
|
||||
"Please use keyword arguments instead.")
|
||||
|
||||
def setfont(self, font):
|
||||
warnings.warn("setfont() is deprecated. " +
|
||||
"Please set the attribute directly instead.")
|
||||
# compatibility
|
||||
self.font = font
|
||||
|
||||
|
@ -256,7 +231,20 @@ class ImageDraw(object):
|
|||
##
|
||||
# Draw text.
|
||||
|
||||
def text(self, xy, text, fill=None, font=None, anchor=None):
|
||||
def _multiline_check(self, text):
|
||||
split_character = "\n" if isinstance(text, type("")) else b"\n"
|
||||
|
||||
return split_character in text
|
||||
|
||||
def _multiline_split(self, text):
|
||||
split_character = "\n" if isinstance(text, type("")) else b"\n"
|
||||
|
||||
return text.split(split_character)
|
||||
|
||||
def text(self, xy, text, fill=None, font=None, anchor=None, *args, **kwargs):
|
||||
if self._multiline_check(text):
|
||||
return self.multiline_text(xy, text, fill, font, anchor, *args, **kwargs)
|
||||
|
||||
ink, fill = self._getink(fill)
|
||||
if font is None:
|
||||
font = self.getfont()
|
||||
|
@ -273,14 +261,50 @@ class ImageDraw(object):
|
|||
mask = font.getmask(text)
|
||||
self.draw.draw_bitmap(xy, mask, ink)
|
||||
|
||||
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
|
||||
spacing=4, align="left"):
|
||||
widths = []
|
||||
max_width = 0
|
||||
lines = self._multiline_split(text)
|
||||
line_spacing = self.textsize('A', font=font)[1] + spacing
|
||||
for line in lines:
|
||||
line_width, line_height = self.textsize(line, font)
|
||||
widths.append(line_width)
|
||||
max_width = max(max_width, line_width)
|
||||
left, top = xy
|
||||
for idx, line in enumerate(lines):
|
||||
if align == "left":
|
||||
pass # left = x
|
||||
elif align == "center":
|
||||
left += (max_width - widths[idx]) / 2.0
|
||||
elif align == "right":
|
||||
left += (max_width - widths[idx])
|
||||
else:
|
||||
assert False, 'align must be "left", "center" or "right"'
|
||||
self.text((left, top), line, fill, font, anchor)
|
||||
top += line_spacing
|
||||
left = xy[0]
|
||||
|
||||
##
|
||||
# Get the size of a given string, in pixels.
|
||||
|
||||
def textsize(self, text, font=None):
|
||||
def textsize(self, text, font=None, *args, **kwargs):
|
||||
if self._multiline_check(text):
|
||||
return self.multiline_textsize(text, font, *args, **kwargs)
|
||||
|
||||
if font is None:
|
||||
font = self.getfont()
|
||||
return font.getsize(text)
|
||||
|
||||
def multiline_textsize(self, text, font=None, spacing=4):
|
||||
max_width = 0
|
||||
lines = self._multiline_split(text)
|
||||
line_spacing = self.textsize('A', font=font)[1] + spacing
|
||||
for line in lines:
|
||||
line_width, line_height = self.textsize(line, font)
|
||||
max_width = max(max_width, line_width)
|
||||
return max_width, len(lines)*line_spacing
|
||||
|
||||
|
||||
##
|
||||
# A simple 2D drawing interface for PIL images.
|
||||
|
@ -301,7 +325,7 @@ def Draw(im, mode=None):
|
|||
# experimental access to the outline API
|
||||
try:
|
||||
Outline = Image.core.outline
|
||||
except:
|
||||
except AttributeError:
|
||||
Outline = None
|
||||
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ from PIL._util import isPath
|
|||
import io
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import struct
|
||||
|
||||
MAXBLOCK = 65536
|
||||
|
||||
|
@ -95,21 +95,11 @@ class ImageFile(Image.Image):
|
|||
|
||||
try:
|
||||
self._open()
|
||||
except IndexError as v: # end of data
|
||||
if Image.DEBUG > 1:
|
||||
traceback.print_exc()
|
||||
raise SyntaxError(v)
|
||||
except TypeError as v: # end of data (ord)
|
||||
if Image.DEBUG > 1:
|
||||
traceback.print_exc()
|
||||
raise SyntaxError(v)
|
||||
except KeyError as v: # unsupported mode
|
||||
if Image.DEBUG > 1:
|
||||
traceback.print_exc()
|
||||
raise SyntaxError(v)
|
||||
except EOFError as v: # got header but not the first frame
|
||||
if Image.DEBUG > 1:
|
||||
traceback.print_exc()
|
||||
except (IndexError, # end of data
|
||||
TypeError, # end of data (ord)
|
||||
KeyError, # unsupported mode
|
||||
EOFError, # got header but not the first frame
|
||||
struct.error) as v:
|
||||
raise SyntaxError(v)
|
||||
|
||||
if not self.mode or self.size[0] <= 0:
|
||||
|
@ -173,10 +163,10 @@ class ImageFile(Image.Image):
|
|||
else:
|
||||
# use mmap, if possible
|
||||
import mmap
|
||||
file = open(self.filename, "r+")
|
||||
fp = open(self.filename, "r+")
|
||||
size = os.path.getsize(self.filename)
|
||||
# FIXME: on Unix, use PROT_READ etc
|
||||
self.map = mmap.mmap(file.fileno(), size)
|
||||
self.map = mmap.mmap(fp.fileno(), size)
|
||||
self.im = Image.core.map_buffer(
|
||||
self.map, self.size, d, e, o, a
|
||||
)
|
||||
|
@ -196,9 +186,6 @@ class ImageFile(Image.Image):
|
|||
except AttributeError:
|
||||
prefix = b""
|
||||
|
||||
# Buffer length read; assign a default value
|
||||
t = 0
|
||||
|
||||
for d, e, o, a in self.tile:
|
||||
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
|
||||
seek(o)
|
||||
|
@ -207,15 +194,14 @@ class ImageFile(Image.Image):
|
|||
except ValueError:
|
||||
continue
|
||||
b = prefix
|
||||
t = len(b)
|
||||
while True:
|
||||
try:
|
||||
s = read(self.decodermaxblock)
|
||||
except IndexError as ie: # truncated png/gif
|
||||
except (IndexError, struct.error): # truncated png/gif
|
||||
if LOAD_TRUNCATED_IMAGES:
|
||||
break
|
||||
else:
|
||||
raise IndexError(ie)
|
||||
raise IOError("image file is truncated")
|
||||
|
||||
if not s and not d.handles_eof: # truncated jpeg
|
||||
self.tile = []
|
||||
|
@ -236,7 +222,6 @@ class ImageFile(Image.Image):
|
|||
if n < 0:
|
||||
break
|
||||
b = b[n:]
|
||||
t = t + n
|
||||
# Need to cleanup here to prevent leaks in PyPy
|
||||
d.cleanup()
|
||||
|
||||
|
@ -245,7 +230,7 @@ class ImageFile(Image.Image):
|
|||
|
||||
self.fp = None # might be shared
|
||||
|
||||
if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0:
|
||||
if not self.map and not LOAD_TRUNCATED_IMAGES and e < 0:
|
||||
# still raised if decoder fails to return anything
|
||||
raise_ioerror(e)
|
||||
|
||||
|
@ -468,6 +453,9 @@ def _save(im, fp, tile, bufsize=0):
|
|||
# But, it would need at least the image size in most cases. RawEncode is
|
||||
# a tricky case.
|
||||
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
|
||||
if fp == sys.stdout:
|
||||
fp.flush()
|
||||
return
|
||||
try:
|
||||
fh = fp.fileno()
|
||||
fp.flush()
|
||||
|
@ -497,10 +485,8 @@ def _save(im, fp, tile, bufsize=0):
|
|||
if s < 0:
|
||||
raise IOError("encoder error %d when writing image file" % s)
|
||||
e.cleanup()
|
||||
try:
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def _safe_read(fp, size):
|
||||
|
|
|
@ -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 sys
|
||||
|
||||
try:
|
||||
import warnings
|
||||
except ImportError:
|
||||
warnings = None
|
||||
|
||||
|
||||
class _imagingft_not_installed(object):
|
||||
# module placeholder
|
||||
|
@ -67,7 +62,7 @@ class ImageFont(object):
|
|||
|
||||
def _load_pilfont(self, filename):
|
||||
|
||||
file = open(filename, "rb")
|
||||
fp = open(filename, "rb")
|
||||
|
||||
for ext in (".png", ".gif", ".pbm"):
|
||||
try:
|
||||
|
@ -83,7 +78,7 @@ class ImageFont(object):
|
|||
|
||||
self.file = fullname
|
||||
|
||||
return self._load_pilfont_data(file, image)
|
||||
return self._load_pilfont_data(fp, image)
|
||||
|
||||
def _load_pilfont_data(self, file, image):
|
||||
|
||||
|
@ -121,15 +116,8 @@ class ImageFont(object):
|
|||
class FreeTypeFont(object):
|
||||
"FreeType font wrapper (requires _imagingft service)"
|
||||
|
||||
def __init__(self, font=None, size=10, index=0, encoding="", file=None):
|
||||
def __init__(self, font=None, size=10, index=0, encoding=""):
|
||||
# FIXME: use service provider instead
|
||||
if file:
|
||||
if warnings:
|
||||
warnings.warn(
|
||||
'file parameter deprecated, '
|
||||
'please use font parameter instead.',
|
||||
DeprecationWarning)
|
||||
font = file
|
||||
|
||||
self.path = font
|
||||
self.size = size
|
||||
|
@ -171,7 +159,7 @@ class FreeTypeFont(object):
|
|||
using any specified arguments to override the settings.
|
||||
|
||||
Parameters are identical to the parameters used to initialize this
|
||||
object, minus the deprecated 'file' argument.
|
||||
object.
|
||||
|
||||
:return: A FreeTypeFont object.
|
||||
"""
|
||||
|
@ -225,7 +213,7 @@ def load(filename):
|
|||
return f
|
||||
|
||||
|
||||
def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
||||
def truetype(font=None, size=10, index=0, encoding=""):
|
||||
"""
|
||||
Load a TrueType or OpenType font file, and create a font object.
|
||||
This function loads a font object from the given file, and creates
|
||||
|
@ -243,19 +231,10 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
|||
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
||||
and "armn" (Apple Roman). See the FreeType documentation
|
||||
for more information.
|
||||
:param filename: Deprecated. Please use font instead.
|
||||
:return: A font object.
|
||||
:exception IOError: If the file could not be read.
|
||||
"""
|
||||
|
||||
if filename:
|
||||
if warnings:
|
||||
warnings.warn(
|
||||
'filename parameter deprecated, '
|
||||
'please use font parameter instead.',
|
||||
DeprecationWarning)
|
||||
font = filename
|
||||
|
||||
try:
|
||||
return FreeTypeFont(font, size, index, encoding)
|
||||
except IOError:
|
||||
|
@ -275,7 +254,8 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
|||
# According to the freedesktop spec, XDG_DATA_DIRS should
|
||||
# default to /usr/share
|
||||
lindirs = '/usr/share'
|
||||
dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
|
||||
dirs += [os.path.join(lindir, "fonts")
|
||||
for lindir in lindirs.split(":")]
|
||||
elif sys.platform == 'darwin':
|
||||
dirs += ['/Library/Fonts', '/System/Library/Fonts',
|
||||
os.path.expanduser('~/Library/Fonts')]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# screen grabber (windows only)
|
||||
# screen grabber (OS X and Windows only)
|
||||
#
|
||||
# History:
|
||||
# 2001-04-26 fl created
|
||||
|
@ -18,31 +18,40 @@
|
|||
from PIL import Image
|
||||
|
||||
import sys
|
||||
if sys.platform != "win32":
|
||||
raise ImportError("ImageGrab is Windows only")
|
||||
if sys.platform not in ["win32", "darwin"]:
|
||||
raise ImportError("ImageGrab is OS X and Windows only")
|
||||
|
||||
try:
|
||||
# built-in driver (1.1.3 and later)
|
||||
if sys.platform == "win32":
|
||||
grabber = Image.core.grabscreen
|
||||
except AttributeError:
|
||||
# stand-alone driver (pil plus)
|
||||
import _grabscreen
|
||||
grabber = _grabscreen.grab
|
||||
elif sys.platform == "darwin":
|
||||
import os
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
|
||||
def grab(bbox=None):
|
||||
size, data = grabber()
|
||||
im = Image.frombytes(
|
||||
"RGB", size, data,
|
||||
# RGB, 32-bit line padding, origo in lower left corner
|
||||
"raw", "BGR", (size[0]*3 + 3) & -4, -1
|
||||
)
|
||||
if sys.platform == "darwin":
|
||||
f, file = tempfile.mkstemp('.png')
|
||||
os.close(f)
|
||||
subprocess.call(['screencapture', '-x', file])
|
||||
im = Image.open(file)
|
||||
im.load()
|
||||
os.unlink(file)
|
||||
else:
|
||||
size, data = grabber()
|
||||
im = Image.frombytes(
|
||||
"RGB", size, data,
|
||||
# RGB, 32-bit line padding, origo in lower left corner
|
||||
"raw", "BGR", (size[0]*3 + 3) & -4, -1
|
||||
)
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
|
||||
def grabclipboard():
|
||||
if sys.platform == "darwin":
|
||||
raise NotImplementedError("Method is not implemented on OS X")
|
||||
debug = 0 # temporary interface
|
||||
data = Image.core.grabclipboard(debug)
|
||||
if isinstance(data, bytes):
|
||||
|
|
|
@ -198,6 +198,8 @@ class MorphOp(object):
|
|||
if self.lut is None:
|
||||
raise Exception('No operator loaded')
|
||||
|
||||
if image.mode != 'L':
|
||||
raise Exception('Image must be binary, meaning it must use mode L')
|
||||
outimage = Image.new(image.mode, image.size, None)
|
||||
count = _imagingmorph.apply(
|
||||
bytes(self.lut), image.im.id, outimage.im.id)
|
||||
|
@ -212,6 +214,8 @@ class MorphOp(object):
|
|||
if self.lut is None:
|
||||
raise Exception('No operator loaded')
|
||||
|
||||
if image.mode != 'L':
|
||||
raise Exception('Image must be binary, meaning it must use mode L')
|
||||
return _imagingmorph.match(bytes(self.lut), image.im.id)
|
||||
|
||||
def get_on_pixels(self, image):
|
||||
|
@ -220,6 +224,8 @@ class MorphOp(object):
|
|||
Returns a list of tuples of (x,y) coordinates
|
||||
of all matching pixels."""
|
||||
|
||||
if image.mode != 'L':
|
||||
raise Exception('Image must be binary, meaning it must use mode L')
|
||||
return _imagingmorph.get_on_pixels(image.im.id)
|
||||
|
||||
def load_lut(self, filename):
|
||||
|
|
|
@ -17,12 +17,23 @@
|
|||
#
|
||||
|
||||
import array
|
||||
import warnings
|
||||
from PIL import ImageColor
|
||||
|
||||
|
||||
class ImagePalette(object):
|
||||
"Color palette for palette mapped images"
|
||||
"""
|
||||
Color palette for palette mapped images
|
||||
|
||||
:param mode: The mode to use for the Palette. See:
|
||||
:ref:`concept-modes`. Defaults to "RGB"
|
||||
:param palette: An optional palette. If given, it must be a bytearray,
|
||||
an array or a list of ints between 0-255 and of length ``size``
|
||||
times the number of colors in ``mode``. The list must be aligned
|
||||
by channel (All R values must be contiguous in the list before G
|
||||
and B values.) Defaults to 0 through 255 per channel.
|
||||
:param size: An optional palette size. If given, it cannot be equal to
|
||||
or greater than 256. Defaults to 0.
|
||||
"""
|
||||
|
||||
def __init__(self, mode="RGB", palette=None, size=0):
|
||||
self.mode = mode
|
||||
|
@ -34,6 +45,18 @@ class ImagePalette(object):
|
|||
(size != 0 and size != len(self.palette))):
|
||||
raise ValueError("wrong palette size")
|
||||
|
||||
def copy(self):
|
||||
new = ImagePalette()
|
||||
|
||||
new.mode = self.mode
|
||||
new.rawmode = self.rawmode
|
||||
if self.palette is not None:
|
||||
new.palette = self.palette[:]
|
||||
new.colors = self.colors.copy()
|
||||
new.dirty = self.dirty
|
||||
|
||||
return new
|
||||
|
||||
def getdata(self):
|
||||
"""
|
||||
Get palette contents in format suitable # for the low-level
|
||||
|
@ -56,7 +79,6 @@ class ImagePalette(object):
|
|||
return self.palette
|
||||
arr = array.array("B", self.palette)
|
||||
if hasattr(arr, 'tobytes'):
|
||||
# py3k has a tobytes, tostring is deprecated.
|
||||
return arr.tobytes()
|
||||
return arr.tostring()
|
||||
|
||||
|
@ -125,26 +147,6 @@ def raw(rawmode, data):
|
|||
# --------------------------------------------------------------------
|
||||
# Factories
|
||||
|
||||
def _make_linear_lut(black, white):
|
||||
warnings.warn(
|
||||
'_make_linear_lut() is deprecated. '
|
||||
'Please call make_linear_lut() instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return make_linear_lut(black, white)
|
||||
|
||||
|
||||
def _make_gamma_lut(exp):
|
||||
warnings.warn(
|
||||
'_make_gamma_lut() is deprecated. '
|
||||
'Please call make_gamma_lut() instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return make_gamma_lut(exp)
|
||||
|
||||
|
||||
def make_linear_lut(black, white):
|
||||
lut = []
|
||||
if black == 0:
|
||||
|
@ -225,8 +227,8 @@ def load(filename):
|
|||
p = PaletteFile.PaletteFile(fp)
|
||||
lut = p.getpalette()
|
||||
except (SyntaxError, ValueError):
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
|
||||
if not lut:
|
||||
|
|
211
PIL/ImageQt.py
211
PIL/ImageQt.py
|
@ -18,82 +18,181 @@
|
|||
|
||||
from PIL import Image
|
||||
from PIL._util import isPath
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
if 'PyQt4.QtGui' not in sys.modules:
|
||||
qt_is_installed = True
|
||||
qt_version = None
|
||||
try:
|
||||
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
||||
from PyQt5.QtCore import QBuffer, QIODevice
|
||||
qt_version = '5'
|
||||
except ImportError:
|
||||
try:
|
||||
from PyQt5.QtGui import QImage, qRgba
|
||||
except:
|
||||
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
||||
from PyQt4.QtCore import QBuffer, QIODevice
|
||||
qt_version = '4'
|
||||
except ImportError:
|
||||
try:
|
||||
from PyQt4.QtGui import QImage, qRgba
|
||||
except:
|
||||
from PySide.QtGui import QImage, qRgba
|
||||
|
||||
else: #PyQt4 is used
|
||||
from PyQt4.QtGui import QImage, qRgba
|
||||
|
||||
##
|
||||
# (Internal) Turns an RGB color into a Qt compatible color integer.
|
||||
from PySide.QtGui import QImage, qRgba, QPixmap
|
||||
from PySide.QtCore import QBuffer, QIODevice
|
||||
qt_version = 'side'
|
||||
except ImportError:
|
||||
qt_is_installed = False
|
||||
|
||||
|
||||
def rgb(r, g, b, a=255):
|
||||
"""(Internal) Turns an RGB color into a Qt compatible color integer."""
|
||||
# use qRgb to pack the colors, and then turn the resulting long
|
||||
# into a negative integer with the same bitpattern.
|
||||
return (qRgba(r, g, b, a) & 0xffffffff)
|
||||
|
||||
|
||||
# :param im A PIL Image object, or a file name
|
||||
# (given either as Python string or a PyQt string object)
|
||||
|
||||
def fromqimage(im):
|
||||
buffer = QBuffer()
|
||||
buffer.open(QIODevice.ReadWrite)
|
||||
# preserve alha channel with png
|
||||
# otherwise ppm is more friendly with Image.open
|
||||
if im.hasAlphaChannel():
|
||||
im.save(buffer, 'png')
|
||||
else:
|
||||
im.save(buffer, 'ppm')
|
||||
|
||||
b = BytesIO()
|
||||
try:
|
||||
b.write(buffer.data())
|
||||
except TypeError:
|
||||
# workaround for Python 2
|
||||
b.write(str(buffer.data()))
|
||||
buffer.close()
|
||||
b.seek(0)
|
||||
|
||||
return Image.open(b)
|
||||
|
||||
|
||||
def fromqpixmap(im):
|
||||
return fromqimage(im)
|
||||
# buffer = QBuffer()
|
||||
# buffer.open(QIODevice.ReadWrite)
|
||||
# # im.save(buffer)
|
||||
# # What if png doesn't support some image features like animation?
|
||||
# im.save(buffer, 'ppm')
|
||||
# bytes_io = BytesIO()
|
||||
# bytes_io.write(buffer.data())
|
||||
# buffer.close()
|
||||
# bytes_io.seek(0)
|
||||
# return Image.open(bytes_io)
|
||||
|
||||
|
||||
def align8to32(bytes, width, mode):
|
||||
"""
|
||||
converts each scanline of data from 8 bit to 32 bit aligned
|
||||
"""
|
||||
|
||||
bits_per_pixel = {
|
||||
'1': 1,
|
||||
'L': 8,
|
||||
'P': 8,
|
||||
}[mode]
|
||||
|
||||
# calculate bytes per line and the extra padding if needed
|
||||
bits_per_line = bits_per_pixel * width
|
||||
full_bytes_per_line, remaining_bits_per_line = divmod(bits_per_line, 8)
|
||||
bytes_per_line = full_bytes_per_line + (1 if remaining_bits_per_line else 0)
|
||||
|
||||
extra_padding = -bytes_per_line % 4
|
||||
|
||||
# already 32 bit aligned by luck
|
||||
if not extra_padding:
|
||||
return bytes
|
||||
|
||||
new_data = []
|
||||
for i in range(len(bytes) // bytes_per_line):
|
||||
new_data.append(bytes[i*bytes_per_line:(i+1)*bytes_per_line] + b'\x00' * extra_padding)
|
||||
|
||||
return b''.join(new_data)
|
||||
|
||||
|
||||
def _toqclass_helper(im):
|
||||
data = None
|
||||
colortable = None
|
||||
|
||||
# handle filename, if given instead of image name
|
||||
if hasattr(im, "toUtf8"):
|
||||
# FIXME - is this really the best way to do this?
|
||||
if str is bytes:
|
||||
im = unicode(im.toUtf8(), "utf-8")
|
||||
else:
|
||||
im = str(im.toUtf8(), "utf-8")
|
||||
if isPath(im):
|
||||
im = Image.open(im)
|
||||
|
||||
if im.mode == "1":
|
||||
format = QImage.Format_Mono
|
||||
elif im.mode == "L":
|
||||
format = QImage.Format_Indexed8
|
||||
colortable = []
|
||||
for i in range(256):
|
||||
colortable.append(rgb(i, i, i))
|
||||
elif im.mode == "P":
|
||||
format = QImage.Format_Indexed8
|
||||
colortable = []
|
||||
palette = im.getpalette()
|
||||
for i in range(0, len(palette), 3):
|
||||
colortable.append(rgb(*palette[i:i+3]))
|
||||
elif im.mode == "RGB":
|
||||
data = im.tobytes("raw", "BGRX")
|
||||
format = QImage.Format_RGB32
|
||||
elif im.mode == "RGBA":
|
||||
try:
|
||||
data = im.tobytes("raw", "BGRA")
|
||||
except SystemError:
|
||||
# workaround for earlier versions
|
||||
r, g, b, a = im.split()
|
||||
im = Image.merge("RGBA", (b, g, r, a))
|
||||
format = QImage.Format_ARGB32
|
||||
else:
|
||||
raise ValueError("unsupported image mode %r" % im.mode)
|
||||
|
||||
# must keep a reference, or Qt will crash!
|
||||
__data = data or align8to32(im.tobytes(), im.size[0], im.mode)
|
||||
return {
|
||||
'data': __data, 'im': im, 'format': format, 'colortable': colortable
|
||||
}
|
||||
|
||||
##
|
||||
# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage
|
||||
# An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
||||
# class.
|
||||
#
|
||||
# @param im A PIL Image object, or a file name (given either as Python
|
||||
# string or a PyQt string object).
|
||||
|
||||
class ImageQt(QImage):
|
||||
if qt_is_installed:
|
||||
class ImageQt(QImage):
|
||||
|
||||
def __init__(self, im):
|
||||
def __init__(self, im):
|
||||
im_data = _toqclass_helper(im)
|
||||
QImage.__init__(self,
|
||||
im_data['data'], im_data['im'].size[0],
|
||||
im_data['im'].size[1], im_data['format'])
|
||||
if im_data['colortable']:
|
||||
self.setColorTable(im_data['colortable'])
|
||||
|
||||
data = None
|
||||
colortable = None
|
||||
|
||||
# handle filename, if given instead of image name
|
||||
if hasattr(im, "toUtf8"):
|
||||
# FIXME - is this really the best way to do this?
|
||||
im = unicode(im.toUtf8(), "utf-8")
|
||||
if isPath(im):
|
||||
im = Image.open(im)
|
||||
def toqimage(im):
|
||||
return ImageQt(im)
|
||||
|
||||
if im.mode == "1":
|
||||
format = QImage.Format_Mono
|
||||
elif im.mode == "L":
|
||||
format = QImage.Format_Indexed8
|
||||
colortable = []
|
||||
for i in range(256):
|
||||
colortable.append(rgb(i, i, i))
|
||||
elif im.mode == "P":
|
||||
format = QImage.Format_Indexed8
|
||||
colortable = []
|
||||
palette = im.getpalette()
|
||||
for i in range(0, len(palette), 3):
|
||||
colortable.append(rgb(*palette[i:i+3]))
|
||||
elif im.mode == "RGB":
|
||||
data = im.tobytes("raw", "BGRX")
|
||||
format = QImage.Format_RGB32
|
||||
elif im.mode == "RGBA":
|
||||
try:
|
||||
data = im.tobytes("raw", "BGRA")
|
||||
except SystemError:
|
||||
# workaround for earlier versions
|
||||
r, g, b, a = im.split()
|
||||
im = Image.merge("RGBA", (b, g, r, a))
|
||||
format = QImage.Format_ARGB32
|
||||
else:
|
||||
raise ValueError("unsupported image mode %r" % im.mode)
|
||||
|
||||
# must keep a reference, or Qt will crash!
|
||||
self.__data = data or im.tobytes()
|
||||
def toqpixmap(im):
|
||||
# # This doesn't work. For now using a dumb approach.
|
||||
# im_data = _toqclass_helper(im)
|
||||
# result = QPixmap(im_data['im'].size[0], im_data['im'].size[1])
|
||||
# result.loadFromData(im_data['data'])
|
||||
# Fix some strange bug that causes
|
||||
if im.mode == 'RGB':
|
||||
im = im.convert('RGBA')
|
||||
|
||||
QImage.__init__(self, self.__data, im.size[0], im.size[1], format)
|
||||
|
||||
if colortable:
|
||||
self.setColorTable(colortable)
|
||||
qimage = toqimage(im)
|
||||
return QPixmap.fromImage(qimage)
|
||||
|
|
|
@ -32,11 +32,25 @@ class Iterator(object):
|
|||
if not hasattr(im, "seek"):
|
||||
raise AttributeError("im must have seek method")
|
||||
self.im = im
|
||||
self.position = 0
|
||||
|
||||
def __getitem__(self, ix):
|
||||
try:
|
||||
if ix:
|
||||
self.im.seek(ix)
|
||||
self.im.seek(ix)
|
||||
return self.im
|
||||
except EOFError:
|
||||
raise IndexError # end of sequence
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
try:
|
||||
self.im.seek(self.position)
|
||||
self.position += 1
|
||||
return self.im
|
||||
except EOFError:
|
||||
raise StopIteration
|
||||
|
||||
def next(self):
|
||||
return self.__next__()
|
||||
|
|
|
@ -76,7 +76,7 @@ class ExtentTransform(Transform):
|
|||
|
||||
|
||||
##
|
||||
# Define an quad image transform.
|
||||
# Define a quad image transform.
|
||||
# <p>
|
||||
# Maps a quadrilateral (a region defined by four corners) from the
|
||||
# image to a rectangle of the given size.
|
||||
|
@ -92,7 +92,7 @@ class QuadTransform(Transform):
|
|||
|
||||
|
||||
##
|
||||
# Define an mesh image transform. A mesh transform consists of one
|
||||
# Define a mesh image transform. A mesh transform consists of one
|
||||
# or more individual quad transforms.
|
||||
#
|
||||
# @def MeshTransform(data)
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
import warnings
|
||||
from PIL import Image
|
||||
|
||||
|
||||
|
@ -183,24 +182,13 @@ class Dib(object):
|
|||
"""
|
||||
return self.image.tobytes()
|
||||
|
||||
##
|
||||
# Deprecated aliases to frombytes & tobytes.
|
||||
|
||||
def fromstring(self, *args, **kw):
|
||||
warnings.warn(
|
||||
'fromstring() is deprecated. Please call frombytes() instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return self.frombytes(*args, **kw)
|
||||
raise Exception("fromstring() has been removed. " +
|
||||
"Please use frombytes() instead.")
|
||||
|
||||
def tostring(self):
|
||||
warnings.warn(
|
||||
'tostring() is deprecated. Please call tobytes() instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return self.tobytes()
|
||||
def tostring(self, *args, **kw):
|
||||
raise Exception("tostring() has been removed. " +
|
||||
"Please use tobytes() instead.")
|
||||
|
||||
|
||||
##
|
||||
|
|
|
@ -15,12 +15,13 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
import re
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
@ -88,7 +89,7 @@ class ImtImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("IMT", ImtImageFile)
|
||||
Image.register_open(ImtImageFile.format, ImtImageFile)
|
||||
|
||||
#
|
||||
# no extension registered (".im" is simply too common)
|
||||
|
|
|
@ -17,13 +17,12 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16be
|
||||
i32 = _binary.i32be
|
||||
|
@ -180,13 +179,13 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
finally:
|
||||
try:
|
||||
os.unlink(outfile)
|
||||
except:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
Image.register_open("IPTC", IptcImageFile)
|
||||
Image.register_open(IptcImageFile.format, IptcImageFile)
|
||||
|
||||
Image.register_extension("IPTC", ".iim")
|
||||
Image.register_extension(IptcImageFile.format, ".iim")
|
||||
|
||||
|
||||
##
|
||||
|
@ -218,7 +217,7 @@ def getiptcinfo(im):
|
|||
while app[offset:offset+4] == b"8BIM":
|
||||
offset += 4
|
||||
# resource code
|
||||
code = JpegImagePlugin.i16(app, offset)
|
||||
code = i16(app, offset)
|
||||
offset += 2
|
||||
# resource name (usually empty)
|
||||
name_len = i8(app[offset])
|
||||
|
@ -227,7 +226,7 @@ def getiptcinfo(im):
|
|||
if offset & 1:
|
||||
offset += 1
|
||||
# resource data block
|
||||
size = JpegImagePlugin.i32(app, offset)
|
||||
size = i32(app, offset)
|
||||
offset += 4
|
||||
if code == 0x0404:
|
||||
# 0x0404 contains IPTC/NAA data
|
||||
|
|
|
@ -84,6 +84,7 @@ def _parse_jp2_header(fp):
|
|||
size = None
|
||||
mode = None
|
||||
bpc = None
|
||||
nc = None
|
||||
|
||||
hio = io.BytesIO(header)
|
||||
while True:
|
||||
|
@ -141,6 +142,9 @@ def _parse_jp2_header(fp):
|
|||
mode = 'RGBA'
|
||||
break
|
||||
|
||||
if size is None or mode is None:
|
||||
raise SyntaxError("Malformed jp2 header")
|
||||
|
||||
return (size, mode)
|
||||
|
||||
##
|
||||
|
@ -262,15 +266,15 @@ def _save(im, fp, filename):
|
|||
# ------------------------------------------------------------
|
||||
# Registry stuff
|
||||
|
||||
Image.register_open('JPEG2000', Jpeg2KImageFile, _accept)
|
||||
Image.register_save('JPEG2000', _save)
|
||||
Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
|
||||
Image.register_save(Jpeg2KImageFile.format, _save)
|
||||
|
||||
Image.register_extension('JPEG2000', '.jp2')
|
||||
Image.register_extension('JPEG2000', '.j2k')
|
||||
Image.register_extension('JPEG2000', '.jpc')
|
||||
Image.register_extension('JPEG2000', '.jpf')
|
||||
Image.register_extension('JPEG2000', '.jpx')
|
||||
Image.register_extension('JPEG2000', '.j2c')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.jp2')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.j2k')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.jpc')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.jpf')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.jpx')
|
||||
Image.register_extension(Jpeg2KImageFile.format, '.j2c')
|
||||
|
||||
Image.register_mime('JPEG2000', 'image/jp2')
|
||||
Image.register_mime('JPEG2000', 'image/jpx')
|
||||
Image.register_mime(Jpeg2KImageFile.format, 'image/jp2')
|
||||
Image.register_mime(Jpeg2KImageFile.format, 'image/jpx')
|
||||
|
|
|
@ -32,12 +32,11 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
import array
|
||||
import struct
|
||||
import io
|
||||
from struct import unpack
|
||||
import warnings
|
||||
from struct import unpack_from
|
||||
from PIL import Image, ImageFile, TiffImagePlugin, _binary
|
||||
from PIL.JpegPresets import presets
|
||||
from PIL._util import isStringType
|
||||
|
@ -47,6 +46,8 @@ o8 = _binary.o8
|
|||
i16 = _binary.i16be
|
||||
i32 = _binary.i32be
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
|
||||
#
|
||||
# Parser
|
||||
|
@ -287,7 +288,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
|
||||
s = self.fp.read(1)
|
||||
|
||||
if i8(s[0]) != 255:
|
||||
if i8(s) != 255:
|
||||
raise SyntaxError("not a JPEG file")
|
||||
|
||||
# Create attributes
|
||||
|
@ -310,7 +311,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
i = i16(s)
|
||||
else:
|
||||
# Skip non-0xFF junk
|
||||
s = b"\xff"
|
||||
s = self.fp.read(1)
|
||||
continue
|
||||
|
||||
if i in MARKER:
|
||||
|
@ -378,7 +379,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
finally:
|
||||
try:
|
||||
os.unlink(path)
|
||||
except:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.mode = self.im.mode
|
||||
|
@ -393,11 +394,17 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
return _getmp(self)
|
||||
|
||||
|
||||
def _fixup(value):
|
||||
# Helper function for _getexif() and _getmp()
|
||||
if len(value) == 1:
|
||||
return value[0]
|
||||
return value
|
||||
def _fixup_dict(src_dict):
|
||||
# Helper function for _getexif()
|
||||
# returns a dict with any single item tuples/lists as individual values
|
||||
def _fixup(value):
|
||||
try:
|
||||
if len(value) == 1 and type(value) != type({}):
|
||||
return value[0]
|
||||
except: pass
|
||||
return value
|
||||
|
||||
return dict([(k, _fixup(v)) for k, v in src_dict.items()])
|
||||
|
||||
|
||||
def _getexif(self):
|
||||
|
@ -413,33 +420,35 @@ def _getexif(self):
|
|||
return None
|
||||
file = io.BytesIO(data[6:])
|
||||
head = file.read(8)
|
||||
exif = {}
|
||||
# process dictionary
|
||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
||||
info.load(file)
|
||||
for key, value in info.items():
|
||||
exif[key] = _fixup(value)
|
||||
exif = dict(_fixup_dict(info))
|
||||
# get exif extension
|
||||
try:
|
||||
# exif field 0x8769 is an offset pointer to the location
|
||||
# of the nested embedded exif ifd.
|
||||
# It should be a long, but may be corrupted.
|
||||
file.seek(exif[0x8769])
|
||||
except KeyError:
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
else:
|
||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
||||
info.load(file)
|
||||
for key, value in info.items():
|
||||
exif[key] = _fixup(value)
|
||||
exif.update(_fixup_dict(info))
|
||||
# get gpsinfo extension
|
||||
try:
|
||||
# exif field 0x8825 is an offset pointer to the location
|
||||
# of the nested embedded gps exif ifd.
|
||||
# It should be a long, but may be corrupted.
|
||||
file.seek(exif[0x8825])
|
||||
except KeyError:
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
else:
|
||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
||||
info.load(file)
|
||||
exif[0x8825] = gps = {}
|
||||
for key, value in info.items():
|
||||
gps[key] = _fixup(value)
|
||||
exif[0x8825] = _fixup_dict(info)
|
||||
|
||||
return exif
|
||||
|
||||
|
||||
|
@ -457,23 +466,25 @@ def _getmp(self):
|
|||
file_contents = io.BytesIO(data)
|
||||
head = file_contents.read(8)
|
||||
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
||||
mp = {}
|
||||
# process dictionary
|
||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||
info.load(file_contents)
|
||||
for key, value in info.items():
|
||||
mp[key] = _fixup(value)
|
||||
try:
|
||||
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||
info.load(file_contents)
|
||||
mp = dict(info)
|
||||
except:
|
||||
raise SyntaxError("malformed MP Index (unreadable directory)")
|
||||
# it's an error not to have a number of images
|
||||
try:
|
||||
quant = mp[0xB001]
|
||||
except KeyError:
|
||||
raise SyntaxError("malformed MP Index (no number of images)")
|
||||
# get MP entries
|
||||
mpentries = []
|
||||
try:
|
||||
mpentries = []
|
||||
rawmpentries = mp[0xB002]
|
||||
for entrynum in range(0, quant):
|
||||
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
|
||||
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
|
||||
unpackedentry = unpack_from(
|
||||
'{0}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
|
||||
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
||||
'EntryNo2')
|
||||
mpentry = dict(zip(labels, unpackedentry))
|
||||
|
@ -527,14 +538,14 @@ RAWMODE = {
|
|||
"YCbCr": "YCbCr",
|
||||
}
|
||||
|
||||
zigzag_index = ( 0, 1, 5, 6, 14, 15, 27, 28,
|
||||
2, 4, 7, 13, 16, 26, 29, 42,
|
||||
3, 8, 12, 17, 25, 30, 41, 43,
|
||||
9, 11, 18, 24, 31, 40, 44, 53,
|
||||
10, 19, 23, 32, 39, 45, 52, 54,
|
||||
20, 22, 33, 38, 46, 51, 55, 60,
|
||||
21, 34, 37, 47, 50, 56, 59, 61,
|
||||
35, 36, 48, 49, 57, 58, 62, 63)
|
||||
zigzag_index = (0, 1, 5, 6, 14, 15, 27, 28,
|
||||
2, 4, 7, 13, 16, 26, 29, 42,
|
||||
3, 8, 12, 17, 25, 30, 41, 43,
|
||||
9, 11, 18, 24, 31, 40, 44, 53,
|
||||
10, 19, 23, 32, 39, 45, 52, 54,
|
||||
20, 22, 33, 38, 46, 51, 55, 60,
|
||||
21, 34, 37, 47, 50, 56, 59, 61,
|
||||
35, 36, 48, 49, 57, 58, 62, 63)
|
||||
|
||||
samplings = {(1, 1, 1, 1, 1, 1): 0,
|
||||
(2, 1, 1, 1, 1, 1): 1,
|
||||
|
@ -681,7 +692,7 @@ def _save(im, fp, filename):
|
|||
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
|
||||
# in a shot. Guessing on the size, at im.size bytes. (raw pizel size is
|
||||
# channels*size, this is a value that's been used in a django patch.
|
||||
# https://github.com/jdriscoll/django-imagekit/issues/50
|
||||
# https://github.com/matthewwithanm/django-imagekit/issues/50
|
||||
bufsize = 0
|
||||
if "optimize" in info or "progressive" in info or "progression" in info:
|
||||
# keep sets quality to 0, but the actual value may be high.
|
||||
|
@ -705,7 +716,7 @@ def _save_cjpeg(im, fp, filename):
|
|||
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
||||
try:
|
||||
os.unlink(tempfile)
|
||||
except:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
|
@ -713,8 +724,8 @@ def _save_cjpeg(im, fp, filename):
|
|||
# Factory for making JPEG and MPO instances
|
||||
def jpeg_factory(fp=None, filename=None):
|
||||
im = JpegImageFile(fp, filename)
|
||||
mpheader = im._getmp()
|
||||
try:
|
||||
mpheader = im._getmp()
|
||||
if mpheader[45057] > 1:
|
||||
# It's actually an MPO
|
||||
from .MpoImagePlugin import MpoImageFile
|
||||
|
@ -722,18 +733,21 @@ def jpeg_factory(fp=None, filename=None):
|
|||
except (TypeError, IndexError):
|
||||
# It is really a JPEG
|
||||
pass
|
||||
except SyntaxError:
|
||||
warnings.warn("Image appears to be a malformed MPO file, it will be "
|
||||
"interpreted as a base JPEG file")
|
||||
return im
|
||||
|
||||
|
||||
# -------------------------------------------------------------------q-
|
||||
# Registry stuff
|
||||
|
||||
Image.register_open("JPEG", jpeg_factory, _accept)
|
||||
Image.register_save("JPEG", _save)
|
||||
Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
|
||||
Image.register_save(JpegImageFile.format, _save)
|
||||
|
||||
Image.register_extension("JPEG", ".jfif")
|
||||
Image.register_extension("JPEG", ".jpe")
|
||||
Image.register_extension("JPEG", ".jpg")
|
||||
Image.register_extension("JPEG", ".jpeg")
|
||||
Image.register_extension(JpegImageFile.format, ".jfif")
|
||||
Image.register_extension(JpegImageFile.format, ".jpe")
|
||||
Image.register_extension(JpegImageFile.format, ".jpg")
|
||||
Image.register_extension(JpegImageFile.format, ".jpeg")
|
||||
|
||||
Image.register_mime("JPEG", "image/jpeg")
|
||||
Image.register_mime(JpegImageFile.format, "image/jpeg")
|
||||
|
|
|
@ -27,7 +27,7 @@ Subsampling
|
|||
|
||||
Subsampling is the practice of encoding images by implementing less resolution
|
||||
for chroma information than for luma information.
|
||||
(ref.: http://en.wikipedia.org/wiki/Chroma_subsampling)
|
||||
(ref.: https://en.wikipedia.org/wiki/Chroma_subsampling)
|
||||
|
||||
Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and
|
||||
4:1:1 (or 4:2:0?).
|
||||
|
@ -41,8 +41,8 @@ Quantization tables
|
|||
|
||||
They are values use by the DCT (Discrete cosine transform) to remove
|
||||
*unnecessary* information from the image (the lossy part of the compression).
|
||||
(ref.: http://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
|
||||
http://en.wikipedia.org/wiki/JPEG#Quantization)
|
||||
(ref.: https://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
|
||||
https://en.wikipedia.org/wiki/JPEG#Quantization)
|
||||
|
||||
You can get the quantization tables of a JPEG with::
|
||||
|
||||
|
@ -62,13 +62,13 @@ The tables format between im.quantization and quantization in presets differ in
|
|||
You can convert the dict format to the preset format with the
|
||||
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
|
||||
|
||||
Libjpeg ref.: http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
||||
Libjpeg ref.: http://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
||||
|
||||
"""
|
||||
|
||||
presets = {
|
||||
'web_low': {'subsampling': 2, # "4:1:1"
|
||||
'quantization': [
|
||||
'quantization': [
|
||||
[20, 16, 25, 39, 50, 46, 62, 68,
|
||||
16, 18, 23, 38, 38, 53, 65, 68,
|
||||
25, 23, 31, 38, 53, 65, 68, 68,
|
||||
|
@ -85,9 +85,9 @@ presets = {
|
|||
68, 68, 68, 68, 68, 68, 68, 68,
|
||||
68, 68, 68, 68, 68, 68, 68, 68,
|
||||
68, 68, 68, 68, 68, 68, 68, 68]
|
||||
]},
|
||||
]},
|
||||
'web_medium': {'subsampling': 2, # "4:1:1"
|
||||
'quantization': [
|
||||
'quantization': [
|
||||
[16, 11, 11, 16, 23, 27, 31, 30,
|
||||
11, 12, 12, 15, 20, 23, 23, 30,
|
||||
11, 12, 13, 16, 23, 26, 35, 47,
|
||||
|
@ -104,10 +104,10 @@ presets = {
|
|||
26, 26, 30, 39, 48, 63, 64, 64,
|
||||
38, 35, 46, 53, 64, 64, 64, 64,
|
||||
48, 43, 53, 64, 64, 64, 64, 64]
|
||||
]},
|
||||
]},
|
||||
'web_high': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 6, 4, 4, 6, 9, 11, 12, 16,
|
||||
'quantization': [
|
||||
[6, 4, 4, 6, 9, 11, 12, 16,
|
||||
4, 5, 5, 6, 8, 10, 12, 12,
|
||||
4, 5, 5, 6, 10, 12, 14, 19,
|
||||
6, 6, 6, 11, 12, 15, 19, 28,
|
||||
|
@ -115,7 +115,7 @@ presets = {
|
|||
11, 10, 12, 15, 20, 27, 31, 31,
|
||||
12, 12, 14, 19, 27, 31, 31, 31,
|
||||
16, 12, 19, 28, 31, 31, 31, 31],
|
||||
[ 7, 7, 13, 24, 26, 31, 31, 31,
|
||||
[7, 7, 13, 24, 26, 31, 31, 31,
|
||||
7, 12, 16, 21, 31, 31, 31, 31,
|
||||
13, 16, 17, 31, 31, 31, 31, 31,
|
||||
24, 21, 31, 31, 31, 31, 31, 31,
|
||||
|
@ -123,10 +123,10 @@ presets = {
|
|||
31, 31, 31, 31, 31, 31, 31, 31,
|
||||
31, 31, 31, 31, 31, 31, 31, 31,
|
||||
31, 31, 31, 31, 31, 31, 31, 31]
|
||||
]},
|
||||
]},
|
||||
'web_very_high': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 2, 2, 2, 2, 3, 4, 5, 6,
|
||||
'quantization': [
|
||||
[2, 2, 2, 2, 3, 4, 5, 6,
|
||||
2, 2, 2, 2, 3, 4, 5, 6,
|
||||
2, 2, 2, 2, 4, 5, 7, 9,
|
||||
2, 2, 2, 4, 5, 7, 9, 12,
|
||||
|
@ -134,7 +134,7 @@ presets = {
|
|||
4, 4, 5, 7, 10, 12, 12, 12,
|
||||
5, 5, 7, 9, 12, 12, 12, 12,
|
||||
6, 6, 9, 12, 12, 12, 12, 12],
|
||||
[ 3, 3, 5, 9, 13, 15, 15, 15,
|
||||
[3, 3, 5, 9, 13, 15, 15, 15,
|
||||
3, 4, 6, 11, 14, 12, 12, 12,
|
||||
5, 6, 9, 14, 12, 12, 12, 12,
|
||||
9, 11, 14, 12, 12, 12, 12, 12,
|
||||
|
@ -142,10 +142,10 @@ presets = {
|
|||
15, 12, 12, 12, 12, 12, 12, 12,
|
||||
15, 12, 12, 12, 12, 12, 12, 12,
|
||||
15, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
]},
|
||||
'web_maximum': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
'quantization': [
|
||||
[1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 2,
|
||||
1, 1, 1, 1, 1, 1, 2, 2,
|
||||
|
@ -153,7 +153,7 @@ presets = {
|
|||
1, 1, 1, 1, 2, 2, 3, 3,
|
||||
1, 1, 1, 2, 2, 3, 3, 3,
|
||||
1, 1, 2, 2, 3, 3, 3, 3],
|
||||
[ 1, 1, 1, 2, 2, 3, 3, 3,
|
||||
[1, 1, 1, 2, 2, 3, 3, 3,
|
||||
1, 1, 1, 2, 3, 3, 3, 3,
|
||||
1, 1, 1, 3, 3, 3, 3, 3,
|
||||
2, 2, 3, 3, 3, 3, 3, 3,
|
||||
|
@ -161,9 +161,9 @@ presets = {
|
|||
3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3]
|
||||
]},
|
||||
]},
|
||||
'low': {'subsampling': 2, # "4:1:1"
|
||||
'quantization': [
|
||||
'quantization': [
|
||||
[18, 14, 14, 21, 30, 35, 34, 17,
|
||||
14, 16, 16, 19, 26, 23, 12, 12,
|
||||
14, 16, 17, 21, 23, 12, 12, 12,
|
||||
|
@ -180,9 +180,9 @@ presets = {
|
|||
20, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
]},
|
||||
'medium': {'subsampling': 2, # "4:1:1"
|
||||
'quantization': [
|
||||
'quantization': [
|
||||
[12, 8, 8, 12, 17, 21, 24, 17,
|
||||
8, 9, 9, 11, 15, 19, 12, 12,
|
||||
8, 9, 10, 12, 19, 12, 12, 12,
|
||||
|
@ -199,10 +199,10 @@ presets = {
|
|||
20, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
]},
|
||||
'high': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 6, 4, 4, 6, 9, 11, 12, 16,
|
||||
'quantization': [
|
||||
[6, 4, 4, 6, 9, 11, 12, 16,
|
||||
4, 5, 5, 6, 8, 10, 12, 12,
|
||||
4, 5, 5, 6, 10, 12, 12, 12,
|
||||
6, 6, 6, 11, 12, 12, 12, 12,
|
||||
|
@ -210,7 +210,7 @@ presets = {
|
|||
11, 10, 12, 12, 12, 12, 12, 12,
|
||||
12, 12, 12, 12, 12, 12, 12, 12,
|
||||
16, 12, 12, 12, 12, 12, 12, 12],
|
||||
[ 7, 7, 13, 24, 20, 20, 17, 17,
|
||||
[7, 7, 13, 24, 20, 20, 17, 17,
|
||||
7, 12, 16, 14, 14, 12, 12, 12,
|
||||
13, 16, 14, 14, 12, 12, 12, 12,
|
||||
24, 14, 14, 12, 12, 12, 12, 12,
|
||||
|
@ -218,10 +218,10 @@ presets = {
|
|||
20, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12,
|
||||
17, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
]},
|
||||
'maximum': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[ 2, 2, 2, 2, 3, 4, 5, 6,
|
||||
'quantization': [
|
||||
[2, 2, 2, 2, 3, 4, 5, 6,
|
||||
2, 2, 2, 2, 3, 4, 5, 6,
|
||||
2, 2, 2, 2, 4, 5, 7, 9,
|
||||
2, 2, 2, 4, 5, 7, 9, 12,
|
||||
|
@ -229,7 +229,7 @@ presets = {
|
|||
4, 4, 5, 7, 10, 12, 12, 12,
|
||||
5, 5, 7, 9, 12, 12, 12, 12,
|
||||
6, 6, 9, 12, 12, 12, 12, 12],
|
||||
[ 3, 3, 5, 9, 13, 15, 15, 15,
|
||||
[3, 3, 5, 9, 13, 15, 15, 15,
|
||||
3, 4, 6, 10, 14, 12, 12, 12,
|
||||
5, 6, 9, 14, 12, 12, 12, 12,
|
||||
9, 10, 14, 12, 12, 12, 12, 12,
|
||||
|
@ -237,5 +237,5 @@ presets = {
|
|||
15, 12, 12, 12, 12, 12, 12, 12,
|
||||
15, 12, 12, 12, 12, 12, 12, 12,
|
||||
15, 12, 12, 12, 12, 12, 12, 12]
|
||||
]},
|
||||
]},
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
import struct
|
||||
from PIL import Image, ImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
||||
def _accept(s):
|
||||
return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04"
|
||||
|
@ -69,6 +69,6 @@ class McIdasImageFile(ImageFile.ImageFile):
|
|||
# --------------------------------------------------------------------
|
||||
# registry
|
||||
|
||||
Image.register_open("MCIDAS", McIdasImageFile, _accept)
|
||||
Image.register_open(McIdasImageFile.format, McIdasImageFile, _accept)
|
||||
|
||||
# no default extension
|
||||
|
|
|
@ -17,12 +17,11 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
from PIL import Image, TiffImagePlugin
|
||||
from PIL.OleFileIO import MAGIC, OleFileIO
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -75,6 +74,10 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
def n_frames(self):
|
||||
return len(self.images)
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
return len(self.images) > 1
|
||||
|
||||
def seek(self, frame):
|
||||
|
||||
try:
|
||||
|
@ -95,6 +98,6 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("MIC", MicImageFile, _accept)
|
||||
Image.register_open(MicImageFile.format, MicImageFile, _accept)
|
||||
|
||||
Image.register_extension("MIC", ".mic")
|
||||
Image.register_extension(MicImageFile.format, ".mic")
|
||||
|
|
|
@ -13,11 +13,12 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL._binary import i8
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
#
|
||||
# Bitstream parser
|
||||
|
@ -77,9 +78,9 @@ class MpegImageFile(ImageFile.ImageFile):
|
|||
# --------------------------------------------------------------------
|
||||
# Registry stuff
|
||||
|
||||
Image.register_open("MPEG", MpegImageFile)
|
||||
Image.register_open(MpegImageFile.format, MpegImageFile)
|
||||
|
||||
Image.register_extension("MPEG", ".mpg")
|
||||
Image.register_extension("MPEG", ".mpeg")
|
||||
Image.register_extension(MpegImageFile.format, ".mpg")
|
||||
Image.register_extension(MpegImageFile.format, ".mpeg")
|
||||
|
||||
Image.register_mime("MPEG", "video/mpeg")
|
||||
Image.register_mime(MpegImageFile.format, "video/mpeg")
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
from PIL import Image, JpegImagePlugin
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return JpegImagePlugin._accept(prefix)
|
||||
|
@ -66,6 +66,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
def n_frames(self):
|
||||
return self.__framecount
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
return self.__framecount > 1
|
||||
|
||||
def seek(self, frame):
|
||||
if frame < 0 or frame >= self.__framecount:
|
||||
raise EOFError("no more images in MPO file")
|
||||
|
@ -86,9 +90,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
|
||||
# Note that since MPO shares a factory with JPEG, we do not need to do a
|
||||
# separate registration for it here.
|
||||
# Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
|
||||
Image.register_save("MPO", _save)
|
||||
# Image.register_open(MpoImageFile.format,
|
||||
# JpegImagePlugin.jpeg_factory, _accept)
|
||||
Image.register_save(MpoImageFile.format, _save)
|
||||
|
||||
Image.register_extension("MPO", ".mpo")
|
||||
Image.register_extension(MpoImageFile.format, ".mpo")
|
||||
|
||||
Image.register_mime("MPO", "image/mpo")
|
||||
Image.register_mime(MpoImageFile.format, "image/mpo")
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
#
|
||||
# read MSP files
|
||||
|
@ -98,7 +98,7 @@ def _save(im, fp, filename):
|
|||
#
|
||||
# registry
|
||||
|
||||
Image.register_open("MSP", MspImageFile, _accept)
|
||||
Image.register_save("MSP", _save)
|
||||
Image.register_open(MspImageFile.format, MspImageFile, _accept)
|
||||
Image.register_save(MspImageFile.format, _save)
|
||||
|
||||
Image.register_extension("MSP", ".msp")
|
||||
Image.register_extension(MspImageFile.format, ".msp")
|
||||
|
|
|
@ -1,26 +1,45 @@
|
|||
OleFileIO_PL
|
||||
============
|
||||
olefile (formerly OleFileIO_PL)
|
||||
===============================
|
||||
|
||||
[OleFileIO_PL](http://www.decalage.info/python/olefileio) is a Python module to parse and read [Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format)](http://en.wikipedia.org/wiki/Compound_File_Binary_Format), such as Microsoft Office documents, Image Composer and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats ...
|
||||
[olefile](http://www.decalage.info/olefile) is a Python package to parse, read and write
|
||||
[Microsoft OLE2 files](http://en.wikipedia.org/wiki/Compound_File_Binary_Format)
|
||||
(also called Structured Storage, Compound File Binary Format or Compound Document File Format),
|
||||
such as Microsoft Office 97-2003 documents, vbaProject.bin in MS Office 2007+ files, Image Composer
|
||||
and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats, McAfee antivirus quarantine files,
|
||||
etc.
|
||||
|
||||
This is an improved version of the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent Python Imaging Library, created and maintained by Fredrik Lundh. The API is still compatible with PIL, but since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust design.
|
||||
|
||||
As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules)
|
||||
**Quick links:** [Home page](http://www.decalage.info/olefile) -
|
||||
[Download/Install](https://bitbucket.org/decalage/olefileio_pl/wiki/Install) -
|
||||
[Documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) -
|
||||
[Report Issues/Suggestions/Questions](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open) -
|
||||
[Contact the author](http://decalage.info/contact) -
|
||||
[Repository](https://bitbucket.org/decalage/olefileio_pl) -
|
||||
[Updates on Twitter](https://twitter.com/decalage2)
|
||||
|
||||
OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-pillow.github.io/), the friendly fork of PIL.
|
||||
|
||||
OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL.
|
||||
|
||||
News
|
||||
----
|
||||
|
||||
Follow all updates and news on Twitter: <https://twitter.com/decalage2>
|
||||
|
||||
- **2014-02-04 v0.30**: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work.
|
||||
- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed parsing of direntry timestamps
|
||||
- 2013-05-27 v0.25: improved metadata extraction, properties parsing and exception handling, fixed [issue #12](https://bitbucket.org/decalage/olefileio_pl/issue/12/error-when-converting-timestamps-in-ole)
|
||||
- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved getproperties to convert timestamps to Python datetime
|
||||
- 2012-10-09: published [python-oletools](http://www.decalage.info/python/oletools), a package of analysis tools based on OleFileIO_PL
|
||||
- **2015-01-25 v0.42**: improved handling of special characters in stream/storage names on Python 2.x (using UTF-8
|
||||
instead of Latin-1), fixed bug in listdir with empty storages.
|
||||
- 2014-11-25 v0.41: OleFileIO.open and isOleFile now support OLE files stored in byte strings, fixed installer for
|
||||
python 3, added support for Jython (Niko Ehrenfeuchter)
|
||||
- 2014-10-01 v0.40: renamed OleFileIO_PL to olefile, added initial write support for streams >4K, updated doc and
|
||||
license, improved the setup script.
|
||||
- 2014-07-27 v0.31: fixed support for large files with 4K sectors, thanks to Niko Ehrenfeuchter, Martijn Berger and
|
||||
Dave Jones. Added test scripts from Pillow (by hugovk). Fixed setup for Python 3 (Martin Panter)
|
||||
- 2014-02-04 v0.30: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work.
|
||||
- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed
|
||||
parsing of direntry timestamps
|
||||
- 2013-05-27 v0.25: improved metadata extraction, properties parsing and exception handling, fixed
|
||||
[issue #12](https://bitbucket.org/decalage/olefileio_pl/issue/12/error-when-converting-timestamps-in-ole)
|
||||
- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved
|
||||
getproperties to convert timestamps to Python datetime
|
||||
- 2012-10-09: published [python-oletools](http://www.decalage.info/python/oletools), a package of analysis tools based
|
||||
on OleFileIO_PL
|
||||
- 2012-09-11 v0.23: added support for file-like objects, fixed [issue #8](https://bitbucket.org/decalage/olefileio_pl/issue/8/bug-with-file-object)
|
||||
- 2012-02-17 v0.22: fixed issues #7 (bug in getproperties) and #2 (added close method)
|
||||
- 2011-10-20: code hosted on bitbucket to ease contributions and bug tracking
|
||||
|
@ -29,20 +48,50 @@ Follow all updates and news on Twitter: <https://twitter.com/decalage2>
|
|||
- 2009-12-10 v0.19: fixed support for 64 bits platforms (thanks to Ben G. and Martijn for reporting the bug)
|
||||
- see changelog in source code for more info.
|
||||
|
||||
Download
|
||||
--------
|
||||
Download/Install
|
||||
----------------
|
||||
|
||||
The archive is available on [the project page](https://bitbucket.org/decalage/olefileio_pl/downloads).
|
||||
If you have pip or setuptools installed (pip is included in Python 2.7.9+), you may simply run **pip install olefile**
|
||||
or **easy_install olefile** for the first installation.
|
||||
|
||||
To update olefile, run **pip install -U olefile**.
|
||||
|
||||
Otherwise, see https://bitbucket.org/decalage/olefileio_pl/wiki/Install
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Parse and read any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls, PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes, Zeiss AxioVision ZVI files, Olympus FluoView OIB files, ...
|
||||
- Parse, read and write any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls,
|
||||
PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes,
|
||||
Zeiss AxioVision ZVI files, Olympus FluoView OIB files, etc
|
||||
- List all the streams and storages contained in an OLE file
|
||||
- Open streams as files
|
||||
- Parse and read property streams, containing metadata of the file
|
||||
- Portable, pure Python module, no dependency
|
||||
|
||||
olefile can be used as an independent package or with PIL/Pillow.
|
||||
|
||||
olefile is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data (especially
|
||||
for security purposes such as malware analysis and forensics), then please also check my
|
||||
[python-oletools](http://www.decalage.info/python/oletools), which are built upon olefile and provide a higher-level interface.
|
||||
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
olefile is based on the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent
|
||||
Python Imaging Library, created and maintained by Fredrik Lundh. The olefile API is still compatible with PIL, but
|
||||
since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust
|
||||
design. From 2005 to 2014 the project was called OleFileIO_PL, and in 2014 I changed its name to olefile to celebrate
|
||||
its 9 years and its new write features.
|
||||
|
||||
As far as I know, olefile is the most complete and robust Python implementation to read MS OLE2 files, portable on
|
||||
several operating systems. (please tell me if you know other similar Python modules)
|
||||
|
||||
Since 2014 olefile/OleFileIO_PL has been integrated into [Pillow](http://python-pillow.org), the friendly fork
|
||||
of PIL. olefile will continue to be improved as a separate project, and new versions will be merged into Pillow
|
||||
regularly.
|
||||
|
||||
|
||||
Main improvements over the original version of OleFileIO in PIL:
|
||||
----------------------------------------------------------------
|
||||
|
@ -58,294 +107,74 @@ Main improvements over the original version of OleFileIO in PIL:
|
|||
- Can open file-like objects
|
||||
- Added setup.py and install.bat to ease installation
|
||||
- More convenient slash-based syntax for stream paths
|
||||
- Write features
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Please see the [online documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) for more information,
|
||||
especially the [OLE overview](https://bitbucket.org/decalage/olefileio_pl/wiki/OLE_Overview) and the
|
||||
[API page](https://bitbucket.org/decalage/olefileio_pl/wiki/API) which describe how to use olefile in Python applications.
|
||||
A copy of the same documentation is also provided in the doc subfolder of the olefile package.
|
||||
|
||||
How to use this module
|
||||
----------------------
|
||||
|
||||
OleFileIO_PL can be used as an independent module or with PIL. The main functions and methods are explained below.
|
||||
|
||||
For more information, see also the file **OleFileIO_PL.html**, sample code at the end of the module itself, and docstrings within the code.
|
||||
|
||||
### About the structure of OLE files ###
|
||||
|
||||
An OLE file can be seen as a mini file system or a Zip archive: It contains **streams** of data that look like files embedded within the OLE file. Each stream has a name. For example, the main stream of a MS Word document containing its text is named "WordDocument".
|
||||
|
||||
An OLE file can also contain **storages**. A storage is a folder that contains streams or other storages. For example, a MS Word document with VBA macros has a storage called "Macros".
|
||||
|
||||
Special streams can contain **properties**. A property is a specific value that can be used to store information such as the metadata of a document (title, author, creation date, etc). Property stream names usually start with the character '\x05'.
|
||||
|
||||
For example, a typical MS Word document may look like this:
|
||||
|
||||
\x05DocumentSummaryInformation (stream)
|
||||
\x05SummaryInformation (stream)
|
||||
WordDocument (stream)
|
||||
Macros (storage)
|
||||
PROJECT (stream)
|
||||
PROJECTwm (stream)
|
||||
VBA (storage)
|
||||
Module1 (stream)
|
||||
ThisDocument (stream)
|
||||
_VBA_PROJECT (stream)
|
||||
dir (stream)
|
||||
ObjectPool (storage)
|
||||
|
||||
|
||||
|
||||
### Import OleFileIO_PL ###
|
||||
|
||||
:::python
|
||||
import OleFileIO_PL
|
||||
|
||||
As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. If your application needs to be compatible with Python 2.5 or older, you may use the following code to load the old version when needed:
|
||||
|
||||
:::python
|
||||
try:
|
||||
import OleFileIO_PL
|
||||
except:
|
||||
import OleFileIO_PL2 as OleFileIO_PL
|
||||
|
||||
If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
|
||||
|
||||
|
||||
### Test if a file is an OLE container ###
|
||||
|
||||
Use isOleFile to check if the first bytes of the file contain the Magic for OLE files, before opening it. isOleFile returns True if it is an OLE file, False otherwise (new in v0.16).
|
||||
|
||||
:::python
|
||||
assert OleFileIO_PL.isOleFile('myfile.doc')
|
||||
|
||||
|
||||
### Open an OLE file from disk ###
|
||||
|
||||
Create an OleFileIO object with the file path as parameter:
|
||||
|
||||
:::python
|
||||
ole = OleFileIO_PL.OleFileIO('myfile.doc')
|
||||
|
||||
### Open an OLE file from a file-like object ###
|
||||
|
||||
This is useful if the file is not on disk, e.g. already stored in a string or as a file-like object.
|
||||
|
||||
:::python
|
||||
ole = OleFileIO_PL.OleFileIO(f)
|
||||
|
||||
For example the code below reads a file into a string, then uses BytesIO to turn it into a file-like object.
|
||||
|
||||
:::python
|
||||
data = open('myfile.doc', 'rb').read()
|
||||
f = io.BytesIO(data) # or StringIO.StringIO for Python 2.x
|
||||
ole = OleFileIO_PL.OleFileIO(f)
|
||||
|
||||
### How to handle malformed OLE files ###
|
||||
|
||||
By default, the parser is configured to be as robust and permissive as possible, allowing to parse most malformed OLE files. Only fatal errors will raise an exception. It is possible to tell the parser to be more strict in order to raise exceptions for files that do not fully conform to the OLE specifications, using the raise_defect option (new in v0.14):
|
||||
|
||||
:::python
|
||||
ole = OleFileIO_PL.OleFileIO('myfile.doc', raise_defects=DEFECT_INCORRECT)
|
||||
|
||||
When the parsing is done, the list of non-fatal issues detected is available as a list in the parsing_issues attribute of the OleFileIO object (new in 0.25):
|
||||
|
||||
:::python
|
||||
print('Non-fatal issues raised during parsing:')
|
||||
if ole.parsing_issues:
|
||||
for exctype, msg in ole.parsing_issues:
|
||||
print('- %s: %s' % (exctype.__name__, msg))
|
||||
else:
|
||||
print('None')
|
||||
|
||||
|
||||
### Syntax for stream and storage path ###
|
||||
|
||||
Two different syntaxes are allowed for methods that need or return the path of streams and storages:
|
||||
|
||||
1) Either a **list of strings** including all the storages from the root up to the stream/storage name. For example a stream called "WordDocument" at the root will have ['WordDocument'] as full path. A stream called "ThisDocument" located in the storage "Macros/VBA" will be ['Macros', 'VBA', 'ThisDocument']. This is the original syntax from PIL. While hard to read and not very convenient, this syntax works in all cases.
|
||||
|
||||
2) Or a **single string with slashes** to separate storage and stream names (similar to the Unix path syntax). The previous examples would be 'WordDocument' and 'Macros/VBA/ThisDocument'. This syntax is easier, but may fail if a stream or storage name contains a slash. (new in v0.15)
|
||||
|
||||
Both are case-insensitive.
|
||||
|
||||
Switching between the two is easy:
|
||||
|
||||
:::python
|
||||
slash_path = '/'.join(list_path)
|
||||
list_path = slash_path.split('/')
|
||||
|
||||
|
||||
### Get the list of streams ###
|
||||
|
||||
listdir() returns a list of all the streams contained in the OLE file, including those stored in storages. Each stream is listed itself as a list, as described above.
|
||||
|
||||
:::python
|
||||
print(ole.listdir())
|
||||
|
||||
Sample result:
|
||||
|
||||
:::python
|
||||
[['\x01CompObj'], ['\x05DocumentSummaryInformation'], ['\x05SummaryInformation']
|
||||
, ['1Table'], ['Macros', 'PROJECT'], ['Macros', 'PROJECTwm'], ['Macros', 'VBA',
|
||||
'Module1'], ['Macros', 'VBA', 'ThisDocument'], ['Macros', 'VBA', '_VBA_PROJECT']
|
||||
, ['Macros', 'VBA', 'dir'], ['ObjectPool'], ['WordDocument']]
|
||||
|
||||
As an option it is possible to choose if storages should also be listed, with or without streams (new in v0.26):
|
||||
|
||||
:::python
|
||||
ole.listdir (streams=False, storages=True)
|
||||
|
||||
|
||||
### Test if known streams/storages exist: ###
|
||||
|
||||
exists(path) checks if a given stream or storage exists in the OLE file (new in v0.16).
|
||||
|
||||
:::python
|
||||
if ole.exists('worddocument'):
|
||||
print("This is a Word document.")
|
||||
if ole.exists('macros/vba'):
|
||||
print("This document seems to contain VBA macros.")
|
||||
|
||||
|
||||
### Read data from a stream ###
|
||||
|
||||
openstream(path) opens a stream as a file-like object.
|
||||
|
||||
The following example extracts the "Pictures" stream from a PPT file:
|
||||
|
||||
:::python
|
||||
pics = ole.openstream('Pictures')
|
||||
data = pics.read()
|
||||
|
||||
|
||||
### Get information about a stream/storage ###
|
||||
|
||||
Several methods can provide the size, type and timestamps of a given stream/storage:
|
||||
|
||||
get_size(path) returns the size of a stream in bytes (new in v0.16):
|
||||
|
||||
:::python
|
||||
s = ole.get_size('WordDocument')
|
||||
|
||||
get_type(path) returns the type of a stream/storage, as one of the following constants: STGTY\_STREAM for a stream, STGTY\_STORAGE for a storage, STGTY\_ROOT for the root entry, and False for a non existing path (new in v0.15).
|
||||
|
||||
:::python
|
||||
t = ole.get_type('WordDocument')
|
||||
|
||||
get\_ctime(path) and get\_mtime(path) return the creation and modification timestamps of a stream/storage, as a Python datetime object with UTC timezone. Please note that these timestamps are only present if the application that created the OLE file explicitly stored them, which is rarely the case. When not present, these methods return None (new in v0.26).
|
||||
|
||||
:::python
|
||||
c = ole.get_ctime('WordDocument')
|
||||
m = ole.get_mtime('WordDocument')
|
||||
|
||||
The root storage is a special case: You can get its creation and modification timestamps using the OleFileIO.root attribute (new in v0.26):
|
||||
|
||||
:::python
|
||||
c = ole.root.getctime()
|
||||
m = ole.root.getmtime()
|
||||
|
||||
### Extract metadata ###
|
||||
|
||||
get_metadata() will check if standard property streams exist, parse all the properties they contain, and return an OleMetadata object with the found properties as attributes (new in v0.24).
|
||||
|
||||
:::python
|
||||
meta = ole.get_metadata()
|
||||
print('Author:', meta.author)
|
||||
print('Title:', meta.title)
|
||||
print('Creation date:', meta.create_time)
|
||||
# print all metadata:
|
||||
meta.dump()
|
||||
|
||||
Available attributes include:
|
||||
|
||||
codepage, title, subject, author, keywords, comments, template,
|
||||
last_saved_by, revision_number, total_edit_time, last_printed, create_time,
|
||||
last_saved_time, num_pages, num_words, num_chars, thumbnail,
|
||||
creating_application, security, codepage_doc, category, presentation_target,
|
||||
bytes, lines, paragraphs, slides, notes, hidden_slides, mm_clips,
|
||||
scale_crop, heading_pairs, titles_of_parts, manager, company, links_dirty,
|
||||
chars_with_spaces, unused, shared_doc, link_base, hlinks, hlinks_changed,
|
||||
version, dig_sig, content_type, content_status, language, doc_version
|
||||
|
||||
See the source code of the OleMetadata class for more information.
|
||||
|
||||
|
||||
### Parse a property stream ###
|
||||
|
||||
get\_properties(path) can be used to parse any property stream that is not handled by get\_metadata. It returns a dictionary indexed by integers. Each integer is the index of the property, pointing to its value. For example in the standard property stream '\x05SummaryInformation', the document title is property #2, and the subject is #3.
|
||||
|
||||
:::python
|
||||
p = ole.getproperties('specialprops')
|
||||
|
||||
By default as in the original PIL version, timestamp properties are converted into a number of seconds since Jan 1,1601. With the option convert\_time, you can obtain more convenient Python datetime objects (UTC timezone). If some time properties should not be converted (such as total editing time in '\x05SummaryInformation'), the list of indexes can be passed as no_conversion (new in v0.25):
|
||||
|
||||
:::python
|
||||
p = ole.getproperties('specialprops', convert_time=True, no_conversion=[10])
|
||||
|
||||
|
||||
### Close the OLE file ###
|
||||
|
||||
Unless your application is a simple script that terminates after processing an OLE file, do not forget to close each OleFileIO object after parsing to close the file on disk. (new in v0.22)
|
||||
|
||||
:::python
|
||||
ole.close()
|
||||
|
||||
### Use OleFileIO_PL as a script ###
|
||||
|
||||
OleFileIO_PL can also be used as a script from the command-line to display the structure of an OLE file and its metadata, for example:
|
||||
|
||||
OleFileIO_PL.py myfile.doc
|
||||
|
||||
You can use the option -c to check that all streams can be read fully, and -d to generate very verbose debugging information.
|
||||
|
||||
## Real-life examples ##
|
||||
|
||||
A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/).
|
||||
|
||||
See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features OleFileIO_PL.
|
||||
See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features olefile.
|
||||
|
||||
About Python 2 and 3
|
||||
--------------------
|
||||
|
||||
OleFileIO\_PL used to support only Python 2.x. As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. See above the "import" section for a workaround.
|
||||
|
||||
If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
|
||||
|
||||
How to contribute
|
||||
-----------------
|
||||
|
||||
The code is available in [a Mercurial repository on bitbucket](https://bitbucket.org/decalage/olefileio_pl). You may use it to submit enhancements or to report any issue.
|
||||
|
||||
If you would like to help us improve this module, or simply provide feedback, please [contact me](http://decalage.info/contact). You can help in many ways:
|
||||
|
||||
- test this module on different platforms / Python versions
|
||||
- find and report bugs
|
||||
- improve documentation, code samples, docstrings
|
||||
- write unittest test cases
|
||||
- provide tricky malformed files
|
||||
|
||||
How to report bugs
|
||||
------------------
|
||||
|
||||
To report a bug, for example a normal file which is not parsed correctly, please use the [issue reporting page](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open), or if you prefer to do it privately, use this [contact form](http://decalage.info/contact). Please provide all the information about the context and how to reproduce the bug.
|
||||
|
||||
If possible please join the debugging output of OleFileIO_PL. For this, launch the following command :
|
||||
|
||||
OleFileIO_PL.py -d -c file >debug.txt
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
OleFileIO_PL is open-source.
|
||||
olefile (formerly OleFileIO_PL) is copyright (c) 2005-2015 Philippe Lagadec
|
||||
([http://www.decalage.info](http://www.decalage.info))
|
||||
|
||||
OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
olefile is based on source code from the OleFileIO module of the Python Imaging Library (PIL) published by Fredrik
|
||||
Lundh under the following license:
|
||||
|
||||
The Python Imaging Library (PIL) is
|
||||
|
||||
- Copyright (c) 1997-2005 by Secret Labs AB
|
||||
Copyright © 1997-2011 by Secret Labs AB
|
||||
Copyright © 1995-2011 by Fredrik Lundh
|
||||
|
||||
- Copyright (c) 1995-2005 by Fredrik Lundh
|
||||
By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read,
|
||||
understood, and will comply with the following terms and conditions:
|
||||
|
||||
By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions:
|
||||
Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and
|
||||
without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that
|
||||
copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or
|
||||
the author not be used in advertising or publicity pertaining to distribution of the software without specific, written
|
||||
prior permission.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission.
|
||||
|
||||
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||
SOFTWARE.
|
||||
|
|
|
@ -16,11 +16,12 @@
|
|||
#
|
||||
|
||||
from PIL import EpsImagePlugin
|
||||
|
||||
import sys
|
||||
|
||||
##
|
||||
# Simple Postscript graphics interface.
|
||||
|
||||
|
||||
class PSDraw(object):
|
||||
"""
|
||||
Sets up printing to the given file. If **file** is omitted,
|
||||
|
@ -29,12 +30,11 @@ class PSDraw(object):
|
|||
|
||||
def __init__(self, fp=None):
|
||||
if not fp:
|
||||
import sys
|
||||
fp = sys.stdout
|
||||
self.fp = fp
|
||||
|
||||
def _fp_write(self, to_write):
|
||||
if bytes is str:
|
||||
if bytes is str or self.fp == sys.stdout:
|
||||
self.fp.write(to_write)
|
||||
else:
|
||||
self.fp.write(bytes(to_write, 'UTF-8'))
|
||||
|
@ -47,7 +47,7 @@ class PSDraw(object):
|
|||
"/showpage { } def\n"
|
||||
"%%EndComments\n"
|
||||
"%%BeginDocument\n")
|
||||
# self.fp_write(ERROR_PS) # debugging!
|
||||
# self._fp_write(ERROR_PS) # debugging!
|
||||
self._fp_write(EDROFF_PS)
|
||||
self._fp_write(VDI_PS)
|
||||
self._fp_write("%%EndProlog\n")
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
# Image plugin for Palm pixmap images (output only).
|
||||
##
|
||||
|
||||
__version__ = "1.0"
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
__version__ = "1.0"
|
||||
|
||||
_Palm8BitColormapValues = (
|
||||
(255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255),
|
||||
(255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204),
|
||||
|
@ -30,15 +30,15 @@ _Palm8BitColormapValues = (
|
|||
(102, 255, 204), (102, 204, 204), (102, 153, 204), (102, 102, 204),
|
||||
(102, 51, 204), (102, 0, 204), (102, 255, 153), (102, 204, 153),
|
||||
(102, 153, 153), (102, 102, 153), (102, 51, 153), (102, 0, 153),
|
||||
( 51, 255, 255), ( 51, 204, 255), ( 51, 153, 255), ( 51, 102, 255),
|
||||
( 51, 51, 255), ( 51, 0, 255), ( 51, 255, 204), ( 51, 204, 204),
|
||||
( 51, 153, 204), ( 51, 102, 204), ( 51, 51, 204), ( 51, 0, 204),
|
||||
( 51, 255, 153), ( 51, 204, 153), ( 51, 153, 153), ( 51, 102, 153),
|
||||
( 51, 51, 153), ( 51, 0, 153), ( 0, 255, 255), ( 0, 204, 255),
|
||||
( 0, 153, 255), ( 0, 102, 255), ( 0, 51, 255), ( 0, 0, 255),
|
||||
( 0, 255, 204), ( 0, 204, 204), ( 0, 153, 204), ( 0, 102, 204),
|
||||
( 0, 51, 204), ( 0, 0, 204), ( 0, 255, 153), ( 0, 204, 153),
|
||||
( 0, 153, 153), ( 0, 102, 153), ( 0, 51, 153), ( 0, 0, 153),
|
||||
(51, 255, 255), (51, 204, 255), (51, 153, 255), (51, 102, 255),
|
||||
(51, 51, 255), (51, 0, 255), (51, 255, 204), (51, 204, 204),
|
||||
(51, 153, 204), (51, 102, 204), (51, 51, 204), (51, 0, 204),
|
||||
(51, 255, 153), (51, 204, 153), (51, 153, 153), (51, 102, 153),
|
||||
(51, 51, 153), (51, 0, 153), (0, 255, 255), (0, 204, 255),
|
||||
(0, 153, 255), (0, 102, 255), (0, 51, 255), (0, 0, 255),
|
||||
(0, 255, 204), (0, 204, 204), (0, 153, 204), (0, 102, 204),
|
||||
(0, 51, 204), (0, 0, 204), (0, 255, 153), (0, 204, 153),
|
||||
(0, 153, 153), (0, 102, 153), (0, 51, 153), (0, 0, 153),
|
||||
(255, 255, 102), (255, 204, 102), (255, 153, 102), (255, 102, 102),
|
||||
(255, 51, 102), (255, 0, 102), (255, 255, 51), (255, 204, 51),
|
||||
(255, 153, 51), (255, 102, 51), (255, 51, 51), (255, 0, 51),
|
||||
|
@ -57,25 +57,25 @@ _Palm8BitColormapValues = (
|
|||
(102, 255, 51), (102, 204, 51), (102, 153, 51), (102, 102, 51),
|
||||
(102, 51, 51), (102, 0, 51), (102, 255, 0), (102, 204, 0),
|
||||
(102, 153, 0), (102, 102, 0), (102, 51, 0), (102, 0, 0),
|
||||
( 51, 255, 102), ( 51, 204, 102), ( 51, 153, 102), ( 51, 102, 102),
|
||||
( 51, 51, 102), ( 51, 0, 102), ( 51, 255, 51), ( 51, 204, 51),
|
||||
( 51, 153, 51), ( 51, 102, 51), ( 51, 51, 51), ( 51, 0, 51),
|
||||
( 51, 255, 0), ( 51, 204, 0), ( 51, 153, 0), ( 51, 102, 0),
|
||||
( 51, 51, 0), ( 51, 0, 0), ( 0, 255, 102), ( 0, 204, 102),
|
||||
( 0, 153, 102), ( 0, 102, 102), ( 0, 51, 102), ( 0, 0, 102),
|
||||
( 0, 255, 51), ( 0, 204, 51), ( 0, 153, 51), ( 0, 102, 51),
|
||||
( 0, 51, 51), ( 0, 0, 51), ( 0, 255, 0), ( 0, 204, 0),
|
||||
( 0, 153, 0), ( 0, 102, 0), ( 0, 51, 0), ( 17, 17, 17),
|
||||
( 34, 34, 34), ( 68, 68, 68), ( 85, 85, 85), (119, 119, 119),
|
||||
(51, 255, 102), (51, 204, 102), (51, 153, 102), (51, 102, 102),
|
||||
(51, 51, 102), (51, 0, 102), (51, 255, 51), (51, 204, 51),
|
||||
(51, 153, 51), (51, 102, 51), (51, 51, 51), (51, 0, 51),
|
||||
(51, 255, 0), (51, 204, 0), (51, 153, 0), (51, 102, 0),
|
||||
(51, 51, 0), (51, 0, 0), (0, 255, 102), (0, 204, 102),
|
||||
(0, 153, 102), (0, 102, 102), (0, 51, 102), (0, 0, 102),
|
||||
(0, 255, 51), (0, 204, 51), (0, 153, 51), (0, 102, 51),
|
||||
(0, 51, 51), (0, 0, 51), (0, 255, 0), (0, 204, 0),
|
||||
(0, 153, 0), (0, 102, 0), (0, 51, 0), (17, 17, 17),
|
||||
(34, 34, 34), (68, 68, 68), (85, 85, 85), (119, 119, 119),
|
||||
(136, 136, 136), (170, 170, 170), (187, 187, 187), (221, 221, 221),
|
||||
(238, 238, 238), (192, 192, 192), (128, 0, 0), (128, 0, 128),
|
||||
( 0, 128, 0), ( 0, 128, 128), ( 0, 0, 0), ( 0, 0, 0),
|
||||
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
|
||||
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
|
||||
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
|
||||
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
|
||||
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
|
||||
( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0))
|
||||
(0, 128, 0), (0, 128, 128), (0, 0, 0), (0, 0, 0),
|
||||
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
|
||||
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
|
||||
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
|
||||
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
|
||||
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
|
||||
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0))
|
||||
|
||||
|
||||
# so build a prototype image to be used for palette resampling
|
||||
|
@ -227,7 +227,8 @@ def _save(im, fp, filename, check=0):
|
|||
ImageFile._save(
|
||||
im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))])
|
||||
|
||||
fp.flush()
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
i8 = _binary.i8
|
||||
|
||||
|
||||
|
@ -55,6 +54,6 @@ class PcdImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# registry
|
||||
|
||||
Image.register_open("PCD", PcdImageFile)
|
||||
Image.register_open(PcdImageFile.format, PcdImageFile)
|
||||
|
||||
Image.register_extension("PCD", ".pcd")
|
||||
Image.register_extension(PcdImageFile.format, ".pcd")
|
||||
|
|
|
@ -27,8 +27,11 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
o8 = _binary.o8
|
||||
|
@ -59,17 +62,15 @@ class PcxImageFile(ImageFile.ImageFile):
|
|||
bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1
|
||||
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
|
||||
raise SyntaxError("bad PCX image size")
|
||||
if Image.DEBUG:
|
||||
print("BBox: %s %s %s %s" % bbox)
|
||||
logger.debug("BBox: %s %s %s %s", *bbox)
|
||||
|
||||
# format
|
||||
version = i8(s[1])
|
||||
bits = i8(s[3])
|
||||
planes = i8(s[65])
|
||||
stride = i16(s, 66)
|
||||
if Image.DEBUG:
|
||||
print("PCX version %s, bits %s, planes %s, stride %s" %
|
||||
(version, bits, planes, stride))
|
||||
logger.debug("PCX version %s, bits %s, planes %s, stride %s",
|
||||
version, bits, planes, stride)
|
||||
|
||||
self.info["dpi"] = i16(s, 12), i16(s, 14)
|
||||
|
||||
|
@ -107,8 +108,7 @@ class PcxImageFile(ImageFile.ImageFile):
|
|||
self.size = bbox[2]-bbox[0], bbox[3]-bbox[1]
|
||||
|
||||
bbox = (0, 0) + self.size
|
||||
if Image.DEBUG:
|
||||
print("size: %sx%s" % self.size)
|
||||
logger.debug("size: %sx%s", *self.size)
|
||||
|
||||
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
|
||||
|
||||
|
@ -144,9 +144,8 @@ def _save(im, fp, filename, check=0):
|
|||
# Ideally it should be passed in in the state, but the bytes value
|
||||
# gets overwritten.
|
||||
|
||||
if Image.DEBUG:
|
||||
print("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
|
||||
im.size[0], bits, stride))
|
||||
logger.debug("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
|
||||
im.size[0], bits, stride)
|
||||
|
||||
# under windows, we could determine the current screen size with
|
||||
# "Image.core.display_mode()[1]", but I think that's overkill...
|
||||
|
@ -182,7 +181,7 @@ def _save(im, fp, filename, check=0):
|
|||
# --------------------------------------------------------------------
|
||||
# registry
|
||||
|
||||
Image.register_open("PCX", PcxImageFile, _accept)
|
||||
Image.register_save("PCX", _save)
|
||||
Image.register_open(PcxImageFile.format, PcxImageFile, _accept)
|
||||
Image.register_save(PcxImageFile.format, _save)
|
||||
|
||||
Image.register_extension("PCX", ".pcx")
|
||||
Image.register_extension(PcxImageFile.format, ".pcx")
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
# Image plugin for PDF images (output only).
|
||||
##
|
||||
|
||||
__version__ = "0.4"
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL._binary import i8
|
||||
import io
|
||||
|
||||
__version__ = "0.4"
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -51,17 +51,21 @@ def _endobj(fp):
|
|||
fp.write("endobj\n")
|
||||
|
||||
|
||||
def _save_all(im, fp, filename):
|
||||
_save(im, fp, filename, save_all=True)
|
||||
|
||||
|
||||
##
|
||||
# (Internal) Image save plugin for the PDF format.
|
||||
|
||||
def _save(im, fp, filename):
|
||||
def _save(im, fp, filename, save_all=False):
|
||||
resolution = im.encoderinfo.get("resolution", 72.0)
|
||||
|
||||
#
|
||||
# make sure image data is available
|
||||
im.load()
|
||||
|
||||
xref = [0]*(5+1) # placeholders
|
||||
xref = [0]
|
||||
|
||||
class TextWriter(object):
|
||||
def __init__(self, fp):
|
||||
|
@ -78,11 +82,6 @@ def _save(im, fp, filename):
|
|||
fp.write("%PDF-1.2\n")
|
||||
fp.write("% created by PIL PDF driver " + __version__ + "\n")
|
||||
|
||||
#
|
||||
# Get image characteristics
|
||||
|
||||
width, height = im.size
|
||||
|
||||
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)
|
||||
# or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports
|
||||
# Flatedecode (zip compression).
|
||||
|
@ -125,7 +124,7 @@ def _save(im, fp, filename):
|
|||
#
|
||||
# catalogue
|
||||
|
||||
xref[1] = fp.tell()
|
||||
xref.append(fp.tell())
|
||||
_obj(
|
||||
fp, 1,
|
||||
Type="/Catalog",
|
||||
|
@ -134,89 +133,108 @@ def _save(im, fp, filename):
|
|||
|
||||
#
|
||||
# pages
|
||||
numberOfPages = 1
|
||||
if save_all:
|
||||
try:
|
||||
numberOfPages = im.n_frames
|
||||
except AttributeError:
|
||||
# Image format does not have n_frames. It is a single frame image
|
||||
pass
|
||||
pages = [str(pageNumber*3+4)+" 0 R"
|
||||
for pageNumber in range(0, numberOfPages)]
|
||||
|
||||
xref[2] = fp.tell()
|
||||
xref.append(fp.tell())
|
||||
_obj(
|
||||
fp, 2,
|
||||
Type="/Pages",
|
||||
Count=1,
|
||||
Kids="[4 0 R]")
|
||||
Count=len(pages),
|
||||
Kids="["+"\n".join(pages)+"]")
|
||||
_endobj(fp)
|
||||
|
||||
#
|
||||
# image
|
||||
for pageNumber in range(0, numberOfPages):
|
||||
im.seek(pageNumber)
|
||||
|
||||
op = io.BytesIO()
|
||||
#
|
||||
# image
|
||||
|
||||
if filter == "/ASCIIHexDecode":
|
||||
if bits == 1:
|
||||
# FIXME: the hex encoder doesn't support packed 1-bit
|
||||
# images; do things the hard way...
|
||||
data = im.tobytes("raw", "1")
|
||||
im = Image.new("L", (len(data), 1), None)
|
||||
im.putdata(data)
|
||||
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
|
||||
elif filter == "/DCTDecode":
|
||||
Image.SAVE["JPEG"](im, op, filename)
|
||||
elif filter == "/FlateDecode":
|
||||
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
|
||||
elif filter == "/RunLengthDecode":
|
||||
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
|
||||
else:
|
||||
raise ValueError("unsupported PDF filter (%s)" % filter)
|
||||
op = io.BytesIO()
|
||||
|
||||
xref[3] = fp.tell()
|
||||
_obj(
|
||||
fp, 3,
|
||||
Type="/XObject",
|
||||
Subtype="/Image",
|
||||
Width=width, # * 72.0 / resolution,
|
||||
Height=height, # * 72.0 / resolution,
|
||||
Length=len(op.getvalue()),
|
||||
Filter=filter,
|
||||
BitsPerComponent=bits,
|
||||
DecodeParams=params,
|
||||
ColorSpace=colorspace)
|
||||
if filter == "/ASCIIHexDecode":
|
||||
if bits == 1:
|
||||
# FIXME: the hex encoder doesn't support packed 1-bit
|
||||
# images; do things the hard way...
|
||||
data = im.tobytes("raw", "1")
|
||||
im = Image.new("L", (len(data), 1), None)
|
||||
im.putdata(data)
|
||||
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
|
||||
elif filter == "/DCTDecode":
|
||||
Image.SAVE["JPEG"](im, op, filename)
|
||||
elif filter == "/FlateDecode":
|
||||
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
|
||||
elif filter == "/RunLengthDecode":
|
||||
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
|
||||
else:
|
||||
raise ValueError("unsupported PDF filter (%s)" % filter)
|
||||
|
||||
fp.write("stream\n")
|
||||
fp.fp.write(op.getvalue())
|
||||
fp.write("\nendstream\n")
|
||||
#
|
||||
# Get image characteristics
|
||||
|
||||
_endobj(fp)
|
||||
width, height = im.size
|
||||
|
||||
#
|
||||
# page
|
||||
xref.append(fp.tell())
|
||||
_obj(
|
||||
fp, pageNumber*3+3,
|
||||
Type="/XObject",
|
||||
Subtype="/Image",
|
||||
Width=width, # * 72.0 / resolution,
|
||||
Height=height, # * 72.0 / resolution,
|
||||
Length=len(op.getvalue()),
|
||||
Filter=filter,
|
||||
BitsPerComponent=bits,
|
||||
DecodeParams=params,
|
||||
ColorSpace=colorspace)
|
||||
|
||||
xref[4] = fp.tell()
|
||||
_obj(fp, 4)
|
||||
fp.write(
|
||||
"<<\n/Type /Page\n/Parent 2 0 R\n"
|
||||
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
|
||||
"/XObject << /image 3 0 R >>\n>>\n"
|
||||
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % (
|
||||
procset,
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution)))
|
||||
_endobj(fp)
|
||||
fp.write("stream\n")
|
||||
fp.fp.write(op.getvalue())
|
||||
fp.write("\nendstream\n")
|
||||
|
||||
#
|
||||
# page contents
|
||||
_endobj(fp)
|
||||
|
||||
op = TextWriter(io.BytesIO())
|
||||
#
|
||||
# page
|
||||
|
||||
op.write(
|
||||
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution)))
|
||||
xref.append(fp.tell())
|
||||
_obj(fp, pageNumber*3+4)
|
||||
fp.write(
|
||||
"<<\n/Type /Page\n/Parent 2 0 R\n"
|
||||
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
|
||||
"/XObject << /image %d 0 R >>\n>>\n"
|
||||
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
|
||||
procset,
|
||||
pageNumber*3+3,
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution),
|
||||
pageNumber*3+5))
|
||||
_endobj(fp)
|
||||
|
||||
xref[5] = fp.tell()
|
||||
_obj(fp, 5, Length=len(op.fp.getvalue()))
|
||||
#
|
||||
# page contents
|
||||
|
||||
fp.write("stream\n")
|
||||
fp.fp.write(op.fp.getvalue())
|
||||
fp.write("\nendstream\n")
|
||||
op = TextWriter(io.BytesIO())
|
||||
|
||||
_endobj(fp)
|
||||
op.write(
|
||||
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution)))
|
||||
|
||||
xref.append(fp.tell())
|
||||
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
|
||||
|
||||
fp.write("stream\n")
|
||||
fp.fp.write(op.fp.getvalue())
|
||||
fp.write("\nendstream\n")
|
||||
|
||||
_endobj(fp)
|
||||
|
||||
#
|
||||
# trailer
|
||||
|
@ -226,12 +244,14 @@ def _save(im, fp, filename):
|
|||
fp.write("%010d 00000 n \n" % x)
|
||||
fp.write("trailer\n<<\n/Size %d\n/Root 1 0 R\n>>\n" % len(xref))
|
||||
fp.write("startxref\n%d\n%%%%EOF\n" % startxref)
|
||||
fp.flush()
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_save("PDF", _save)
|
||||
Image.register_save_all("PDF", _save_all)
|
||||
|
||||
Image.register_extension("PDF", ".pdf")
|
||||
|
||||
|
|
|
@ -19,15 +19,14 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
#
|
||||
# helpers
|
||||
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
##
|
||||
|
@ -63,7 +62,7 @@ class PixarImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("PIXAR", PixarImageFile)
|
||||
Image.register_open(PixarImageFile.format, PixarImageFile)
|
||||
|
||||
#
|
||||
# FIXME: what's the standard extension?
|
||||
|
|
|
@ -33,12 +33,15 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
__version__ = "0.9"
|
||||
|
||||
import logging
|
||||
import re
|
||||
import zlib
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
import zlib
|
||||
|
||||
__version__ = "0.9"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16be
|
||||
|
@ -129,8 +132,7 @@ class ChunkStream(object):
|
|||
def call(self, cid, pos, length):
|
||||
"Call the appropriate chunk handler"
|
||||
|
||||
if Image.DEBUG:
|
||||
print("STREAM", cid, pos, length)
|
||||
logger.debug("STREAM %s %s %s", cid, pos, length)
|
||||
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length)
|
||||
|
||||
def crc(self, cid, data):
|
||||
|
@ -293,9 +295,8 @@ class PngStream(ChunkStream):
|
|||
# Compression method 1 byte (0)
|
||||
# Compressed profile n bytes (zlib with deflate compression)
|
||||
i = s.find(b"\0")
|
||||
if Image.DEBUG:
|
||||
print("iCCP profile name", s[:i])
|
||||
print("Compression method", i8(s[i]))
|
||||
logger.debug("iCCP profile name %s", s[:i])
|
||||
logger.debug("Compression method %s", i8(s[i]))
|
||||
comp_method = i8(s[i])
|
||||
if comp_method != 0:
|
||||
raise SyntaxError("Unknown compression method %s in iCCP chunk" %
|
||||
|
@ -507,8 +508,7 @@ class PngImageFile(ImageFile.ImageFile):
|
|||
except EOFError:
|
||||
break
|
||||
except AttributeError:
|
||||
if Image.DEBUG:
|
||||
print(cid, pos, length, "(unknown)")
|
||||
logger.debug("%s %s %s (unknown)", cid, pos, length)
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
|
||||
self.png.crc(cid, s)
|
||||
|
@ -762,10 +762,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
|||
|
||||
chunk(fp, b"IEND", b"")
|
||||
|
||||
try:
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -803,9 +801,9 @@ def getchunks(im, **params):
|
|||
# --------------------------------------------------------------------
|
||||
# Registry
|
||||
|
||||
Image.register_open("PNG", PngImageFile, _accept)
|
||||
Image.register_save("PNG", _save)
|
||||
Image.register_open(PngImageFile.format, PngImageFile, _accept)
|
||||
Image.register_save(PngImageFile.format, _save)
|
||||
|
||||
Image.register_extension("PNG", ".png")
|
||||
Image.register_extension(PngImageFile.format, ".png")
|
||||
|
||||
Image.register_mime("PNG", "image/png")
|
||||
Image.register_mime(PngImageFile.format, "image/png")
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
import string
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
@ -93,6 +93,8 @@ class PpmImageFile(ImageFile.ImageFile):
|
|||
s = self.fp.read(1)
|
||||
if s not in b_whitespace:
|
||||
break
|
||||
if s == b"":
|
||||
raise ValueError("File does not extend beyond magic number")
|
||||
if s != b"#":
|
||||
break
|
||||
s = self.fp.readline()
|
||||
|
@ -164,9 +166,9 @@ def _save(im, fp, filename):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("PPM", PpmImageFile, _accept)
|
||||
Image.register_save("PPM", _save)
|
||||
Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
|
||||
Image.register_save(PpmImageFile.format, _save)
|
||||
|
||||
Image.register_extension("PPM", ".pbm")
|
||||
Image.register_extension("PPM", ".pgm")
|
||||
Image.register_extension("PPM", ".ppm")
|
||||
Image.register_extension(PpmImageFile.format, ".pbm")
|
||||
Image.register_extension(PpmImageFile.format, ".pgm")
|
||||
Image.register_extension(PpmImageFile.format, ".ppm")
|
||||
|
|
|
@ -136,6 +136,10 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
def n_frames(self):
|
||||
return len(self.layers)
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
return len(self.layers) > 1
|
||||
|
||||
def seek(self, layer):
|
||||
# seek to given layer (1..max)
|
||||
if layer == self.frame:
|
||||
|
@ -303,6 +307,6 @@ def _maketile(file, mode, bbox, channels):
|
|||
# --------------------------------------------------------------------
|
||||
# registry
|
||||
|
||||
Image.register_open("PSD", PsdImageFile, _accept)
|
||||
Image.register_open(PsdImageFile.format, PsdImageFile, _accept)
|
||||
|
||||
Image.register_extension("PSD", ".psd")
|
||||
Image.register_extension(PsdImageFile.format, ".psd")
|
||||
|
|
|
@ -22,10 +22,14 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
from cffi import FFI
|
||||
import logging
|
||||
import sys
|
||||
|
||||
DEBUG = 0
|
||||
from cffi import FFI
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
defs = """
|
||||
struct Pixel_RGBA {
|
||||
|
@ -50,8 +54,9 @@ class PyAccess(object):
|
|||
self.xsize = vals['xsize']
|
||||
self.ysize = vals['ysize']
|
||||
|
||||
if DEBUG:
|
||||
print(vals)
|
||||
# Debugging is polluting test traces, only useful here
|
||||
# when hacking on PyAccess
|
||||
# logger.debug("%s", vals)
|
||||
self._post_init()
|
||||
|
||||
def _post_init(self):
|
||||
|
@ -305,11 +310,8 @@ else:
|
|||
def new(img, readonly=False):
|
||||
access_type = mode_map.get(img.mode, None)
|
||||
if not access_type:
|
||||
if DEBUG:
|
||||
print("PyAccess Not Implemented: %s" % img.mode)
|
||||
logger.debug("PyAccess Not Implemented: %s", img.mode)
|
||||
return None
|
||||
if DEBUG:
|
||||
print("New PyAccess: %s" % img.mode)
|
||||
return access_type(img, readonly)
|
||||
|
||||
# End of file
|
||||
|
|
|
@ -18,18 +18,16 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16be
|
||||
i32 = _binary.i32be
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return i16(prefix) == 474
|
||||
return len(prefix) >= 2 and i16(prefix) == 474
|
||||
|
||||
|
||||
##
|
||||
|
@ -81,11 +79,11 @@ class SgiImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# registry
|
||||
|
||||
Image.register_open("SGI", SgiImageFile, _accept)
|
||||
Image.register_open(SgiImageFile.format, SgiImageFile, _accept)
|
||||
|
||||
Image.register_extension("SGI", ".bw")
|
||||
Image.register_extension("SGI", ".rgb")
|
||||
Image.register_extension("SGI", ".rgba")
|
||||
Image.register_extension("SGI", ".sgi")
|
||||
Image.register_extension(SgiImageFile.format, ".bw")
|
||||
Image.register_extension(SgiImageFile.format, ".rgb")
|
||||
Image.register_extension(SgiImageFile.format, ".rgba")
|
||||
Image.register_extension(SgiImageFile.format, ".sgi")
|
||||
|
||||
# End of file
|
||||
|
|
|
@ -27,10 +27,10 @@
|
|||
# image data from electron microscopy and tomography.
|
||||
#
|
||||
# Spider home page:
|
||||
# http://www.wadsworth.org/spider_doc/spider/docs/spider.html
|
||||
# http://spider.wadsworth.org/spider_doc/spider/docs/spider.html
|
||||
#
|
||||
# Details about the Spider image format:
|
||||
# http://www.wadsworth.org/spider_doc/spider/docs/image_doc.html
|
||||
# http://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
@ -50,6 +50,8 @@ def isInt(f):
|
|||
return 0
|
||||
except ValueError:
|
||||
return 0
|
||||
except OverflowError:
|
||||
return 0
|
||||
|
||||
iforms = [1, 3, -11, -12, -21, -22]
|
||||
|
||||
|
@ -158,6 +160,10 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
def n_frames(self):
|
||||
return self._nimages
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
return self._nimages > 1
|
||||
|
||||
# 1st image index is zero (although SPIDER imgnumber starts at 1)
|
||||
def tell(self):
|
||||
if self.imgnumber < 1:
|
||||
|
@ -281,8 +287,8 @@ def _save_spider(im, fp, filename):
|
|||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("SPIDER", SpiderImageFile)
|
||||
Image.register_save("SPIDER", _save_spider)
|
||||
Image.register_open(SpiderImageFile.format, SpiderImageFile)
|
||||
Image.register_save(SpiderImageFile.format, _save_spider)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
|
|
@ -17,17 +17,15 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
i16 = _binary.i16be
|
||||
__version__ = "0.3"
|
||||
|
||||
i32 = _binary.i32be
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return i32(prefix) == 0x59a66a95
|
||||
return len(prefix) >= 4 and i32(prefix) == 0x59a66a95
|
||||
|
||||
|
||||
##
|
||||
|
@ -78,6 +76,6 @@ class SunImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# registry
|
||||
|
||||
Image.register_open("SUN", SunImageFile, _accept)
|
||||
Image.register_open(SunImageFile.format, SunImageFile, _accept)
|
||||
|
||||
Image.register_extension("SUN", ".ras")
|
||||
Image.register_extension(SunImageFile.format, ".ras")
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -28,7 +28,6 @@ from PIL import Image, ImageFile, ImagePalette, _binary
|
|||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
MODES = {
|
||||
|
@ -193,7 +192,7 @@ def _save(im, fp, filename, check=0):
|
|||
# --------------------------------------------------------------------
|
||||
# Registry
|
||||
|
||||
Image.register_open("TGA", TgaImageFile)
|
||||
Image.register_save("TGA", _save)
|
||||
Image.register_open(TgaImageFile.format, TgaImageFile)
|
||||
Image.register_save(TgaImageFile.format, _save)
|
||||
|
||||
Image.register_extension("TGA", ".tga")
|
||||
Image.register_extension(TgaImageFile.format, ".tga")
|
||||
|
|
File diff suppressed because it is too large
Load Diff
620
PIL/TiffTags.py
620
PIL/TiffTags.py
|
@ -17,291 +17,387 @@
|
|||
# well-known TIFF tags.
|
||||
##
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class TagInfo(namedtuple("_TagInfo", "value name type length enum")):
|
||||
__slots__ = []
|
||||
|
||||
def __new__(cls, value=None, name="unknown", type=None, length=0, enum=None):
|
||||
return super(TagInfo, cls).__new__(
|
||||
cls, value, name, type, length, enum or {})
|
||||
|
||||
def cvt_enum(self, value):
|
||||
return self.enum.get(value, value)
|
||||
|
||||
|
||||
def lookup(tag):
|
||||
"""
|
||||
:param tag: Integer tag number
|
||||
:returns: Taginfo namedtuple, From the TAGS_V2 info if possible,
|
||||
otherwise just populating the value and name from TAGS.
|
||||
If the tag is not recognized, "unknown" is returned for the name
|
||||
|
||||
"""
|
||||
|
||||
return TAGS_V2.get(tag, TagInfo(tag, TAGS.get(tag, 'unknown')))
|
||||
|
||||
|
||||
##
|
||||
# Map tag numbers (or tag number, tag value tuples) to tag names.
|
||||
# Map tag numbers to tag info.
|
||||
#
|
||||
# id: (Name, Type, Length, enum_values)
|
||||
#
|
||||
|
||||
TAGS = {
|
||||
ASCII = 2
|
||||
SHORT = 3
|
||||
LONG = 4
|
||||
RATIONAL = 5
|
||||
|
||||
254: "NewSubfileType",
|
||||
255: "SubfileType",
|
||||
256: "ImageWidth",
|
||||
257: "ImageLength",
|
||||
258: "BitsPerSample",
|
||||
TAGS_V2 = {
|
||||
|
||||
259: "Compression",
|
||||
(259, 1): "Uncompressed",
|
||||
(259, 2): "CCITT 1d",
|
||||
(259, 3): "Group 3 Fax",
|
||||
(259, 4): "Group 4 Fax",
|
||||
(259, 5): "LZW",
|
||||
(259, 6): "JPEG",
|
||||
(259, 32773): "PackBits",
|
||||
254: ("NewSubfileType", LONG, 1),
|
||||
255: ("SubfileType", SHORT, 1),
|
||||
256: ("ImageWidth", LONG, 1),
|
||||
257: ("ImageLength", LONG, 1),
|
||||
258: ("BitsPerSample", SHORT, 0),
|
||||
259: ("Compression", SHORT, 1,
|
||||
{"Uncompressed": 1, "CCITT 1d": 2, "Group 3 Fax": 3, "Group 4 Fax": 4,
|
||||
"LZW": 5, "JPEG": 6, "PackBits": 32773}),
|
||||
|
||||
262: "PhotometricInterpretation",
|
||||
(262, 0): "WhiteIsZero",
|
||||
(262, 1): "BlackIsZero",
|
||||
(262, 2): "RGB",
|
||||
(262, 3): "RGB Palette",
|
||||
(262, 4): "Transparency Mask",
|
||||
(262, 5): "CMYK",
|
||||
(262, 6): "YCbCr",
|
||||
(262, 8): "CieLAB",
|
||||
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
|
||||
(262, 32892): "LinearRaw", # Adobe DNG
|
||||
262: ("PhotometricInterpretation", SHORT, 1,
|
||||
{"WhiteIsZero": 0, "BlackIsZero": 1, "RGB": 2, "RBG Palette": 3,
|
||||
"Transparency Mask": 4, "CMYK": 5, "YCbCr": 6, "CieLAB": 8,
|
||||
"CFA": 32803, # TIFF/EP, Adobe DNG
|
||||
"LinearRaw": 32892}), # Adobe DNG
|
||||
263: ("Threshholding", SHORT, 1),
|
||||
264: ("CellWidth", SHORT, 1),
|
||||
265: ("CellLength", SHORT, 1),
|
||||
266: ("FillOrder", SHORT, 1),
|
||||
269: ("DocumentName", ASCII, 1),
|
||||
|
||||
263: "Thresholding",
|
||||
264: "CellWidth",
|
||||
265: "CellHeight",
|
||||
266: "FillOrder",
|
||||
269: "DocumentName",
|
||||
270: ("ImageDescription", ASCII, 1),
|
||||
271: ("Make", ASCII, 1),
|
||||
272: ("Model", ASCII, 1),
|
||||
273: ("StripOffsets", LONG, 0),
|
||||
274: ("Orientation", SHORT, 1),
|
||||
277: ("SamplesPerPixel", SHORT, 1),
|
||||
278: ("RowsPerStrip", LONG, 1),
|
||||
279: ("StripByteCounts", LONG, 0),
|
||||
|
||||
270: "ImageDescription",
|
||||
271: "Make",
|
||||
272: "Model",
|
||||
273: "StripOffsets",
|
||||
274: "Orientation",
|
||||
277: "SamplesPerPixel",
|
||||
278: "RowsPerStrip",
|
||||
279: "StripByteCounts",
|
||||
280: ("MinSampleValue", LONG, 0),
|
||||
281: ("MaxSampleValue", SHORT, 0),
|
||||
282: ("XResolution", RATIONAL, 1),
|
||||
283: ("YResolution", RATIONAL, 1),
|
||||
284: ("PlanarConfiguration", SHORT, 1, {"Contigous": 1, "Separate": 2}),
|
||||
285: ("PageName", ASCII, 1),
|
||||
286: ("XPosition", RATIONAL, 1),
|
||||
287: ("YPosition", RATIONAL, 1),
|
||||
288: ("FreeOffsets", LONG, 1),
|
||||
289: ("FreeByteCounts", LONG, 1),
|
||||
|
||||
280: "MinSampleValue",
|
||||
281: "MaxSampleValue",
|
||||
282: "XResolution",
|
||||
283: "YResolution",
|
||||
284: "PlanarConfiguration",
|
||||
(284, 1): "Contigous",
|
||||
(284, 2): "Separate",
|
||||
290: ("GrayResponseUnit", SHORT, 1),
|
||||
291: ("GrayResponseCurve", SHORT, 0),
|
||||
292: ("T4Options", LONG, 1),
|
||||
293: ("T6Options", LONG, 1),
|
||||
296: ("ResolutionUnit", SHORT, 1, {"inch": 1, "cm": 2}),
|
||||
297: ("PageNumber", SHORT, 2),
|
||||
|
||||
285: "PageName",
|
||||
286: "XPosition",
|
||||
287: "YPosition",
|
||||
288: "FreeOffsets",
|
||||
289: "FreeByteCounts",
|
||||
301: ("TransferFunction", SHORT, 0),
|
||||
305: ("Software", ASCII, 1),
|
||||
306: ("DateTime", ASCII, 1),
|
||||
|
||||
290: "GrayResponseUnit",
|
||||
291: "GrayResponseCurve",
|
||||
292: "T4Options",
|
||||
293: "T6Options",
|
||||
296: "ResolutionUnit",
|
||||
297: "PageNumber",
|
||||
315: ("Artist", ASCII, 1),
|
||||
316: ("HostComputer", ASCII, 1),
|
||||
317: ("Predictor", SHORT, 1),
|
||||
318: ("WhitePoint", RATIONAL, 2),
|
||||
319: ("PrimaryChromaticities", SHORT, 6),
|
||||
|
||||
301: "TransferFunction",
|
||||
305: "Software",
|
||||
306: "DateTime",
|
||||
320: ("ColorMap", SHORT, 0),
|
||||
321: ("HalftoneHints", SHORT, 2),
|
||||
322: ("TileWidth", LONG, 1),
|
||||
323: ("TileLength", LONG, 1),
|
||||
324: ("TileOffsets", LONG, 0),
|
||||
325: ("TileByteCounts", LONG, 0),
|
||||
|
||||
315: "Artist",
|
||||
316: "HostComputer",
|
||||
317: "Predictor",
|
||||
318: "WhitePoint",
|
||||
319: "PrimaryChromaticies",
|
||||
332: ("InkSet", SHORT, 1),
|
||||
333: ("InkNames", ASCII, 1),
|
||||
334: ("NumberOfInks", SHORT, 1),
|
||||
336: ("DotRange", SHORT, 0),
|
||||
337: ("TargetPrinter", ASCII, 1),
|
||||
338: ("ExtraSamples", SHORT, 0),
|
||||
339: ("SampleFormat", SHORT, 0),
|
||||
|
||||
320: "ColorMap",
|
||||
321: "HalftoneHints",
|
||||
322: "TileWidth",
|
||||
323: "TileLength",
|
||||
324: "TileOffsets",
|
||||
325: "TileByteCounts",
|
||||
|
||||
332: "InkSet",
|
||||
333: "InkNames",
|
||||
334: "NumberOfInks",
|
||||
336: "DotRange",
|
||||
337: "TargetPrinter",
|
||||
338: "ExtraSamples",
|
||||
339: "SampleFormat",
|
||||
|
||||
340: "SMinSampleValue",
|
||||
341: "SMaxSampleValue",
|
||||
342: "TransferRange",
|
||||
|
||||
347: "JPEGTables",
|
||||
340: ("SMinSampleValue", 12, 0),
|
||||
341: ("SMaxSampleValue", 12, 0),
|
||||
342: ("TransferRange", SHORT, 6),
|
||||
|
||||
# obsolete JPEG tags
|
||||
512: "JPEGProc",
|
||||
513: "JPEGInterchangeFormat",
|
||||
514: "JPEGInterchangeFormatLength",
|
||||
515: "JPEGRestartInterval",
|
||||
517: "JPEGLosslessPredictors",
|
||||
518: "JPEGPointTransforms",
|
||||
519: "JPEGQTables",
|
||||
520: "JPEGDCTables",
|
||||
521: "JPEGACTables",
|
||||
512: ("JPEGProc", SHORT, 1),
|
||||
513: ("JPEGInterchangeFormat", LONG, 1),
|
||||
514: ("JPEGInterchangeFormatLength", LONG, 1),
|
||||
515: ("JPEGRestartInterval", SHORT, 1),
|
||||
517: ("JPEGLosslessPredictors", SHORT, 0),
|
||||
518: ("JPEGPointTransforms", SHORT, 0),
|
||||
519: ("JPEGQTables", LONG, 0),
|
||||
520: ("JPEGDCTables", LONG, 0),
|
||||
521: ("JPEGACTables", LONG, 0),
|
||||
|
||||
529: "YCbCrCoefficients",
|
||||
530: "YCbCrSubSampling",
|
||||
531: "YCbCrPositioning",
|
||||
532: "ReferenceBlackWhite",
|
||||
529: ("YCbCrCoefficients", RATIONAL, 3),
|
||||
530: ("YCbCrSubSampling", SHORT, 2),
|
||||
531: ("YCbCrPositioning", SHORT, 1),
|
||||
532: ("ReferenceBlackWhite", LONG, 0),
|
||||
|
||||
# XMP
|
||||
700: "XMP",
|
||||
33432: ("Copyright", ASCII, 1),
|
||||
|
||||
33432: "Copyright",
|
||||
# FIXME add more tags here
|
||||
34665: ("ExifIFD", SHORT, 1),
|
||||
34675: ('ICCProfile', 7, 0),
|
||||
34853: ('GPSInfoIFD', 1, 1),
|
||||
|
||||
# various extensions (should check specs for "official" names)
|
||||
33723: "IptcNaaInfo",
|
||||
34377: "PhotoshopInfo",
|
||||
# MPInfo
|
||||
45056: ("MPFVersion", 7, 1),
|
||||
45057: ("NumberOfImages", LONG, 1),
|
||||
45058: ("MPEntry", 7, 1),
|
||||
45059: ("ImageUIDList", 7, 0),
|
||||
45060: ("TotalFrames", LONG, 1),
|
||||
45313: ("MPIndividualNum", LONG, 1),
|
||||
45569: ("PanOrientation", LONG, 1),
|
||||
45570: ("PanOverlap_H", RATIONAL, 1),
|
||||
45571: ("PanOverlap_V", RATIONAL, 1),
|
||||
45572: ("BaseViewpointNum", LONG, 1),
|
||||
45573: ("ConvergenceAngle", 10, 1),
|
||||
45574: ("BaselineLength", RATIONAL, 1),
|
||||
45575: ("VerticalDivergence", 10, 1),
|
||||
45576: ("AxisDistance_X", 10, 1),
|
||||
45577: ("AxisDistance_Y", 10, 1),
|
||||
45578: ("AxisDistance_Z", 10, 1),
|
||||
45579: ("YawAngle", 10, 1),
|
||||
45580: ("PitchAngle", 10, 1),
|
||||
45581: ("RollAngle", 10, 1),
|
||||
|
||||
# Exif IFD
|
||||
34665: "ExifIFD",
|
||||
|
||||
# ICC Profile
|
||||
34675: "ICCProfile",
|
||||
|
||||
# Additional Exif Info
|
||||
33434: "ExposureTime",
|
||||
33437: "FNumber",
|
||||
34850: "ExposureProgram",
|
||||
34852: "SpectralSensitivity",
|
||||
34853: "GPSInfoIFD",
|
||||
34855: "ISOSpeedRatings",
|
||||
34856: "OECF",
|
||||
34864: "SensitivityType",
|
||||
34865: "StandardOutputSensitivity",
|
||||
34866: "RecommendedExposureIndex",
|
||||
34867: "ISOSpeed",
|
||||
34868: "ISOSpeedLatitudeyyy",
|
||||
34869: "ISOSpeedLatitudezzz",
|
||||
36864: "ExifVersion",
|
||||
36867: "DateTimeOriginal",
|
||||
36868: "DateTImeDigitized",
|
||||
37121: "ComponentsConfiguration",
|
||||
37122: "CompressedBitsPerPixel",
|
||||
37377: "ShutterSpeedValue",
|
||||
37378: "ApertureValue",
|
||||
37379: "BrightnessValue",
|
||||
37380: "ExposureBiasValue",
|
||||
37381: "MaxApertureValue",
|
||||
37382: "SubjectDistance",
|
||||
37383: "MeteringMode",
|
||||
37384: "LightSource",
|
||||
37385: "Flash",
|
||||
37386: "FocalLength",
|
||||
37396: "SubjectArea",
|
||||
37500: "MakerNote",
|
||||
37510: "UserComment",
|
||||
37520: "SubSec",
|
||||
37521: "SubSecTimeOriginal",
|
||||
37522: "SubsecTimeDigitized",
|
||||
40960: "FlashPixVersion",
|
||||
40961: "ColorSpace",
|
||||
40962: "PixelXDimension",
|
||||
40963: "PixelYDimension",
|
||||
40964: "RelatedSoundFile",
|
||||
40965: "InteroperabilityIFD",
|
||||
41483: "FlashEnergy",
|
||||
41484: "SpatialFrequencyResponse",
|
||||
41486: "FocalPlaneXResolution",
|
||||
41487: "FocalPlaneYResolution",
|
||||
41488: "FocalPlaneResolutionUnit",
|
||||
41492: "SubjectLocation",
|
||||
41493: "ExposureIndex",
|
||||
41495: "SensingMethod",
|
||||
41728: "FileSource",
|
||||
41729: "SceneType",
|
||||
41730: "CFAPattern",
|
||||
41985: "CustomRendered",
|
||||
41986: "ExposureMode",
|
||||
41987: "WhiteBalance",
|
||||
41988: "DigitalZoomRatio",
|
||||
41989: "FocalLengthIn35mmFilm",
|
||||
41990: "SceneCaptureType",
|
||||
41991: "GainControl",
|
||||
41992: "Contrast",
|
||||
41993: "Saturation",
|
||||
41994: "Sharpness",
|
||||
41995: "DeviceSettingDescription",
|
||||
41996: "SubjectDistanceRange",
|
||||
42016: "ImageUniqueID",
|
||||
42032: "CameraOwnerName",
|
||||
42033: "BodySerialNumber",
|
||||
42034: "LensSpecification",
|
||||
42035: "LensMake",
|
||||
42036: "LensModel",
|
||||
42037: "LensSerialNumber",
|
||||
42240: "Gamma",
|
||||
|
||||
# MP Info
|
||||
45056: "MPFVersion",
|
||||
45057: "NumberOfImages",
|
||||
45058: "MPEntry",
|
||||
45059: "ImageUIDList",
|
||||
45060: "TotalFrames",
|
||||
45313: "MPIndividualNum",
|
||||
45569: "PanOrientation",
|
||||
45570: "PanOverlap_H",
|
||||
45571: "PanOverlap_V",
|
||||
45572: "BaseViewpointNum",
|
||||
45573: "ConvergenceAngle",
|
||||
45574: "BaselineLength",
|
||||
45575: "VerticalDivergence",
|
||||
45576: "AxisDistance_X",
|
||||
45577: "AxisDistance_Y",
|
||||
45578: "AxisDistance_Z",
|
||||
45579: "YawAngle",
|
||||
45580: "PitchAngle",
|
||||
45581: "RollAngle",
|
||||
|
||||
# Adobe DNG
|
||||
50706: "DNGVersion",
|
||||
50707: "DNGBackwardVersion",
|
||||
50708: "UniqueCameraModel",
|
||||
50709: "LocalizedCameraModel",
|
||||
50710: "CFAPlaneColor",
|
||||
50711: "CFALayout",
|
||||
50712: "LinearizationTable",
|
||||
50713: "BlackLevelRepeatDim",
|
||||
50714: "BlackLevel",
|
||||
50715: "BlackLevelDeltaH",
|
||||
50716: "BlackLevelDeltaV",
|
||||
50717: "WhiteLevel",
|
||||
50718: "DefaultScale",
|
||||
50719: "DefaultCropOrigin",
|
||||
50720: "DefaultCropSize",
|
||||
50778: "CalibrationIlluminant1",
|
||||
50779: "CalibrationIlluminant2",
|
||||
50721: "ColorMatrix1",
|
||||
50722: "ColorMatrix2",
|
||||
50723: "CameraCalibration1",
|
||||
50724: "CameraCalibration2",
|
||||
50725: "ReductionMatrix1",
|
||||
50726: "ReductionMatrix2",
|
||||
50727: "AnalogBalance",
|
||||
50728: "AsShotNeutral",
|
||||
50729: "AsShotWhiteXY",
|
||||
50730: "BaselineExposure",
|
||||
50731: "BaselineNoise",
|
||||
50732: "BaselineSharpness",
|
||||
50733: "BayerGreenSplit",
|
||||
50734: "LinearResponseLimit",
|
||||
50735: "CameraSerialNumber",
|
||||
50736: "LensInfo",
|
||||
50737: "ChromaBlurRadius",
|
||||
50738: "AntiAliasStrength",
|
||||
50740: "DNGPrivateData",
|
||||
50741: "MakerNoteSafety",
|
||||
50780: "BestQualityScale",
|
||||
|
||||
# ImageJ
|
||||
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
|
||||
50839: "ImageJMetaData", # private tag registered with Adobe
|
||||
50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}),
|
||||
50780: ("BestQualityScale", RATIONAL, 1),
|
||||
50838: ("ImageJMetaDataByteCounts", LONG, 1),
|
||||
50839: ("ImageJMetaData", 7, 1)
|
||||
}
|
||||
|
||||
# Legacy Tags structure
|
||||
# these tags aren't included above, but were in the previous versions
|
||||
TAGS = {347: 'JPEGTables',
|
||||
700: 'XMP',
|
||||
|
||||
# Additional Exif Info
|
||||
33434: 'ExposureTime',
|
||||
33437: 'FNumber',
|
||||
33723: 'IptcNaaInfo',
|
||||
34377: 'PhotoshopInfo',
|
||||
34850: 'ExposureProgram',
|
||||
34852: 'SpectralSensitivity',
|
||||
34855: 'ISOSpeedRatings',
|
||||
34856: 'OECF',
|
||||
34864: 'SensitivityType',
|
||||
34865: 'StandardOutputSensitivity',
|
||||
34866: 'RecommendedExposureIndex',
|
||||
34867: 'ISOSpeed',
|
||||
34868: 'ISOSpeedLatitudeyyy',
|
||||
34869: 'ISOSpeedLatitudezzz',
|
||||
36864: 'ExifVersion',
|
||||
36867: 'DateTimeOriginal',
|
||||
36868: 'DateTImeDigitized',
|
||||
37121: 'ComponentsConfiguration',
|
||||
37122: 'CompressedBitsPerPixel',
|
||||
37377: 'ShutterSpeedValue',
|
||||
37378: 'ApertureValue',
|
||||
37379: 'BrightnessValue',
|
||||
37380: 'ExposureBiasValue',
|
||||
37381: 'MaxApertureValue',
|
||||
37382: 'SubjectDistance',
|
||||
37383: 'MeteringMode',
|
||||
37384: 'LightSource',
|
||||
37385: 'Flash',
|
||||
37386: 'FocalLength',
|
||||
37396: 'SubjectArea',
|
||||
37500: 'MakerNote',
|
||||
37510: 'UserComment',
|
||||
37520: 'SubSec',
|
||||
37521: 'SubSecTimeOriginal',
|
||||
37522: 'SubsecTimeDigitized',
|
||||
40960: 'FlashPixVersion',
|
||||
40961: 'ColorSpace',
|
||||
40962: 'PixelXDimension',
|
||||
40963: 'PixelYDimension',
|
||||
40964: 'RelatedSoundFile',
|
||||
40965: 'InteroperabilityIFD',
|
||||
41483: 'FlashEnergy',
|
||||
41484: 'SpatialFrequencyResponse',
|
||||
41486: 'FocalPlaneXResolution',
|
||||
41487: 'FocalPlaneYResolution',
|
||||
41488: 'FocalPlaneResolutionUnit',
|
||||
41492: 'SubjectLocation',
|
||||
41493: 'ExposureIndex',
|
||||
41495: 'SensingMethod',
|
||||
41728: 'FileSource',
|
||||
41729: 'SceneType',
|
||||
41730: 'CFAPattern',
|
||||
41985: 'CustomRendered',
|
||||
41986: 'ExposureMode',
|
||||
41987: 'WhiteBalance',
|
||||
41988: 'DigitalZoomRatio',
|
||||
41989: 'FocalLengthIn35mmFilm',
|
||||
41990: 'SceneCaptureType',
|
||||
41991: 'GainControl',
|
||||
41992: 'Contrast',
|
||||
41993: 'Saturation',
|
||||
41994: 'Sharpness',
|
||||
41995: 'DeviceSettingDescription',
|
||||
41996: 'SubjectDistanceRange',
|
||||
42016: 'ImageUniqueID',
|
||||
42032: 'CameraOwnerName',
|
||||
42033: 'BodySerialNumber',
|
||||
42034: 'LensSpecification',
|
||||
42035: 'LensMake',
|
||||
42036: 'LensModel',
|
||||
42037: 'LensSerialNumber',
|
||||
42240: 'Gamma',
|
||||
|
||||
# Adobe DNG
|
||||
50706: 'DNGVersion',
|
||||
50707: 'DNGBackwardVersion',
|
||||
50708: 'UniqueCameraModel',
|
||||
50709: 'LocalizedCameraModel',
|
||||
50710: 'CFAPlaneColor',
|
||||
50711: 'CFALayout',
|
||||
50712: 'LinearizationTable',
|
||||
50713: 'BlackLevelRepeatDim',
|
||||
50714: 'BlackLevel',
|
||||
50715: 'BlackLevelDeltaH',
|
||||
50716: 'BlackLevelDeltaV',
|
||||
50717: 'WhiteLevel',
|
||||
50718: 'DefaultScale',
|
||||
50719: 'DefaultCropOrigin',
|
||||
50720: 'DefaultCropSize',
|
||||
50721: 'ColorMatrix1',
|
||||
50722: 'ColorMatrix2',
|
||||
50723: 'CameraCalibration1',
|
||||
50724: 'CameraCalibration2',
|
||||
50725: 'ReductionMatrix1',
|
||||
50726: 'ReductionMatrix2',
|
||||
50727: 'AnalogBalance',
|
||||
50728: 'AsShotNeutral',
|
||||
50729: 'AsShotWhiteXY',
|
||||
50730: 'BaselineExposure',
|
||||
50731: 'BaselineNoise',
|
||||
50732: 'BaselineSharpness',
|
||||
50733: 'BayerGreenSplit',
|
||||
50734: 'LinearResponseLimit',
|
||||
50735: 'CameraSerialNumber',
|
||||
50736: 'LensInfo',
|
||||
50737: 'ChromaBlurRadius',
|
||||
50738: 'AntiAliasStrength',
|
||||
50740: 'DNGPrivateData',
|
||||
50778: 'CalibrationIlluminant1',
|
||||
50779: 'CalibrationIlluminant2',
|
||||
}
|
||||
|
||||
|
||||
def _populate():
|
||||
for k, v in TAGS_V2.items():
|
||||
# Populate legacy structure.
|
||||
TAGS[k] = v[0]
|
||||
if len(v) == 4:
|
||||
for sk, sv in v[3].items():
|
||||
TAGS[(k, sv)] = sk
|
||||
|
||||
TAGS_V2[k] = TagInfo(k, *v)
|
||||
|
||||
_populate()
|
||||
##
|
||||
# Map type numbers to type names.
|
||||
# Map type numbers to type names -- defined in ImageFileDirectory.
|
||||
|
||||
TYPES = {
|
||||
TYPES = {}
|
||||
|
||||
1: "byte",
|
||||
2: "ascii",
|
||||
3: "short",
|
||||
4: "long",
|
||||
5: "rational",
|
||||
6: "signed byte",
|
||||
7: "undefined",
|
||||
8: "signed short",
|
||||
9: "signed long",
|
||||
10: "signed rational",
|
||||
11: "float",
|
||||
12: "double",
|
||||
# was:
|
||||
# TYPES = {
|
||||
# 1: "byte",
|
||||
# 2: "ascii",
|
||||
# 3: "short",
|
||||
# 4: "long",
|
||||
# 5: "rational",
|
||||
# 6: "signed byte",
|
||||
# 7: "undefined",
|
||||
# 8: "signed short",
|
||||
# 9: "signed long",
|
||||
# 10: "signed rational",
|
||||
# 11: "float",
|
||||
# 12: "double",
|
||||
# }
|
||||
|
||||
}
|
||||
#
|
||||
# These tags are handled by default in libtiff, without
|
||||
# adding to the custom dictionary. From tif_dir.c, searching for
|
||||
# case TIFFTAG in the _TIFFVSetField function:
|
||||
# Line: item.
|
||||
# 148: case TIFFTAG_SUBFILETYPE:
|
||||
# 151: case TIFFTAG_IMAGEWIDTH:
|
||||
# 154: case TIFFTAG_IMAGELENGTH:
|
||||
# 157: case TIFFTAG_BITSPERSAMPLE:
|
||||
# 181: case TIFFTAG_COMPRESSION:
|
||||
# 202: case TIFFTAG_PHOTOMETRIC:
|
||||
# 205: case TIFFTAG_THRESHHOLDING:
|
||||
# 208: case TIFFTAG_FILLORDER:
|
||||
# 214: case TIFFTAG_ORIENTATION:
|
||||
# 221: case TIFFTAG_SAMPLESPERPIXEL:
|
||||
# 228: case TIFFTAG_ROWSPERSTRIP:
|
||||
# 238: case TIFFTAG_MINSAMPLEVALUE:
|
||||
# 241: case TIFFTAG_MAXSAMPLEVALUE:
|
||||
# 244: case TIFFTAG_SMINSAMPLEVALUE:
|
||||
# 247: case TIFFTAG_SMAXSAMPLEVALUE:
|
||||
# 250: case TIFFTAG_XRESOLUTION:
|
||||
# 256: case TIFFTAG_YRESOLUTION:
|
||||
# 262: case TIFFTAG_PLANARCONFIG:
|
||||
# 268: case TIFFTAG_XPOSITION:
|
||||
# 271: case TIFFTAG_YPOSITION:
|
||||
# 274: case TIFFTAG_RESOLUTIONUNIT:
|
||||
# 280: case TIFFTAG_PAGENUMBER:
|
||||
# 284: case TIFFTAG_HALFTONEHINTS:
|
||||
# 288: case TIFFTAG_COLORMAP:
|
||||
# 294: case TIFFTAG_EXTRASAMPLES:
|
||||
# 298: case TIFFTAG_MATTEING:
|
||||
# 305: case TIFFTAG_TILEWIDTH:
|
||||
# 316: case TIFFTAG_TILELENGTH:
|
||||
# 327: case TIFFTAG_TILEDEPTH:
|
||||
# 333: case TIFFTAG_DATATYPE:
|
||||
# 344: case TIFFTAG_SAMPLEFORMAT:
|
||||
# 361: case TIFFTAG_IMAGEDEPTH:
|
||||
# 364: case TIFFTAG_SUBIFD:
|
||||
# 376: case TIFFTAG_YCBCRPOSITIONING:
|
||||
# 379: case TIFFTAG_YCBCRSUBSAMPLING:
|
||||
# 383: case TIFFTAG_TRANSFERFUNCTION:
|
||||
# 389: case TIFFTAG_REFERENCEBLACKWHITE:
|
||||
# 393: case TIFFTAG_INKNAMES:
|
||||
|
||||
# some of these are not in our TAGS_V2 dict and were included from tiff.h
|
||||
|
||||
LIBTIFF_CORE = set([255, 256, 257, 258, 259, 262, 263, 266, 274, 277,
|
||||
278, 280, 281, 340, 341, 282, 283, 284, 286, 287,
|
||||
296, 297, 321, 320, 338, 32995, 322, 323, 32998,
|
||||
32996, 339, 32997, 330, 531, 530, 301, 532, 333,
|
||||
# as above
|
||||
269 # this has been in our tests forever, and works
|
||||
])
|
||||
|
||||
LIBTIFF_CORE.remove(320) # Array of short, crashes
|
||||
LIBTIFF_CORE.remove(301) # Array of short, crashes
|
||||
LIBTIFF_CORE.remove(532) # Array of long, crashes
|
||||
|
||||
LIBTIFF_CORE.remove(255) # We don't have support for subfiletypes
|
||||
LIBTIFF_CORE.remove(322) # We don't have support for tiled images in libtiff
|
||||
LIBTIFF_CORE.remove(323) # Tiled images
|
||||
LIBTIFF_CORE.remove(333) # Ink Names either
|
||||
|
||||
# Note to advanced users: There may be combinations of these
|
||||
# parameters and values that when added properly, will work and
|
||||
# produce valid tiff images that may work in your application.
|
||||
# It is safe to add and remove tags from this set from Pillow's point
|
||||
# of view so long as you test against libtiff.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
|
@ -124,8 +126,3 @@ quake2palette = (
|
|||
b"\x10\x0f\x0d\x0d\x0b\x3c\x2e\x2a\x36\x27\x20\x30\x21\x18\x29\x1b"
|
||||
b"\x10\x3c\x39\x37\x37\x32\x2f\x31\x2c\x28\x2b\x26\x21\x30\x22\x20"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
im = open("../hacks/sample.wal")
|
||||
print(im.info, im.mode, im.size)
|
||||
im.save("../out.png")
|
||||
|
|
|
@ -73,8 +73,8 @@ def _save(im, fp, filename):
|
|||
fp.write(data)
|
||||
|
||||
|
||||
Image.register_open("WEBP", WebPImageFile, _accept)
|
||||
Image.register_save("WEBP", _save)
|
||||
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
|
||||
Image.register_save(WebPImageFile.format, _save)
|
||||
|
||||
Image.register_extension("WEBP", ".webp")
|
||||
Image.register_mime("WEBP", "image/webp")
|
||||
Image.register_extension(WebPImageFile.format, ".webp")
|
||||
Image.register_mime(WebPImageFile.format, "image/webp")
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
_handler = None
|
||||
|
||||
if str != bytes:
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
# FIXME: make save work (this requires quantization support)
|
||||
#
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
o8 = _binary.o8
|
||||
|
||||
# standard color palette for thumbnails (RGB332)
|
||||
|
@ -72,4 +72,4 @@ class XVThumbImageFile(ImageFile.ImageFile):
|
|||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("XVThumb", XVThumbImageFile)
|
||||
Image.register_open(XVThumbImageFile.format, XVThumbImageFile)
|
||||
|
|
|
@ -19,15 +19,15 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
import re
|
||||
from PIL import Image, ImageFile
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
# XBM header
|
||||
xbm_head = re.compile(
|
||||
b"\s*#define[ \t]+[^_]*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
|
||||
b"#define[ \t]+[^_]*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
|
||||
b"\s*#define[ \t]+.*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
|
||||
b"#define[ \t]+.*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
|
||||
b"(?P<hotspot>"
|
||||
b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
|
||||
b"#define[ \t]+[^_]*_y_hot[ \t]+(?P<yhot>[0-9]+)[\r\n]+"
|
||||
|
@ -88,9 +88,9 @@ def _save(im, fp, filename):
|
|||
fp.write(b"};\n")
|
||||
|
||||
|
||||
Image.register_open("XBM", XbmImageFile, _accept)
|
||||
Image.register_save("XBM", _save)
|
||||
Image.register_open(XbmImageFile.format, XbmImageFile, _accept)
|
||||
Image.register_save(XbmImageFile.format, _save)
|
||||
|
||||
Image.register_extension("XBM", ".xbm")
|
||||
Image.register_extension(XbmImageFile.format, ".xbm")
|
||||
|
||||
Image.register_mime("XBM", "image/xbm")
|
||||
Image.register_mime(XbmImageFile.format, "image/xbm")
|
||||
|
|
|
@ -15,13 +15,12 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
||||
import re
|
||||
from PIL import Image, ImageFile, ImagePalette
|
||||
from PIL._binary import i8, o8
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
# XPM header
|
||||
xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)")
|
||||
|
||||
|
@ -124,8 +123,8 @@ class XpmImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# Registry
|
||||
|
||||
Image.register_open("XPM", XpmImageFile, _accept)
|
||||
Image.register_open(XpmImageFile.format, XpmImageFile, _accept)
|
||||
|
||||
Image.register_extension("XPM", ".xpm")
|
||||
Image.register_extension(XpmImageFile.format, ".xpm")
|
||||
|
||||
Image.register_mime("XPM", "image/xpm")
|
||||
Image.register_mime(XpmImageFile.format, "image/xpm")
|
||||
|
|
|
@ -12,16 +12,18 @@
|
|||
# ;-)
|
||||
|
||||
VERSION = '1.1.7' # PIL version
|
||||
PILLOW_VERSION = '2.9.0.dev0' # Pillow
|
||||
PILLOW_VERSION = '3.3.0.dev0' # Pillow
|
||||
|
||||
_plugins = ['BmpImagePlugin',
|
||||
'BufrStubImagePlugin',
|
||||
'CurImagePlugin',
|
||||
'DcxImagePlugin',
|
||||
'DdsImagePlugin',
|
||||
'EpsImagePlugin',
|
||||
'FitsStubImagePlugin',
|
||||
'FliImagePlugin',
|
||||
'FpxImagePlugin',
|
||||
'FtexImagePlugin',
|
||||
'GbrImagePlugin',
|
||||
'GifImagePlugin',
|
||||
'GribStubImagePlugin',
|
||||
|
|
54
README.rst
54
README.rst
|
@ -4,45 +4,57 @@ Pillow
|
|||
Python Imaging Library (Fork)
|
||||
-----------------------------
|
||||
|
||||
Pillow is the "friendly PIL fork" by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
||||
Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
||||
|
||||
..
|
||||
image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
|
||||
.. image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
|
||||
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
|
||||
|
||||
.. image:: https://readthedocs.org/projects/pillow/badge/?version=latest
|
||||
:target: http://pillow.readthedocs.org/?badge=latest
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build
|
||||
:target: https://travis-ci.org/python-pillow/Pillow
|
||||
:alt: Travis CI build status (Linux)
|
||||
|
||||
..
|
||||
image:: https://pypip.in/v/Pillow/badge.png
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Latest PyPI version
|
||||
.. image:: https://img.shields.io/travis/python-pillow/pillow-wheels/latest.svg?label=OS%20X%20build
|
||||
:target: https://travis-ci.org/python-pillow/pillow-wheels
|
||||
:alt: Travis CI build status (OS X)
|
||||
|
||||
..
|
||||
image:: https://pypip.in/d/Pillow/badge.png
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Number of PyPI downloads
|
||||
.. image:: https://img.shields.io/appveyor/ci/python-pillow/Pillow/master.svg?label=Windows%20build
|
||||
:target: https://ci.appveyor.com/project/python-pillow/Pillow
|
||||
:alt: AppVeyor CI build status (Windows)
|
||||
|
||||
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
|
||||
.. image:: https://img.shields.io/pypi/v/pillow.svg
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/pillow.svg
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Number of PyPI downloads
|
||||
|
||||
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github
|
||||
:target: https://coveralls.io/github/python-pillow/Pillow?branch=master
|
||||
:alt: Code coverage
|
||||
|
||||
.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.png
|
||||
.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.svg
|
||||
:target: https://landscape.io/github/python-pillow/Pillow/master
|
||||
:alt: Code health
|
||||
|
||||
|
||||
More Information
|
||||
----------------
|
||||
|
||||
- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
|
||||
- `Documentation <https://pillow.readthedocs.org/>`_
|
||||
|
||||
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#02b5---117-1995-2010>`_
|
||||
- `Installation <https://pillow.readthedocs.org/en/latest/installation.html>`_
|
||||
- `Handbook <https://pillow.readthedocs.org/en/latest/handbook/index.html>`_
|
||||
|
||||
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_
|
||||
|
||||
- `Issues <https://github.com/python-pillow/Pillow/issues>`_
|
||||
- `Pull requests <https://github.com/python-pillow/Pillow/pulls>`_
|
||||
|
||||
- `Documentation <http://pillow.readthedocs.org/>`_
|
||||
- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
|
||||
|
||||
- `About <http://pillow.readthedocs.org/about.html>`_
|
||||
- `Guides <http://pillow.readthedocs.org/guides.html>`_
|
||||
- `Installation <http://pillow.readthedocs.org/installation.html>`_
|
||||
- `Reference <http://pillow.readthedocs.org/reference/index.html>`_
|
||||
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork>`_
|
||||
|
|
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
|
||||
* [ ] Develop and prepare release in ``master`` branch.
|
||||
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
|
||||
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
|
||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
|
||||
```
|
||||
PIL/__init__.py setup.py _imaging.c
|
||||
PIL/__init__.py setup.py _imaging.c appveyor.yml
|
||||
```
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] Run pre-release check via `make pre`.
|
||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||
* [ ] Create branch and tag for release e.g.:
|
||||
```
|
||||
$ git branch 2.9.x
|
||||
|
@ -22,10 +22,11 @@ Released quarterly on the first day of January, April, July, October.
|
|||
```
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
```
|
||||
$ make sdistup
|
||||
$ make sdist
|
||||
$ make upload
|
||||
```
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
* [ ] Manually hide old versions on PyPI as needed, such that only the latest main release is visible when viewing https://pypi.python.org/pypi/Pillow
|
||||
* [ ] Manually hide old versions on PyPI such that only the latest major release is visible when viewing https://pypi.python.org/pypi/Pillow (https://pypi.python.org/pypi?:action=pkg_edit&name=Pillow)
|
||||
|
||||
## Point Release
|
||||
|
||||
|
@ -41,9 +42,12 @@ Released as needed for security, installation or critical bug fixes.
|
|||
```
|
||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
|
||||
```
|
||||
PIL/__init__.py setup.py _imaging.c
|
||||
PIL/__init__.py
|
||||
setup.py
|
||||
_imaging.c
|
||||
appveyor.yml
|
||||
```
|
||||
* [ ] Run pre-release check via `make pre`.
|
||||
* [ ] Run pre-release check via `make release-test`.
|
||||
* [ ] Create tag for release e.g.:
|
||||
```
|
||||
$ git tag 2.9.1
|
||||
|
@ -63,7 +67,7 @@ Released as needed privately to individual vendors for critical security-related
|
|||
* [ ] Commit against master, cherry pick to affected release branches.
|
||||
* [ ] Run local test matrix on each release & Python version.
|
||||
* [ ] Privately send to distros.
|
||||
* [ ] Run pre-release check via `make pre`
|
||||
* [ ] Run pre-release check via `make release-test`
|
||||
* [ ] Amend any commits with the CVE #
|
||||
* [ ] On release date, tag and push to GitHub.
|
||||
```
|
||||
|
@ -103,3 +107,7 @@ Released as needed privately to individual vendors for critical security-related
|
|||
## Publicize Release
|
||||
|
||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328.
|
||||
|
||||
## Documentation
|
||||
|
||||
* [ ] Make sure the default version for Read the Docs is the latest release version, e.g. ``3.1.x`` rather than ``latest``: https://readthedocs.org/projects/pillow/versions/
|
||||
|
|
|
@ -12,7 +12,7 @@ pildriver.py (by Eric S. Raymond)
|
|||
--------------------------------------------------------------------
|
||||
|
||||
A class implementing an image-processing calculator for scripts.
|
||||
Parses lists of commnds (or, called interactively, command-line
|
||||
Parses lists of commands (or, called interactively, command-line
|
||||
arguments) into image loads, transformations, and saves.
|
||||
|
||||
viewer.py
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
import base64
|
||||
import os
|
||||
|
|
|
@ -14,120 +14,9 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
#
|
||||
# For special purposes, you can import this module and call
|
||||
# the makedelta or compress functions yourself. For example,
|
||||
# if you have an application that generates a sequence of
|
||||
# images, you can convert it to a GIF animation using some-
|
||||
# thing like the following code:
|
||||
#
|
||||
# import Image
|
||||
# import gifmaker
|
||||
#
|
||||
# sequence = []
|
||||
#
|
||||
# # generate sequence
|
||||
# for i in range(100):
|
||||
# im = <generate image i>
|
||||
# sequence.append(im)
|
||||
#
|
||||
# # write GIF animation
|
||||
# fp = open("out.gif", "wb")
|
||||
# gifmaker.makedelta(fp, sequence)
|
||||
# fp.close()
|
||||
#
|
||||
# Alternatively, use an iterator to generate the sequence, and
|
||||
# write data directly to a socket. Or something...
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
from PIL.GifImagePlugin import getheader, getdata
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# sequence iterator
|
||||
|
||||
|
||||
class image_sequence(object):
|
||||
def __init__(self, im):
|
||||
self.im = im
|
||||
|
||||
def __getitem__(self, ix):
|
||||
try:
|
||||
if ix:
|
||||
self.im.seek(ix)
|
||||
return self.im
|
||||
except EOFError:
|
||||
raise IndexError # end of sequence
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# straightforward delta encoding
|
||||
|
||||
|
||||
def makedelta(fp, sequence):
|
||||
"""Convert list of image frames to a GIF animation file"""
|
||||
|
||||
frames = 0
|
||||
|
||||
previous = None
|
||||
|
||||
for im in sequence:
|
||||
|
||||
# To specify duration, add the time in milliseconds to getdata(),
|
||||
# e.g. getdata(im, duration=1000)
|
||||
|
||||
if not previous:
|
||||
|
||||
# global header
|
||||
for s in getheader(im)[0] + getdata(im):
|
||||
fp.write(s)
|
||||
|
||||
else:
|
||||
|
||||
# delta frame
|
||||
delta = ImageChops.subtract_modulo(im, previous)
|
||||
|
||||
bbox = delta.getbbox()
|
||||
|
||||
if bbox:
|
||||
|
||||
# compress difference
|
||||
for s in getdata(im.crop(bbox), offset=bbox[:2]):
|
||||
fp.write(s)
|
||||
|
||||
else:
|
||||
# FIXME: what should we do in this case?
|
||||
pass
|
||||
|
||||
previous = im.copy()
|
||||
|
||||
frames += 1
|
||||
|
||||
fp.write(";")
|
||||
|
||||
return frames
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# main hack
|
||||
|
||||
|
||||
def compress(infile, outfile):
|
||||
|
||||
# open input image, and force loading of first frame
|
||||
im = Image.open(infile)
|
||||
im.load()
|
||||
|
||||
# open output file
|
||||
fp = open(outfile, "wb")
|
||||
|
||||
seq = image_sequence(im)
|
||||
|
||||
makedelta(fp, seq)
|
||||
|
||||
fp.close()
|
||||
|
||||
from PIL import Image
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
@ -138,4 +27,5 @@ if __name__ == "__main__":
|
|||
print("Usage: gifmaker infile outfile")
|
||||
sys.exit(1)
|
||||
|
||||
compress(sys.argv[1], sys.argv[2])
|
||||
im = Image.open(sys.argv[1])
|
||||
im.save(sys.argv[2], save_all=True)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
# this demo script illustrates pasting into an already displayed
|
||||
# photoimage. note that the current version of Tk updates the whole
|
||||
# image everytime we paste, so to get decent performance, we split
|
||||
# image every time we paste, so to get decent performance, we split
|
||||
# the image into a set of tiles.
|
||||
#
|
||||
|
||||
|
@ -68,6 +68,10 @@ class PaintCanvas(Canvas):
|
|||
|
||||
root = Tk()
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: painter file")
|
||||
sys.exit(1)
|
||||
|
||||
im = Image.open(sys.argv[1])
|
||||
|
||||
if im.mode != "RGB":
|
||||
|
|
|
@ -70,7 +70,7 @@ class PILDriver(object):
|
|||
|
||||
def push(self, item):
|
||||
"Push an argument onto the evaluation stack."
|
||||
self.stack = [item] + self.stack
|
||||
self.stack.insert(0, item)
|
||||
|
||||
def top(self):
|
||||
"Return the top-of-stack element."
|
||||
|
@ -90,9 +90,7 @@ class PILDriver(object):
|
|||
|
||||
Discard the top element on the stack.
|
||||
"""
|
||||
top = self.stack[0]
|
||||
self.stack = self.stack[1:]
|
||||
return top
|
||||
return self.stack.pop(0)
|
||||
|
||||
def do_dup(self):
|
||||
"""usage: dup
|
||||
|
@ -103,7 +101,7 @@ class PILDriver(object):
|
|||
dup = self.stack[0].copy()
|
||||
else:
|
||||
dup = self.stack[0]
|
||||
self.stack = [dup] + self.stack
|
||||
self.push(dup)
|
||||
|
||||
def do_swap(self):
|
||||
"""usage: swap
|
||||
|
@ -152,7 +150,8 @@ class PILDriver(object):
|
|||
self.push(Image.composite(image1, image2, mask))
|
||||
|
||||
def do_merge(self):
|
||||
"""usage: merge <string:mode> <image:pic1> [<image:pic2> [<image:pic3> [<image:pic4>]]]
|
||||
"""usage: merge <string:mode> <image:pic1>
|
||||
[<image:pic2> [<image:pic3> [<image:pic4>]]]
|
||||
|
||||
Merge top-of stack images in a way described by the mode.
|
||||
"""
|
||||
|
@ -181,7 +180,8 @@ class PILDriver(object):
|
|||
self.dup()
|
||||
|
||||
def do_crop(self):
|
||||
"""usage: crop <int:left> <int:upper> <int:right> <int:lower> <image:pic1>
|
||||
"""usage: crop <int:left> <int:upper> <int:right> <int:lower>
|
||||
<image:pic1>
|
||||
|
||||
Crop and push a rectangular region from the current image.
|
||||
"""
|
||||
|
@ -243,7 +243,8 @@ class PILDriver(object):
|
|||
self.push(image.offset(xoff, yoff))
|
||||
|
||||
def do_paste(self):
|
||||
"""usage: paste <image:figure> <int:xoffset> <int:yoffset> <image:ground>
|
||||
"""usage: paste <image:figure> <int:xoffset> <int:yoffset>
|
||||
<image:ground>
|
||||
|
||||
Paste figure image into ground with upper left at given offsets.
|
||||
"""
|
||||
|
|
|
@ -21,6 +21,7 @@ from __future__ import print_function
|
|||
|
||||
import getopt
|
||||
import glob
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
@ -42,6 +43,7 @@ except getopt.error as v:
|
|||
sys.exit(1)
|
||||
|
||||
verbose = quiet = verify = 0
|
||||
logging_level = "WARNING"
|
||||
|
||||
for o, a in opt:
|
||||
if o == "-f":
|
||||
|
@ -58,7 +60,9 @@ for o, a in opt:
|
|||
elif o == "-v":
|
||||
verify = 1
|
||||
elif o == "-D":
|
||||
Image.DEBUG += 1
|
||||
logging_level = "DEBUG"
|
||||
|
||||
logging.basicConfig(level=logging_level)
|
||||
|
||||
|
||||
def globfix(files):
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
VERSION = "0.4"
|
||||
|
||||
import glob
|
||||
import sys
|
||||
|
||||
|
@ -21,6 +19,8 @@ import sys
|
|||
from PIL import BdfFontFile
|
||||
from PIL import PcfFontFile
|
||||
|
||||
VERSION = "0.4"
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
print("PILFONT", VERSION, "-- PIL font compiler.")
|
||||
print()
|
||||
|
|
20
Scripts/pilprint.py
Normal file → Executable file
20
Scripts/pilprint.py
Normal file → Executable file
|
@ -15,6 +15,7 @@ from __future__ import print_function
|
|||
import getopt
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
VERSION = "pilprint 0.3/2003-05-05"
|
||||
|
||||
|
@ -32,10 +33,11 @@ def description(filepath, image):
|
|||
return title + format % image.size + image.mode + ")"
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
print("PIL Print 0.2a1/96-10-04 -- print image files")
|
||||
print("PIL Print 0.3/2003-05-05 -- print image files")
|
||||
print("Usage: pilprint files...")
|
||||
print("Options:")
|
||||
print(" -c colour printer (default is monochrome)")
|
||||
print(" -d debug (show available drivers)")
|
||||
print(" -p print via lpr (default is stdout)")
|
||||
print(" -P <printer> same as -p but use given printer")
|
||||
sys.exit(1)
|
||||
|
@ -46,8 +48,8 @@ except getopt.error as v:
|
|||
print(v)
|
||||
sys.exit(1)
|
||||
|
||||
printer = None # print to stdout
|
||||
monochrome = 1 # reduce file size for most common case
|
||||
printerArgs = [] # print to stdout
|
||||
monochrome = 1 # reduce file size for most common case
|
||||
|
||||
for o, a in opt:
|
||||
if o == "-d":
|
||||
|
@ -60,10 +62,10 @@ for o, a in opt:
|
|||
monochrome = 0
|
||||
elif o == "-p":
|
||||
# default printer channel
|
||||
printer = "lpr"
|
||||
printerArgs = ["lpr"]
|
||||
elif o == "-P":
|
||||
# printer channel
|
||||
printer = "lpr -P%s" % a
|
||||
printerArgs = ["lpr", "-P%s" % a]
|
||||
|
||||
for filepath in argv:
|
||||
try:
|
||||
|
@ -76,8 +78,9 @@ for filepath in argv:
|
|||
im.draft("L", im.size)
|
||||
im = im.convert("L")
|
||||
|
||||
if printer:
|
||||
fp = os.popen(printer, "w")
|
||||
if printerArgs:
|
||||
p = subprocess.Popen(printerArgs, stdin=subprocess.PIPE)
|
||||
fp = p.stdin
|
||||
else:
|
||||
fp = sys.stdout
|
||||
|
||||
|
@ -91,6 +94,9 @@ for filepath in argv:
|
|||
ps.image(letter, im)
|
||||
ps.end_document()
|
||||
|
||||
if printerArgs:
|
||||
fp.close()
|
||||
|
||||
except:
|
||||
print("cannot print image", end=' ')
|
||||
print("(%s:%s)" % (sys.exc_info()[0], sys.exc_info()[1]))
|
||||
|
|
|
@ -15,9 +15,6 @@ from PIL import Image, ImageTk
|
|||
import sys
|
||||
|
||||
|
||||
Image.DEBUG = 0
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# an image animation player
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
import helper
|
||||
import timeit
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
|
||||
def bench(mode):
|
||||
im = helper.hopper(mode)
|
||||
|
|
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 helper import unittest, PillowTestCase
|
||||
import sys
|
||||
from PIL import Image, ImageFilter
|
||||
from PIL import Image
|
||||
|
||||
min_iterations = 100
|
||||
max_iterations = 10000
|
||||
|
@ -31,7 +31,8 @@ class TestImagingLeaks(PillowTestCase):
|
|||
|
||||
def test_leak_putdata(self):
|
||||
im = Image.new('RGB', (25, 25))
|
||||
self._test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
|
||||
self._test_leak(min_iterations, max_iterations,
|
||||
im.putdata, im.getdata())
|
||||
|
||||
def test_leak_getlist(self):
|
||||
im = Image.new('P', (25, 25))
|
||||
|
|
|
@ -6,7 +6,8 @@ iterations = 5000
|
|||
|
||||
|
||||
"""
|
||||
When run on a system without the jpeg leak fixes, the valgrind runs look like this.
|
||||
When run on a system without the jpeg leak fixes,
|
||||
the valgrind runs look like this.
|
||||
|
||||
NOSE_PROCESSES=0 NOSE_TIMEOUT=600 valgrind --tool=massif \
|
||||
python test-installed.py -s -v Tests/check_jpeg_leaks.py
|
||||
|
@ -105,7 +106,8 @@ post-patch:
|
|||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", qtables=qtables)
|
||||
|
||||
"""
|
||||
def test_exif_leak(self):
|
||||
"""
|
||||
pre patch:
|
||||
|
||||
MB
|
||||
|
@ -160,8 +162,6 @@ post patch:
|
|||
0 11.33
|
||||
|
||||
"""
|
||||
|
||||
def test_exif_leak(self):
|
||||
im = hopper('RGB')
|
||||
exif = b'12345678'*4096
|
||||
|
||||
|
|
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
|
||||
for txt in im2.text.values():
|
||||
total_len += len(txt)
|
||||
self.assertLess(total_len, 64*1024*1024, "Total text chunks greater than 64M")
|
||||
self.assertLess(total_len, 64*1024*1024,
|
||||
"Total text chunks greater than 64M")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -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:
|
||||
print("=== orphaned temp file: %s" % path)
|
||||
|
||||
def assert_almost_equal(self, a, b, msg=None, eps=1e-6):
|
||||
self.assertLess(
|
||||
abs(a-b), eps,
|
||||
msg or "got %r, expected %r" % (a, b))
|
||||
|
||||
def assert_deep_equal(self, a, b, msg=None):
|
||||
try:
|
||||
self.assertEqual(
|
||||
|
@ -234,6 +229,9 @@ def imagemagick_available():
|
|||
return IMCONVERT and command_succeeds([IMCONVERT, '-version'])
|
||||
|
||||
|
||||
def on_appveyor():
|
||||
return 'APPVEYOR' in os.environ
|
||||
|
||||
if sys.platform == 'win32':
|
||||
IMCONVERT = os.environ.get('MAGICK_HOME', '')
|
||||
if IMCONVERT:
|
||||
|
|
BIN
Tests/images/7x13.png
Normal file
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