mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-13 16:54:45 +03:00
Merge
This commit is contained in:
commit
7d129d4aca
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
|
37
.github/CONTRIBUTING.md
vendored
Normal file
37
.github/CONTRIBUTING.md
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
# 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/pulls). All contributions are welcome.
|
||||
|
||||
## Bug fixes, feature additions, etc.
|
||||
|
||||
Please send a pull request to the master branch. Please include [documentation](http://pillow.readthedocs.org) and [tests](Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new) or irc://irc.freenode.net#pil
|
||||
|
||||
- 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](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
|
||||
|
||||
- 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
|
||||
|
||||
When reporting issues, please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. The best reproductions are self-contained scripts with minimal dependencies.
|
||||
|
||||
### Provide details
|
||||
|
||||
- What did you do?
|
||||
- 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
|
||||
|
|
39
.travis.yml
39
.travis.yml
|
@ -9,23 +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"
|
||||
|
||||
# Pyroma installation is slow on Py3, so just do it for Py2.
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then travis_retry pip install pyroma; fi
|
||||
- "travis_retry pip install 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
|
||||
|
||||
|
@ -33,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
|
||||
|
@ -56,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
|
||||
- |
|
||||
|
@ -106,6 +121,8 @@ after_script:
|
|||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- python: nightly
|
||||
|
||||
env:
|
||||
global:
|
||||
|
|
602
CHANGES.rst
602
CHANGES.rst
|
@ -1,9 +1,551 @@
|
|||
Changelog (Pillow)
|
||||
==================
|
||||
|
||||
2.8.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, #1269
|
||||
[radarhere]
|
||||
|
||||
- Ico files are little endian #1232
|
||||
[wiredfool]
|
||||
|
||||
- Upgrade olefile from 0.30 to 0.42b #1226
|
||||
[radarhere, decalage2]
|
||||
|
||||
- Setting transparency value to 0 when the tRNS contains only null byte(s) #1239
|
||||
[juztin]
|
||||
|
||||
- Separated out feature checking from selftest #1233
|
||||
[radarhere]
|
||||
|
||||
- Style/health fixes
|
||||
[radarhere]
|
||||
|
||||
- Update WebP from 0.4.1 to 0.4.3 #1235
|
||||
[radarhere]
|
||||
|
||||
- Release GIL during image load (decode) #1224
|
||||
[lkesteloot]
|
||||
|
||||
- Added icns save #1185
|
||||
[radarhere]
|
||||
|
||||
- Fix putdata memory leak #1196
|
||||
[benoit-pierre]
|
||||
|
||||
- Keep user-specified ordering of icon sizes #1193
|
||||
[karimbahgat]
|
||||
|
||||
- Tiff: allow writing floating point tag values #1113
|
||||
[bpedersen2]
|
||||
|
||||
2.8.2 (2015-06-06)
|
||||
------------------
|
||||
|
||||
- Bug fix: Fixed Tiff handling of bad EXIF data
|
||||
[radarhere]
|
||||
|
||||
2.8.1 (2015-04-02)
|
||||
------------------
|
||||
|
||||
- Bug fix: Catch struct.error on invalid JPEG, fixes #1163
|
||||
[wiredfool, hugovk]
|
||||
|
||||
2.8.0 (2015-04-01)
|
||||
------------------
|
||||
|
||||
- Fix 32-bit BMP loading (RGBA or RGBX)
|
||||
[artscoop]
|
||||
|
||||
- Fix UnboundLocalError in ImageFile #1131
|
||||
[davarisg]
|
||||
|
||||
- Re-enable test image caching
|
||||
[hugovk, homm]
|
||||
|
||||
- Fix: Cannot identify EPS images, fixes #1104
|
||||
[hugovk]
|
||||
|
||||
- Configure setuptools to run nosetests, fixes #729
|
||||
[aclark4life]
|
||||
|
||||
- Style/health fixes
|
||||
[radarhere, hugovk]
|
||||
|
||||
- Add support for HTTP response objects to Image.open()
|
||||
[mfitzp]
|
||||
|
||||
- Improve reference docs for PIL.ImageDraw.Draw.pieslice() #1145
|
||||
[audreyr]
|
||||
|
||||
|
@ -37,7 +579,7 @@ Changelog (Pillow)
|
|||
- Adjust buffer size when quality=keep, fixes #148 (again)
|
||||
[wiredfool]
|
||||
|
||||
- Fix for corrupted bitmaps embedded in truetype fonts. #1072
|
||||
- Fix for corrupted bitmaps embedded in truetype fonts. #1072
|
||||
[jackyyf, wiredfool]
|
||||
|
||||
2.7.0 (2015-01-01)
|
||||
|
@ -46,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
|
||||
|
@ -124,7 +666,7 @@ Changelog (Pillow)
|
|||
2.6.2 (2015-01-01)
|
||||
------------------
|
||||
|
||||
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
|
||||
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
|
||||
[wiredfool]
|
||||
|
||||
- Fix Regression in PyPy 2.4 in streamio #958
|
||||
|
@ -137,7 +679,7 @@ Changelog (Pillow)
|
|||
[wiredfool]
|
||||
|
||||
- Fix manifest to include all test files.
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
2.6.0 (2014-10-01)
|
||||
------------------
|
||||
|
@ -307,7 +849,7 @@ Changelog (Pillow)
|
|||
[wirefool]
|
||||
|
||||
- Top level flake8 fixes #741
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
- Remove obsolete Animated Raster Graphics (ARG) support
|
||||
[hugovk]
|
||||
|
@ -436,7 +978,7 @@ Changelog (Pillow)
|
|||
[larsmans]
|
||||
|
||||
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
- Merge from Philippe Lagadec’s OleFileIO_PL fork
|
||||
[vadmium]
|
||||
|
@ -654,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
|
||||
|
@ -786,14 +1328,14 @@ 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)
|
||||
------------------
|
||||
|
||||
.. Note:: Special thanks to Christoph Gohlke and Eric Soroos for assisting with a pre-PyCon 2013 release!
|
||||
|
||||
- Many other bug fixes and enhancements by many other people.
|
||||
- Many other bug fixes and enhancements by many other people.
|
||||
|
||||
- Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.)
|
||||
[fluggo]
|
||||
|
@ -809,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.
|
||||
|
@ -851,13 +1393,13 @@ Changelog (Pillow)
|
|||
[blueyed]
|
||||
|
||||
- Package cleanup and additional documentation
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.7.4 (2011-07-21)
|
||||
------------------
|
||||
|
||||
- Fix brown bag release
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.7.3 (2011-07-20)
|
||||
------------------
|
||||
|
@ -869,19 +1411,19 @@ Changelog (Pillow)
|
|||
------------------
|
||||
|
||||
- Bug fix: Python 2.4 compat
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.7.1 (2011-05-31)
|
||||
------------------
|
||||
|
||||
- More multi-arch support
|
||||
[SteveM, regebro, barry, aclark]
|
||||
[SteveM, regebro, barry, aclark4life]
|
||||
|
||||
1.7.0 (2011-05-27)
|
||||
------------------
|
||||
|
||||
- Add support for multi-arch library directory /usr/lib/x86_64-linux-gnu
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.6 (12/01/2010)
|
||||
----------------
|
||||
|
@ -890,28 +1432,28 @@ Changelog (Pillow)
|
|||
[elro]
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.5 (11/28/2010)
|
||||
----------------
|
||||
|
||||
- Module and package fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.4 (11/28/2010)
|
||||
----------------
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.3 (11/28/2010)
|
||||
----------------
|
||||
|
||||
- Add support for /lib64 and /usr/lib64 library directories on Linux
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.2 (08/02/2010)
|
||||
----------------
|
||||
|
@ -920,25 +1462,29 @@ Changelog (Pillow)
|
|||
[jezdez]
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.1 (07/31/2010)
|
||||
----------------
|
||||
|
||||
- Removed setuptools_hg requirement
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
- Doc fixes
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
1.0 (07/30/2010)
|
||||
----------------
|
||||
|
||||
- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required.
|
||||
- Forked PIL based on `Hanno Schlichting's re-packaging <http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
|
||||
[aclark]
|
||||
[aclark4life]
|
||||
|
||||
.. Note:: What follows is the original PIL 1.1.7 CHANGES
|
||||
Pre-fork
|
||||
--------
|
||||
|
||||
0.2b5-1.1.7
|
||||
+++++++++++
|
||||
|
||||
::
|
||||
|
||||
|
@ -977,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)
|
||||
|
||||
|
@ -1950,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
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
# Contributing
|
||||
|
||||
## Fixes, Features and Changes
|
||||
|
||||
Send a pull request to the master branch. We'll generally want documentation and [tests](Tests/README.rst) for new features. Tests or documentation on their own are also welcomed. Feel free to ask questions as an [issue](https://github.com/python-pillow/Pillow/issues/new) or on IRC (irc.freenode.net, #pil)
|
||||
|
||||
- Fork the repo
|
||||
- Make a branch from master
|
||||
- Add your changes + Tests
|
||||
- Run the test suite. Try to run on both Python 2.x and 3.x, or you'll get tripped up. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
|
||||
- Push to your fork, and make a pull request onto master.
|
||||
|
||||
A few guidelines:
|
||||
- Try to keep any code commits clean and separate from reformatting commits.
|
||||
- All new code is going to need tests.
|
||||
- Try to follow PEP8.
|
||||
|
||||
## Bugs
|
||||
|
||||
When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. The best reproductions are self-contained scripts that pull in as few dependencies as possible. An entire Django stack is harder to handle.
|
||||
|
||||
Let us know:
|
||||
- What did you do?
|
||||
- What did you expect to happen?
|
||||
- What actually happened?
|
||||
- What versions of Pillow and Python are you using?
|
|
@ -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.
|
96
MANIFEST.in
96
MANIFEST.in
|
@ -1,79 +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
|
||||
|
|
116
Makefile
116
Makefile
|
@ -1,28 +1,6 @@
|
|||
.PHONY: pre clean install test inplace coverage test-dep help docs livedocs
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " clean remove build products"
|
||||
@echo " install make and install"
|
||||
@echo " test run tests on installed pillow"
|
||||
@echo " inplace make inplace extension"
|
||||
@echo " coverage run coverage test (in progress)"
|
||||
@echo " docs make html docs"
|
||||
@echo " docserver run an http server on the docs directory"
|
||||
@echo " test-dep install coveraget and test dependencies"
|
||||
|
||||
pre:
|
||||
virtualenv .
|
||||
bin/pip install -r requirements.txt
|
||||
bin/python setup.py develop
|
||||
bin/python selftest.py
|
||||
bin/nosetests Tests/test_*.py
|
||||
bin/python setup.py install
|
||||
bin/python test-installed.py
|
||||
check-manifest
|
||||
pyroma .
|
||||
viewdoc
|
||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||
.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test
|
||||
.DEFAULT_GOAL := release-test
|
||||
|
||||
clean:
|
||||
python setup.py clean
|
||||
|
@ -30,32 +8,84 @@ clean:
|
|||
rm -r build || true
|
||||
find . -name __pycache__ | xargs rm -r || true
|
||||
|
||||
install:
|
||||
python setup.py install
|
||||
python selftest.py --installed
|
||||
|
||||
test:
|
||||
python test-installed.py
|
||||
|
||||
inplace: clean
|
||||
python setup.py build_ext --inplace
|
||||
BRANCHES=`git branch -a | grep -v HEAD | grep -v master | grep remote`
|
||||
co:
|
||||
-for i in $(BRANCHES) ; do \
|
||||
git checkout -t $$i ; \
|
||||
done
|
||||
|
||||
coverage:
|
||||
# requires nose-cov
|
||||
coverage erase
|
||||
coverage run --parallel-mode --include=PIL/* selftest.py
|
||||
nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py
|
||||
# doesn't combine properly before report,
|
||||
# writing report instead of displaying invalid report
|
||||
# Doesn't combine properly before report, writing report instead of displaying invalid report.
|
||||
rm -r htmlcov || true
|
||||
coverage combine
|
||||
coverage report
|
||||
|
||||
test-dep:
|
||||
pip install coveralls nose nose-cov pep8 pyflakes
|
||||
|
||||
docs:
|
||||
doc:
|
||||
$(MAKE) -C docs html
|
||||
|
||||
docserver:
|
||||
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
|
||||
docserve:
|
||||
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
|
||||
|
||||
help:
|
||||
@echo "Welcome to Pillow development. Please use \`make <target>' where <target> is one of"
|
||||
@echo " clean remove build products"
|
||||
@echo " coverage run coverage test (in progress)"
|
||||
@echo " doc make html docs"
|
||||
@echo " docserve run an http server on the docs directory"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " inplace make inplace extension"
|
||||
@echo " install make and install"
|
||||
@echo " install-req install documentation and test dependencies"
|
||||
@echo " 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"
|
||||
@echo " upload-test build and upload sdists to test.pythonpackages.com"
|
||||
|
||||
inplace: clean
|
||||
python setup.py build_ext --inplace
|
||||
|
||||
install:
|
||||
python setup.py install
|
||||
python selftest.py --installed
|
||||
|
||||
install-req:
|
||||
pip install -r requirements.txt
|
||||
|
||||
install-venv:
|
||||
virtualenv .
|
||||
bin/pip install -r requirements.txt
|
||||
|
||||
release-test:
|
||||
$(MAKE) install-req
|
||||
python setup.py develop
|
||||
python selftest.py
|
||||
nosetests Tests/test_*.py
|
||||
python setup.py install
|
||||
python test-installed.py
|
||||
check-manifest
|
||||
pyroma .
|
||||
viewdoc
|
||||
|
||||
sdist:
|
||||
python setup.py sdist --format=gztar,zip
|
||||
|
||||
test:
|
||||
python test-installed.py
|
||||
|
||||
# https://docs.python.org/2/distutils/packageindex.html#the-pypirc-file
|
||||
upload-test:
|
||||
# [test]
|
||||
# username:
|
||||
# password:
|
||||
# repository = http://test.pythonpackages.com
|
||||
python setup.py sdist --format=gztar,zip upload -r test
|
||||
|
||||
upload:
|
||||
python setup.py sdist --format=gztar,zip upload
|
||||
|
||||
readme:
|
||||
viewdoc
|
||||
|
|
|
@ -69,8 +69,8 @@ def bdf_char(f):
|
|||
bitmap.append(s[:-1])
|
||||
bitmap = b"".join(bitmap)
|
||||
|
||||
[x, y, l, d] = [int(s) for s in props["BBX"].split()]
|
||||
[dx, dy] = [int(s) for s in props["DWIDTH"].split()]
|
||||
[x, y, l, d] = [int(p) for p in props["BBX"].split()]
|
||||
[dx, dy] = [int(p) for p in props["DWIDTH"].split()]
|
||||
|
||||
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
|
||||
|
||||
|
@ -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,11 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
import math
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
@ -48,7 +47,7 @@ BIT2MODE = {
|
|||
8: ("P", "P"),
|
||||
16: ("RGB", "BGR;15"),
|
||||
24: ("RGB", "BGR"),
|
||||
32: ("RGB", "BGRX")
|
||||
32: ("RGB", "BGRX"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -56,131 +55,153 @@ def _accept(prefix):
|
|||
return prefix[:2] == b"BM"
|
||||
|
||||
|
||||
##
|
||||
# ==============================================================================
|
||||
# Image plugin for the Windows BMP format.
|
||||
|
||||
# ==============================================================================
|
||||
class BmpImageFile(ImageFile.ImageFile):
|
||||
""" Image plugin for the Windows Bitmap format (BMP) """
|
||||
|
||||
format = "BMP"
|
||||
# -------------------------------------------------------------- Description
|
||||
format_description = "Windows Bitmap"
|
||||
format = "BMP"
|
||||
# --------------------------------------------------- BMP Compression values
|
||||
COMPRESSIONS = {'RAW': 0, 'RLE8': 1, 'RLE4': 2, 'BITFIELDS': 3, 'JPEG': 4, 'PNG': 5}
|
||||
RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5
|
||||
|
||||
def _bitmap(self, header=0, offset=0):
|
||||
""" Read relevant info about the BMP """
|
||||
read, seek = self.fp.read, self.fp.seek
|
||||
if header:
|
||||
self.fp.seek(header)
|
||||
|
||||
read = self.fp.read
|
||||
|
||||
# CORE/INFO
|
||||
s = read(4)
|
||||
s = s + ImageFile._safe_read(self.fp, i32(s)-4)
|
||||
|
||||
if len(s) == 12:
|
||||
|
||||
# OS/2 1.0 CORE
|
||||
bits = i16(s[10:])
|
||||
self.size = i16(s[4:]), i16(s[6:])
|
||||
compression = 0
|
||||
lutsize = 3
|
||||
colors = 0
|
||||
direction = -1
|
||||
|
||||
elif len(s) in [40, 64, 108, 124]:
|
||||
|
||||
# WIN 3.1 or OS/2 2.0 INFO
|
||||
bits = i16(s[14:])
|
||||
self.size = i32(s[4:]), i32(s[8:])
|
||||
compression = i32(s[16:])
|
||||
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
|
||||
lutsize = 4
|
||||
colors = i32(s[32:])
|
||||
direction = -1
|
||||
if i8(s[11]) == 0xff:
|
||||
# upside-down storage
|
||||
self.size = self.size[0], 2**32 - self.size[1]
|
||||
direction = 0
|
||||
|
||||
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701),
|
||||
pxperm))
|
||||
|
||||
seek(header)
|
||||
file_info = dict()
|
||||
file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size)
|
||||
file_info['direction'] = -1
|
||||
# --------------------- If requested, read header at a specific position
|
||||
header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size
|
||||
# --------------------------------------------------- IBM OS/2 Bitmap v1
|
||||
# ------ This format has different offsets because of width/height types
|
||||
if file_info['header_size'] == 12:
|
||||
file_info['width'] = i16(header_data[0:2])
|
||||
file_info['height'] = i16(header_data[2:4])
|
||||
file_info['planes'] = i16(header_data[4:6])
|
||||
file_info['bits'] = i16(header_data[6:8])
|
||||
file_info['compression'] = self.RAW
|
||||
file_info['palette_padding'] = 3
|
||||
# ---------------------------------------------- Windows Bitmap v2 to v5
|
||||
elif file_info['header_size'] in (40, 64, 108, 124): # v3, OS/2 v2, v4, v5
|
||||
if file_info['header_size'] >= 40: # v3 and OS/2
|
||||
file_info['y_flip'] = i8(header_data[7]) == 0xff
|
||||
file_info['direction'] = 1 if file_info['y_flip'] else -1
|
||||
file_info['width'] = i32(header_data[0:4])
|
||||
file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8])
|
||||
file_info['planes'] = i16(header_data[8:10])
|
||||
file_info['bits'] = i16(header_data[10:12])
|
||||
file_info['compression'] = i32(header_data[12:16])
|
||||
file_info['data_size'] = i32(header_data[16:20]) # byte size of pixel data
|
||||
file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))
|
||||
file_info['colors'] = i32(header_data[28:32])
|
||||
file_info['palette_padding'] = 4
|
||||
self.info["dpi"] = tuple(
|
||||
map(lambda x: 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']):
|
||||
file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
|
||||
else:
|
||||
for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']:
|
||||
file_info[mask] = i32(read(4))
|
||||
file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'])
|
||||
file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask'])
|
||||
else:
|
||||
raise IOError("Unsupported BMP header type (%d)" % len(s))
|
||||
|
||||
if (self.size[0]*self.size[1]) > 2**31:
|
||||
# Prevent DOS for > 2gb images
|
||||
raise IOError("Unsupported BMP header type (%d)" % file_info['header_size'])
|
||||
# ------------------ Special case : header is reported 40, which
|
||||
# ---------------------- is shorter than real size for bpp >= 16
|
||||
self.size = file_info['width'], file_info['height']
|
||||
# -------- If color count was not found in the header, compute from bits
|
||||
file_info['colors'] = file_info['colors'] if file_info.get('colors', 0) else (1 << file_info['bits'])
|
||||
# -------------------------------- Check abnormal values for DOS attacks
|
||||
if file_info['width'] * file_info['height'] > 2**31:
|
||||
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
|
||||
|
||||
if not colors:
|
||||
colors = 1 << bits
|
||||
|
||||
# MODE
|
||||
try:
|
||||
self.mode, rawmode = BIT2MODE[bits]
|
||||
except KeyError:
|
||||
raise IOError("Unsupported BMP pixel depth (%d)" % bits)
|
||||
|
||||
if compression == 3:
|
||||
# BI_BITFIELDS compression
|
||||
mask = i32(read(4)), i32(read(4)), i32(read(4))
|
||||
if bits == 32 and mask == (0xff0000, 0x00ff00, 0x0000ff):
|
||||
rawmode = "BGRX"
|
||||
elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f):
|
||||
rawmode = "BGR;16"
|
||||
elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f):
|
||||
rawmode = "BGR;15"
|
||||
else:
|
||||
# print bits, map(hex, mask)
|
||||
raise IOError("Unsupported BMP bitfields layout")
|
||||
elif compression != 0:
|
||||
raise IOError("Unsupported BMP compression (%d)" % compression)
|
||||
|
||||
# LUT
|
||||
if self.mode == "P":
|
||||
palette = []
|
||||
greyscale = 1
|
||||
if colors == 2:
|
||||
indices = (0, 255)
|
||||
elif colors > 2**16 or colors <= 0: # We're reading a i32.
|
||||
raise IOError("Unsupported BMP Palette size (%d)" % colors)
|
||||
else:
|
||||
indices = list(range(colors))
|
||||
for i in indices:
|
||||
rgb = read(lutsize)[:3]
|
||||
if rgb != o8(i)*3:
|
||||
greyscale = 0
|
||||
palette.append(rgb)
|
||||
if greyscale:
|
||||
if colors == 2:
|
||||
self.mode = rawmode = "1"
|
||||
# ----------------------- Check bit depth for unusual unsupported values
|
||||
self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None))
|
||||
if self.mode is None:
|
||||
raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits'])
|
||||
# ----------------- Process BMP with Bitfields compression (not palette)
|
||||
if file_info['compression'] == self.BITFIELDS:
|
||||
SUPPORTED = {
|
||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
|
||||
24: [(0xff0000, 0xff00, 0xff)],
|
||||
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
|
||||
}
|
||||
MASK_MODES = {
|
||||
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
|
||||
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
|
||||
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
||||
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
||||
(16, (0xf800, 0x7e0, 0x1f)): "BGR;16",
|
||||
(16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"
|
||||
}
|
||||
if file_info['bits'] in SUPPORTED:
|
||||
if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
|
||||
raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])]
|
||||
self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
|
||||
elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]:
|
||||
raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])]
|
||||
else:
|
||||
self.mode = rawmode = "L"
|
||||
raise IOError("Unsupported BMP bitfields layout")
|
||||
else:
|
||||
self.mode = "P"
|
||||
self.palette = ImagePalette.raw(
|
||||
"BGR", b"".join(palette)
|
||||
)
|
||||
raise IOError("Unsupported BMP bitfields layout")
|
||||
elif file_info['compression'] == self.RAW:
|
||||
if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset
|
||||
raw_mode, self.mode = "BGRA", "RGBA"
|
||||
else:
|
||||
raise IOError("Unsupported BMP compression (%d)" % file_info['compression'])
|
||||
# ---------------- Once the header is processed, process the palette/LUT
|
||||
if self.mode == "P": # Paletted for 1, 4 and 8 bit images
|
||||
# ----------------------------------------------------- 1-bit images
|
||||
if not (0 < file_info['colors'] <= 65536):
|
||||
raise IOError("Unsupported BMP Palette size (%d)" % file_info['colors'])
|
||||
else:
|
||||
padding = file_info['palette_padding']
|
||||
palette = read(padding * file_info['colors'])
|
||||
greyscale = True
|
||||
indices = (0, 255) if file_info['colors'] == 2 else list(range(file_info['colors']))
|
||||
# ------------------ Check if greyscale and ignore palette if so
|
||||
for ind, val in enumerate(indices):
|
||||
rgb = palette[ind*padding:ind*padding + 3]
|
||||
if rgb != o8(val) * 3:
|
||||
greyscale = False
|
||||
# -------- If all colors are grey, white or black, ditch palette
|
||||
if greyscale:
|
||||
self.mode = "1" if file_info['colors'] == 2 else "L"
|
||||
raw_mode = self.mode
|
||||
else:
|
||||
self.mode = "P"
|
||||
self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", palette)
|
||||
|
||||
if not offset:
|
||||
offset = self.fp.tell()
|
||||
|
||||
self.tile = [("raw",
|
||||
(0, 0) + self.size,
|
||||
offset,
|
||||
(rawmode, ((self.size[0]*bits+31) >> 3) & (~3),
|
||||
direction))]
|
||||
|
||||
self.info["compression"] = compression
|
||||
# ----------------------------- Finally set the tile data for the plugin
|
||||
self.info['compression'] = file_info['compression']
|
||||
self.tile = [('raw', (0, 0, file_info['width'], file_info['height']), offset or self.fp.tell(),
|
||||
(raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction'])
|
||||
)]
|
||||
|
||||
def _open(self):
|
||||
|
||||
# HEAD
|
||||
s = self.fp.read(14)
|
||||
if s[:2] != b"BM":
|
||||
""" Open file, check magic number and read header """
|
||||
# read 14 bytes: magic number, filesize, reserved, header final offset
|
||||
head_data = self.fp.read(14)
|
||||
# choke if the file does not have the required magic bytes
|
||||
if head_data[0:2] != b"BM":
|
||||
raise SyntaxError("Not a BMP file")
|
||||
offset = i32(s[10:])
|
||||
|
||||
# read the start position of the BMP image data (u32)
|
||||
offset = i32(head_data[10:14])
|
||||
# load bitmap information (offset=raster info)
|
||||
self._bitmap(offset=offset)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Image plugin for the DIB format (BMP alias)
|
||||
# ==============================================================================
|
||||
class DibImageFile(BmpImageFile):
|
||||
|
||||
format = "DIB"
|
||||
|
@ -198,6 +219,7 @@ SAVE = {
|
|||
"L": ("L", 8, 256),
|
||||
"P": ("P", 8, 256),
|
||||
"RGB": ("BGR", 24, 0),
|
||||
"RGBA": ("BGRA", 32, 0),
|
||||
}
|
||||
|
||||
|
||||
|
@ -262,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")
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
# file (for example a TAR file).
|
||||
|
||||
|
||||
class ContainerIO:
|
||||
class ContainerIO(object):
|
||||
|
||||
##
|
||||
# Create file object.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
##
|
||||
|
@ -62,6 +61,14 @@ class DcxImageFile(PcxImageFile):
|
|||
self.__fp = self.fp
|
||||
self.seek(0)
|
||||
|
||||
@property
|
||||
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")
|
||||
|
@ -74,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,13 +151,13 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
os.unlink(outfile)
|
||||
if infile_temp:
|
||||
os.unlink(infile_temp)
|
||||
except:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return im
|
||||
|
||||
|
||||
class PSFile:
|
||||
class PSFile(object):
|
||||
"""
|
||||
Wrapper for bytesio object that treats either CR or LF as end of line.
|
||||
"""
|
||||
|
@ -187,7 +187,8 @@ class PSFile:
|
|||
|
||||
|
||||
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
|
||||
|
@ -248,7 +249,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
# Note: The DSC spec says that BoundingBox
|
||||
# fields should be integers, but some drivers
|
||||
# put floating point values there anyway.
|
||||
box = [int(float(s)) for s in v.split()]
|
||||
box = [int(float(i)) for i in v.split()]
|
||||
self.size = box[2] - box[0], box[3] - box[1]
|
||||
self.tile = [("eps", (0, 0) + self.size, offset,
|
||||
(length, box))]
|
||||
|
@ -275,26 +276,26 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
|
||||
s = fp.readline().strip('\r\n')
|
||||
|
||||
if s[0] != "%":
|
||||
if s[:1] != "%":
|
||||
break
|
||||
|
||||
#
|
||||
# Scan for an "ImageData" descriptor
|
||||
|
||||
while s[0] == "%":
|
||||
while s[:1] == "%":
|
||||
|
||||
if len(s) > 255:
|
||||
raise SyntaxError("not an EPS file")
|
||||
|
||||
if s[:11] == "%ImageData:":
|
||||
# Encoded bitmapped image.
|
||||
[x, y, bi, mo, z3, z4, en, id] = s[11:].split(None, 7)
|
||||
x, y, bi, mo = s[11:].split(None, 7)[:4]
|
||||
|
||||
if int(bi) != 8:
|
||||
break
|
||||
try:
|
||||
self.mode = self.mode_map[int(mo)]
|
||||
except:
|
||||
except ValueError:
|
||||
break
|
||||
|
||||
self.size = int(x), int(y)
|
||||
|
@ -365,7 +366,7 @@ def _save(im, fp, filename, eps=1):
|
|||
else:
|
||||
raise ValueError("image mode is not supported")
|
||||
|
||||
class NoCloseStream:
|
||||
class NoCloseStream(object):
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
|
||||
|
@ -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",
|
||||
|
|
|
@ -18,6 +18,7 @@ _handler = None
|
|||
#
|
||||
# @param handler Handler object.
|
||||
|
||||
|
||||
def register_handler(handler):
|
||||
global _handler
|
||||
_handler = handler
|
||||
|
@ -25,9 +26,11 @@ def register_handler(handler):
|
|||
# --------------------------------------------------------------------
|
||||
# Image adapter
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:6] == b"SIMPLE"
|
||||
|
||||
|
||||
class FITSStubImageFile(ImageFile.StubImageFile):
|
||||
|
||||
format = "FITS"
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
||||
##
|
||||
|
@ -86,9 +86,11 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
||||
|
||||
# set things up to decode first frame
|
||||
self.frame = -1
|
||||
self.__frame = -1
|
||||
self.__fp = self.fp
|
||||
|
||||
self.__rewind = self.fp.tell()
|
||||
self._n_frames = None
|
||||
self._is_animated = None
|
||||
self.seek(0)
|
||||
|
||||
def _palette(self, palette, shift):
|
||||
|
@ -109,11 +111,55 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
palette[i] = (r, g, b)
|
||||
i += 1
|
||||
|
||||
def seek(self, frame):
|
||||
@property
|
||||
def n_frames(self):
|
||||
if self._n_frames is None:
|
||||
current = self.tell()
|
||||
try:
|
||||
while True:
|
||||
self.seek(self.tell() + 1)
|
||||
except EOFError:
|
||||
self._n_frames = self.tell() + 1
|
||||
self.seek(current)
|
||||
return self._n_frames
|
||||
|
||||
if frame != self.frame + 1:
|
||||
@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):
|
||||
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:
|
||||
self.__frame = -1
|
||||
self.__fp.seek(self.__rewind)
|
||||
self.__offset = 128
|
||||
|
||||
if frame != self.__frame + 1:
|
||||
raise ValueError("cannot seek to frame %d" % frame)
|
||||
self.frame = frame
|
||||
self.__frame = frame
|
||||
|
||||
# move to next frame
|
||||
self.fp = self.__fp
|
||||
|
@ -128,16 +174,15 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
self.decodermaxblock = framesize
|
||||
self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
|
||||
|
||||
self.__offset = self.__offset + framesize
|
||||
self.__offset += framesize
|
||||
|
||||
def tell(self):
|
||||
|
||||
return self.frame
|
||||
return self.__frame
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
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")
|
||||
|
|
|
@ -17,11 +17,6 @@
|
|||
import os
|
||||
from PIL import Image, _binary
|
||||
|
||||
try:
|
||||
import zlib
|
||||
except ImportError:
|
||||
zlib = None
|
||||
|
||||
WIDTH = 800
|
||||
|
||||
|
||||
|
@ -36,7 +31,7 @@ def puti16(fp, values):
|
|||
##
|
||||
# Base class for raster font file handlers.
|
||||
|
||||
class FontFile:
|
||||
class FontFile(object):
|
||||
|
||||
bitmap = None
|
||||
|
||||
|
@ -83,7 +78,8 @@ class FontFile:
|
|||
glyph = self[i]
|
||||
if glyph:
|
||||
d, dst, src, im = glyph
|
||||
xx, yy = src[2] - src[0], src[3] - src[1]
|
||||
xx = src[2] - src[0]
|
||||
# yy = src[3] - src[1]
|
||||
x0, y0 = x, y
|
||||
x = x + xx
|
||||
if x > WIDTH:
|
||||
|
|
|
@ -16,11 +16,10 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL.OleFileIO import *
|
||||
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||
|
@ -130,15 +129,15 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
fp = self.ole.openstream(stream)
|
||||
|
||||
# skip prefix
|
||||
p = fp.read(28)
|
||||
fp.read(28)
|
||||
|
||||
# header stream
|
||||
s = fp.read(36)
|
||||
|
||||
size = i32(s, 4), i32(s, 8)
|
||||
tilecount = i32(s, 12)
|
||||
# tilecount = i32(s, 12)
|
||||
tilesize = i32(s, 16), i32(s, 20)
|
||||
channels = i32(s, 24)
|
||||
# channels = i32(s, 24)
|
||||
offset = i32(s, 28)
|
||||
length = i32(s, 32)
|
||||
|
||||
|
@ -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))
|
||||
bytes = i32(self.fp.read(4))
|
||||
if width <= 0 or height <= 0 or bytes != 1:
|
||||
color_depth = i32(self.fp.read(4))
|
||||
if width <= 0 or height <= 0:
|
||||
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,13 +24,12 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, \
|
||||
ImageChops, ImageSequence, _binary
|
||||
|
||||
__version__ = "0.9"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
|
@ -89,9 +88,51 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
|
||||
self.__fp = self.fp # FIXME: hack
|
||||
self.__rewind = self.fp.tell()
|
||||
self.seek(0) # get ready to read first frame
|
||||
self._n_frames = None
|
||||
self._is_animated = None
|
||||
self._seek(0) # get ready to read first frame
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
if self._n_frames is None:
|
||||
current = self.tell()
|
||||
try:
|
||||
while True:
|
||||
self.seek(self.tell() + 1)
|
||||
except EOFError:
|
||||
self._n_frames = self.tell() + 1
|
||||
self.seek(current)
|
||||
return self._n_frames
|
||||
|
||||
@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):
|
||||
try:
|
||||
self._seek(f)
|
||||
except EOFError:
|
||||
self.seek(last_frame)
|
||||
raise EOFError("no more images in GIF file")
|
||||
|
||||
def _seek(self, frame):
|
||||
|
||||
if frame == 0:
|
||||
# rewind
|
||||
|
@ -222,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:
|
||||
|
@ -260,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:
|
||||
|
@ -271,17 +331,9 @@ def _save(im, fp, filename):
|
|||
pass # write uncompressed file
|
||||
|
||||
if im.mode in RAWMODE:
|
||||
imOut = 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
|
||||
imOut = im.convert("P", palette=1, colors=palette_size)
|
||||
else:
|
||||
imOut = im.convert("L")
|
||||
im_out = _convert_mode(im, True)
|
||||
|
||||
# header
|
||||
try:
|
||||
|
@ -290,12 +342,66 @@ def _save(im, fp, filename):
|
|||
palette = None
|
||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||
|
||||
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
|
||||
for s in header:
|
||||
fp.write(s)
|
||||
if save_all:
|
||||
previous = None
|
||||
|
||||
flags = 0
|
||||
first_frame = None
|
||||
for im_frame in ImageSequence.Iterator(im):
|
||||
im_frame = _convert_mode(im_frame)
|
||||
|
||||
# To specify duration, add the time in milliseconds to getdata(),
|
||||
# e.g. getdata(im_frame, duration=1000)
|
||||
if not previous:
|
||||
# global header
|
||||
first_frame = getheader(im_frame, palette, im.encoderinfo)[0]
|
||||
first_frame += getdata(im_frame, (0, 0), **im.encoderinfo)
|
||||
else:
|
||||
if first_frame:
|
||||
for s in first_frame:
|
||||
fp.write(s)
|
||||
first_frame = None
|
||||
|
||||
# delta frame
|
||||
delta = ImageChops.subtract_modulo(im_frame, previous.copy())
|
||||
bbox = delta.getbbox()
|
||||
|
||||
if bbox:
|
||||
# compress difference
|
||||
for s in getdata(im_frame.crop(bbox),
|
||||
bbox[:2], **im.encoderinfo):
|
||||
fp.write(s)
|
||||
else:
|
||||
# FIXME: what should we do in this case?
|
||||
pass
|
||||
previous = im_frame
|
||||
if first_frame:
|
||||
save_all = False
|
||||
if not save_all:
|
||||
header = getheader(im_out, palette, im.encoderinfo)[0]
|
||||
for s in header:
|
||||
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
|
||||
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
|
||||
|
||||
def get_interlace(im):
|
||||
try:
|
||||
interlace = im.encoderinfo["interlace"]
|
||||
except KeyError:
|
||||
|
@ -305,9 +411,11 @@ def _save(im, fp, filename):
|
|||
if min(im.size) < 16:
|
||||
interlace = 0
|
||||
|
||||
if interlace:
|
||||
flags = flags | 64
|
||||
return interlace
|
||||
|
||||
|
||||
def _get_local_header(fp, im, offset, flags):
|
||||
transparent_color_exists = False
|
||||
try:
|
||||
transparency = im.encoderinfo["transparency"]
|
||||
except KeyError:
|
||||
|
@ -315,47 +423,55 @@ def _save(im, fp, filename):
|
|||
else:
|
||||
transparency = int(transparency)
|
||||
# optimize the block away if transparent color is not used
|
||||
transparentColorExists = True
|
||||
# adjust the transparency index after optimize
|
||||
if usedPaletteColors is not None and len(usedPaletteColors) < 256:
|
||||
for i in range(len(usedPaletteColors)):
|
||||
if usedPaletteColors[i] == transparency:
|
||||
transparency = i
|
||||
transparentColorExists = True
|
||||
break
|
||||
else:
|
||||
transparentColorExists = False
|
||||
transparent_color_exists = True
|
||||
|
||||
# transparency extension block
|
||||
if transparentColorExists:
|
||||
fp.write(b"!" +
|
||||
o8(249) + # extension intro
|
||||
o8(4) + # length
|
||||
o8(1) + # transparency info present
|
||||
o16(0) + # duration
|
||||
o8(transparency) # transparency index
|
||||
+ o8(0))
|
||||
if _get_optimize(im, im.encoderinfo):
|
||||
used_palette_colors = _get_used_palette_colors(im)
|
||||
|
||||
# local image header
|
||||
# adjust the transparency index after optimize
|
||||
if len(used_palette_colors) < 256:
|
||||
for i in range(len(used_palette_colors)):
|
||||
if used_palette_colors[i] == transparency:
|
||||
transparency = i
|
||||
transparent_color_exists = True
|
||||
break
|
||||
else:
|
||||
transparent_color_exists = False
|
||||
|
||||
if "duration" in im.encoderinfo:
|
||||
duration = int(im.encoderinfo["duration"] / 10)
|
||||
else:
|
||||
duration = 0
|
||||
if transparent_color_exists or duration != 0:
|
||||
transparency_flag = 1 if transparent_color_exists else 0
|
||||
if not transparent_color_exists:
|
||||
transparency = 0
|
||||
|
||||
fp.write(b"!" +
|
||||
o8(249) + # extension intro
|
||||
o8(4) + # length
|
||||
o8(transparency_flag) + # transparency info present
|
||||
o16(duration) + # duration
|
||||
o8(transparency) + # transparency index
|
||||
o8(0))
|
||||
|
||||
if "loop" in im.encoderinfo:
|
||||
number_of_loops = im.encoderinfo["loop"]
|
||||
fp.write(b"!" +
|
||||
o8(255) + # extension intro
|
||||
o8(11) +
|
||||
b"NETSCAPE2.0" +
|
||||
o8(3) +
|
||||
o8(1) +
|
||||
o16(number_of_loops) + # number of loops
|
||||
o8(0))
|
||||
fp.write(b"," +
|
||||
o16(0) + o16(0) + # bounding box
|
||||
o16(im.size[0]) + # size
|
||||
o16(offset[0]) + # offset
|
||||
o16(offset[1]) +
|
||||
o16(im.size[0]) + # size
|
||||
o16(im.size[1]) +
|
||||
o8(flags) + # flags
|
||||
o8(8)) # bits
|
||||
|
||||
imOut.encoderconfig = (8, interlace)
|
||||
ImageFile._save(imOut, fp, [("gif", (0, 0)+im.size, 0,
|
||||
RAWMODE[imOut.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
|
||||
fp.write(b";") # end of file
|
||||
|
||||
try:
|
||||
fp.flush()
|
||||
except:
|
||||
pass
|
||||
o8(flags) + # flags
|
||||
o8(8)) # bits
|
||||
|
||||
|
||||
def _save_netpbm(im, fp, filename):
|
||||
|
@ -400,96 +516,126 @@ def _save_netpbm(im, fp, filename):
|
|||
|
||||
try:
|
||||
os.unlink(file)
|
||||
except:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# GIF utilities
|
||||
|
||||
def _get_optimize(im, info):
|
||||
return im.mode in ("P", "L") and info and info.get("optimize", 0)
|
||||
|
||||
|
||||
def _get_used_palette_colors(im):
|
||||
used_palette_colors = []
|
||||
|
||||
# check which colors are used
|
||||
i = 0
|
||||
for count in im.histogram():
|
||||
if count:
|
||||
used_palette_colors.append(i)
|
||||
i += 1
|
||||
|
||||
return used_palette_colors
|
||||
|
||||
|
||||
def getheader(im, palette=None, info=None):
|
||||
"""Return a list of strings representing a GIF header"""
|
||||
|
||||
optimize = info and info.get("optimize", 0)
|
||||
|
||||
# Header Block
|
||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||
|
||||
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
|
||||
]
|
||||
|
||||
if im.mode == "P":
|
||||
if palette and isinstance(palette, bytes):
|
||||
sourcePalette = palette[:768]
|
||||
source_palette = palette[:768]
|
||||
else:
|
||||
sourcePalette = im.im.getpalette("RGB")[:768]
|
||||
source_palette = im.im.getpalette("RGB")[:768]
|
||||
else: # L-mode
|
||||
if palette and isinstance(palette, bytes):
|
||||
sourcePalette = palette[:768]
|
||||
source_palette = palette[:768]
|
||||
else:
|
||||
sourcePalette = bytearray([i//3 for i in range(768)])
|
||||
source_palette = bytearray([i//3 for i in range(768)])
|
||||
|
||||
usedPaletteColors = paletteBytes = None
|
||||
used_palette_colors = palette_bytes = None
|
||||
|
||||
if im.mode in ("P", "L") and optimize:
|
||||
usedPaletteColors = []
|
||||
|
||||
# check which colors are used
|
||||
i = 0
|
||||
for count in im.histogram():
|
||||
if count:
|
||||
usedPaletteColors.append(i)
|
||||
i += 1
|
||||
if _get_optimize(im, info):
|
||||
used_palette_colors = _get_used_palette_colors(im)
|
||||
|
||||
# create the new palette if not every color is used
|
||||
if len(usedPaletteColors) < 256:
|
||||
paletteBytes = b""
|
||||
newPositions = {}
|
||||
if len(used_palette_colors) < 256:
|
||||
palette_bytes = b""
|
||||
new_positions = {}
|
||||
|
||||
i = 0
|
||||
# pick only the used colors from the palette
|
||||
for oldPosition in usedPaletteColors:
|
||||
paletteBytes += sourcePalette[oldPosition*3:oldPosition*3+3]
|
||||
newPositions[oldPosition] = i
|
||||
for oldPosition in used_palette_colors:
|
||||
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
|
||||
new_positions[oldPosition] = i
|
||||
i += 1
|
||||
|
||||
# replace the palette color id of all pixel with the new id
|
||||
imageBytes = bytearray(im.tobytes())
|
||||
for i in range(len(imageBytes)):
|
||||
imageBytes[i] = newPositions[imageBytes[i]]
|
||||
im.frombytes(bytes(imageBytes))
|
||||
newPaletteBytes = (paletteBytes +
|
||||
(768 - len(paletteBytes)) * b'\x00')
|
||||
im.putpalette(newPaletteBytes)
|
||||
im.palette = ImagePalette.ImagePalette("RGB", palette=paletteBytes,
|
||||
size=len(paletteBytes))
|
||||
image_bytes = bytearray(im.tobytes())
|
||||
for i in range(len(image_bytes)):
|
||||
image_bytes[i] = new_positions[image_bytes[i]]
|
||||
im.frombytes(bytes(image_bytes))
|
||||
new_palette_bytes = (palette_bytes +
|
||||
(768 - len(palette_bytes)) * b'\x00')
|
||||
im.putpalette(new_palette_bytes)
|
||||
im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=palette_bytes,
|
||||
size=len(palette_bytes))
|
||||
|
||||
if not paletteBytes:
|
||||
paletteBytes = sourcePalette
|
||||
if not palette_bytes:
|
||||
palette_bytes = source_palette
|
||||
|
||||
# Logical Screen Descriptor
|
||||
# calculate the palette size for the header
|
||||
import math
|
||||
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1
|
||||
if colorTableSize < 0:
|
||||
colorTableSize = 0
|
||||
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
|
||||
if color_table_size < 0:
|
||||
color_table_size = 0
|
||||
# size of global color table + global color table flag
|
||||
header.append(o8(colorTableSize + 128))
|
||||
header.append(o8(color_table_size + 128))
|
||||
# background + reserved/aspect
|
||||
header.append(o8(0) + o8(0))
|
||||
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
|
||||
# the palette has to be 2<<n in size
|
||||
actualTargetSizeDiff = (2 << colorTableSize) - len(paletteBytes)//3
|
||||
if actualTargetSizeDiff > 0:
|
||||
paletteBytes += o8(0) * 3 * actualTargetSizeDiff
|
||||
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
|
||||
if actual_target_size_diff > 0:
|
||||
palette_bytes += o8(0) * 3 * actual_target_size_diff
|
||||
|
||||
# Header + Logical Screen Descriptor + Global Color Table
|
||||
header.append(paletteBytes)
|
||||
return header, usedPaletteColors
|
||||
header.append(palette_bytes)
|
||||
return header, used_palette_colors
|
||||
|
||||
|
||||
def getdata(im, offset=(0, 0), **params):
|
||||
|
@ -497,7 +643,7 @@ def getdata(im, offset=(0, 0), **params):
|
|||
The first string is a local image header, the rest contains
|
||||
encoded image data."""
|
||||
|
||||
class collector:
|
||||
class Collector(object):
|
||||
data = []
|
||||
|
||||
def write(self, data):
|
||||
|
@ -505,19 +651,13 @@ def getdata(im, offset=(0, 0), **params):
|
|||
|
||||
im.load() # make sure raster data is available
|
||||
|
||||
fp = collector()
|
||||
fp = Collector()
|
||||
|
||||
try:
|
||||
im.encoderinfo = params
|
||||
|
||||
# local image header
|
||||
fp.write(b"," +
|
||||
o16(offset[0]) + # offset
|
||||
o16(offset[1]) +
|
||||
o16(im.size[0]) + # size
|
||||
o16(im.size[1]) +
|
||||
o8(0) + # flags
|
||||
o8(8)) # bits
|
||||
_get_local_header(fp, im, offset, 0)
|
||||
|
||||
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
||||
|
||||
|
@ -534,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")
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ def sphere_decreasing(middle, pos):
|
|||
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
|
||||
|
||||
|
||||
class GradientFile:
|
||||
class GradientFile(object):
|
||||
|
||||
gradient = None
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ from PIL._binary import o8
|
|||
##
|
||||
# File handler for GIMP's palette format.
|
||||
|
||||
class GimpPaletteFile:
|
||||
class GimpPaletteFile(object):
|
||||
|
||||
rawmode = "RGB"
|
||||
|
||||
|
|
|
@ -17,7 +17,11 @@
|
|||
|
||||
from PIL import Image, ImageFile, PngImagePlugin, _binary
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
|
||||
if enable_jpeg2k:
|
||||
|
@ -90,7 +94,7 @@ def read_32(fobj, start_length, size):
|
|||
|
||||
def read_mk(fobj, start_length, size):
|
||||
# Alpha masks seem to be uncompressed
|
||||
(start, length) = start_length
|
||||
start = start_length[0]
|
||||
fobj.seek(start)
|
||||
pixel_size = (size[0] * size[2], size[1] * size[2])
|
||||
sizesq = pixel_size[0] * pixel_size[1]
|
||||
|
@ -126,7 +130,7 @@ def read_png_or_jpeg2000(fobj, start_length, size):
|
|||
raise ValueError('Unsupported icon subimage format')
|
||||
|
||||
|
||||
class IcnsFile:
|
||||
class IcnsFile(object):
|
||||
|
||||
SIZES = {
|
||||
(512, 512, 2): [
|
||||
|
@ -247,7 +251,7 @@ class IcnsFile:
|
|||
|
||||
class IcnsImageFile(ImageFile.ImageFile):
|
||||
"""
|
||||
PIL read-only image support for Mac OS .icns files.
|
||||
PIL image support for Mac OS .icns files.
|
||||
Chooses the best resolution, but will possibly load
|
||||
a different size image if you mutate the size attribute
|
||||
before calling 'load'.
|
||||
|
@ -293,12 +297,63 @@ class IcnsImageFile(ImageFile.ImageFile):
|
|||
self.tile = ()
|
||||
self.load_end()
|
||||
|
||||
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
|
||||
Image.register_extension("ICNS", '.icns')
|
||||
|
||||
def _save(im, fp, filename):
|
||||
"""
|
||||
Saves the image as a series of PNG files,
|
||||
that are then converted to a .icns file
|
||||
using the OS X command line utility 'iconutil'.
|
||||
|
||||
OS X only.
|
||||
"""
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
|
||||
# create the temporary set of pngs
|
||||
iconset = tempfile.mkdtemp('.iconset')
|
||||
last_w = None
|
||||
last_im = None
|
||||
for w in [16, 32, 128, 256, 512]:
|
||||
prefix = 'icon_{}x{}'.format(w, w)
|
||||
|
||||
if last_w == w:
|
||||
im_scaled = last_im
|
||||
else:
|
||||
im_scaled = im.resize((w, w), Image.LANCZOS)
|
||||
im_scaled.save(os.path.join(iconset, prefix+'.png'))
|
||||
|
||||
im_scaled = im.resize((w*2, w*2), Image.LANCZOS)
|
||||
im_scaled.save(os.path.join(iconset, prefix+'@2x.png'))
|
||||
last_im = im_scaled
|
||||
|
||||
# iconutil -c icns -o {} {}
|
||||
from subprocess import Popen, PIPE, CalledProcessError
|
||||
|
||||
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
|
||||
stderr = tempfile.TemporaryFile()
|
||||
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr)
|
||||
|
||||
convert_proc.stdout.close()
|
||||
|
||||
retcode = convert_proc.wait()
|
||||
|
||||
# remove the temporary files
|
||||
shutil.rmtree(iconset)
|
||||
|
||||
if retcode:
|
||||
raise CalledProcessError(retcode, convert_cmd)
|
||||
|
||||
Image.register_open(IcnsImageFile.format, IcnsImageFile,
|
||||
lambda x: x[:4] == b'icns')
|
||||
Image.register_extension(IcnsImageFile.format, '.icns')
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
Image.register_save(IcnsImageFile.format, _save)
|
||||
|
||||
Image.register_mime(IcnsImageFile.format, "image/icns")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
import sys
|
||||
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
|
||||
for size in imf.info['sizes']:
|
||||
imf.size = size
|
||||
|
|
|
@ -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"
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
@ -48,8 +48,7 @@ def _save(im, fp, filename):
|
|||
width, height = im.size
|
||||
filter(lambda x: False if (x[0] > width or x[1] > height or
|
||||
x[0] > 255 or x[1] > 255) else True, sizes)
|
||||
sizes = sorted(sizes, key=lambda x: x[0])
|
||||
fp.write(struct.pack("H", len(sizes))) # idCount(2)
|
||||
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
|
||||
offset = fp.tell() + len(sizes)*16
|
||||
for size in sizes:
|
||||
width, height = size
|
||||
|
@ -58,7 +57,7 @@ def _save(im, fp, filename):
|
|||
fp.write(b"\0") # bColorCount(1)
|
||||
fp.write(b"\0") # bReserved(1)
|
||||
fp.write(b"\0\0") # wPlanes(2)
|
||||
fp.write(struct.pack("H", 32)) # wBitCount(2)
|
||||
fp.write(struct.pack("<H", 32)) # wBitCount(2)
|
||||
|
||||
image_io = BytesIO()
|
||||
tmp = im.copy()
|
||||
|
@ -67,8 +66,8 @@ def _save(im, fp, filename):
|
|||
image_io.seek(0)
|
||||
image_bytes = image_io.read()
|
||||
bytes_len = len(image_bytes)
|
||||
fp.write(struct.pack("I", bytes_len)) # dwBytesInRes(4)
|
||||
fp.write(struct.pack("I", offset)) # dwImageOffset(4)
|
||||
fp.write(struct.pack("<I", bytes_len)) # dwBytesInRes(4)
|
||||
fp.write(struct.pack("<I", offset)) # dwImageOffset(4)
|
||||
current = fp.tell()
|
||||
fp.seek(offset)
|
||||
fp.write(image_bytes)
|
||||
|
@ -80,7 +79,7 @@ def _accept(prefix):
|
|||
return prefix[:4] == _MAGIC
|
||||
|
||||
|
||||
class IcoFile:
|
||||
class IcoFile(object):
|
||||
def __init__(self, buf):
|
||||
"""
|
||||
Parse image from file-like object containing ico file data
|
||||
|
@ -253,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"
|
||||
|
@ -279,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
|
||||
|
@ -260,6 +260,14 @@ class ImImageFile(ImageFile.ImageFile):
|
|||
self.tile = [("raw", (0, 0)+self.size, offs,
|
||||
(self.rawmode, 0, -1))]
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return self.info[FRAMES]
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
return self.info[FRAMES] > 1
|
||||
|
||||
def seek(self, frame):
|
||||
|
||||
if frame < 0 or frame >= self.info[FRAMES]:
|
||||
|
@ -313,7 +321,7 @@ SAVE = {
|
|||
def _save(im, fp, filename, check=0):
|
||||
|
||||
try:
|
||||
type, rawmode = SAVE[im.mode]
|
||||
image_type, rawmode = SAVE[im.mode]
|
||||
except KeyError:
|
||||
raise ValueError("Cannot save %s images as IM" % im.mode)
|
||||
|
||||
|
@ -325,7 +333,7 @@ def _save(im, fp, filename, check=0):
|
|||
if check:
|
||||
return check
|
||||
|
||||
fp.write(("Image type: %s image\r\n" % type).encode('ascii'))
|
||||
fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
|
||||
if filename:
|
||||
fp.write(("Name: %s\r\n" % filename).encode('ascii'))
|
||||
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
|
||||
|
@ -341,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")
|
||||
|
|
333
PIL/Image.py
333
PIL/Image.py
|
@ -28,14 +28,17 @@ from __future__ import print_function
|
|||
|
||||
from PIL import VERSION, PILLOW_VERSION, _plugins
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DecompressionBombWarning(RuntimeWarning):
|
||||
pass
|
||||
|
||||
|
||||
class _imaging_not_installed:
|
||||
class _imaging_not_installed(object):
|
||||
# module placeholder
|
||||
def __getattr__(self, id):
|
||||
raise ImportError("The _imaging C module is not installed")
|
||||
|
@ -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:
|
||||
|
@ -109,6 +116,8 @@ from PIL._util import deferred_error
|
|||
|
||||
import os
|
||||
import sys
|
||||
import io
|
||||
import struct
|
||||
|
||||
# type stuff
|
||||
import collections
|
||||
|
@ -119,7 +128,7 @@ USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info')
|
|||
try:
|
||||
import cffi
|
||||
HAS_CFFI = True
|
||||
except:
|
||||
except ImportError:
|
||||
HAS_CFFI = False
|
||||
|
||||
|
||||
|
@ -136,11 +145,6 @@ def isImageType(t):
|
|||
"""
|
||||
return hasattr(t, "im")
|
||||
|
||||
#
|
||||
# Debug level
|
||||
|
||||
DEBUG = 0
|
||||
|
||||
#
|
||||
# Constants (also defined in _imagingmodule.c!)
|
||||
|
||||
|
@ -202,6 +206,7 @@ ID = []
|
|||
OPEN = {}
|
||||
MIME = {}
|
||||
SAVE = {}
|
||||
SAVE_ALL = {}
|
||||
EXTENSION = {}
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -249,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),
|
||||
|
@ -384,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
|
||||
|
@ -441,7 +444,7 @@ def coerce_e(value):
|
|||
return value if isinstance(value, _E) else _E(value)
|
||||
|
||||
|
||||
class _E:
|
||||
class _E(object):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
|
@ -476,7 +479,7 @@ def _getscaleoffset(expr):
|
|||
# --------------------------------------------------------------------
|
||||
# Implementation wrapper
|
||||
|
||||
class Image:
|
||||
class Image(object):
|
||||
"""
|
||||
This class represents an image object. To create
|
||||
:py:class:`~PIL.Image.Image` objects, use the appropriate factory
|
||||
|
@ -502,12 +505,21 @@ class Image:
|
|||
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()
|
||||
|
@ -543,8 +555,7 @@ class Image:
|
|||
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
|
||||
|
@ -600,7 +611,7 @@ class Image:
|
|||
|
||||
def _repr_png_(self):
|
||||
""" iPython display hook support
|
||||
|
||||
|
||||
:returns: png version of the image as bytes
|
||||
"""
|
||||
from io import BytesIO
|
||||
|
@ -616,6 +627,7 @@ class Image:
|
|||
new['shape'] = shape
|
||||
new['typestr'] = typestr
|
||||
new['data'] = self.tobytes()
|
||||
new['version'] = 3
|
||||
return new
|
||||
raise AttributeError(name)
|
||||
|
||||
|
@ -641,7 +653,14 @@ class Image:
|
|||
|
||||
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.
|
||||
|
@ -675,18 +694,9 @@ class Image:
|
|||
|
||||
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"):
|
||||
"""
|
||||
|
@ -736,14 +746,8 @@ class Image:
|
|||
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):
|
||||
"""
|
||||
|
@ -816,7 +820,7 @@ class Image:
|
|||
|
||||
: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).
|
||||
|
@ -875,6 +879,12 @@ class Image:
|
|||
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'):
|
||||
|
@ -892,12 +902,11 @@ class Image:
|
|||
if isinstance(t, bytes):
|
||||
self.im.putpalettealphas(t)
|
||||
elif isinstance(t, int):
|
||||
self.im.putpalettealpha(t,0)
|
||||
self.im.putpalettealpha(t, 0)
|
||||
else:
|
||||
raise ValueError("Transparency for P mode should" +
|
||||
" be bytes or int")
|
||||
|
||||
|
||||
if mode == "P" and palette == ADAPTIVE:
|
||||
im = self.im.quantize(colors)
|
||||
new = self._new(im)
|
||||
|
@ -1003,6 +1012,8 @@ class Image:
|
|||
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
|
||||
|
@ -1243,27 +1254,8 @@ class Image:
|
|||
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):
|
||||
"""
|
||||
|
@ -1287,11 +1279,11 @@ class Image:
|
|||
images (in the latter case, the alpha band is used as mask).
|
||||
Where the mask is 255, the given image is copied as is. Where
|
||||
the mask is 0, the current value is preserved. Intermediate
|
||||
values can be used for transparency effects.
|
||||
values will mix the two images together, including their alpha
|
||||
channels if they have them.
|
||||
|
||||
Note that if you paste an "RGBA" image, the alpha band is
|
||||
ignored. You can work around this by using the same image as
|
||||
both source image and mask.
|
||||
See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
|
||||
combine images with respect to their alpha channels.
|
||||
|
||||
:param im: Source image or pixel value (integer or tuple).
|
||||
:param box: An optional 4-tuple giving the region to paste into.
|
||||
|
@ -1315,7 +1307,7 @@ class Image:
|
|||
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):
|
||||
|
@ -1545,7 +1537,7 @@ class Image:
|
|||
|
||||
self.load()
|
||||
|
||||
size=tuple(size)
|
||||
size = tuple(size)
|
||||
if self.size == size:
|
||||
return self._new(self.im)
|
||||
|
||||
|
@ -1616,7 +1608,7 @@ class Image:
|
|||
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):
|
||||
"""
|
||||
|
@ -1635,7 +1627,7 @@ class Image:
|
|||
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
|
||||
|
@ -1648,17 +1640,27 @@ class Image:
|
|||
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 = ()
|
||||
|
||||
|
@ -1667,32 +1669,25 @@ class Image:
|
|||
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):
|
||||
|
@ -1809,7 +1804,7 @@ class Image:
|
|||
self.readonly = 0
|
||||
self.pyaccess = None
|
||||
|
||||
# FIXME: the different tranform methods need further explanation
|
||||
# FIXME: the different transform methods need further explanation
|
||||
# instead of bloating the method docs, add a separate chapter.
|
||||
def transform(self, size, method, data=None, resample=NEAREST, fill=1):
|
||||
"""
|
||||
|
@ -1934,6 +1929,20 @@ class Image:
|
|||
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
|
||||
|
@ -1944,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:
|
||||
|
@ -1974,12 +1985,12 @@ class _ImageCrop(Image):
|
|||
# --------------------------------------------------------------------
|
||||
# Abstract handlers.
|
||||
|
||||
class ImagePointHandler:
|
||||
class ImagePointHandler(object):
|
||||
# used as a mixin by point transforms (for use with im.point)
|
||||
pass
|
||||
|
||||
|
||||
class ImageTransformHandler:
|
||||
class ImageTransformHandler(object):
|
||||
# used as a mixin by geometry transforms (for use with im.transform)
|
||||
pass
|
||||
|
||||
|
@ -2062,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):
|
||||
|
@ -2088,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.
|
||||
|
@ -2115,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))
|
||||
|
@ -2183,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
|
||||
|
@ -2230,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
|
||||
|
@ -2242,31 +2261,26 @@ 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)
|
||||
except (AttributeError, io.UnsupportedOperation):
|
||||
fp = io.BytesIO(fp.read())
|
||||
|
||||
prefix = fp.read(16)
|
||||
|
||||
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):
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
|
||||
if init():
|
||||
|
||||
def _open_core(fp, filename, prefix):
|
||||
for i in ID:
|
||||
try:
|
||||
factory, accept = OPEN[i]
|
||||
|
@ -2275,24 +2289,35 @@ def open(fp, mode="r"):
|
|||
im = factory(fp, filename)
|
||||
_decompression_bomb_check(im.size)
|
||||
return im
|
||||
except (SyntaxError, IndexError, TypeError):
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
except (SyntaxError, IndexError, TypeError, struct.error):
|
||||
# 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.
|
||||
"""
|
||||
|
@ -2424,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
|
||||
|
@ -64,7 +65,7 @@ pyCMS
|
|||
|
||||
0.0.2 alpha Jan 6, 2002
|
||||
|
||||
Added try/except statements arount type() checks of
|
||||
Added try/except statements around type() checks of
|
||||
potential CObjects... Python won't let you use type()
|
||||
on them, and raises a TypeError (stupid, if you ask
|
||||
me!)
|
||||
|
@ -123,8 +124,8 @@ FLAGS = {
|
|||
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
||||
"NOTPRECALC": 256,
|
||||
"NULLTRANSFORM": 512, # Don't transform anyway
|
||||
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
|
||||
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces
|
||||
"HIGHRESPRECALC": 1024, # Use more memory to give better accuracy
|
||||
"LOWRESPRECALC": 2048, # Use less memory to minimize resources
|
||||
"WHITEBLACKCOMPENSATION": 8192,
|
||||
"BLACKPOINTCOMPENSATION": 8192,
|
||||
"GAMUTCHECK": 4096, # Out of Gamut alarm
|
||||
|
@ -147,7 +148,7 @@ for flag in FLAGS.values():
|
|||
##
|
||||
# Profile.
|
||||
|
||||
class ImageCmsProfile:
|
||||
class ImageCmsProfile(object):
|
||||
|
||||
def __init__(self, profile):
|
||||
"""
|
||||
|
@ -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):
|
||||
|
@ -573,7 +573,7 @@ def applyTransform(im, transform, inPlace=0):
|
|||
This function applies a pre-calculated transform (from
|
||||
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
|
||||
to an image. The transform can be used for multiple images, saving
|
||||
considerable calcuation time if doing the same conversion multiple times.
|
||||
considerable calculation time if doing the same conversion multiple times.
|
||||
|
||||
If you want to modify im in-place instead of receiving a new image as
|
||||
the return value, set inPlace to TRUE. This can only be done if
|
||||
|
@ -858,7 +858,7 @@ def getDefaultIntent(profile):
|
|||
If an error occurs while trying to obtain the default intent, a
|
||||
PyCMSError is raised.
|
||||
|
||||
Use this function to determine the default (and usually best optomized)
|
||||
Use this function to determine the default (and usually best optimized)
|
||||
rendering intent for this profile. Most profiles support multiple
|
||||
rendering intents, but are intended mostly for one type of conversion.
|
||||
If you wish to use a different intent than returned, use
|
||||
|
@ -914,7 +914,7 @@ def isIntentSupported(profile, intent, direction):
|
|||
|
||||
see the pyCMS documentation for details on rendering intents and what
|
||||
they do.
|
||||
:param direction: Integer specifing if the profile is to be used for input,
|
||||
:param direction: Integer specifying if the profile is to be used for input,
|
||||
output, or proof
|
||||
|
||||
INPUT = 0 (or use ImageCms.DIRECTION_INPUT)
|
||||
|
@ -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,23 +31,19 @@
|
|||
#
|
||||
|
||||
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:
|
||||
|
||||
class ImageDraw(object):
|
||||
|
||||
##
|
||||
# Create a drawing instance.
|
||||
|
@ -90,38 +86,17 @@ class ImageDraw:
|
|||
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:
|
|||
##
|
||||
# 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:
|
|||
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
|
||||
|
||||
|
||||
|
|
|
@ -19,25 +19,25 @@
|
|||
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||
|
||||
|
||||
class Pen:
|
||||
class Pen(object):
|
||||
def __init__(self, color, width=1, opacity=255):
|
||||
self.color = ImageColor.getrgb(color)
|
||||
self.width = width
|
||||
|
||||
|
||||
class Brush:
|
||||
class Brush(object):
|
||||
def __init__(self, color, opacity=255):
|
||||
self.color = ImageColor.getrgb(color)
|
||||
|
||||
|
||||
class Font:
|
||||
class Font(object):
|
||||
def __init__(self, color, file, size=12):
|
||||
# FIXME: add support for bitmap fonts
|
||||
self.color = ImageColor.getrgb(color)
|
||||
self.font = ImageFont.truetype(file, size)
|
||||
|
||||
|
||||
class Draw:
|
||||
class Draw(object):
|
||||
|
||||
def __init__(self, image, size=None, color=None):
|
||||
if not hasattr(image, "im"):
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
from PIL import Image, ImageFilter, ImageStat
|
||||
|
||||
|
||||
class _Enhance:
|
||||
class _Enhance(object):
|
||||
|
||||
def enhance(self, factor):
|
||||
"""
|
||||
|
@ -53,6 +53,7 @@ class Color(_Enhance):
|
|||
|
||||
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
|
||||
|
||||
|
||||
class Contrast(_Enhance):
|
||||
"""Adjust image contrast.
|
||||
|
||||
|
@ -72,7 +73,7 @@ class Contrast(_Enhance):
|
|||
class Brightness(_Enhance):
|
||||
"""Adjust image brightness.
|
||||
|
||||
This class can be used to control the brighntess of an image. An
|
||||
This class can be used to control the brightness of an image. An
|
||||
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
|
||||
original image.
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
@ -204,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 = []
|
||||
|
@ -233,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()
|
||||
|
||||
|
@ -242,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)
|
||||
|
||||
|
@ -308,7 +296,7 @@ class StubImageFile(ImageFile):
|
|||
)
|
||||
|
||||
|
||||
class Parser:
|
||||
class Parser(object):
|
||||
"""
|
||||
Incremental image parser. This class implements the standard
|
||||
feed/close consumer interface.
|
||||
|
@ -319,6 +307,7 @@ class Parser:
|
|||
image = None
|
||||
data = None
|
||||
decoder = None
|
||||
offset = 0
|
||||
finished = 0
|
||||
|
||||
def reset(self):
|
||||
|
@ -464,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()
|
||||
|
@ -493,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)
|
|
@ -15,7 +15,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from functools import reduce
|
||||
import functools
|
||||
|
||||
|
||||
class Filter(object):
|
||||
|
@ -43,7 +43,7 @@ class Kernel(Filter):
|
|||
def __init__(self, size, kernel, scale=None, offset=0):
|
||||
if scale is None:
|
||||
# default scale is sum of kernel
|
||||
scale = reduce(lambda a, b: a+b, kernel)
|
||||
scale = functools.reduce(lambda a, b: a+b, kernel)
|
||||
if size[0] * size[1] != len(kernel):
|
||||
raise ValueError("not enough coefficients in kernel")
|
||||
self.filterargs = size, scale, offset, kernel
|
||||
|
|
|
@ -25,20 +25,13 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isDirectory, isPath
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import warnings
|
||||
except ImportError:
|
||||
warnings = None
|
||||
|
||||
|
||||
class _imagingft_not_installed:
|
||||
class _imagingft_not_installed(object):
|
||||
# module placeholder
|
||||
def __getattr__(self, id):
|
||||
raise ImportError("The _imagingft C module is not installed")
|
||||
|
@ -64,12 +57,12 @@ except ImportError:
|
|||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
class ImageFont:
|
||||
class ImageFont(object):
|
||||
"PIL font wrapper"
|
||||
|
||||
def _load_pilfont(self, filename):
|
||||
|
||||
file = open(filename, "rb")
|
||||
fp = open(filename, "rb")
|
||||
|
||||
for ext in (".png", ".gif", ".pbm"):
|
||||
try:
|
||||
|
@ -85,7 +78,7 @@ class ImageFont:
|
|||
|
||||
self.file = fullname
|
||||
|
||||
return self._load_pilfont_data(file, image)
|
||||
return self._load_pilfont_data(fp, image)
|
||||
|
||||
def _load_pilfont_data(self, file, image):
|
||||
|
||||
|
@ -120,18 +113,11 @@ class ImageFont:
|
|||
# Wrapper for FreeType fonts. Application code should use the
|
||||
# <b>truetype</b> factory function to create font objects.
|
||||
|
||||
class FreeTypeFont:
|
||||
class FreeTypeFont(object):
|
||||
"FreeType font wrapper (requires _imagingft service)"
|
||||
|
||||
def __init__(self, font=None, size=10, index=0, encoding="", file=None):
|
||||
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
|
||||
|
@ -173,7 +159,7 @@ class FreeTypeFont:
|
|||
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.
|
||||
"""
|
||||
|
@ -193,7 +179,7 @@ class FreeTypeFont:
|
|||
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
||||
|
||||
|
||||
class TransposedFont:
|
||||
class TransposedFont(object):
|
||||
"Wrapper for writing rotated or mirrored text"
|
||||
|
||||
def __init__(self, font, orientation=None):
|
||||
|
@ -227,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
|
||||
|
@ -245,54 +231,52 @@ 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:
|
||||
if font.endswith(".ttf"):
|
||||
ttf_filename = font
|
||||
else:
|
||||
ttf_filename = "%s.ttf" % font
|
||||
ttf_filename = os.path.basename(font)
|
||||
|
||||
dirs = []
|
||||
if sys.platform == "win32":
|
||||
# check the windows font repository
|
||||
# NOTE: must use uppercase WINDIR, to work around bugs in
|
||||
# 1.5.2's os.environ.get()
|
||||
windir = os.environ.get("WINDIR")
|
||||
if windir:
|
||||
filename = os.path.join(windir, "fonts", font)
|
||||
return FreeTypeFont(filename, size, index, encoding)
|
||||
dirs.append(os.path.join(windir, "fonts"))
|
||||
elif sys.platform in ('linux', 'linux2'):
|
||||
lindirs = os.environ.get("XDG_DATA_DIRS", "")
|
||||
if not lindirs:
|
||||
# According to the freedesktop spec, XDG_DATA_DIRS should
|
||||
# default to /usr/share
|
||||
lindirs = '/usr/share'
|
||||
lindirs = lindirs.split(":")
|
||||
for lindir in lindirs:
|
||||
parentpath = os.path.join(lindir, "fonts")
|
||||
for walkroot, walkdir, walkfilenames in os.walk(parentpath):
|
||||
if ttf_filename in walkfilenames:
|
||||
filepath = os.path.join(walkroot, ttf_filename)
|
||||
return FreeTypeFont(filepath, size, index, encoding)
|
||||
dirs += [os.path.join(lindir, "fonts")
|
||||
for lindir in lindirs.split(":")]
|
||||
elif sys.platform == 'darwin':
|
||||
macdirs = ['/Library/Fonts/', '/System/Library/Fonts/',
|
||||
os.path.expanduser('~/Library/Fonts/')]
|
||||
for macdir in macdirs:
|
||||
filepath = os.path.join(macdir, ttf_filename)
|
||||
if os.path.exists(filepath):
|
||||
return FreeTypeFont(filepath, size, index, encoding)
|
||||
dirs += ['/Library/Fonts', '/System/Library/Fonts',
|
||||
os.path.expanduser('~/Library/Fonts')]
|
||||
|
||||
ext = os.path.splitext(ttf_filename)[1]
|
||||
first_font_with_a_different_extension = None
|
||||
for directory in dirs:
|
||||
for walkroot, walkdir, walkfilenames in os.walk(directory):
|
||||
for walkfilename in walkfilenames:
|
||||
if ext and walkfilename == ttf_filename:
|
||||
fontpath = os.path.join(walkroot, walkfilename)
|
||||
return FreeTypeFont(fontpath, size, index, encoding)
|
||||
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
|
||||
fontpath = os.path.join(walkroot, walkfilename)
|
||||
if os.path.splitext(fontpath)[1] == '.ttf':
|
||||
return FreeTypeFont(fontpath, size, index, encoding)
|
||||
if not ext and first_font_with_a_different_extension is None:
|
||||
first_font_with_a_different_extension = fontpath
|
||||
if first_font_with_a_different_extension:
|
||||
return FreeTypeFont(first_font_with_a_different_extension, size,
|
||||
index, encoding)
|
||||
raise
|
||||
|
||||
|
||||
|
@ -305,15 +289,15 @@ def load_path(filename):
|
|||
:return: A font object.
|
||||
:exception IOError: If the file could not be read.
|
||||
"""
|
||||
for dir in sys.path:
|
||||
if isDirectory(dir):
|
||||
for directory in sys.path:
|
||||
if isDirectory(directory):
|
||||
if not isinstance(filename, str):
|
||||
if bytes is str:
|
||||
filename = filename.encode("utf-8")
|
||||
else:
|
||||
filename = filename.decode("utf-8")
|
||||
try:
|
||||
return load(os.path.join(dir, filename))
|
||||
return load(os.path.join(directory, filename))
|
||||
except IOError:
|
||||
pass
|
||||
raise IOError("cannot find font file")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -31,7 +31,7 @@ def _isconstant(v):
|
|||
return isinstance(v, int) or isinstance(v, float)
|
||||
|
||||
|
||||
class _Operand:
|
||||
class _Operand(object):
|
||||
# wraps an image operand, providing standard operators
|
||||
|
||||
def __init__(self, im):
|
||||
|
|
|
@ -20,7 +20,7 @@ _modes = {}
|
|||
##
|
||||
# Wrapper for mode strings.
|
||||
|
||||
class ModeDescriptor:
|
||||
class ModeDescriptor(object):
|
||||
|
||||
def __init__(self, mode, bands, basemode, basetype):
|
||||
self.mode = mode
|
||||
|
|
|
@ -12,7 +12,7 @@ import re
|
|||
LUT_SIZE = 1 << 9
|
||||
|
||||
|
||||
class LutBuilder:
|
||||
class LutBuilder(object):
|
||||
"""A class for building a MorphLut from a descriptive language
|
||||
|
||||
The input patterns is a list of a strings sequences like these::
|
||||
|
@ -176,7 +176,7 @@ class LutBuilder:
|
|||
return self.lut
|
||||
|
||||
|
||||
class MorphOp:
|
||||
class MorphOp(object):
|
||||
"""A class for binary morphological operators"""
|
||||
|
||||
def __init__(self,
|
||||
|
@ -198,6 +198,8 @@ class MorphOp:
|
|||
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:
|
|||
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:
|
|||
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):
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
from PIL import Image
|
||||
from PIL._util import isStringType
|
||||
import operator
|
||||
from functools import reduce
|
||||
import functools
|
||||
|
||||
|
||||
#
|
||||
|
@ -213,7 +213,7 @@ def equalize(image, mask=None):
|
|||
if len(histo) <= 1:
|
||||
lut.extend(list(range(256)))
|
||||
else:
|
||||
step = (reduce(operator.add, histo) - histo[-1]) // 255
|
||||
step = (functools.reduce(operator.add, histo) - histo[-1]) // 255
|
||||
if not step:
|
||||
lut.extend(list(range(256)))
|
||||
else:
|
||||
|
@ -233,7 +233,6 @@ def expand(image, border=0, fill=0):
|
|||
:param fill: Pixel fill value (a color value). Default is 0 (black).
|
||||
:return: An image.
|
||||
"""
|
||||
"Add border to image"
|
||||
left, top, right, bottom = _border(border)
|
||||
width = left + image.size[0] + right
|
||||
height = top + image.size[1] + bottom
|
||||
|
|
|
@ -17,12 +17,23 @@
|
|||
#
|
||||
|
||||
import array
|
||||
import warnings
|
||||
from PIL import ImageColor
|
||||
|
||||
|
||||
class ImagePalette:
|
||||
"Color palette for palette mapped images"
|
||||
class ImagePalette(object):
|
||||
"""
|
||||
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:
|
|||
(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:
|
|||
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:
|
||||
|
|
|
@ -20,7 +20,7 @@ from PIL import Image
|
|||
# the Python class below is overridden by the C implementation.
|
||||
|
||||
|
||||
class Path:
|
||||
class Path(object):
|
||||
|
||||
def __init__(self, xy):
|
||||
pass
|
||||
|
|
210
PIL/ImageQt.py
210
PIL/ImageQt.py
|
@ -18,81 +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
|
||||
from PySide.QtGui import QImage, qRgba, QPixmap
|
||||
from PySide.QtCore import QBuffer, QIODevice
|
||||
qt_version = 'side'
|
||||
except ImportError:
|
||||
qt_is_installed = False
|
||||
|
||||
else: #PyQt4 is used
|
||||
from PyQt4.QtGui import QImage, qRgba
|
||||
|
||||
##
|
||||
# (Internal) Turns an RGB color into a Qt compatible color integer.
|
||||
|
||||
def rgb(r, g, b, a=255):
|
||||
"""(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)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
##
|
||||
|
||||
|
||||
class Iterator:
|
||||
class Iterator(object):
|
||||
"""
|
||||
This class implements an iterator object that can be used to loop
|
||||
over an image sequence.
|
||||
|
@ -32,11 +32,25 @@ class Iterator:
|
|||
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__()
|
||||
|
|
|
@ -56,7 +56,7 @@ def show(image, title=None, **options):
|
|||
##
|
||||
# Base class for viewers.
|
||||
|
||||
class Viewer:
|
||||
class Viewer(object):
|
||||
|
||||
# main api
|
||||
|
||||
|
|
|
@ -23,10 +23,10 @@
|
|||
|
||||
import math
|
||||
import operator
|
||||
from functools import reduce
|
||||
import functools
|
||||
|
||||
|
||||
class Stat:
|
||||
class Stat(object):
|
||||
|
||||
def __init__(self, image_or_list, mask=None):
|
||||
try:
|
||||
|
@ -71,7 +71,7 @@ class Stat:
|
|||
|
||||
v = []
|
||||
for i in range(0, len(self.h), 256):
|
||||
v.append(reduce(operator.add, self.h[i:i+256]))
|
||||
v.append(functools.reduce(operator.add, self.h[i:i+256]))
|
||||
return v
|
||||
|
||||
def _getsum(self):
|
||||
|
@ -79,10 +79,10 @@ class Stat:
|
|||
|
||||
v = []
|
||||
for i in range(0, len(self.h), 256):
|
||||
sum = 0.0
|
||||
layerSum = 0.0
|
||||
for j in range(256):
|
||||
sum += j * self.h[i + j]
|
||||
v.append(sum)
|
||||
layerSum += j * self.h[i + j]
|
||||
v.append(layerSum)
|
||||
return v
|
||||
|
||||
def _getsum2(self):
|
||||
|
|
|
@ -56,7 +56,7 @@ def _pilbitmap_check():
|
|||
# --------------------------------------------------------------------
|
||||
# PhotoImage
|
||||
|
||||
class PhotoImage:
|
||||
class PhotoImage(object):
|
||||
"""
|
||||
A Tkinter-compatible photo image. This can be used
|
||||
everywhere Tkinter expects an image object. If the image is an RGBA
|
||||
|
@ -190,7 +190,7 @@ class PhotoImage:
|
|||
# BitmapImage
|
||||
|
||||
|
||||
class BitmapImage:
|
||||
class BitmapImage(object):
|
||||
"""
|
||||
|
||||
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
|
||||
|
|
|
@ -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,11 +17,10 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
import warnings
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class HDC:
|
||||
class HDC(object):
|
||||
"""
|
||||
Wraps an HDC integer. The resulting object can be passed to the
|
||||
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
|
||||
|
@ -34,7 +33,7 @@ class HDC:
|
|||
return self.dc
|
||||
|
||||
|
||||
class HWND:
|
||||
class HWND(object):
|
||||
"""
|
||||
Wraps an HWND integer. The resulting object can be passed to the
|
||||
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
|
||||
|
@ -47,7 +46,7 @@ class HWND:
|
|||
return self.wnd
|
||||
|
||||
|
||||
class Dib:
|
||||
class Dib(object):
|
||||
"""
|
||||
A Windows bitmap with the given mode and size. The mode can be one of "1",
|
||||
"L", "P", or "RGB".
|
||||
|
@ -183,30 +182,19 @@ class Dib:
|
|||
"""
|
||||
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.")
|
||||
|
||||
|
||||
##
|
||||
# Create a Window with the given title size.
|
||||
|
||||
class Window:
|
||||
class Window(object):
|
||||
|
||||
def __init__(self, title="PIL", width=None, height=None):
|
||||
self.hwnd = Image.core.createwindow(
|
||||
|
|
|
@ -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,16 +217,16 @@ 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])
|
||||
name = app[offset+1:offset+1+name_len]
|
||||
# name = app[offset+1:offset+1+name_len]
|
||||
offset = 1 + offset + name_len
|
||||
if offset & 1:
|
||||
offset += 1
|
||||
# resource data block
|
||||
size = JpegImagePlugin.i32(app, offset)
|
||||
size = i32(app, offset)
|
||||
offset += 4
|
||||
if code == 0x0404:
|
||||
# 0x0404 contains IPTC/NAA data
|
||||
|
@ -251,7 +250,7 @@ def getiptcinfo(im):
|
|||
return None # no properties
|
||||
|
||||
# create an IptcImagePlugin object without initializing it
|
||||
class FakeImage:
|
||||
class FakeImage(object):
|
||||
pass
|
||||
im = FakeImage()
|
||||
im.__class__ = IptcImageFile
|
||||
|
|
|
@ -12,14 +12,13 @@
|
|||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
import struct
|
||||
import os
|
||||
import io
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
def _parse_codestream(fp):
|
||||
"""Parse the JPEG 2000 codestream to extract the size and component
|
||||
|
@ -85,7 +84,8 @@ def _parse_jp2_header(fp):
|
|||
size = None
|
||||
mode = None
|
||||
bpc = None
|
||||
|
||||
nc = None
|
||||
|
||||
hio = io.BytesIO(header)
|
||||
while True:
|
||||
lbox, tbox = struct.unpack('>I4s', hio.read(8))
|
||||
|
@ -142,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)
|
||||
|
||||
##
|
||||
|
@ -208,8 +211,8 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
|||
|
||||
|
||||
def _accept(prefix):
|
||||
return (prefix[:4] == b'\xff\x4f\xff\x51'
|
||||
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
||||
return (prefix[:4] == b'\xff\x4f\xff\x51' or
|
||||
prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
@ -263,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')
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# JPEG (JFIF) file handling
|
||||
#
|
||||
# See "Digital Compression and Coding of Continous-Tone Still Images,
|
||||
# See "Digital Compression and Coding of Continuous-Tone Still Images,
|
||||
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
|
||||
#
|
||||
# History:
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -454,26 +463,28 @@ def _getmp(self):
|
|||
data = self.info["mp"]
|
||||
except KeyError:
|
||||
return None
|
||||
file = io.BytesIO(data)
|
||||
head = file.read(8)
|
||||
file_contents = io.BytesIO(data)
|
||||
head = file_contents.read(8)
|
||||
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
||||
mp = {}
|
||||
# process dictionary
|
||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||
info.load(file)
|
||||
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,10 +692,10 @@ 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.
|
||||
# keep sets quality to 0, but the actual value may be high.
|
||||
if quality >= 95 or quality == 0:
|
||||
bufsize = 2 * im.size[0] * im.size[1]
|
||||
else:
|
||||
|
@ -704,8 +715,8 @@ def _save_cjpeg(im, fp, filename):
|
|||
tempfile = im._dump()
|
||||
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
||||
try:
|
||||
os.unlink(file)
|
||||
except:
|
||||
os.unlink(tempfile)
|
||||
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': [
|
||||
'web_low': {'subsampling': 2, # "4:1:1"
|
||||
'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': [
|
||||
]},
|
||||
'web_medium': {'subsampling': 2, # "4:1:1"
|
||||
'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,
|
||||
]},
|
||||
'web_high': {'subsampling': 0, # "4:4:4"
|
||||
'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,
|
||||
]},
|
||||
'web_very_high': {'subsampling': 0, # "4:4:4"
|
||||
'quantization': [
|
||||
[2, 2, 2, 2, 3, 4, 5, 6,
|
||||
2, 2, 2, 2, 3, 4, 5, 6,
|
||||
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,
|
||||
]},
|
||||
'web_maximum': {'subsampling': 0, # "4:4:4"
|
||||
'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': [
|
||||
]},
|
||||
'low': {'subsampling': 2, # "4:1:1"
|
||||
'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': [
|
||||
]},
|
||||
'medium': {'subsampling': 2, # "4:1:1"
|
||||
'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,
|
||||
]},
|
||||
'high': {'subsampling': 0, # "4:4:4"
|
||||
'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,
|
||||
]},
|
||||
'maximum': {'subsampling': 0, # "4:4:4"
|
||||
'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,11 +17,10 @@
|
|||
#
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
from PIL import Image, TiffImagePlugin
|
||||
from PIL.OleFileIO import *
|
||||
from PIL.OleFileIO import MAGIC, OleFileIO
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
#
|
||||
|
@ -54,9 +53,9 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
# best way to identify MIC files, but what the... ;-)
|
||||
|
||||
self.images = []
|
||||
for file in self.ole.listdir():
|
||||
if file[1:] and file[0][-4:] == ".ACI" and file[1] == "Image":
|
||||
self.images.append(file)
|
||||
for path in self.ole.listdir():
|
||||
if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image":
|
||||
self.images.append(path)
|
||||
|
||||
# if we didn't find any images, this is probably not
|
||||
# an MIC file.
|
||||
|
@ -71,6 +70,14 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
|
||||
self.seek(0)
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return len(self.images)
|
||||
|
||||
@property
|
||||
def is_animated(self):
|
||||
return len(self.images) > 1
|
||||
|
||||
def seek(self, frame):
|
||||
|
||||
try:
|
||||
|
@ -91,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,16 +13,17 @@
|
|||
# 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
|
||||
|
||||
class BitStream:
|
||||
class BitStream(object):
|
||||
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
|
@ -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)
|
||||
|
@ -62,6 +62,14 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
def load_seek(self, pos):
|
||||
self.__fp.seek(pos)
|
||||
|
||||
@property
|
||||
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")
|
||||
|
@ -82,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
|
||||
|
@ -49,10 +49,10 @@ class MspImageFile(ImageFile.ImageFile):
|
|||
raise SyntaxError("not an MSP file")
|
||||
|
||||
# Header checksum
|
||||
sum = 0
|
||||
checksum = 0
|
||||
for i in range(0, 32, 2):
|
||||
sum = sum ^ i16(s[i:i+2])
|
||||
if sum != 0:
|
||||
checksum = checksum ^ i16(s[i:i+2])
|
||||
if checksum != 0:
|
||||
raise SyntaxError("bad MSP checksum")
|
||||
|
||||
self.mode = "1"
|
||||
|
@ -83,10 +83,10 @@ def _save(im, fp, filename):
|
|||
header[6], header[7] = 1, 1
|
||||
header[8], header[9] = im.size
|
||||
|
||||
sum = 0
|
||||
checksum = 0
|
||||
for h in header:
|
||||
sum = sum ^ h
|
||||
header[12] = sum # FIXME: is this the right field?
|
||||
checksum = checksum ^ h
|
||||
header[12] = checksum # FIXME: is this the right field?
|
||||
|
||||
# header
|
||||
for h in header:
|
||||
|
@ -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.
|
||||
**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)
|
||||
|
||||
As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules)
|
||||
|
||||
OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-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.
|
||||
|
|
1138
PIL/OleFileIO.py
1138
PIL/OleFileIO.py
File diff suppressed because it is too large
Load Diff
|
@ -15,15 +15,14 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import EpsImagePlugin
|
||||
|
||||
import sys
|
||||
|
||||
##
|
||||
# Simple Postscript graphics interface.
|
||||
|
||||
class PSDraw:
|
||||
|
||||
class PSDraw(object):
|
||||
"""
|
||||
Sets up printing to the given file. If **file** is omitted,
|
||||
:py:attr:`sys.stdout` is assumed.
|
||||
|
@ -31,12 +30,11 @@ class PSDraw:
|
|||
|
||||
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'))
|
||||
|
@ -49,7 +47,7 @@ class PSDraw:
|
|||
"/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")
|
||||
|
|
|
@ -19,7 +19,7 @@ from PIL._binary import o8
|
|||
##
|
||||
# File handler for Teragon-style palette files.
|
||||
|
||||
class PaletteFile:
|
||||
class PaletteFile(object):
|
||||
|
||||
rawmode = "RGB"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
@ -52,28 +51,9 @@ class PcdImageFile(ImageFile.ImageFile):
|
|||
self.size = 768, 512 # FIXME: not correct for rotated images!
|
||||
self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)]
|
||||
|
||||
def draft(self, mode, size):
|
||||
|
||||
if len(self.tile) != 1:
|
||||
return
|
||||
|
||||
d, e, o, a = self.tile[0]
|
||||
|
||||
if size:
|
||||
scale = max(self.size[0] / size[0], self.size[1] / size[1])
|
||||
for s, o in [(4, 0*2048), (2, 0*2048), (1, 96*2048)]:
|
||||
if scale >= s:
|
||||
break
|
||||
# e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1]
|
||||
# self.size = ((self.size[0]+s-1)/s, (self.size[1]+s-1)/s)
|
||||
|
||||
self.tile = [(d, e, o, a)]
|
||||
|
||||
return self
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
Image.register_open("PCD", PcdImageFile)
|
||||
Image.register_open(PcdImageFile.format, PcdImageFile)
|
||||
|
||||
Image.register_extension("PCD", ".pcd")
|
||||
Image.register_extension(PcdImageFile.format, ".pcd")
|
||||
|
|
|
@ -204,7 +204,7 @@ class PcfFontFile(FontFile.FontFile):
|
|||
for i in range(4):
|
||||
bitmapSizes.append(i32(fp.read(4)))
|
||||
|
||||
byteorder = format & 4 # non-zero => MSB
|
||||
# byteorder = format & 4 # non-zero => MSB
|
||||
bitorder = format & 8 # non-zero => MSB
|
||||
padindex = format & 3
|
||||
|
||||
|
|
|
@ -25,14 +25,19 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
__version__ = "0.6"
|
||||
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
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
|
||||
|
@ -57,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)
|
||||
|
||||
|
@ -105,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))]
|
||||
|
||||
|
@ -142,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...
|
||||
|
@ -180,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,19 +51,23 @@ 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:
|
||||
class TextWriter(object):
|
||||
def __init__(self, fp):
|
||||
self.fp = 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
|
||||
|
@ -71,6 +74,7 @@ _MODES = {
|
|||
|
||||
|
||||
_simple_palette = re.compile(b'^\xff+\x00\xff*$')
|
||||
_null_palette = re.compile(b'^\x00*$')
|
||||
|
||||
# Maximum decompressed size for a iTXt or zTXt chunk.
|
||||
# Eliminates decompression bombs where compressed chunks can expand 1000x
|
||||
|
@ -78,6 +82,7 @@ MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK
|
|||
# Set the maximum total text chunk size.
|
||||
MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK
|
||||
|
||||
|
||||
def _safe_zlib_decompress(s):
|
||||
dobj = zlib.decompressobj()
|
||||
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
|
||||
|
@ -89,7 +94,7 @@ def _safe_zlib_decompress(s):
|
|||
# --------------------------------------------------------------------
|
||||
# Support classes. Suitable for PNG and related formats like MNG etc.
|
||||
|
||||
class ChunkStream:
|
||||
class ChunkStream(object):
|
||||
|
||||
def __init__(self, fp):
|
||||
|
||||
|
@ -127,8 +132,7 @@ class ChunkStream:
|
|||
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):
|
||||
|
@ -182,7 +186,7 @@ class iTXt(str):
|
|||
return self
|
||||
|
||||
|
||||
class PngInfo:
|
||||
class PngInfo(object):
|
||||
"""
|
||||
PNG chunk container (for use with save(pnginfo=))
|
||||
|
||||
|
@ -291,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" %
|
||||
|
@ -349,6 +352,8 @@ class PngStream(ChunkStream):
|
|||
i = s.find(b"\0")
|
||||
if i >= 0:
|
||||
self.im_info["transparency"] = i
|
||||
elif _null_palette.match(s):
|
||||
self.im_info["transparency"] = 0
|
||||
else:
|
||||
self.im_info["transparency"] = s
|
||||
elif self.im_mode == "L":
|
||||
|
@ -503,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)
|
||||
|
@ -619,7 +623,7 @@ def putchunk(fp, cid, *data):
|
|||
fp.write(o16(hi) + o16(lo))
|
||||
|
||||
|
||||
class _idat:
|
||||
class _idat(object):
|
||||
# wrap output from the encoder in IDAT chunks
|
||||
|
||||
def __init__(self, fp, chunk):
|
||||
|
@ -758,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
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -770,7 +772,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
|||
def getchunks(im, **params):
|
||||
"""Return a list of PNG chunks representing this image."""
|
||||
|
||||
class collector:
|
||||
class collector(object):
|
||||
data = []
|
||||
|
||||
def write(self, data):
|
||||
|
@ -799,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")
|
||||
|
|
|
@ -132,6 +132,14 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
self._fp = self.fp
|
||||
self.frame = 0
|
||||
|
||||
@property
|
||||
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:
|
||||
|
@ -299,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,11 +54,12 @@ 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():
|
||||
def _post_init(self):
|
||||
pass
|
||||
|
||||
def __setitem__(self, xy, color):
|
||||
|
@ -194,7 +199,7 @@ class _PyAccessI16_L(PyAccess):
|
|||
pixel = self.pixels[y][x]
|
||||
try:
|
||||
color = min(color, 65535)
|
||||
except:
|
||||
except TypeError:
|
||||
color = min(color[0], 65535)
|
||||
|
||||
pixel.l = color & 0xFF
|
||||
|
@ -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
|
||||
|
@ -48,7 +48,9 @@ def isInt(f):
|
|||
return 1
|
||||
else:
|
||||
return 0
|
||||
except:
|
||||
except ValueError:
|
||||
return 0
|
||||
except OverflowError:
|
||||
return 0
|
||||
|
||||
iforms = [1, 3, -11, -12, -21, -22]
|
||||
|
@ -127,12 +129,12 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
if self.istack == 0 and self.imgnumber == 0:
|
||||
# stk=0, img=0: a regular 2D image
|
||||
offset = hdrlen
|
||||
self.nimages = 1
|
||||
self._nimages = 1
|
||||
elif self.istack > 0 and self.imgnumber == 0:
|
||||
# stk>0, img=0: Opening the stack for the first time
|
||||
self.imgbytes = int(h[12]) * int(h[2]) * 4
|
||||
self.hdrlen = hdrlen
|
||||
self.nimages = int(h[26])
|
||||
self._nimages = int(h[26])
|
||||
# Point to the first image in the stack
|
||||
offset = hdrlen * 2
|
||||
self.imgnumber = 1
|
||||
|
@ -154,6 +156,14 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
(self.rawmode, 0, 1))]
|
||||
self.__fp = self.fp # FIXME: hack
|
||||
|
||||
@property
|
||||
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:
|
||||
|
@ -164,7 +174,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
def seek(self, frame):
|
||||
if self.istack == 0:
|
||||
return
|
||||
if frame >= self.nimages:
|
||||
if frame >= self._nimages:
|
||||
raise EOFError("attempt to seek past end of file")
|
||||
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
||||
self.fp = self.__fp
|
||||
|
@ -173,11 +183,11 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
|
||||
# returns a byte image after rescaling to 0..255
|
||||
def convert2byte(self, depth=255):
|
||||
(min, max) = self.getextrema()
|
||||
(minimum, maximum) = self.getextrema()
|
||||
m = 1
|
||||
if max != min:
|
||||
m = depth / (max-min)
|
||||
b = -m * min
|
||||
if maximum != minimum:
|
||||
m = depth / (maximum-minimum)
|
||||
b = -m * minimum
|
||||
return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
|
||||
|
||||
# returns a ImageTk.PhotoImage object, after rescaling to 0..255
|
||||
|
@ -271,14 +281,14 @@ def _save(im, fp, filename):
|
|||
|
||||
def _save_spider(im, fp, filename):
|
||||
# get the filename extension and register it with Image
|
||||
fn, ext = os.path.splitext(filename)
|
||||
ext = os.path.splitext(filename)[1]
|
||||
Image.register_extension("SPIDER", ext)
|
||||
_save(im, fp, filename)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
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:
|
||||
|
@ -37,7 +37,7 @@ def register_handler(handler):
|
|||
if hasattr(Image.core, "drawwmf"):
|
||||
# install default handler (windows only)
|
||||
|
||||
class WmfHandler:
|
||||
class WmfHandler(object):
|
||||
|
||||
def open(self, im):
|
||||
im.mode = "RGB"
|
||||
|
|
|
@ -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.7.0' # Pillow
|
||||
PILLOW_VERSION = '3.3.0.dev0' # Pillow
|
||||
|
||||
_plugins = ['BmpImagePlugin',
|
||||
'BufrStubImagePlugin',
|
||||
'CurImagePlugin',
|
||||
'DcxImagePlugin',
|
||||
'DdsImagePlugin',
|
||||
'EpsImagePlugin',
|
||||
'FitsStubImagePlugin',
|
||||
'FliImagePlugin',
|
||||
'FpxImagePlugin',
|
||||
'FtexImagePlugin',
|
||||
'GbrImagePlugin',
|
||||
'GifImagePlugin',
|
||||
'GribStubImagePlugin',
|
||||
|
|
67
PIL/features.py
Normal file
67
PIL/features.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from PIL import Image
|
||||
|
||||
modules = {
|
||||
"pil": "PIL._imaging",
|
||||
"tkinter": "PIL._imagingtk",
|
||||
"freetype2": "PIL._imagingft",
|
||||
"littlecms2": "PIL._imagingcms",
|
||||
"webp": "PIL._webp",
|
||||
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
|
||||
}
|
||||
|
||||
|
||||
def check_module(feature):
|
||||
if feature not in modules:
|
||||
raise ValueError("Unknown module %s" % feature)
|
||||
|
||||
module = modules[feature]
|
||||
|
||||
method_to_call = None
|
||||
if type(module) is tuple:
|
||||
module, method_to_call = module
|
||||
|
||||
try:
|
||||
imported_module = __import__(module)
|
||||
except ImportError:
|
||||
# If a method is being checked, None means that
|
||||
# rather than the method failing, the module required for the method
|
||||
# failed to be imported first
|
||||
return None if method_to_call else False
|
||||
|
||||
if method_to_call:
|
||||
method = getattr(imported_module, method_to_call)
|
||||
return method() is True
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def get_supported_modules():
|
||||
supported_modules = []
|
||||
for feature in modules:
|
||||
if check_module(feature):
|
||||
supported_modules.append(feature)
|
||||
return supported_modules
|
||||
|
||||
codecs = {
|
||||
"jpg": "jpeg",
|
||||
"jpg_2000": "jpeg2k",
|
||||
"zlib": "zip",
|
||||
"libtiff": "libtiff"
|
||||
}
|
||||
|
||||
|
||||
def check_codec(feature):
|
||||
if feature not in codecs:
|
||||
raise ValueError("Unknown codec %s" % feature)
|
||||
|
||||
codec = codecs[feature]
|
||||
|
||||
return codec + "_encoder" in dir(Image.core)
|
||||
|
||||
|
||||
def get_supported_codecs():
|
||||
supported_codecs = []
|
||||
for feature in codecs:
|
||||
if check_codec(feature):
|
||||
supported_codecs.append(feature)
|
||||
return supported_codecs
|
57
README.rst
57
README.rst
|
@ -4,40 +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://travis-ci.org/python-pillow/pillow-wheels.svg?branch=latest
|
||||
.. 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/v/Pillow/badge.png
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
.. 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>`_
|
||||
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_
|
||||
- `Documentation <http://pillow.readthedocs.org/>`_
|
||||
- `Documentation <https://pillow.readthedocs.org/>`_
|
||||
|
||||
- `About <http://pillow.readthedocs.org/about.html>`_
|
||||
- `Guides <http://pillow.readthedocs.org/guides.html>`_
|
||||
- `Installation <http://pillow.readthedocs.org/installation.html>`_
|
||||
- `Reference <http://pillow.readthedocs.org/reference/index.html>`_
|
||||
- `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>`_
|
||||
|
||||
- `Changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_
|
||||
|
||||
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork>`_
|
||||
|
|
124
RELEASING.md
124
RELEASING.md
|
@ -2,58 +2,112 @@
|
|||
|
||||
## Main Release
|
||||
|
||||
Released quarterly.
|
||||
Released quarterly on the first day of January, April, July, October.
|
||||
|
||||
* [ ] Get master to the appropriate code release state. [Travis CI](https://travis-ci.org/python-pillow/Pillow) should be running cleanly for all merges to master.
|
||||
* [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c`, Update date in `CHANGES.rst`.
|
||||
* [ ] Run pre-release check via `make pre`
|
||||
* [ ] Tag and push to release branch in python-pillow repo.
|
||||
* [ ] Upload binaries.
|
||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174
|
||||
* [ ] Develop and prepare release in ``master`` branch.
|
||||
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) 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 appveyor.yml
|
||||
```
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] 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
|
||||
$ git tag 2.9.0
|
||||
$ git push --all
|
||||
$ git push --tags
|
||||
```
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
```
|
||||
$ make sdist
|
||||
$ make upload
|
||||
```
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
* [ ] 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
|
||||
|
||||
Released as required for security or installation fixes.
|
||||
Released as needed for security, installation or critical bug fixes.
|
||||
|
||||
* [ ] Make necessary changes in master.
|
||||
* [ ] Cherry pick individual commits. Touch up `CHANGES.rst` to reflect reality.
|
||||
* [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c`
|
||||
* [ ] Run pre-release check via `make pre`
|
||||
* [ ] Push to release branch in personal repo. Let Travis run cleanly.
|
||||
* [ ] Tag and push to release branch in python-pillow repo.
|
||||
* [ ] Upload binaries.
|
||||
* [ ] Make necessary changes in ``master`` branch.
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``2.9.x``.
|
||||
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``2.9.x``.
|
||||
* [ ] Checkout release branch e.g.:
|
||||
```
|
||||
git checkout -t remotes/origin/2.9.x
|
||||
```
|
||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
|
||||
```
|
||||
PIL/__init__.py
|
||||
setup.py
|
||||
_imaging.c
|
||||
appveyor.yml
|
||||
```
|
||||
* [ ] Run pre-release check via `make release-test`.
|
||||
* [ ] Create tag for release e.g.:
|
||||
```
|
||||
$ git tag 2.9.1
|
||||
$ git push --tags
|
||||
```
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
```
|
||||
$ make sdistup
|
||||
```
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
|
||||
## Embargoed Release
|
||||
|
||||
Security fixes that need to be pushed to the distros prior to public release.
|
||||
Released as needed privately to individual vendors for critical security-related bug fixes.
|
||||
|
||||
* [ ] Prepare patch for all versions that will get a fix. Test against local installations.
|
||||
* [ ] Commit against master, cherry pick to affected release branches.
|
||||
* [ ] 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.
|
||||
```
|
||||
git checkout 2.5.x
|
||||
git tag 2.5.3
|
||||
git push origin 2.5.x
|
||||
git push origin --tags
|
||||
git checkout 2.5.x
|
||||
git tag 2.5.3
|
||||
git push origin 2.5.x
|
||||
git push origin --tags
|
||||
```
|
||||
* [ ] Upload binaries
|
||||
|
||||
|
||||
## Binary Upload Process
|
||||
|
||||
* [ ] Ping cgohlke for Windows binaries
|
||||
* [ ] From a clean source directory with no extra temp files:
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
```
|
||||
python setup.py register
|
||||
python setup.py sdist --format=zip upload
|
||||
python setup.py sdist upload
|
||||
$ make sdistup
|
||||
```
|
||||
(Debian requests a tarball, everyone else would just prefer that we choose one and stick to it. So both it is)
|
||||
* [ ] Push a commit to https://github.com/python-pillow/pillow-wheels to build OSX versions (UNDONE latest tag or specific release???)
|
||||
* [ ] Retrieve the OS X Wheels from Rackspace files, upload to PyPi (Twine?)
|
||||
* [ ] Grab Windows binaries, `twine upload dist/*.[whl|egg]`. Manually upload .exe installers.
|
||||
* [ ] Announce release availability. [Twitter](https://twitter.com/pythonpillow), web.
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
|
||||
## Binary Distributions
|
||||
|
||||
### Windows
|
||||
* [ ] Contact @cgohlke for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174.
|
||||
* [ ] Download and extract tarball from @cgohlke and ``twine upload *``.
|
||||
|
||||
### OS X
|
||||
* [ ] Use the [Pillow OS X Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
||||
```
|
||||
$ git checkout https://github.com/python-pillow/pillow-wheels
|
||||
$ cd pillow-wheels
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
$ cd Pillow
|
||||
$ git fetch --all
|
||||
$ git commit -a -m "Pillow -> 2.9.0"
|
||||
$ git push
|
||||
```
|
||||
* [ ] Download distributions from the [Pillow OS X Wheel Builder container](http://cdf58691c5cf45771290-6a3b6a0f5f6ab91aadc447b2a897dd9a.r50.cf2.rackcdn.com/) and ``twine upload *``.
|
||||
|
||||
### Linux
|
||||
|
||||
## Publicize Release
|
||||
|
||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328.
|
||||
|
||||
## 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
|
||||
|
@ -63,20 +63,3 @@ explode.py
|
|||
--------------------------------------------------------------------
|
||||
|
||||
Split a sequence file into individual frames.
|
||||
|
||||
image2py.py
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Convert an image to a Python module containing an IMAGE variable.
|
||||
Note that the module using the module must include JPEG and ZIP
|
||||
decoders, unless the -u option is used.
|
||||
|
||||
olesummary.py
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Uses the OleFileIO module to dump the summary information from an OLE
|
||||
structured storage file. This works with most OLE files, including
|
||||
Word documents, FlashPix images, etc.
|
||||
|
||||
Note that datetime fields currently show the number of seconds since
|
||||
January 1st, 1601.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
#
|
||||
|
||||
try:
|
||||
from tkinter import *
|
||||
from tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
|
||||
except ImportError:
|
||||
from Tkinter import *
|
||||
from Tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
|
||||
|
||||
from PIL import Image, ImageTk, ImageEnhance
|
||||
import sys
|
||||
|
@ -18,6 +18,7 @@ import sys
|
|||
#
|
||||
# enhancer widget
|
||||
|
||||
|
||||
class Enhance(Frame):
|
||||
def __init__(self, master, image, name, enhancer, lo, hi):
|
||||
Frame.__init__(self, master)
|
||||
|
@ -25,7 +26,7 @@ class Enhance(Frame):
|
|||
# set up the image
|
||||
self.tkim = ImageTk.PhotoImage(image.mode, image.size)
|
||||
self.enhancer = enhancer(image)
|
||||
self.update("1.0") # normalize
|
||||
self.update("1.0") # normalize
|
||||
|
||||
# image window
|
||||
Label(self, image=self.tkim).pack()
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from PIL import Image
|
||||
import os, sys
|
||||
import os
|
||||
import sys
|
||||
|
||||
class Interval:
|
||||
|
||||
def __init__(self, interval = "0"):
|
||||
class Interval(object):
|
||||
|
||||
def __init__(self, interval="0"):
|
||||
|
||||
self.setinterval(interval)
|
||||
|
||||
|
|
|
@ -14,116 +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:
|
||||
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:
|
||||
|
||||
#
|
||||
# FIXME: write graphics control block before each frame
|
||||
|
||||
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__":
|
||||
|
||||
|
@ -134,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,14 +5,14 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
try:
|
||||
from tkinter import *
|
||||
from tkinter import Tk, Canvas, NW
|
||||
except ImportError:
|
||||
from Tkinter import *
|
||||
from Tkinter import Tk, Canvas, NW
|
||||
|
||||
from PIL import Image, ImageTk
|
||||
import sys
|
||||
|
@ -20,6 +20,7 @@ import sys
|
|||
#
|
||||
# painter widget
|
||||
|
||||
|
||||
class PaintCanvas(Canvas):
|
||||
def __init__(self, master, image):
|
||||
Canvas.__init__(self, master, width=image.size[0], height=image.size[1])
|
||||
|
@ -33,7 +34,7 @@ class PaintCanvas(Canvas):
|
|||
box = x, y, min(xsize, x+tilesize), min(ysize, y+tilesize)
|
||||
tile = ImageTk.PhotoImage(image.crop(box))
|
||||
self.create_image(x, y, image=tile, anchor=NW)
|
||||
self.tile[(x,y)] = box, tile
|
||||
self.tile[(x, y)] = box, tile
|
||||
|
||||
self.image = image
|
||||
|
||||
|
@ -59,7 +60,7 @@ class PaintCanvas(Canvas):
|
|||
xy, tile = self.tile[(x, y)]
|
||||
tile.paste(self.image.crop(xy))
|
||||
except KeyError:
|
||||
pass # outside the image
|
||||
pass # outside the image
|
||||
self.update_idletasks()
|
||||
|
||||
#
|
||||
|
@ -67,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":
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user