This commit is contained in:
hugovk 2016-04-03 23:25:41 +03:00
commit 7d129d4aca
387 changed files with 12706 additions and 5366 deletions

24
.editorconfig Normal file
View File

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

37
.github/CONTRIBUTING.md vendored Normal file
View 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
View File

@ -0,0 +1,13 @@
### What did you do?
### What did you expect to happen?
### What actually happened?
### What versions of Pillow and Python are you using?
Please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. The best reproductions are self-contained scripts with minimal dependencies.
```python
code goes here
```

View File

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

View File

@ -9,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:

View File

@ -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 Lagadecs 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

View File

@ -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?

View File

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

View File

@ -1,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
View File

@ -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

View File

@ -69,8 +69,8 @@ def bdf_char(f):
bitmap.append(s[:-1])
bitmap = b"".join(bitmap)
[x, y, l, d] = [int(s) for s in props["BBX"].split()]
[dx, dy] = [int(s) for s in props["DWIDTH"].split()]
[x, y, l, d] = [int(p) for p in props["BBX"].split()]
[dx, dy] = [int(p) for p in props["DWIDTH"].split()]
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
@ -109,10 +109,10 @@ class BdfFontFile(FontFile.FontFile):
if s.find(b"LogicalFontDescription") < 0:
comments.append(s[i+1:-1].decode('ascii'))
font = props["FONT"].split("-")
# font = props["FONT"].split("-")
font[4] = bdf_slant[font[4].upper()]
font[11] = bdf_spacing[font[11].upper()]
# font[4] = bdf_slant[font[4].upper()]
# font[11] = bdf_spacing[font[11].upper()]
# ascent = int(props["FONT_ASCENT"])
# descent = int(props["FONT_DESCENT"])
@ -123,7 +123,6 @@ class BdfFontFile(FontFile.FontFile):
# for i in comments:
# print "#", i
font = []
while True:
c = bdf_char(fp)
if not c:

View File

@ -24,12 +24,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")

View File

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

View File

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

View File

@ -21,19 +21,18 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.2"
from PIL import Image, _binary
from PIL.PcxImagePlugin import PcxImageFile
__version__ = "0.2"
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
i32 = _binary.i32le
def _accept(prefix):
return i32(prefix) == MAGIC
return len(prefix) >= 4 and i32(prefix) == MAGIC
##
@ -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
View File

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

View File

@ -20,12 +20,13 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.5"
import re
import io
import sys
from PIL import Image, ImageFile, _binary
__version__ = "0.5"
#
# --------------------------------------------------------------------
@ -36,7 +37,6 @@ split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
gs_windows_binary = None
import sys
if sys.platform.startswith('win'):
import shutil
if hasattr(shutil, 'which'):
@ -123,8 +123,8 @@ def Ghostscript(tile, size, fp, scale=1):
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages,
# safe mode
"-dNOPAUSE", # don't pause between pages,
"-dSAFER", # safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
@ -151,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()
#
# --------------------------------------------------------------------

View File

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

View File

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

View File

@ -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")

View File

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

View File

@ -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
View File

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

View File

@ -1,17 +1,28 @@
#
# The Python Imaging Library
# $Id$
#
# load a GIMP brush file
#
# History:
# 96-03-14 fl Created
# 16-01-08 es Version 2
#
# Copyright (c) Secret Labs AB 1997.
# Copyright (c) Fredrik Lundh 1996.
# Copyright (c) Eric Soroos 2016.
#
# See the README file for information on usage and redistribution.
#
#
# See https://github.com/GNOME/gimp/blob/master/devel-docs/gbr.txt for
# format documentation.
#
# This code Interprets version 1 and 2 .gbr files.
# Version 1 files are obsolete, and should not be used for new
# brushes.
# Version 2 files are saved by GIMP v2.8 (at least)
# Version 3 files have a format specifier of 18 for 16bit floats in
# the color depth field. This is currently unsupported by Pillow.
from PIL import Image, ImageFile, _binary
@ -19,7 +30,7 @@ i32 = _binary.i32be
def _accept(prefix):
return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
##
@ -31,41 +42,53 @@ class GbrImageFile(ImageFile.ImageFile):
format_description = "GIMP brush file"
def _open(self):
header_size = i32(self.fp.read(4))
version = i32(self.fp.read(4))
if header_size < 20 or version != 1:
if header_size < 20:
raise SyntaxError("not a GIMP brush")
if version not in (1, 2):
raise SyntaxError("Unsupported GIMP brush version: %s" % version)
width = i32(self.fp.read(4))
height = i32(self.fp.read(4))
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")

View File

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

View File

@ -24,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")

View File

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

View File

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

View File

@ -17,7 +17,11 @@
from PIL import Image, ImageFile, PngImagePlugin, _binary
import io
import os
import shutil
import struct
import sys
import tempfile
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
if enable_jpeg2k:
@ -90,7 +94,7 @@ def read_32(fobj, start_length, size):
def read_mk(fobj, start_length, size):
# Alpha masks seem to be uncompressed
(start, length) = start_length
start = start_length[0]
fobj.seek(start)
pixel_size = (size[0] * size[2], size[1] * size[2])
sizesq = pixel_size[0] * pixel_size[1]
@ -126,7 +130,7 @@ def read_png_or_jpeg2000(fobj, start_length, size):
raise ValueError('Unsupported icon subimage format')
class IcnsFile:
class IcnsFile(object):
SIZES = {
(512, 512, 2): [
@ -247,7 +251,7 @@ class IcnsFile:
class IcnsImageFile(ImageFile.ImageFile):
"""
PIL read-only image support for Mac OS .icns files.
PIL image support for Mac OS .icns files.
Chooses the best resolution, but will possibly load
a different size image if you mutate the size attribute
before calling 'load'.
@ -293,12 +297,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

View File

@ -15,21 +15,21 @@
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
# <casadebender@gmail.com>.
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
#
# Icon format references:
# * http://en.wikipedia.org/wiki/ICO_(file_format)
# * http://msdn.microsoft.com/en-us/library/ms997538.aspx
# * https://en.wikipedia.org/wiki/ICO_(file_format)
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
__version__ = "0.1"
import struct
from io import BytesIO
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
from math import log, ceil
__version__ = "0.1"
#
# --------------------------------------------------------------------
@ -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")

View File

@ -26,12 +26,12 @@
#
__version__ = "0.7"
import re
from PIL import Image, ImageFile, ImagePalette
from PIL._binary import i8
__version__ = "0.7"
# --------------------------------------------------------------------
# Standard tags
@ -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")

View File

@ -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

View File

@ -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))

View File

@ -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

View File

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

View File

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

View File

@ -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):

View File

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

View File

@ -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

View File

@ -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")

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@
from PIL import Image
from PIL._util import isStringType
import operator
from functools import reduce
import functools
#
@ -213,7 +213,7 @@ def equalize(image, mask=None):
if len(histo) <= 1:
lut.extend(list(range(256)))
else:
step = (reduce(operator.add, histo) - histo[-1]) // 255
step = (functools.reduce(operator.add, histo) - histo[-1]) // 255
if not step:
lut.extend(list(range(256)))
else:
@ -233,7 +233,6 @@ def expand(image, border=0, fill=0):
:param fill: Pixel fill value (a color value). Default is 0 (black).
:return: An image.
"""
"Add border to image"
left, top, right, bottom = _border(border)
width = left + image.size[0] + right
height = top + image.size[1] + bottom

View File

@ -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:

View File

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

View File

@ -18,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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,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(

View File

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

View File

@ -17,13 +17,12 @@
from __future__ import print_function
__version__ = "0.3"
from PIL import Image, ImageFile, _binary
import os
import tempfile
__version__ = "0.3"
i8 = _binary.i8
i16 = _binary.i16be
i32 = _binary.i32be
@ -180,13 +179,13 @@ class IptcImageFile(ImageFile.ImageFile):
finally:
try:
os.unlink(outfile)
except:
except OSError:
pass
Image.register_open("IPTC", IptcImageFile)
Image.register_open(IptcImageFile.format, IptcImageFile)
Image.register_extension("IPTC", ".iim")
Image.register_extension(IptcImageFile.format, ".iim")
##
@ -218,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

View File

@ -12,14 +12,13 @@
#
# See the README file for information on usage and redistribution.
#
__version__ = "0.1"
from PIL import Image, ImageFile
import struct
import os
import io
__version__ = "0.1"
def _parse_codestream(fp):
"""Parse the JPEG 2000 codestream to extract the size and component
@ -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')

View File

@ -4,7 +4,7 @@
#
# JPEG (JFIF) file handling
#
# See "Digital Compression and Coding of Continous-Tone Still Images,
# See "Digital Compression and Coding of Continuous-Tone Still Images,
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
#
# History:
@ -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")

View File

@ -27,7 +27,7 @@ Subsampling
Subsampling is the practice of encoding images by implementing less resolution
for chroma information than for luma information.
(ref.: http://en.wikipedia.org/wiki/Chroma_subsampling)
(ref.: https://en.wikipedia.org/wiki/Chroma_subsampling)
Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and
4:1:1 (or 4:2:0?).
@ -41,8 +41,8 @@ Quantization tables
They are values use by the DCT (Discrete cosine transform) to remove
*unnecessary* information from the image (the lossy part of the compression).
(ref.: http://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
http://en.wikipedia.org/wiki/JPEG#Quantization)
(ref.: https://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
https://en.wikipedia.org/wiki/JPEG#Quantization)
You can get the quantization tables of a JPEG with::
@ -62,13 +62,13 @@ The tables format between im.quantization and quantization in presets differ in
You can convert the dict format to the preset format with the
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
Libjpeg ref.: http://www.jpegcameras.com/libjpeg/libjpeg-3.html
Libjpeg ref.: http://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
"""
presets = {
'web_low': {'subsampling': 2, # "4:1:1"
'quantization': [
'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]
]},
]},
}

View File

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

View File

@ -17,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")

View File

@ -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")

View File

@ -18,10 +18,10 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.1"
from PIL import Image, JpegImagePlugin
__version__ = "0.1"
def _accept(prefix):
return JpegImagePlugin._accept(prefix)
@ -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")

View File

@ -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")

View File

@ -1,26 +1,45 @@
OleFileIO_PL
============
olefile (formerly OleFileIO_PL)
===============================
[OleFileIO_PL](http://www.decalage.info/python/olefileio) is a Python module to parse and read [Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format)](http://en.wikipedia.org/wiki/Compound_File_Binary_Format), such as Microsoft Office documents, Image Composer and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats ...
[olefile](http://www.decalage.info/olefile) is a Python package to parse, read and write
[Microsoft OLE2 files](http://en.wikipedia.org/wiki/Compound_File_Binary_Format)
(also called Structured Storage, Compound File Binary Format or Compound Document File Format),
such as Microsoft Office 97-2003 documents, vbaProject.bin in MS Office 2007+ files, Image Composer
and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats, McAfee antivirus quarantine files,
etc.
This is an improved version of the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent Python Imaging Library, created and maintained by Fredrik Lundh. The API is still compatible with PIL, but since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust design.
**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.

File diff suppressed because it is too large Load Diff

View File

@ -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")

View File

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

View File

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

View File

@ -15,11 +15,10 @@
#
__version__ = "0.1"
from PIL import Image, ImageFile, _binary
__version__ = "0.1"
i8 = _binary.i8
@ -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")

View File

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

View File

@ -25,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")

View File

@ -20,12 +20,12 @@
# Image plugin for PDF images (output only).
##
__version__ = "0.4"
from PIL import Image, ImageFile
from PIL._binary import i8
import io
__version__ = "0.4"
#
# --------------------------------------------------------------------
@ -51,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")

View File

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

View File

@ -33,12 +33,15 @@
from __future__ import print_function
__version__ = "0.9"
import logging
import re
import zlib
from PIL import Image, ImageFile, ImagePalette, _binary
import zlib
__version__ = "0.9"
logger = logging.getLogger(__name__)
i8 = _binary.i8
i16 = _binary.i16be
@ -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")

View File

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

View File

@ -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")

View File

@ -22,10 +22,14 @@
from __future__ import print_function
from cffi import FFI
import logging
import sys
DEBUG = 0
from cffi import FFI
logger = logging.getLogger(__name__)
defs = """
struct Pixel_RGBA {
@ -50,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

View File

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

View File

@ -27,10 +27,10 @@
# image data from electron microscopy and tomography.
#
# Spider home page:
# http://www.wadsworth.org/spider_doc/spider/docs/spider.html
# http://spider.wadsworth.org/spider_doc/spider/docs/spider.html
#
# Details about the Spider image format:
# http://www.wadsworth.org/spider_doc/spider/docs/image_doc.html
# http://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html
#
from __future__ import print_function
@ -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__":

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -15,10 +15,10 @@
# See the README file for information on usage and redistribution.
#
__version__ = "0.2"
from PIL import Image, ImageFile, _binary
__version__ = "0.2"
_handler = None
if str != bytes:
@ -37,7 +37,7 @@ def register_handler(handler):
if hasattr(Image.core, "drawwmf"):
# install default handler (windows only)
class WmfHandler:
class WmfHandler(object):
def open(self, im):
im.mode = "RGB"

View File

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

View File

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

View File

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

View File

@ -12,16 +12,18 @@
# ;-)
VERSION = '1.1.7' # PIL version
PILLOW_VERSION = '2.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
View File

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

View File

@ -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>`_

View File

@ -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/

View File

@ -12,7 +12,7 @@ pildriver.py (by Eric S. Raymond)
--------------------------------------------------------------------
A class implementing an image-processing calculator for scripts.
Parses lists of commnds (or, called interactively, command-line
Parses lists of commands (or, called interactively, command-line
arguments) into image loads, transformations, and saves.
viewer.py
@ -63,20 +63,3 @@ explode.py
--------------------------------------------------------------------
Split a sequence file into individual frames.
image2py.py
--------------------------------------------------------------------
Convert an image to a Python module containing an IMAGE variable.
Note that the module using the module must include JPEG and ZIP
decoders, unless the -u option is used.
olesummary.py
--------------------------------------------------------------------
Uses the OleFileIO module to dump the summary information from an OLE
structured storage file. This works with most OLE files, including
Word documents, FlashPix images, etc.
Note that datetime fields currently show the number of seconds since
January 1st, 1601.

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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