mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 09:26:16 +03:00
merge a year of master into winbuild
This commit is contained in:
commit
fd55099ffc
2
.landscape.yaml
Normal file
2
.landscape.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
strictness: medium
|
||||||
|
test-warnings: yes
|
75
.travis.yml
75
.travis.yml
|
@ -3,25 +3,26 @@ language: python
|
||||||
notifications:
|
notifications:
|
||||||
irc: "chat.freenode.net#pil"
|
irc: "chat.freenode.net#pil"
|
||||||
|
|
||||||
env: MAX_CONCURRENCY=4
|
# Run slow PyPy* first, to give them a headstart and reduce waiting time.
|
||||||
|
# Run latest 3.x and 2.x next, to get quick compatibility results.
|
||||||
|
# Then run the remainder.
|
||||||
python:
|
python:
|
||||||
- "pypy"
|
- "pypy"
|
||||||
- "pypy3"
|
- "pypy3"
|
||||||
- 2.6
|
- 3.4
|
||||||
- 2.7
|
- 2.7
|
||||||
|
- 2.6
|
||||||
- "2.7_with_system_site_packages" # For PyQt4
|
- "2.7_with_system_site_packages" # For PyQt4
|
||||||
- 3.2
|
- 3.2
|
||||||
- 3.3
|
- 3.3
|
||||||
- 3.4
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick lcov"
|
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
|
||||||
- "pip install cffi"
|
- "travis_retry pip install cffi"
|
||||||
- "pip install coveralls nose coveralls-merge"
|
- "travis_retry pip install coverage nose"
|
||||||
- "gem install coveralls-lcov"
|
- "travis_retry pip install pyroma"
|
||||||
- travis_retry pip install pyroma
|
|
||||||
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi
|
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
|
||||||
|
|
||||||
# webp
|
# webp
|
||||||
- pushd depends && ./install_webp.sh && popd
|
- pushd depends && ./install_webp.sh && popd
|
||||||
|
@ -39,24 +40,72 @@ script:
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
# gather the coverage data
|
# gather the coverage data
|
||||||
|
- travis_retry sudo apt-get -qq install lcov
|
||||||
- lcov --capture --directory . -b . --output-file coverage.info
|
- lcov --capture --directory . -b . --output-file coverage.info
|
||||||
# filter to remove system headers
|
# filter to remove system headers
|
||||||
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
|
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
|
||||||
# convert to json
|
# convert to json
|
||||||
|
- travis_retry gem install coveralls-lcov
|
||||||
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
|
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
|
||||||
|
|
||||||
- coverage report
|
- coverage report
|
||||||
|
- travis_retry pip install coveralls-merge
|
||||||
- coveralls-merge coverage.c.json
|
- coveralls-merge coverage.c.json
|
||||||
|
|
||||||
|
- travis_retry pip install pep8 pyflakes
|
||||||
- pip install pep8 pyflakes
|
|
||||||
- pep8 --statistics --count PIL/*.py
|
- 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 PIL/*.py | tee >(wc -l)
|
||||||
- pyflakes Tests/*.py | tee >(wc -l)
|
- pyflakes Tests/*.py | tee >(wc -l)
|
||||||
|
|
||||||
|
|
||||||
# Coverage and quality reports on just the latest diff.
|
# Coverage and quality reports on just the latest diff.
|
||||||
# (Installation is very slow on Py3, so just do it for Py2.)
|
# (Installation is very slow on Py3, so just do it for Py2.)
|
||||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
|
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then 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 Scripts/diffcover-run.sh; fi
|
||||||
|
|
||||||
|
# after_all
|
||||||
|
- |
|
||||||
|
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
||||||
|
curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
|
||||||
|
python travis_after_all.py
|
||||||
|
export $(cat .to_export_back)
|
||||||
|
if [ "$BUILD_LEADER" = "YES" ]; then
|
||||||
|
if [ "$BUILD_AGGREGATE_STATUS" = "others_succeeded" ]; then
|
||||||
|
echo "All jobs succeded! Triggering OS X build..."
|
||||||
|
# Trigger an OS X build at the pillow-wheels repo
|
||||||
|
./build_children.sh
|
||||||
|
else
|
||||||
|
echo "Some jobs failed"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
after_failure:
|
||||||
|
- |
|
||||||
|
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
||||||
|
curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
|
||||||
|
python travis_after_all.py
|
||||||
|
export $(cat .to_export_back)
|
||||||
|
if [ "$BUILD_LEADER" = "YES" ]; then
|
||||||
|
if [ "$BUILD_AGGREGATE_STATUS" = "others_failed" ]; then
|
||||||
|
echo "All jobs failed"
|
||||||
|
else
|
||||||
|
echo "Some jobs failed"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- |
|
||||||
|
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
||||||
|
echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS
|
||||||
|
fi
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
# travis encrypt AUTH_TOKEN=
|
||||||
|
secure: "Vzm7aG1Qv0SDQcqiPzZMedNLn5ZmpL7IzF0DYnqcD+/l+zmKU22SnJBcX0uVXumo+r7eZfpsShpqfcdsZvMlvmQnwz+Y6AGKQru9tCKZbTMnuRjWKKXekC+tr8Xt9CKvRVtte5PyXW31paxUI3/e+fQGBwoFjEEC+6EpEOjeRfE="
|
||||||
|
|
318
CHANGES.rst
318
CHANGES.rst
|
@ -1,10 +1,283 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
2.6.0 (unreleased)
|
2.9.0 (Unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport)
|
- 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]
|
||||||
|
|
||||||
|
- Added copy method font_variant() and accessible properties to truetype() #1123
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix ImagingEffectNoise #1128
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Remove unreachable code
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Let Python do the endian stuff + tests #1121
|
||||||
|
[amoibos, radarhere]
|
||||||
|
|
||||||
|
- Fix webp decode memory leak #1114
|
||||||
|
[benoit-pierre]
|
||||||
|
|
||||||
|
- Fast path for opaque pixels in RGBa unpacker #1088
|
||||||
|
[bgilbert]
|
||||||
|
|
||||||
|
- Enable basic support for 'RGBa' raw encoding/decoding #1096
|
||||||
|
[immerrr]
|
||||||
|
|
||||||
|
- Fix pickling L mode images with no palette, #1095
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- iPython display hook #1091
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Adjust buffer size when quality=keep, fixes #148 (again)
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix for corrupted bitmaps embedded in truetype fonts. #1072
|
||||||
|
[jackyyf, wiredfool]
|
||||||
|
|
||||||
|
2.7.0 (2015-01-01)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Split Sane into a separate repo: https://github.com/python-pillow/Sane
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Look for OSX and Linux fonts in common places. #1054
|
||||||
|
[charleslaw]
|
||||||
|
|
||||||
|
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Use underscores, not spaces, in TIFF tag kwargs. #1044, #1058
|
||||||
|
[anntzer, hugovk]
|
||||||
|
|
||||||
|
- Update PSDraw for Python3, add tests. #1055
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Use Bicubic filtering by default for thumbnails. Don't use Jpeg Draft mode for thumbnails. #1029
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Fix MSVC compiler error: Use Py_ssize_t instead of ssize_t #1051
|
||||||
|
[cgohlke]
|
||||||
|
|
||||||
|
- Fix compiler error: MSVC needs variables defined at the start of the block #1048
|
||||||
|
[cgohlke]
|
||||||
|
|
||||||
|
- The GIF Palette optimization algorithm is only applicable to mode='P' or 'L' #993
|
||||||
|
[moriyoshi]
|
||||||
|
|
||||||
|
- Use PySide as an alternative to PyQt4/5.
|
||||||
|
[holg]
|
||||||
|
|
||||||
|
- Replace affine-based im.resize implementation with convolution-based im.stretch #997
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Replace Gaussian Blur implementation with iterated fast box blur. #961 Note: Radius parameter is interpreted differently than before.
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Better docs explaining import _imaging failure #1016, build #1017, mode #1018, PyAccess, PixelAccess objects #1019 Image.quantize #1020 and Image.save #1021
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix for saving TIFF image into an io.BytesIO buffer #1011
|
||||||
|
[mfergie]
|
||||||
|
|
||||||
|
- Fix antialias compilation on debug versions of Python #1010
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix for Image.putdata segfault #1009
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Ico save, additional tests #1007
|
||||||
|
[exherb]
|
||||||
|
|
||||||
|
- Use PyQt4 if it has already been imported, otherwise prefer PyQt5. #1003
|
||||||
|
[AurelienBallier]
|
||||||
|
|
||||||
|
- Speedup resample implementation up to 2.5 times. #977
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Speed up rotation by using cache aware loops, added transpose to rotations. #994
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Fix Bicubic interpolation #970
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Support for 4-bit greyscale TIFF images #980
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Updated manifest #957
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix PyPy 2.4 regression #952
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Webp Metadata Skip Test comments #954
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fixes for things rpmlint complains about #942
|
||||||
|
[manisandro]
|
||||||
|
|
||||||
|
2.6.2 (2015-01-01)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Fix CVE-2014-9601, potential PNG decompression DOS #1060
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix Regression in PyPy 2.4 in streamio #958
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
2.6.1 (2014-10-11)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Fix SciPy regression in Image.resize #945
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix manifest to include all test files.
|
||||||
|
[aclark4life]
|
||||||
|
|
||||||
|
2.6.0 (2014-10-01)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Relax precision of ImageDraw tests for x86, GimpGradient for PPC
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
2.6.0-rc1 (2014-09-29)
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- Use redistributable image for testing #884
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Use redistributable ICC profiles for testing, skip if not available #923
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Additional documentation for JPEG info and save options #890
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix JPEG Encoding memory leak when exif or qtables were specified
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Image.tobytes() and Image.tostring() documentation update #916 #917
|
||||||
|
[mgedmin]
|
||||||
|
|
||||||
|
- On Windows, do not execute convert.exe without specifying path #912
|
||||||
|
[cgohlke]
|
||||||
|
|
||||||
|
- Fix msvc build error #911
|
||||||
|
[cgohlke]
|
||||||
|
|
||||||
|
- Fix for handling P + transparency -> RGBA conversions #904
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Retain alpha in ImageEnhance operations #909
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Jpeg2k Decode/encode memory leak fix #898
|
||||||
|
[joshware, wiredfool]
|
||||||
|
|
||||||
|
- EpsFilePlugin Speed improvements #886
|
||||||
|
[wiredfool, karstenw]
|
||||||
|
|
||||||
|
- Don't resize if already the right size #892
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix for reading multipage TIFFs #885
|
||||||
|
[kostrom, wiredfool]
|
||||||
|
|
||||||
|
- Correctly handle saving gray and CMYK JPEGs with quality=keep #857
|
||||||
|
[etienned]
|
||||||
|
|
||||||
|
- Correct duplicate Tiff Metadata and Exif tag values
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Windows fixes #871
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix TGA files with image ID field #856
|
||||||
|
[megabuz]
|
||||||
|
|
||||||
|
- Fixed wrong P-mode of small, unoptimized L-mode GIF #843
|
||||||
|
[uvNikita]
|
||||||
|
|
||||||
|
- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin
|
||||||
[Andrew Drake]
|
[Andrew Drake]
|
||||||
|
|
||||||
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin
|
- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin
|
||||||
|
@ -49,7 +322,7 @@ Changelog (Pillow)
|
||||||
- Added docs for ExifTags
|
- Added docs for ExifTags
|
||||||
[Wintermute3]
|
[Wintermute3]
|
||||||
|
|
||||||
- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
|
- More tests for CurImagePlugin, DcxImagePlugin, Effects.c, GimpGradientFile, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- Fix return value of FreeTypeFont.textsize() does not include font offsets
|
- Fix return value of FreeTypeFont.textsize() does not include font offsets
|
||||||
|
@ -109,7 +382,7 @@ Changelog (Pillow)
|
||||||
[wirefool]
|
[wirefool]
|
||||||
|
|
||||||
- Top level flake8 fixes #741
|
- Top level flake8 fixes #741
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
- Remove obsolete Animated Raster Graphics (ARG) support
|
- Remove obsolete Animated Raster Graphics (ARG) support
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
@ -238,7 +511,7 @@ Changelog (Pillow)
|
||||||
[larsmans]
|
[larsmans]
|
||||||
|
|
||||||
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
|
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
- Merge from Philippe Lagadec’s OleFileIO_PL fork
|
- Merge from Philippe Lagadec’s OleFileIO_PL fork
|
||||||
[vadmium]
|
[vadmium]
|
||||||
|
@ -653,13 +926,13 @@ Changelog (Pillow)
|
||||||
[blueyed]
|
[blueyed]
|
||||||
|
|
||||||
- Package cleanup and additional documentation
|
- Package cleanup and additional documentation
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.7.4 (2011-07-21)
|
1.7.4 (2011-07-21)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- Fix brown bag release
|
- Fix brown bag release
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.7.3 (2011-07-20)
|
1.7.3 (2011-07-20)
|
||||||
------------------
|
------------------
|
||||||
|
@ -671,19 +944,19 @@ Changelog (Pillow)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- Bug fix: Python 2.4 compat
|
- Bug fix: Python 2.4 compat
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.7.1 (2011-05-31)
|
1.7.1 (2011-05-31)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- More multi-arch support
|
- More multi-arch support
|
||||||
[SteveM, regebro, barry, aclark]
|
[SteveM, regebro, barry, aclark4life]
|
||||||
|
|
||||||
1.7.0 (2011-05-27)
|
1.7.0 (2011-05-27)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- Add support for multi-arch library directory /usr/lib/x86_64-linux-gnu
|
- Add support for multi-arch library directory /usr/lib/x86_64-linux-gnu
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.6 (12/01/2010)
|
1.6 (12/01/2010)
|
||||||
----------------
|
----------------
|
||||||
|
@ -692,28 +965,28 @@ Changelog (Pillow)
|
||||||
[elro]
|
[elro]
|
||||||
|
|
||||||
- Doc fixes
|
- Doc fixes
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.5 (11/28/2010)
|
1.5 (11/28/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Module and package fixes
|
- Module and package fixes
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.4 (11/28/2010)
|
1.4 (11/28/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Doc fixes
|
- Doc fixes
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.3 (11/28/2010)
|
1.3 (11/28/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Add support for /lib64 and /usr/lib64 library directories on Linux
|
- Add support for /lib64 and /usr/lib64 library directories on Linux
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
- Doc fixes
|
- Doc fixes
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.2 (08/02/2010)
|
1.2 (08/02/2010)
|
||||||
----------------
|
----------------
|
||||||
|
@ -722,26 +995,29 @@ Changelog (Pillow)
|
||||||
[jezdez]
|
[jezdez]
|
||||||
|
|
||||||
- Doc fixes
|
- Doc fixes
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.1 (07/31/2010)
|
1.1 (07/31/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Removed setuptools_hg requirement
|
- Removed setuptools_hg requirement
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
- Doc fixes
|
- Doc fixes
|
||||||
[aclark]
|
[aclark4life]
|
||||||
|
|
||||||
1.0 (07/30/2010)
|
1.0 (07/30/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required.
|
- 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>`_
|
- 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
|
.. Note:: What follows is the original PIL 1.1.7 CHANGES
|
||||||
|
|
||||||
|
0.2b5 - 1.1.7 (1995-2010)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
-*- coding: utf-8 -*-
|
-*- coding: utf-8 -*-
|
||||||
|
@ -1601,7 +1877,7 @@ Changelog (Pillow)
|
||||||
(1.1.2c1 and 1.1.2 final released)
|
(1.1.2c1 and 1.1.2 final released)
|
||||||
|
|
||||||
+ Adapted to Python 2.1. Among other things, all uses of the
|
+ Adapted to Python 2.1. Among other things, all uses of the
|
||||||
"regex" module has been repleased with "re".
|
"regex" module have been replaced with "re".
|
||||||
|
|
||||||
+ Fixed attribute error when reading large PNG files (this bug
|
+ Fixed attribute error when reading large PNG files (this bug
|
||||||
was introduced in maintenance code released after the 1.1.1
|
was introduced in maintenance code released after the 1.1.1
|
||||||
|
@ -2225,7 +2501,7 @@ Changelog (Pillow)
|
||||||
the default value is 75.
|
the default value is 75.
|
||||||
|
|
||||||
JPEG smooth smooth dithered images. value
|
JPEG smooth smooth dithered images. value
|
||||||
is strengh (1-100). default is
|
is strength (1-100). default is
|
||||||
off (0).
|
off (0).
|
||||||
|
|
||||||
PNG optimize minimize output file at the
|
PNG optimize minimize output file at the
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
# Contributing
|
# Contributing to Pillow
|
||||||
|
|
||||||
## Fixes, Features and Changes
|
Bug fixes, feature additions, tests, documentation and more can be contributed via [issues](https://github.com/python-pillow/Pillow/issues) and/or [pull requests](https://github.com/python-pillow/Pillow/issues). All contributions are welcome.
|
||||||
|
|
||||||
Send a pull request. 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)
|
## Bug fixes, feature additions, etc.
|
||||||
|
|
||||||
- Fork the repo
|
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
|
||||||
- Make a branch
|
|
||||||
- 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.
|
|
||||||
- Push to your fork, and make a pull request.
|
|
||||||
|
|
||||||
A few guidelines:
|
- Fork the Pillow repository.
|
||||||
- Try to keep any code commits clean and separate from reformatting commits.
|
- Create a branch from master.
|
||||||
- All new code is going to need tests.
|
- Develop bug fixes, features, tests, etc.
|
||||||
- Try to follow PEP8.
|
- Run the test suite on both Python 2.x and 3.x. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
|
||||||
|
- Create a pull request to pull the changes from your branch to the Pillow master.
|
||||||
|
|
||||||
## Bugs
|
### Guidelines
|
||||||
|
|
||||||
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.
|
- Separate code commits from reformatting commits.
|
||||||
|
- Provide tests for any newly added code.
|
||||||
|
- Follow PEP8.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
Let us know:
|
|
||||||
- What did you do?
|
- What did you do?
|
||||||
- What did you expect to happen?
|
- What did you expect to happen?
|
||||||
- What actually happened?
|
- What actually happened?
|
||||||
|
|
32
MANIFEST.in
32
MANIFEST.in
|
@ -1,40 +1,22 @@
|
||||||
|
|
||||||
include *.c
|
include *.c
|
||||||
include *.h
|
include *.h
|
||||||
include *.md
|
include *.md
|
||||||
include *.py
|
include *.py
|
||||||
|
include *.sh
|
||||||
include *.rst
|
include *.rst
|
||||||
include *.txt
|
include *.txt
|
||||||
|
include *.yaml
|
||||||
include .coveragerc
|
include .coveragerc
|
||||||
include .gitattributes
|
include .gitattributes
|
||||||
include .travis.yml
|
include .travis.yml
|
||||||
|
include LICENSE
|
||||||
include Makefile
|
include Makefile
|
||||||
include tox.ini
|
include tox.ini
|
||||||
recursive-include Images *.bdf
|
|
||||||
recursive-include Images *.fli
|
|
||||||
recursive-include Images *.gif
|
|
||||||
recursive-include Images *.icns
|
|
||||||
recursive-include Images *.ico
|
|
||||||
recursive-include Images *.jpg
|
|
||||||
recursive-include Images *.pbm
|
|
||||||
recursive-include Images *.pil
|
|
||||||
recursive-include Images *.png
|
|
||||||
recursive-include Images *.ppm
|
|
||||||
recursive-include Images *.psd
|
|
||||||
recursive-include Images *.tar
|
|
||||||
recursive-include Images *.webp
|
|
||||||
recursive-include Images *.xpm
|
|
||||||
recursive-include PIL *.md
|
recursive-include PIL *.md
|
||||||
recursive-include Sane *.c
|
|
||||||
recursive-include Sane *.py
|
|
||||||
recursive-include Sane *.rst
|
|
||||||
recursive-include Sane *.txt
|
|
||||||
recursive-include Sane CHANGES
|
|
||||||
recursive-include Sane README
|
|
||||||
recursive-include Scripts *.py
|
recursive-include Scripts *.py
|
||||||
recursive-include Scripts *.rst
|
recursive-include Scripts *.rst
|
||||||
recursive-include Scripts *.sh
|
recursive-include Scripts *.sh
|
||||||
recursive-include Scripts README
|
recursive-include Scripts README.rst
|
||||||
recursive-include Tests *.bdf
|
recursive-include Tests *.bdf
|
||||||
recursive-include Tests *.bin
|
recursive-include Tests *.bin
|
||||||
recursive-include Tests *.bmp
|
recursive-include Tests *.bmp
|
||||||
|
@ -44,10 +26,11 @@ recursive-include Tests *.dcx
|
||||||
recursive-include Tests *.doc
|
recursive-include Tests *.doc
|
||||||
recursive-include Tests *.eps
|
recursive-include Tests *.eps
|
||||||
recursive-include Tests *.fli
|
recursive-include Tests *.fli
|
||||||
|
recursive-include Tests *.ggr
|
||||||
recursive-include Tests *.gif
|
recursive-include Tests *.gif
|
||||||
recursive-include Tests *.gnuplot
|
recursive-include Tests *.gnuplot
|
||||||
recursive-include Tests *.html
|
recursive-include Tests *.html
|
||||||
recursive-include Tests *.icm
|
recursive-include Tests *.icc
|
||||||
recursive-include Tests *.icns
|
recursive-include Tests *.icns
|
||||||
recursive-include Tests *.ico
|
recursive-include Tests *.ico
|
||||||
recursive-include Tests *.j2k
|
recursive-include Tests *.j2k
|
||||||
|
@ -70,15 +53,16 @@ recursive-include Tests *.rst
|
||||||
recursive-include Tests *.sgi
|
recursive-include Tests *.sgi
|
||||||
recursive-include Tests *.spider
|
recursive-include Tests *.spider
|
||||||
recursive-include Tests *.tar
|
recursive-include Tests *.tar
|
||||||
|
recursive-include Tests *.tga
|
||||||
recursive-include Tests *.tif
|
recursive-include Tests *.tif
|
||||||
recursive-include Tests *.tiff
|
recursive-include Tests *.tiff
|
||||||
recursive-include Tests *.ttf
|
recursive-include Tests *.ttf
|
||||||
recursive-include Tests *.txt
|
recursive-include Tests *.txt
|
||||||
recursive-include Tests *.webp
|
recursive-include Tests *.webp
|
||||||
recursive-include Tests *.xpm
|
recursive-include Tests *.xpm
|
||||||
|
recursive-include Tests *.msp
|
||||||
recursive-include Tk *.c
|
recursive-include Tk *.c
|
||||||
recursive-include Tk *.rst
|
recursive-include Tk *.rst
|
||||||
recursive-include Tk *.txt
|
|
||||||
recursive-include depends *.rst
|
recursive-include depends *.rst
|
||||||
recursive-include depends *.sh
|
recursive-include depends *.sh
|
||||||
recursive-include docs *.bat
|
recursive-include docs *.bat
|
||||||
|
|
101
Makefile
101
Makefile
|
@ -1,28 +1,5 @@
|
||||||
.PHONY: pre clean install test inplace coverage test-dep help docs livedocs
|
# 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
|
||||||
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
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
python setup.py clean
|
python setup.py clean
|
||||||
|
@ -30,32 +7,70 @@ clean:
|
||||||
rm -r build || true
|
rm -r build || true
|
||||||
find . -name __pycache__ | xargs rm -r || 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
|
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
# requires nose-cov
|
|
||||||
coverage erase
|
coverage erase
|
||||||
coverage run --parallel-mode --include=PIL/* selftest.py
|
coverage run --parallel-mode --include=PIL/* selftest.py
|
||||||
nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py
|
nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py
|
||||||
# doesn't combine properly before report,
|
# Doesn't combine properly before report, writing report instead of displaying invalid report.
|
||||||
# writing report instead of displaying invalid report
|
|
||||||
rm -r htmlcov || true
|
rm -r htmlcov || true
|
||||||
coverage combine
|
coverage combine
|
||||||
coverage report
|
coverage report
|
||||||
|
|
||||||
test-dep:
|
doc:
|
||||||
pip install coveralls nose nose-cov pep8 pyflakes
|
|
||||||
|
|
||||||
docs:
|
|
||||||
$(MAKE) -C docs html
|
$(MAKE) -C docs html
|
||||||
|
|
||||||
docserver:
|
docserve:
|
||||||
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
|
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 " 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
@ -26,12 +26,12 @@ from PIL import FontFile
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
bdf_slant = {
|
bdf_slant = {
|
||||||
"R": "Roman",
|
"R": "Roman",
|
||||||
"I": "Italic",
|
"I": "Italic",
|
||||||
"O": "Oblique",
|
"O": "Oblique",
|
||||||
"RI": "Reverse Italic",
|
"RI": "Reverse Italic",
|
||||||
"RO": "Reverse Oblique",
|
"RO": "Reverse Oblique",
|
||||||
"OT": "Other"
|
"OT": "Other"
|
||||||
}
|
}
|
||||||
|
|
||||||
bdf_spacing = {
|
bdf_spacing = {
|
||||||
|
@ -40,8 +40,8 @@ bdf_spacing = {
|
||||||
"C": "Cell"
|
"C": "Cell"
|
||||||
}
|
}
|
||||||
|
|
||||||
def bdf_char(f):
|
|
||||||
|
|
||||||
|
def bdf_char(f):
|
||||||
# skip to STARTCHAR
|
# skip to STARTCHAR
|
||||||
while True:
|
while True:
|
||||||
s = f.readline()
|
s = f.readline()
|
||||||
|
@ -69,8 +69,8 @@ def bdf_char(f):
|
||||||
bitmap.append(s[:-1])
|
bitmap.append(s[:-1])
|
||||||
bitmap = b"".join(bitmap)
|
bitmap = b"".join(bitmap)
|
||||||
|
|
||||||
[x, y, l, d] = [int(s) for s in props["BBX"].split()]
|
[x, y, l, d] = [int(p) for p in props["BBX"].split()]
|
||||||
[dx, dy] = [int(s) for s in props["DWIDTH"].split()]
|
[dx, dy] = [int(p) for p in props["DWIDTH"].split()]
|
||||||
|
|
||||||
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
|
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
|
||||||
|
|
||||||
|
@ -82,6 +82,7 @@ def bdf_char(f):
|
||||||
|
|
||||||
return id, int(props["ENCODING"]), bbox, im
|
return id, int(props["ENCODING"]), bbox, im
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Font file plugin for the X11 BDF format.
|
# Font file plugin for the X11 BDF format.
|
||||||
|
|
||||||
|
@ -113,10 +114,10 @@ class BdfFontFile(FontFile.FontFile):
|
||||||
font[4] = bdf_slant[font[4].upper()]
|
font[4] = bdf_slant[font[4].upper()]
|
||||||
font[11] = bdf_spacing[font[11].upper()]
|
font[11] = bdf_spacing[font[11].upper()]
|
||||||
|
|
||||||
ascent = int(props["FONT_ASCENT"])
|
# ascent = int(props["FONT_ASCENT"])
|
||||||
descent = int(props["FONT_DESCENT"])
|
# descent = int(props["FONT_DESCENT"])
|
||||||
|
|
||||||
fontname = ";".join(font[1:])
|
# fontname = ";".join(font[1:])
|
||||||
|
|
||||||
# print "#", fontname
|
# print "#", fontname
|
||||||
# for i in comments:
|
# for i in comments:
|
||||||
|
|
|
@ -30,6 +30,7 @@ __version__ = "0.7"
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
i32 = _binary.i32le
|
i32 = _binary.i32le
|
||||||
|
@ -48,136 +49,154 @@ BIT2MODE = {
|
||||||
8: ("P", "P"),
|
8: ("P", "P"),
|
||||||
16: ("RGB", "BGR;15"),
|
16: ("RGB", "BGR;15"),
|
||||||
24: ("RGB", "BGR"),
|
24: ("RGB", "BGR"),
|
||||||
32: ("RGB", "BGRX")
|
32: ("RGB", "BGRX"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:2] == b"BM"
|
return prefix[:2] == b"BM"
|
||||||
|
|
||||||
##
|
|
||||||
|
# ==============================================================================
|
||||||
# Image plugin for the Windows BMP format.
|
# Image plugin for the Windows BMP format.
|
||||||
|
# ==============================================================================
|
||||||
class BmpImageFile(ImageFile.ImageFile):
|
class BmpImageFile(ImageFile.ImageFile):
|
||||||
|
""" Image plugin for the Windows Bitmap format (BMP) """
|
||||||
|
|
||||||
format = "BMP"
|
# -------------------------------------------------------------- Description
|
||||||
format_description = "Windows Bitmap"
|
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):
|
def _bitmap(self, header=0, offset=0):
|
||||||
|
""" Read relevant info about the BMP """
|
||||||
|
read, seek = self.fp.read, self.fp.seek
|
||||||
if header:
|
if header:
|
||||||
self.fp.seek(header)
|
seek(header)
|
||||||
|
file_info = dict()
|
||||||
read = self.fp.read
|
file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size)
|
||||||
|
file_info['direction'] = -1
|
||||||
# CORE/INFO
|
# --------------------- If requested, read header at a specific position
|
||||||
s = read(4)
|
header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size
|
||||||
s = s + ImageFile._safe_read(self.fp, i32(s)-4)
|
# --------------------------------------------------- IBM OS/2 Bitmap v1
|
||||||
|
# ------ This format has different offsets because of width/height types
|
||||||
if len(s) == 12:
|
if file_info['header_size'] == 12:
|
||||||
|
file_info['width'] = i16(header_data[0:2])
|
||||||
# OS/2 1.0 CORE
|
file_info['height'] = i16(header_data[2:4])
|
||||||
bits = i16(s[10:])
|
file_info['planes'] = i16(header_data[4:6])
|
||||||
self.size = i16(s[4:]), i16(s[6:])
|
file_info['bits'] = i16(header_data[6:8])
|
||||||
compression = 0
|
file_info['compression'] = self.RAW
|
||||||
lutsize = 3
|
file_info['palette_padding'] = 3
|
||||||
colors = 0
|
# ---------------------------------------------- Windows Bitmap v2 to v5
|
||||||
direction = -1
|
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
|
||||||
elif len(s) in [40, 64, 108, 124]:
|
file_info['y_flip'] = i8(header_data[7]) == 0xff
|
||||||
|
file_info['direction'] = 1 if file_info['y_flip'] else -1
|
||||||
# WIN 3.1 or OS/2 2.0 INFO
|
file_info['width'] = i32(header_data[0:4])
|
||||||
bits = i16(s[14:])
|
file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8])
|
||||||
self.size = i32(s[4:]), i32(s[8:])
|
file_info['planes'] = i16(header_data[8:10])
|
||||||
compression = i32(s[16:])
|
file_info['bits'] = i16(header_data[10:12])
|
||||||
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
|
file_info['compression'] = i32(header_data[12:16])
|
||||||
lutsize = 4
|
file_info['data_size'] = i32(header_data[16:20]) # byte size of pixel data
|
||||||
colors = i32(s[32:])
|
file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))
|
||||||
direction = -1
|
file_info['colors'] = i32(header_data[28:32])
|
||||||
if i8(s[11]) == 0xff:
|
file_info['palette_padding'] = 4
|
||||||
# upside-down storage
|
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), file_info['pixels_per_meter']))
|
||||||
self.size = self.size[0], 2**32 - self.size[1]
|
if file_info['compression'] == self.BITFIELDS:
|
||||||
direction = 0
|
if len(header_data) >= 52:
|
||||||
|
for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
|
||||||
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm))
|
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:
|
else:
|
||||||
raise IOError("Unsupported BMP header type (%d)" % len(s))
|
raise IOError("Unsupported BMP header type (%d)" % file_info['header_size'])
|
||||||
|
# ------------------ Special case : header is reported 40, which
|
||||||
if (self.size[0]*self.size[1]) > 2**31:
|
# ---------------------- is shorter than real size for bpp >= 16
|
||||||
# Prevent DOS for > 2gb images
|
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)
|
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
|
||||||
|
# ----------------------- Check bit depth for unusual unsupported values
|
||||||
if not colors:
|
self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None))
|
||||||
colors = 1 << bits
|
if self.mode is None:
|
||||||
|
raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits'])
|
||||||
# MODE
|
# ----------------- Process BMP with Bitfields compression (not palette)
|
||||||
try:
|
if file_info['compression'] == self.BITFIELDS:
|
||||||
self.mode, rawmode = BIT2MODE[bits]
|
SUPPORTED = {
|
||||||
except KeyError:
|
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
|
||||||
raise IOError("Unsupported BMP pixel depth (%d)" % bits)
|
24: [(0xff0000, 0xff00, 0xff)],
|
||||||
|
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]}
|
||||||
if compression == 3:
|
MASK_MODES = {
|
||||||
# BI_BITFIELDS compression
|
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
||||||
mask = i32(read(4)), i32(read(4)), i32(read(4))
|
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
||||||
if bits == 32 and mask == (0xff0000, 0x00ff00, 0x0000ff):
|
(16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"}
|
||||||
rawmode = "BGRX"
|
if file_info['bits'] in SUPPORTED:
|
||||||
elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f):
|
if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
|
||||||
rawmode = "BGR;16"
|
raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])]
|
||||||
elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f):
|
self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
|
||||||
rawmode = "BGR;15"
|
elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]:
|
||||||
else:
|
raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])]
|
||||||
# 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"
|
|
||||||
else:
|
else:
|
||||||
self.mode = rawmode = "L"
|
raise IOError("Unsupported BMP bitfields layout")
|
||||||
else:
|
else:
|
||||||
self.mode = "P"
|
raise IOError("Unsupported BMP bitfields layout")
|
||||||
self.palette = ImagePalette.raw(
|
elif file_info['compression'] == self.RAW:
|
||||||
"BGR", b"".join(palette)
|
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:
|
# ----------------------------- Finally set the tile data for the plugin
|
||||||
offset = self.fp.tell()
|
self.info['compression'] = file_info['compression']
|
||||||
|
self.tile = [('raw', (0, 0, file_info['width'], file_info['height']), offset or self.fp.tell(),
|
||||||
self.tile = [("raw",
|
(raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction'])
|
||||||
(0, 0) + self.size,
|
)]
|
||||||
offset,
|
|
||||||
(rawmode, ((self.size[0]*bits+31)>>3)&(~3), direction))]
|
|
||||||
|
|
||||||
self.info["compression"] = compression
|
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
""" Open file, check magic number and read header """
|
||||||
# HEAD
|
# read 14 bytes: magic number, filesize, reserved, header final offset
|
||||||
s = self.fp.read(14)
|
head_data = self.fp.read(14)
|
||||||
if s[:2] != b"BM":
|
# choke if the file does not have the required magic bytes
|
||||||
|
if head_data[0:2] != b"BM":
|
||||||
raise SyntaxError("Not a BMP file")
|
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)
|
self._bitmap(offset=offset)
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Image plugin for the DIB format (BMP alias)
|
||||||
|
# ==============================================================================
|
||||||
class DibImageFile(BmpImageFile):
|
class DibImageFile(BmpImageFile):
|
||||||
|
|
||||||
format = "DIB"
|
format = "DIB"
|
||||||
|
@ -195,10 +214,11 @@ SAVE = {
|
||||||
"L": ("L", 8, 256),
|
"L": ("L", 8, 256),
|
||||||
"P": ("P", 8, 256),
|
"P": ("P", 8, 256),
|
||||||
"RGB": ("BGR", 24, 0),
|
"RGB": ("BGR", 24, 0),
|
||||||
|
"RGBA": ("BGRA", 32, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _save(im, fp, filename, check=0):
|
|
||||||
|
|
||||||
|
def _save(im, fp, filename, check=0):
|
||||||
try:
|
try:
|
||||||
rawmode, bits, colors = SAVE[im.mode]
|
rawmode, bits, colors = SAVE[im.mode]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -214,10 +234,10 @@ def _save(im, fp, filename, check=0):
|
||||||
# 1 meter == 39.3701 inches
|
# 1 meter == 39.3701 inches
|
||||||
ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
|
ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
|
||||||
|
|
||||||
stride = ((im.size[0]*bits+7)//8+3)&(~3)
|
stride = ((im.size[0]*bits+7)//8+3) & (~3)
|
||||||
header = 40 # or 64 for OS/2 version 2
|
header = 40 # or 64 for OS/2 version 2
|
||||||
offset = 14 + header + colors * 4
|
offset = 14 + header + colors * 4
|
||||||
image = stride * im.size[1]
|
image = stride * im.size[1]
|
||||||
|
|
||||||
# bitmap header
|
# bitmap header
|
||||||
fp.write(b"BM" + # file type (magic)
|
fp.write(b"BM" + # file type (magic)
|
||||||
|
@ -248,7 +268,8 @@ def _save(im, fp, filename, check=0):
|
||||||
elif im.mode == "P":
|
elif im.mode == "P":
|
||||||
fp.write(im.im.getpalette("RGB", "BGRX"))
|
fp.write(im.im.getpalette("RGB", "BGRX"))
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, stride, -1))])
|
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0,
|
||||||
|
(rawmode, stride, -1))])
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -13,6 +13,7 @@ from PIL import Image, ImageFile
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Install application-specific BUFR image handler.
|
# Install application-specific BUFR image handler.
|
||||||
#
|
#
|
||||||
|
@ -22,12 +23,14 @@ def register_handler(handler):
|
||||||
global _handler
|
global _handler
|
||||||
_handler = handler
|
_handler = handler
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Image adapter
|
# Image adapter
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
|
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
|
||||||
|
|
||||||
|
|
||||||
class BufrStubImageFile(ImageFile.StubImageFile):
|
class BufrStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
||||||
format = "BUFR"
|
format = "BUFR"
|
||||||
|
@ -53,6 +56,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
|
||||||
def _load(self):
|
def _load(self):
|
||||||
return _handler
|
return _handler
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
if _handler is None or not hasattr("_handler", "save"):
|
if _handler is None or not hasattr("_handler", "save"):
|
||||||
raise IOError("BUFR save handler not installed")
|
raise IOError("BUFR save handler not installed")
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
# A file object that provides read access to a part of an existing
|
# A file object that provides read access to a part of an existing
|
||||||
# file (for example a TAR file).
|
# file (for example a TAR file).
|
||||||
|
|
||||||
class ContainerIO:
|
|
||||||
|
class ContainerIO(object):
|
||||||
|
|
||||||
##
|
##
|
||||||
# Create file object.
|
# Create file object.
|
||||||
|
@ -48,7 +49,7 @@ class ContainerIO:
|
||||||
# for current offset, and 2 for end of region. You cannot move
|
# for current offset, and 2 for end of region. You cannot move
|
||||||
# the pointer outside the defined region.
|
# the pointer outside the defined region.
|
||||||
|
|
||||||
def seek(self, offset, mode = 0):
|
def seek(self, offset, mode=0):
|
||||||
if mode == 1:
|
if mode == 1:
|
||||||
self.pos = self.pos + offset
|
self.pos = self.pos + offset
|
||||||
elif mode == 2:
|
elif mode == 2:
|
||||||
|
@ -75,12 +76,12 @@ class ContainerIO:
|
||||||
# read until end of region.
|
# read until end of region.
|
||||||
# @return An 8-bit string.
|
# @return An 8-bit string.
|
||||||
|
|
||||||
def read(self, n = 0):
|
def read(self, n=0):
|
||||||
if n:
|
if n:
|
||||||
n = min(n, self.length - self.pos)
|
n = min(n, self.length - self.pos)
|
||||||
else:
|
else:
|
||||||
n = self.length - self.pos
|
n = self.length - self.pos
|
||||||
if not n: # EOF
|
if not n: # EOF
|
||||||
return ""
|
return ""
|
||||||
self.pos = self.pos + n
|
self.pos = self.pos + n
|
||||||
return self.fh.read(n)
|
return self.fh.read(n)
|
||||||
|
|
|
@ -62,6 +62,10 @@ class DcxImageFile(PcxImageFile):
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return len(self._offset)
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame >= len(self._offset):
|
if frame >= len(self._offset):
|
||||||
raise EOFError("attempt to seek outside DCX directory")
|
raise EOFError("attempt to seek outside DCX directory")
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
# 1996-08-23 fl Handle files from Macintosh (0.3)
|
# 1996-08-23 fl Handle files from Macintosh (0.3)
|
||||||
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
|
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
|
||||||
# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
|
# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
|
||||||
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution resizing
|
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution
|
||||||
|
# resizing
|
||||||
#
|
#
|
||||||
# Copyright (c) 1997-2003 by Secret Labs AB.
|
# Copyright (c) 1997-2003 by Secret Labs AB.
|
||||||
# Copyright (c) 1995-2003 by Fredrik Lundh
|
# Copyright (c) 1995-2003 by Fredrik Lundh
|
||||||
|
@ -51,13 +52,14 @@ if sys.platform.startswith('win'):
|
||||||
else:
|
else:
|
||||||
gs_windows_binary = False
|
gs_windows_binary = False
|
||||||
|
|
||||||
|
|
||||||
def has_ghostscript():
|
def has_ghostscript():
|
||||||
if gs_windows_binary:
|
if gs_windows_binary:
|
||||||
return True
|
return True
|
||||||
if not sys.platform.startswith('win'):
|
if not sys.platform.startswith('win'):
|
||||||
import subprocess
|
import subprocess
|
||||||
try:
|
try:
|
||||||
gs = subprocess.Popen(['gs','--version'], stdout=subprocess.PIPE)
|
gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
|
||||||
gs.stdout.read()
|
gs.stdout.read()
|
||||||
return True
|
return True
|
||||||
except OSError:
|
except OSError:
|
||||||
|
@ -73,52 +75,62 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
decoder, tile, offset, data = tile[0]
|
decoder, tile, offset, data = tile[0]
|
||||||
length, bbox = data
|
length, bbox = data
|
||||||
|
|
||||||
#Hack to support hi-res rendering
|
# Hack to support hi-res rendering
|
||||||
scale = int(scale) or 1
|
scale = int(scale) or 1
|
||||||
orig_size = size
|
# orig_size = size
|
||||||
orig_bbox = bbox
|
# orig_bbox = bbox
|
||||||
size = (size[0] * scale, size[1] * scale)
|
size = (size[0] * scale, size[1] * scale)
|
||||||
# resolution is dependend on bbox and size
|
# resolution is dependent on bbox and size
|
||||||
res = ( float((72.0 * size[0]) / (bbox[2]-bbox[0])), float((72.0 * size[1]) / (bbox[3]-bbox[1])) )
|
res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
|
||||||
#print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
|
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
|
||||||
|
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
|
||||||
|
|
||||||
import tempfile, os, subprocess
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
out_fd, outfile = tempfile.mkstemp()
|
out_fd, outfile = tempfile.mkstemp()
|
||||||
os.close(out_fd)
|
os.close(out_fd)
|
||||||
in_fd, infile = tempfile.mkstemp()
|
|
||||||
os.close(in_fd)
|
|
||||||
|
|
||||||
# ignore length and offset!
|
infile_temp = None
|
||||||
# ghostscript can read it
|
if hasattr(fp, 'name') and os.path.exists(fp.name):
|
||||||
# copy whole file to read in ghostscript
|
infile = fp.name
|
||||||
with open(infile, 'wb') as f:
|
else:
|
||||||
# fetch length of fp
|
in_fd, infile_temp = tempfile.mkstemp()
|
||||||
fp.seek(0, 2)
|
os.close(in_fd)
|
||||||
fsize = fp.tell()
|
infile = infile_temp
|
||||||
# ensure start position
|
|
||||||
# go back
|
# ignore length and offset!
|
||||||
fp.seek(0)
|
# ghostscript can read it
|
||||||
lengthfile = fsize
|
# copy whole file to read in ghostscript
|
||||||
while lengthfile > 0:
|
with open(infile_temp, 'wb') as f:
|
||||||
s = fp.read(min(lengthfile, 100*1024))
|
# fetch length of fp
|
||||||
if not s:
|
fp.seek(0, 2)
|
||||||
break
|
fsize = fp.tell()
|
||||||
length -= len(s)
|
# ensure start position
|
||||||
f.write(s)
|
# go back
|
||||||
|
fp.seek(0)
|
||||||
|
lengthfile = fsize
|
||||||
|
while lengthfile > 0:
|
||||||
|
s = fp.read(min(lengthfile, 100*1024))
|
||||||
|
if not s:
|
||||||
|
break
|
||||||
|
lengthfile -= len(s)
|
||||||
|
f.write(s)
|
||||||
|
|
||||||
# Build ghostscript command
|
# Build ghostscript command
|
||||||
command = ["gs",
|
command = ["gs",
|
||||||
"-q", # quiet mode
|
"-q", # quiet mode
|
||||||
"-g%dx%d" % size, # set output geometry (pixels)
|
"-g%dx%d" % size, # set output geometry (pixels)
|
||||||
"-r%fx%f" % res, # set input DPI (dots per inch)
|
"-r%fx%f" % res, # set input DPI (dots per inch)
|
||||||
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
|
"-dNOPAUSE -dSAFER", # don't pause between pages,
|
||||||
"-sDEVICE=ppmraw", # ppm driver
|
# safe mode
|
||||||
"-sOutputFile=%s" % outfile, # output file
|
"-sDEVICE=ppmraw", # ppm driver
|
||||||
|
"-sOutputFile=%s" % outfile, # output file
|
||||||
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
|
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
|
||||||
# adjust for image origin
|
# adjust for image origin
|
||||||
"-f", infile, # input file
|
"-f", infile, # input file
|
||||||
]
|
]
|
||||||
|
|
||||||
if gs_windows_binary is not None:
|
if gs_windows_binary is not None:
|
||||||
if not gs_windows_binary:
|
if not gs_windows_binary:
|
||||||
|
@ -127,7 +139,8 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
|
|
||||||
# push data through ghostscript
|
# push data through ghostscript
|
||||||
try:
|
try:
|
||||||
gs = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
gs = subprocess.Popen(command, stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
gs.stdin.close()
|
gs.stdin.close()
|
||||||
status = gs.wait()
|
status = gs.wait()
|
||||||
if status:
|
if status:
|
||||||
|
@ -136,48 +149,41 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(outfile)
|
os.unlink(outfile)
|
||||||
os.unlink(infile)
|
if infile_temp:
|
||||||
except: pass
|
os.unlink(infile_temp)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
class PSFile:
|
class PSFile(object):
|
||||||
"""Wrapper that treats either CR or LF as end of line."""
|
"""
|
||||||
|
Wrapper for bytesio object that treats either CR or LF as end of line.
|
||||||
|
"""
|
||||||
def __init__(self, fp):
|
def __init__(self, fp):
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
self.char = None
|
self.char = None
|
||||||
def __getattr__(self, id):
|
|
||||||
v = getattr(self.fp, id)
|
|
||||||
setattr(self, id, v)
|
|
||||||
return v
|
|
||||||
def seek(self, offset, whence=0):
|
def seek(self, offset, whence=0):
|
||||||
self.char = None
|
self.char = None
|
||||||
self.fp.seek(offset, whence)
|
self.fp.seek(offset, whence)
|
||||||
def read(self, count):
|
|
||||||
return self.fp.read(count).decode('latin-1')
|
|
||||||
def readbinary(self, count):
|
|
||||||
return self.fp.read(count)
|
|
||||||
def tell(self):
|
|
||||||
pos = self.fp.tell()
|
|
||||||
if self.char:
|
|
||||||
pos -= 1
|
|
||||||
return pos
|
|
||||||
def readline(self):
|
def readline(self):
|
||||||
s = b""
|
s = self.char or b""
|
||||||
if self.char:
|
self.char = None
|
||||||
c = self.char
|
|
||||||
self.char = None
|
c = self.fp.read(1)
|
||||||
else:
|
|
||||||
c = self.fp.read(1)
|
|
||||||
while c not in b"\r\n":
|
while c not in b"\r\n":
|
||||||
s = s + c
|
s = s + c
|
||||||
c = self.fp.read(1)
|
c = self.fp.read(1)
|
||||||
if c == b"\r":
|
|
||||||
self.char = self.fp.read(1)
|
self.char = self.fp.read(1)
|
||||||
if self.char == b"\n":
|
# line endings can be 1 or 2 of \r \n, in either order
|
||||||
self.char = None
|
if self.char in b"\r\n":
|
||||||
return s.decode('latin-1') + "\n"
|
self.char = None
|
||||||
|
|
||||||
|
return s.decode('latin-1')
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
|
@ -187,38 +193,30 @@ def _accept(prefix):
|
||||||
# Image plugin for Encapsulated Postscript. This plugin supports only
|
# Image plugin for Encapsulated Postscript. This plugin supports only
|
||||||
# a few variants of this format.
|
# a few variants of this format.
|
||||||
|
|
||||||
|
|
||||||
class EpsImageFile(ImageFile.ImageFile):
|
class EpsImageFile(ImageFile.ImageFile):
|
||||||
"""EPS File Parser for the Python Imaging Library"""
|
"""EPS File Parser for the Python Imaging Library"""
|
||||||
|
|
||||||
format = "EPS"
|
format = "EPS"
|
||||||
format_description = "Encapsulated Postscript"
|
format_description = "Encapsulated Postscript"
|
||||||
|
|
||||||
|
mode_map = {1: "L", 2: "LAB", 3: "RGB"}
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
(length, offset) = self._find_offset(self.fp)
|
||||||
|
|
||||||
fp = PSFile(self.fp)
|
# Rewrap the open file pointer in something that will
|
||||||
|
# convert line endings and decode to latin-1.
|
||||||
# FIX for: Some EPS file not handled correctly / issue #302
|
try:
|
||||||
# EPS can contain binary data
|
if bytes is str:
|
||||||
# or start directly with latin coding
|
# Python2, no encoding conversion necessary
|
||||||
# read header in both ways to handle both
|
fp = open(self.fp.name, "Ur")
|
||||||
# file types
|
else:
|
||||||
# more info see http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
# Python3, can use bare open command.
|
||||||
|
fp = open(self.fp.name, "Ur", encoding='latin-1')
|
||||||
# for HEAD without binary preview
|
except:
|
||||||
s = fp.read(4)
|
# Expect this for bytesio/stringio
|
||||||
# for HEAD with binary preview
|
fp = PSFile(self.fp)
|
||||||
fp.seek(0)
|
|
||||||
sb = fp.readbinary(160)
|
|
||||||
|
|
||||||
if s[:4] == "%!PS":
|
|
||||||
fp.seek(0, 2)
|
|
||||||
length = fp.tell()
|
|
||||||
offset = 0
|
|
||||||
elif i32(sb[0:4]) == 0xC6D3D0C5:
|
|
||||||
offset = i32(sb[4:8])
|
|
||||||
length = i32(sb[8:12])
|
|
||||||
else:
|
|
||||||
raise SyntaxError("not an EPS file")
|
|
||||||
|
|
||||||
# go to offset - start of "%!PS"
|
# go to offset - start of "%!PS"
|
||||||
fp.seek(offset)
|
fp.seek(offset)
|
||||||
|
@ -226,23 +224,17 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
box = None
|
box = None
|
||||||
|
|
||||||
self.mode = "RGB"
|
self.mode = "RGB"
|
||||||
self.size = 1, 1 # FIXME: huh?
|
self.size = 1, 1 # FIXME: huh?
|
||||||
|
|
||||||
#
|
#
|
||||||
# Load EPS header
|
# Load EPS header
|
||||||
|
|
||||||
s = fp.readline()
|
s = fp.readline().strip('\r\n')
|
||||||
|
|
||||||
while s:
|
while s:
|
||||||
|
|
||||||
if len(s) > 255:
|
if len(s) > 255:
|
||||||
raise SyntaxError("not an EPS file")
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
if s[-2:] == '\r\n':
|
|
||||||
s = s[:-2]
|
|
||||||
elif s[-1:] == '\n':
|
|
||||||
s = s[:-1]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
m = split.match(s)
|
m = split.match(s)
|
||||||
except re.error as v:
|
except re.error as v:
|
||||||
|
@ -256,17 +248,15 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
# Note: The DSC spec says that BoundingBox
|
# Note: The DSC spec says that BoundingBox
|
||||||
# fields should be integers, but some drivers
|
# fields should be integers, but some drivers
|
||||||
# put floating point values there anyway.
|
# 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.size = box[2] - box[0], box[3] - box[1]
|
||||||
self.tile = [("eps", (0,0) + self.size, offset,
|
self.tile = [("eps", (0, 0) + self.size, offset,
|
||||||
(length, box))]
|
(length, box))]
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
m = field.match(s)
|
m = field.match(s)
|
||||||
|
|
||||||
if m:
|
if m:
|
||||||
k = m.group(1)
|
k = m.group(1)
|
||||||
|
|
||||||
|
@ -276,84 +266,69 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
self.info[k[:8]] = k[9:]
|
self.info[k[:8]] = k[9:]
|
||||||
else:
|
else:
|
||||||
self.info[k] = ""
|
self.info[k] = ""
|
||||||
elif s[0:1] == '%':
|
elif s[0] == '%':
|
||||||
# handle non-DSC Postscript comments that some
|
# handle non-DSC Postscript comments that some
|
||||||
# tools mistakenly put in the Comments section
|
# tools mistakenly put in the Comments section
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise IOError("bad EPS header")
|
raise IOError("bad EPS header")
|
||||||
|
|
||||||
s = fp.readline()
|
s = fp.readline().strip('\r\n')
|
||||||
|
|
||||||
if s[:1] != "%":
|
if s[:1] != "%":
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Scan for an "ImageData" descriptor
|
# Scan for an "ImageData" descriptor
|
||||||
|
|
||||||
while s[0] == "%":
|
while s[:1] == "%":
|
||||||
|
|
||||||
if len(s) > 255:
|
if len(s) > 255:
|
||||||
raise SyntaxError("not an EPS file")
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
if s[-2:] == '\r\n':
|
|
||||||
s = s[:-2]
|
|
||||||
elif s[-1:] == '\n':
|
|
||||||
s = s[:-1]
|
|
||||||
|
|
||||||
if s[:11] == "%ImageData:":
|
if s[:11] == "%ImageData:":
|
||||||
|
# Encoded bitmapped image.
|
||||||
|
x, y, bi, mo = s[11:].split(None, 7)[:4]
|
||||||
|
|
||||||
[x, y, bi, mo, z3, z4, en, id] =\
|
if int(bi) != 8:
|
||||||
s[11:].split(None, 7)
|
|
||||||
|
|
||||||
x = int(x); y = int(y)
|
|
||||||
|
|
||||||
bi = int(bi)
|
|
||||||
mo = int(mo)
|
|
||||||
|
|
||||||
en = int(en)
|
|
||||||
|
|
||||||
if en == 1:
|
|
||||||
decoder = "eps_binary"
|
|
||||||
elif en == 2:
|
|
||||||
decoder = "eps_hex"
|
|
||||||
else:
|
|
||||||
break
|
break
|
||||||
if bi != 8:
|
try:
|
||||||
break
|
self.mode = self.mode_map[int(mo)]
|
||||||
if mo == 1:
|
except:
|
||||||
self.mode = "L"
|
|
||||||
elif mo == 2:
|
|
||||||
self.mode = "LAB"
|
|
||||||
elif mo == 3:
|
|
||||||
self.mode = "RGB"
|
|
||||||
else:
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if id[:1] == id[-1:] == '"':
|
self.size = int(x), int(y)
|
||||||
id = id[1:-1]
|
return
|
||||||
|
|
||||||
# Scan forward to the actual image data
|
s = fp.readline().strip('\r\n')
|
||||||
while True:
|
|
||||||
s = fp.readline()
|
|
||||||
if not s:
|
|
||||||
break
|
|
||||||
if s[:len(id)] == id:
|
|
||||||
self.size = x, y
|
|
||||||
self.tile2 = [(decoder,
|
|
||||||
(0, 0, x, y),
|
|
||||||
fp.tell(),
|
|
||||||
0)]
|
|
||||||
return
|
|
||||||
|
|
||||||
s = fp.readline()
|
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
|
|
||||||
if not box:
|
if not box:
|
||||||
raise IOError("cannot determine EPS bounding box")
|
raise IOError("cannot determine EPS bounding box")
|
||||||
|
|
||||||
|
def _find_offset(self, fp):
|
||||||
|
|
||||||
|
s = fp.read(160)
|
||||||
|
|
||||||
|
if s[:4] == b"%!PS":
|
||||||
|
# for HEAD without binary preview
|
||||||
|
fp.seek(0, 2)
|
||||||
|
length = fp.tell()
|
||||||
|
offset = 0
|
||||||
|
elif i32(s[0:4]) == 0xC6D3D0C5:
|
||||||
|
# FIX for: Some EPS file not handled correctly / issue #302
|
||||||
|
# EPS can contain binary data
|
||||||
|
# or start directly with latin coding
|
||||||
|
# more info see:
|
||||||
|
# http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
||||||
|
offset = i32(s[4:8])
|
||||||
|
length = i32(s[8:12])
|
||||||
|
else:
|
||||||
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
|
return (length, offset)
|
||||||
|
|
||||||
def load(self, scale=1):
|
def load(self, scale=1):
|
||||||
# Load EPS via Ghostscript
|
# Load EPS via Ghostscript
|
||||||
if not self.tile:
|
if not self.tile:
|
||||||
|
@ -363,11 +338,12 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
self.size = self.im.size
|
self.size = self.im.size
|
||||||
self.tile = []
|
self.tile = []
|
||||||
|
|
||||||
def load_seek(self,*args,**kwargs):
|
def load_seek(self, *args, **kwargs):
|
||||||
# we can't incrementally load, so force ImageFile.parser to
|
# we can't incrementally load, so force ImageFile.parser to
|
||||||
# use our custom load method by defining this method.
|
# use our custom load method by defining this method.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -389,11 +365,13 @@ def _save(im, fp, filename, eps=1):
|
||||||
else:
|
else:
|
||||||
raise ValueError("image mode is not supported")
|
raise ValueError("image mode is not supported")
|
||||||
|
|
||||||
class NoCloseStream:
|
class NoCloseStream(object):
|
||||||
def __init__(self, fp):
|
def __init__(self, fp):
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
return getattr(self.fp, name)
|
return getattr(self.fp, name)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -407,7 +385,7 @@ def _save(im, fp, filename, eps=1):
|
||||||
# write EPS header
|
# write EPS header
|
||||||
fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
|
fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
|
||||||
fp.write("%%Creator: PIL 0.1 EpsEncode\n")
|
fp.write("%%Creator: PIL 0.1 EpsEncode\n")
|
||||||
#fp.write("%%CreationDate: %s"...)
|
# fp.write("%%CreationDate: %s"...)
|
||||||
fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
|
fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
|
||||||
fp.write("%%Pages: 1\n")
|
fp.write("%%Pages: 1\n")
|
||||||
fp.write("%%EndComments\n")
|
fp.write("%%EndComments\n")
|
||||||
|
@ -421,13 +399,13 @@ def _save(im, fp, filename, eps=1):
|
||||||
fp.write("10 dict begin\n")
|
fp.write("10 dict begin\n")
|
||||||
fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
|
fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
|
||||||
fp.write("%d %d scale\n" % im.size)
|
fp.write("%d %d scale\n" % im.size)
|
||||||
fp.write("%d %d 8\n" % im.size) # <= bits
|
fp.write("%d %d 8\n" % im.size) # <= bits
|
||||||
fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
|
fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
|
||||||
fp.write("{ currentfile buf readhexstring pop } bind\n")
|
fp.write("{ currentfile buf readhexstring pop } bind\n")
|
||||||
fp.write(operator[2] + "\n")
|
fp.write(operator[2] + "\n")
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
ImageFile._save(im, base_fp, [("eps", (0,0)+im.size, 0, None)])
|
ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
|
||||||
|
|
||||||
fp.write("\n%%%%EndBinary\n")
|
fp.write("\n%%%%EndBinary\n")
|
||||||
fp.write("grestore end\n")
|
fp.write("grestore end\n")
|
||||||
|
|
|
@ -67,8 +67,8 @@ TAGS = {
|
||||||
0x0213: "YCbCrPositioning",
|
0x0213: "YCbCrPositioning",
|
||||||
0x0214: "ReferenceBlackWhite",
|
0x0214: "ReferenceBlackWhite",
|
||||||
0x1000: "RelatedImageFileFormat",
|
0x1000: "RelatedImageFileFormat",
|
||||||
0x1001: "RelatedImageLength", # FIXME / Dictionary contains duplicate keys
|
0x1001: "RelatedImageWidth",
|
||||||
0x1001: "RelatedImageWidth", # FIXME \ Dictionary contains duplicate keys
|
0x1002: "RelatedImageLength",
|
||||||
0x828d: "CFARepeatPatternDim",
|
0x828d: "CFARepeatPatternDim",
|
||||||
0x828e: "CFAPattern",
|
0x828e: "CFAPattern",
|
||||||
0x828f: "BatteryLevel",
|
0x828f: "BatteryLevel",
|
||||||
|
|
|
@ -18,6 +18,7 @@ _handler = None
|
||||||
#
|
#
|
||||||
# @param handler Handler object.
|
# @param handler Handler object.
|
||||||
|
|
||||||
|
|
||||||
def register_handler(handler):
|
def register_handler(handler):
|
||||||
global _handler
|
global _handler
|
||||||
_handler = handler
|
_handler = handler
|
||||||
|
@ -25,9 +26,11 @@ def register_handler(handler):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Image adapter
|
# Image adapter
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:6] == b"SIMPLE"
|
return prefix[:6] == b"SIMPLE"
|
||||||
|
|
||||||
|
|
||||||
class FITSStubImageFile(ImageFile.StubImageFile):
|
class FITSStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
||||||
format = "FITS"
|
format = "FITS"
|
||||||
|
|
|
@ -25,12 +25,14 @@ i16 = _binary.i16le
|
||||||
i32 = _binary.i32le
|
i32 = _binary.i32le
|
||||||
o8 = _binary.o8
|
o8 = _binary.o8
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# decoder
|
# decoder
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i16(prefix[4:6]) in [0xAF11, 0xAF12]
|
return i16(prefix[4:6]) in [0xAF11, 0xAF12]
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
|
# Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
|
||||||
# method to load individual frames.
|
# method to load individual frames.
|
||||||
|
@ -47,7 +49,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
magic = i16(s[4:6])
|
magic = i16(s[4:6])
|
||||||
if not (magic in [0xAF11, 0xAF12] and
|
if not (magic in [0xAF11, 0xAF12] and
|
||||||
i16(s[14:16]) in [0, 3] and # flags
|
i16(s[14:16]) in [0, 3] and # flags
|
||||||
s[20:22] == b"\x00\x00"): # reserved
|
s[20:22] == b"\x00\x00"): # reserved
|
||||||
raise SyntaxError("not an FLI/FLC file")
|
raise SyntaxError("not an FLI/FLC file")
|
||||||
|
|
||||||
# image characteristics
|
# image characteristics
|
||||||
|
@ -61,7 +63,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
self.info["duration"] = duration
|
self.info["duration"] = duration
|
||||||
|
|
||||||
# look for palette
|
# look for palette
|
||||||
palette = [(a,a,a) for a in range(256)]
|
palette = [(a, a, a) for a in range(256)]
|
||||||
|
|
||||||
s = self.fp.read(16)
|
s = self.fp.read(16)
|
||||||
|
|
||||||
|
@ -80,13 +82,14 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
elif i16(s[4:6]) == 4:
|
elif i16(s[4:6]) == 4:
|
||||||
self._palette(palette, 0)
|
self._palette(palette, 0)
|
||||||
|
|
||||||
palette = [o8(r)+o8(g)+o8(b) for (r,g,b) in palette]
|
palette = [o8(r)+o8(g)+o8(b) for (r, g, b) in palette]
|
||||||
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
||||||
|
|
||||||
# set things up to decode first frame
|
# set things up to decode first frame
|
||||||
self.frame = -1
|
self.__frame = -1
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
|
self.__rewind = self.fp.tell()
|
||||||
|
self._n_frames = None
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
def _palette(self, palette, shift):
|
def _palette(self, palette, shift):
|
||||||
|
@ -107,11 +110,35 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
palette[i] = (r, g, b)
|
palette[i] = (r, g, b)
|
||||||
i += 1
|
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:
|
def seek(self, frame):
|
||||||
|
if frame == self.__frame:
|
||||||
|
return
|
||||||
|
if frame < self.__frame:
|
||||||
|
self._seek(0)
|
||||||
|
for f in range(self.__frame + 1, frame + 1):
|
||||||
|
self._seek(f)
|
||||||
|
|
||||||
|
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)
|
raise ValueError("cannot seek to frame %d" % frame)
|
||||||
self.frame = frame
|
self.__frame = frame
|
||||||
|
|
||||||
# move to next frame
|
# move to next frame
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
|
@ -124,13 +151,12 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
framesize = i32(s)
|
framesize = i32(s)
|
||||||
|
|
||||||
self.decodermaxblock = framesize
|
self.decodermaxblock = framesize
|
||||||
self.tile = [("fli", (0,0)+self.size, self.__offset, None)]
|
self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
|
||||||
|
|
||||||
self.__offset = self.__offset + framesize
|
self.__offset += framesize
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
|
return self.__frame
|
||||||
return self.frame
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
|
@ -17,11 +17,6 @@
|
||||||
import os
|
import os
|
||||||
from PIL import Image, _binary
|
from PIL import Image, _binary
|
||||||
|
|
||||||
try:
|
|
||||||
import zlib
|
|
||||||
except ImportError:
|
|
||||||
zlib = None
|
|
||||||
|
|
||||||
WIDTH = 800
|
WIDTH = 800
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +31,7 @@ def puti16(fp, values):
|
||||||
##
|
##
|
||||||
# Base class for raster font file handlers.
|
# Base class for raster font file handlers.
|
||||||
|
|
||||||
class FontFile:
|
class FontFile(object):
|
||||||
|
|
||||||
bitmap = None
|
bitmap = None
|
||||||
|
|
||||||
|
@ -83,7 +78,8 @@ class FontFile:
|
||||||
glyph = self[i]
|
glyph = self[i]
|
||||||
if glyph:
|
if glyph:
|
||||||
d, dst, src, im = 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
|
x0, y0 = x, y
|
||||||
x = x + xx
|
x = x + xx
|
||||||
if x > WIDTH:
|
if x > WIDTH:
|
||||||
|
|
|
@ -20,7 +20,7 @@ __version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
from PIL.OleFileIO import *
|
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
|
||||||
|
|
||||||
|
|
||||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||||
|
@ -34,16 +34,18 @@ MODES = {
|
||||||
(0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
|
(0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
|
||||||
(0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"),
|
(0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"),
|
||||||
# standard RGB (NIFRGB)
|
# standard RGB (NIFRGB)
|
||||||
(0x00030000, 0x00030001, 0x00030002): ("RGB","RGB"),
|
(0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
|
||||||
(0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA","RGBA"),
|
(0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA", "RGBA"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:8] == MAGIC
|
return prefix[:8] == MAGIC
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for the FlashPix images.
|
# Image plugin for the FlashPix images.
|
||||||
|
|
||||||
|
@ -67,7 +69,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self._open_index(1)
|
self._open_index(1)
|
||||||
|
|
||||||
def _open_index(self, index = 1):
|
def _open_index(self, index=1):
|
||||||
#
|
#
|
||||||
# get the Image Contents Property Set
|
# get the Image Contents Property Set
|
||||||
|
|
||||||
|
@ -95,7 +97,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
id = self.maxid << 16
|
id = self.maxid << 16
|
||||||
|
|
||||||
s = prop[0x2000002|id]
|
s = prop[0x2000002 | id]
|
||||||
|
|
||||||
colors = []
|
colors = []
|
||||||
for i in range(i32(s, 4)):
|
for i in range(i32(s, 4)):
|
||||||
|
@ -107,7 +109,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
# load JPEG tables, if any
|
# load JPEG tables, if any
|
||||||
self.jpeg = {}
|
self.jpeg = {}
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
id = 0x3000001|(i << 16)
|
id = 0x3000001 | (i << 16)
|
||||||
if id in prop:
|
if id in prop:
|
||||||
self.jpeg[i] = prop[id]
|
self.jpeg[i] = prop[id]
|
||||||
|
|
||||||
|
@ -115,7 +117,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self._open_subimage(1, self.maxid)
|
self._open_subimage(1, self.maxid)
|
||||||
|
|
||||||
def _open_subimage(self, index = 1, subimage = 0):
|
def _open_subimage(self, index=1, subimage=0):
|
||||||
#
|
#
|
||||||
# setup tile descriptors for a given subimage
|
# setup tile descriptors for a given subimage
|
||||||
|
|
||||||
|
@ -128,15 +130,15 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
fp = self.ole.openstream(stream)
|
fp = self.ole.openstream(stream)
|
||||||
|
|
||||||
# skip prefix
|
# skip prefix
|
||||||
p = fp.read(28)
|
fp.read(28)
|
||||||
|
|
||||||
# header stream
|
# header stream
|
||||||
s = fp.read(36)
|
s = fp.read(36)
|
||||||
|
|
||||||
size = i32(s, 4), i32(s, 8)
|
size = i32(s, 4), i32(s, 8)
|
||||||
tilecount = i32(s, 12)
|
# tilecount = i32(s, 12)
|
||||||
tilesize = i32(s, 16), i32(s, 20)
|
tilesize = i32(s, 16), i32(s, 20)
|
||||||
channels = i32(s, 24)
|
# channels = i32(s, 24)
|
||||||
offset = i32(s, 28)
|
offset = i32(s, 28)
|
||||||
length = i32(s, 32)
|
length = i32(s, 32)
|
||||||
|
|
||||||
|
@ -159,14 +161,14 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
compression = i32(s, i+8)
|
compression = i32(s, i+8)
|
||||||
|
|
||||||
if compression == 0:
|
if compression == 0:
|
||||||
self.tile.append(("raw", (x,y,x+xtile,y+ytile),
|
self.tile.append(("raw", (x, y, x+xtile, y+ytile),
|
||||||
i32(s, i) + 28, (self.rawmode)))
|
i32(s, i) + 28, (self.rawmode)))
|
||||||
|
|
||||||
elif compression == 1:
|
elif compression == 1:
|
||||||
|
|
||||||
# FIXME: the fill decoder is not implemented
|
# FIXME: the fill decoder is not implemented
|
||||||
self.tile.append(("fill", (x,y,x+xtile,y+ytile),
|
self.tile.append(("fill", (x, y, x+xtile, y+ytile),
|
||||||
i32(s, i) + 28, (self.rawmode, s[12:16])))
|
i32(s, i) + 28, (self.rawmode, s[12:16])))
|
||||||
|
|
||||||
elif compression == 2:
|
elif compression == 2:
|
||||||
|
|
||||||
|
@ -182,14 +184,14 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
# this problem :
|
# this problem :
|
||||||
jpegmode, rawmode = "YCbCrK", "CMYK"
|
jpegmode, rawmode = "YCbCrK", "CMYK"
|
||||||
else:
|
else:
|
||||||
jpegmode = None # let the decoder decide
|
jpegmode = None # let the decoder decide
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# The image is stored as defined by rawmode
|
# The image is stored as defined by rawmode
|
||||||
jpegmode = rawmode
|
jpegmode = rawmode
|
||||||
|
|
||||||
self.tile.append(("jpeg", (x,y,x+xtile,y+ytile),
|
self.tile.append(("jpeg", (x, y, x+xtile, y+ytile),
|
||||||
i32(s, i) + 28, (rawmode, jpegmode)))
|
i32(s, i) + 28, (rawmode, jpegmode)))
|
||||||
|
|
||||||
# FIXME: jpeg tables are tile dependent; the prefix
|
# FIXME: jpeg tables are tile dependent; the prefix
|
||||||
# data must be placed in the tile descriptor itself!
|
# data must be placed in the tile descriptor itself!
|
||||||
|
@ -204,7 +206,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
if x >= xsize:
|
if x >= xsize:
|
||||||
x, y = 0, y + ytile
|
x, y = 0, y + ytile
|
||||||
if y >= ysize:
|
if y >= ysize:
|
||||||
break # isn't really required
|
break # isn't really required
|
||||||
|
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
self.fp = None
|
self.fp = None
|
||||||
|
@ -212,7 +214,8 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
def load(self):
|
def load(self):
|
||||||
|
|
||||||
if not self.fp:
|
if not self.fp:
|
||||||
self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
|
self.fp = self.ole.openstream(self.stream[:2] +
|
||||||
|
["Subimage 0000 Data"])
|
||||||
|
|
||||||
ImageFile.ImageFile.load(self)
|
ImageFile.ImageFile.load(self)
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,11 @@ from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
i32 = _binary.i32be
|
i32 = _binary.i32be
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
|
return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for the GIMP brush format.
|
# Image plugin for the GIMP brush format.
|
||||||
|
|
||||||
|
@ -37,8 +39,8 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
width = i32(self.fp.read(4))
|
width = i32(self.fp.read(4))
|
||||||
height = i32(self.fp.read(4))
|
height = i32(self.fp.read(4))
|
||||||
bytes = i32(self.fp.read(4))
|
color_depth = i32(self.fp.read(4))
|
||||||
if width <= 0 or height <= 0 or bytes != 1:
|
if width <= 0 or height <= 0 or color_depth != 1:
|
||||||
raise SyntaxError("not a GIMP brush")
|
raise SyntaxError("not a GIMP brush")
|
||||||
|
|
||||||
comment = self.fp.read(header_size - 20)[:-1]
|
comment = self.fp.read(header_size - 20)[:-1]
|
||||||
|
|
|
@ -36,6 +36,7 @@ except ImportError:
|
||||||
|
|
||||||
i16 = _binary.i16be
|
i16 = _binary.i16be
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for the GD uncompressed format. Note that this format
|
# Image plugin for the GD uncompressed format. Note that this format
|
||||||
# is not supported by the standard <b>Image.open</b> function. To use
|
# is not supported by the standard <b>Image.open</b> function. To use
|
||||||
|
@ -52,7 +53,7 @@ class GdImageFile(ImageFile.ImageFile):
|
||||||
# Header
|
# Header
|
||||||
s = self.fp.read(775)
|
s = self.fp.read(775)
|
||||||
|
|
||||||
self.mode = "L" # FIXME: "P"
|
self.mode = "L" # FIXME: "P"
|
||||||
self.size = i16(s[0:2]), i16(s[2:4])
|
self.size = i16(s[0:2]), i16(s[2:4])
|
||||||
|
|
||||||
# transparency index
|
# transparency index
|
||||||
|
@ -62,7 +63,8 @@ class GdImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.palette = ImagePalette.raw("RGB", s[7:])
|
self.palette = ImagePalette.raw("RGB", s[7:])
|
||||||
|
|
||||||
self.tile = [("raw", (0,0)+self.size, 775, ("L", 0, -1))]
|
self.tile = [("raw", (0, 0)+self.size, 775, ("L", 0, -1))]
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Load texture from a GD image file.
|
# Load texture from a GD image file.
|
||||||
|
@ -73,7 +75,7 @@ class GdImageFile(ImageFile.ImageFile):
|
||||||
# @return An image instance.
|
# @return An image instance.
|
||||||
# @exception IOError If the image could not be read.
|
# @exception IOError If the image could not be read.
|
||||||
|
|
||||||
def open(fp, mode = "r"):
|
def open(fp, mode="r"):
|
||||||
|
|
||||||
if mode != "r":
|
if mode != "r":
|
||||||
raise ValueError("bad mode")
|
raise ValueError("bad mode")
|
||||||
|
|
|
@ -24,13 +24,11 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
|
||||||
__version__ = "0.9"
|
__version__ = "0.9"
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Helpers
|
# Helpers
|
||||||
|
|
||||||
|
@ -46,6 +44,7 @@ o16 = _binary.o16le
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:6] in [b"GIF87a", b"GIF89a"]
|
return prefix[:6] in [b"GIF87a", b"GIF89a"]
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for GIF images. This plugin supports both GIF87 and
|
# Image plugin for GIF images. This plugin supports both GIF87 and
|
||||||
# GIF89 images.
|
# GIF89 images.
|
||||||
|
@ -79,24 +78,45 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
# get global palette
|
# get global palette
|
||||||
self.info["background"] = i8(s[11])
|
self.info["background"] = i8(s[11])
|
||||||
# check if palette contains colour indices
|
# check if palette contains colour indices
|
||||||
p = self.fp.read(3<<bits)
|
p = self.fp.read(3 << bits)
|
||||||
for i in range(0, len(p), 3):
|
for i in range(0, len(p), 3):
|
||||||
if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])):
|
if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])):
|
||||||
p = ImagePalette.raw("RGB", p)
|
p = ImagePalette.raw("RGB", p)
|
||||||
self.global_palette = self.palette = p
|
self.global_palette = self.palette = p
|
||||||
break
|
break
|
||||||
|
|
||||||
self.__fp = self.fp # FIXME: hack
|
self.__fp = self.fp # FIXME: hack
|
||||||
self.__rewind = self.fp.tell()
|
self.__rewind = self.fp.tell()
|
||||||
self.seek(0) # get ready to read first frame
|
self._n_frames = 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
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
if frame == self.__frame:
|
||||||
|
return
|
||||||
|
if frame < self.__frame:
|
||||||
|
self._seek(0)
|
||||||
|
for f in range(self.__frame + 1, frame + 1):
|
||||||
|
self._seek(f)
|
||||||
|
|
||||||
|
def _seek(self, frame):
|
||||||
|
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
# rewind
|
# rewind
|
||||||
self.__offset = 0
|
self.__offset = 0
|
||||||
self.dispose = None
|
self.dispose = None
|
||||||
self.dispose_extent = [0, 0, 0, 0] #x0, y0, x1, y1
|
self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1
|
||||||
self.__frame = -1
|
self.__frame = -1
|
||||||
self.__fp.seek(self.__rewind)
|
self.__fp.seek(self.__rewind)
|
||||||
self._prev_im = None
|
self._prev_im = None
|
||||||
|
@ -185,7 +205,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if flags & 128:
|
if flags & 128:
|
||||||
bits = (flags & 7) + 1
|
bits = (flags & 7) + 1
|
||||||
self.palette =\
|
self.palette =\
|
||||||
ImagePalette.raw("RGB", self.fp.read(3<<bits))
|
ImagePalette.raw("RGB", self.fp.read(3 << bits))
|
||||||
|
|
||||||
# image data
|
# image data
|
||||||
bits = i8(self.fp.read(1))
|
bits = i8(self.fp.read(1))
|
||||||
|
@ -219,7 +239,6 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if not self.tile:
|
if not self.tile:
|
||||||
# self.__fp = None
|
# self.__fp = None
|
||||||
raise EOFError("no more images in GIF file")
|
raise EOFError("no more images in GIF file")
|
||||||
|
@ -240,7 +259,8 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
# we do this by pasting the updated area onto the previous
|
# we do this by pasting the updated area onto the previous
|
||||||
# frame which we then use as the current image content
|
# frame which we then use as the current image content
|
||||||
updated = self.im.crop(self.dispose_extent)
|
updated = self.im.crop(self.dispose_extent)
|
||||||
self._prev_im.paste(updated, self.dispose_extent, updated.convert('RGBA'))
|
self._prev_im.paste(updated, self.dispose_extent,
|
||||||
|
updated.convert('RGBA'))
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
self._prev_im = self.im.copy()
|
self._prev_im = self.im.copy()
|
||||||
|
|
||||||
|
@ -258,6 +278,7 @@ RAWMODE = {
|
||||||
"P": "P",
|
"P": "P",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
|
|
||||||
if _imaging_gif:
|
if _imaging_gif:
|
||||||
|
@ -266,10 +287,10 @@ def _save(im, fp, filename):
|
||||||
_imaging_gif.save(im, fp, filename)
|
_imaging_gif.save(im, fp, filename)
|
||||||
return
|
return
|
||||||
except IOError:
|
except IOError:
|
||||||
pass # write uncompressed file
|
pass # write uncompressed file
|
||||||
|
|
||||||
if im.mode in RAWMODE:
|
if im.mode in RAWMODE:
|
||||||
imOut = im
|
im_out = im
|
||||||
else:
|
else:
|
||||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
||||||
# should automatically convert images on save...)
|
# should automatically convert images on save...)
|
||||||
|
@ -277,9 +298,9 @@ def _save(im, fp, filename):
|
||||||
palette_size = 256
|
palette_size = 256
|
||||||
if im.palette:
|
if im.palette:
|
||||||
palette_size = len(im.palette.getdata()[1]) // 3
|
palette_size = len(im.palette.getdata()[1]) // 3
|
||||||
imOut = im.convert("P", palette=1, colors=palette_size)
|
im_out = im.convert("P", palette=1, colors=palette_size)
|
||||||
else:
|
else:
|
||||||
imOut = im.convert("L")
|
im_out = im.convert("L")
|
||||||
|
|
||||||
# header
|
# header
|
||||||
try:
|
try:
|
||||||
|
@ -288,12 +309,33 @@ def _save(im, fp, filename):
|
||||||
palette = None
|
palette = None
|
||||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||||
|
|
||||||
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
|
header, used_palette_colors = getheader(im_out, palette, im.encoderinfo)
|
||||||
for s in header:
|
for s in header:
|
||||||
fp.write(s)
|
fp.write(s)
|
||||||
|
|
||||||
flags = 0
|
flags = 0
|
||||||
|
|
||||||
|
if get_interlace(im):
|
||||||
|
flags = flags | 64
|
||||||
|
|
||||||
|
# local image header
|
||||||
|
get_local_header(fp, im, (0, 0), flags)
|
||||||
|
|
||||||
|
im_out.encoderconfig = (8, get_interlace(im))
|
||||||
|
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||||
|
RAWMODE[im_out.mode])])
|
||||||
|
|
||||||
|
fp.write(b"\0") # end of image data
|
||||||
|
|
||||||
|
fp.write(b";") # end of file
|
||||||
|
|
||||||
|
try:
|
||||||
|
fp.flush()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_interlace(im):
|
||||||
try:
|
try:
|
||||||
interlace = im.encoderinfo["interlace"]
|
interlace = im.encoderinfo["interlace"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -303,9 +345,11 @@ def _save(im, fp, filename):
|
||||||
if min(im.size) < 16:
|
if min(im.size) < 16:
|
||||||
interlace = 0
|
interlace = 0
|
||||||
|
|
||||||
if interlace:
|
return interlace
|
||||||
flags = flags | 64
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_local_header(fp, im, offset, flags):
|
||||||
|
transparent_color_exists = False
|
||||||
try:
|
try:
|
||||||
transparency = im.encoderinfo["transparency"]
|
transparency = im.encoderinfo["transparency"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -313,45 +357,55 @@ def _save(im, fp, filename):
|
||||||
else:
|
else:
|
||||||
transparency = int(transparency)
|
transparency = int(transparency)
|
||||||
# optimize the block away if transparent color is not used
|
# optimize the block away if transparent color is not used
|
||||||
transparentColorExists = True
|
transparent_color_exists = 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
|
|
||||||
|
|
||||||
# transparency extension block
|
if _get_optimize(im, im.encoderinfo):
|
||||||
if transparentColorExists:
|
used_palette_colors = _get_used_palette_colors(im)
|
||||||
fp.write(b"!" +
|
|
||||||
o8(249) + # extension intro
|
|
||||||
o8(4) + # length
|
|
||||||
o8(1) + # transparency info present
|
|
||||||
o16(0) + # duration
|
|
||||||
o8(transparency) # transparency index
|
|
||||||
+ o8(0))
|
|
||||||
|
|
||||||
# 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"," +
|
fp.write(b"," +
|
||||||
o16(0) + o16(0) + # bounding box
|
o16(offset[0]) + # offset
|
||||||
o16(im.size[0]) + # size
|
o16(offset[1]) +
|
||||||
|
o16(im.size[0]) + # size
|
||||||
o16(im.size[1]) +
|
o16(im.size[1]) +
|
||||||
o8(flags) + # flags
|
o8(flags) + # flags
|
||||||
o8(8)) # bits
|
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
|
|
||||||
|
|
||||||
|
|
||||||
def _save_netpbm(im, fp, filename):
|
def _save_netpbm(im, fp, filename):
|
||||||
|
@ -380,7 +434,8 @@ def _save_netpbm(im, fp, filename):
|
||||||
stderr = tempfile.TemporaryFile()
|
stderr = tempfile.TemporaryFile()
|
||||||
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
|
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
|
||||||
stderr = tempfile.TemporaryFile()
|
stderr = tempfile.TemporaryFile()
|
||||||
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=stderr)
|
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
|
||||||
|
stderr=stderr)
|
||||||
|
|
||||||
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
||||||
quant_proc.stdout.close()
|
quant_proc.stdout.close()
|
||||||
|
@ -402,11 +457,26 @@ def _save_netpbm(im, fp, filename):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# GIF utilities
|
# 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):
|
def getheader(im, palette=None, info=None):
|
||||||
"""Return a list of strings representing a GIF header"""
|
"""Return a list of strings representing a GIF header"""
|
||||||
|
|
||||||
optimize = info and info.get("optimize", 0)
|
|
||||||
|
|
||||||
# Header Block
|
# Header Block
|
||||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||||
header = [
|
header = [
|
||||||
|
@ -417,102 +487,94 @@ def getheader(im, palette=None, info=None):
|
||||||
|
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
if palette and isinstance(palette, bytes):
|
if palette and isinstance(palette, bytes):
|
||||||
sourcePalette = palette[:768]
|
source_palette = palette[:768]
|
||||||
else:
|
else:
|
||||||
sourcePalette = im.im.getpalette("RGB")[:768]
|
source_palette = im.im.getpalette("RGB")[:768]
|
||||||
else: # L-mode
|
else: # L-mode
|
||||||
if palette and isinstance(palette, bytes):
|
if palette and isinstance(palette, bytes):
|
||||||
sourcePalette = palette[:768]
|
source_palette = palette[:768]
|
||||||
else:
|
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 optimize:
|
if _get_optimize(im, info):
|
||||||
usedPaletteColors = []
|
used_palette_colors = _get_used_palette_colors(im)
|
||||||
|
|
||||||
# check which colors are used
|
|
||||||
i = 0
|
|
||||||
for count in im.histogram():
|
|
||||||
if count:
|
|
||||||
usedPaletteColors.append(i)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# create the new palette if not every color is used
|
# create the new palette if not every color is used
|
||||||
if len(usedPaletteColors) < 256:
|
if len(used_palette_colors) < 256:
|
||||||
paletteBytes = b""
|
palette_bytes = b""
|
||||||
newPositions = {}
|
new_positions = {}
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
# pick only the used colors from the palette
|
# pick only the used colors from the palette
|
||||||
for oldPosition in usedPaletteColors:
|
for oldPosition in used_palette_colors:
|
||||||
paletteBytes += sourcePalette[oldPosition*3:oldPosition*3+3]
|
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
|
||||||
newPositions[oldPosition] = i
|
new_positions[oldPosition] = i
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
# replace the palette color id of all pixel with the new id
|
# replace the palette color id of all pixel with the new id
|
||||||
imageBytes = bytearray(im.tobytes())
|
image_bytes = bytearray(im.tobytes())
|
||||||
for i in range(len(imageBytes)):
|
for i in range(len(image_bytes)):
|
||||||
imageBytes[i] = newPositions[imageBytes[i]]
|
image_bytes[i] = new_positions[image_bytes[i]]
|
||||||
im.frombytes(bytes(imageBytes))
|
im.frombytes(bytes(image_bytes))
|
||||||
newPaletteBytes = paletteBytes + (768 - len(paletteBytes)) * b'\x00'
|
new_palette_bytes = (palette_bytes +
|
||||||
im.putpalette(newPaletteBytes)
|
(768 - len(palette_bytes)) * b'\x00')
|
||||||
im.palette = ImagePalette.ImagePalette("RGB", palette = paletteBytes, size = len(paletteBytes))
|
im.putpalette(new_palette_bytes)
|
||||||
|
im.palette = ImagePalette.ImagePalette("RGB",
|
||||||
|
palette=palette_bytes,
|
||||||
|
size=len(palette_bytes))
|
||||||
|
|
||||||
if not paletteBytes:
|
if not palette_bytes:
|
||||||
paletteBytes = sourcePalette
|
palette_bytes = source_palette
|
||||||
|
|
||||||
# Logical Screen Descriptor
|
# Logical Screen Descriptor
|
||||||
# calculate the palette size for the header
|
# calculate the palette size for the header
|
||||||
import math
|
import math
|
||||||
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1
|
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
|
||||||
if colorTableSize < 0: colorTableSize = 0
|
if color_table_size < 0:
|
||||||
|
color_table_size = 0
|
||||||
# size of global color table + global color table flag
|
# size of global color table + global color table flag
|
||||||
header.append(o8(colorTableSize + 128))
|
header.append(o8(color_table_size + 128))
|
||||||
# background + reserved/aspect
|
# background + reserved/aspect
|
||||||
header.append(o8(0) + o8(0))
|
header.append(o8(0) + o8(0))
|
||||||
# end of Logical Screen Descriptor
|
# end of Logical Screen Descriptor
|
||||||
|
|
||||||
# add the missing amount of bytes
|
# add the missing amount of bytes
|
||||||
# the palette has to be 2<<n in size
|
# the palette has to be 2<<n in size
|
||||||
actualTargetSizeDiff = (2<<colorTableSize) - len(paletteBytes)//3
|
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
|
||||||
if actualTargetSizeDiff > 0:
|
if actual_target_size_diff > 0:
|
||||||
paletteBytes += o8(0) * 3 * actualTargetSizeDiff
|
palette_bytes += o8(0) * 3 * actual_target_size_diff
|
||||||
|
|
||||||
# Header + Logical Screen Descriptor + Global Color Table
|
# Header + Logical Screen Descriptor + Global Color Table
|
||||||
header.append(paletteBytes)
|
header.append(palette_bytes)
|
||||||
return header, usedPaletteColors
|
return header, used_palette_colors
|
||||||
|
|
||||||
|
|
||||||
def getdata(im, offset = (0, 0), **params):
|
def getdata(im, offset=(0, 0), **params):
|
||||||
"""Return a list of strings representing this image.
|
"""Return a list of strings representing this image.
|
||||||
The first string is a local image header, the rest contains
|
The first string is a local image header, the rest contains
|
||||||
encoded image data."""
|
encoded image data."""
|
||||||
|
|
||||||
class collector:
|
class Collector(object):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
self.data.append(data)
|
self.data.append(data)
|
||||||
|
|
||||||
im.load() # make sure raster data is available
|
im.load() # make sure raster data is available
|
||||||
|
|
||||||
fp = collector()
|
fp = Collector()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
im.encoderinfo = params
|
im.encoderinfo = params
|
||||||
|
|
||||||
# local image header
|
# local image header
|
||||||
fp.write(b"," +
|
get_local_header(fp, im, offset, 0)
|
||||||
o16(offset[0]) + # offset
|
|
||||||
o16(offset[1]) +
|
|
||||||
o16(im.size[0]) + # size
|
|
||||||
o16(im.size[1]) +
|
|
||||||
o8(0) + # flags
|
|
||||||
o8(8)) # bits
|
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("gif", (0,0)+im.size, 0, RAWMODE[im.mode])])
|
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
||||||
|
|
||||||
fp.write(b"\0") # end of image data
|
fp.write(b"\0") # end of image data
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
del im.encoderinfo
|
del im.encoderinfo
|
||||||
|
|
|
@ -24,6 +24,7 @@ from PIL._binary import o8
|
||||||
|
|
||||||
EPSILON = 1e-10
|
EPSILON = 1e-10
|
||||||
|
|
||||||
|
|
||||||
def linear(middle, pos):
|
def linear(middle, pos):
|
||||||
if pos <= middle:
|
if pos <= middle:
|
||||||
if middle < EPSILON:
|
if middle < EPSILON:
|
||||||
|
@ -38,25 +39,30 @@ def linear(middle, pos):
|
||||||
else:
|
else:
|
||||||
return 0.5 + 0.5 * pos / middle
|
return 0.5 + 0.5 * pos / middle
|
||||||
|
|
||||||
|
|
||||||
def curved(middle, pos):
|
def curved(middle, pos):
|
||||||
return pos ** (log(0.5) / log(max(middle, EPSILON)))
|
return pos ** (log(0.5) / log(max(middle, EPSILON)))
|
||||||
|
|
||||||
|
|
||||||
def sine(middle, pos):
|
def sine(middle, pos):
|
||||||
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
|
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
|
||||||
|
|
||||||
|
|
||||||
def sphere_increasing(middle, pos):
|
def sphere_increasing(middle, pos):
|
||||||
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
|
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
|
||||||
|
|
||||||
|
|
||||||
def sphere_decreasing(middle, pos):
|
def sphere_decreasing(middle, pos):
|
||||||
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
|
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
|
||||||
|
|
||||||
SEGMENTS = [ linear, curved, sine, sphere_increasing, sphere_decreasing ]
|
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
|
||||||
|
|
||||||
class GradientFile:
|
|
||||||
|
class GradientFile(object):
|
||||||
|
|
||||||
gradient = None
|
gradient = None
|
||||||
|
|
||||||
def getpalette(self, entries = 256):
|
def getpalette(self, entries=256):
|
||||||
|
|
||||||
palette = []
|
palette = []
|
||||||
|
|
||||||
|
@ -89,6 +95,7 @@ class GradientFile:
|
||||||
|
|
||||||
return b"".join(palette), "RGBA"
|
return b"".join(palette), "RGBA"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# File handler for GIMP's gradient format.
|
# File handler for GIMP's gradient format.
|
||||||
|
|
||||||
|
@ -99,7 +106,13 @@ class GimpGradientFile(GradientFile):
|
||||||
if fp.readline()[:13] != b"GIMP Gradient":
|
if fp.readline()[:13] != b"GIMP Gradient":
|
||||||
raise SyntaxError("not a GIMP gradient file")
|
raise SyntaxError("not a GIMP gradient file")
|
||||||
|
|
||||||
count = int(fp.readline())
|
line = fp.readline()
|
||||||
|
|
||||||
|
# GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
|
||||||
|
if line.startswith(b"Name: "):
|
||||||
|
line = fp.readline().strip()
|
||||||
|
|
||||||
|
count = int(line)
|
||||||
|
|
||||||
gradient = []
|
gradient = []
|
||||||
|
|
||||||
|
@ -108,13 +121,13 @@ class GimpGradientFile(GradientFile):
|
||||||
s = fp.readline().split()
|
s = fp.readline().split()
|
||||||
w = [float(x) for x in s[:11]]
|
w = [float(x) for x in s[:11]]
|
||||||
|
|
||||||
x0, x1 = w[0], w[2]
|
x0, x1 = w[0], w[2]
|
||||||
xm = w[1]
|
xm = w[1]
|
||||||
rgb0 = w[3:7]
|
rgb0 = w[3:7]
|
||||||
rgb1 = w[7:11]
|
rgb1 = w[7:11]
|
||||||
|
|
||||||
segment = SEGMENTS[int(s[11])]
|
segment = SEGMENTS[int(s[11])]
|
||||||
cspace = int(s[12])
|
cspace = int(s[12])
|
||||||
|
|
||||||
if cspace != 0:
|
if cspace != 0:
|
||||||
raise IOError("cannot handle HSV colour space")
|
raise IOError("cannot handle HSV colour space")
|
||||||
|
|
|
@ -17,10 +17,11 @@
|
||||||
import re
|
import re
|
||||||
from PIL._binary import o8
|
from PIL._binary import o8
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# File handler for GIMP's palette format.
|
# File handler for GIMP's palette format.
|
||||||
|
|
||||||
class GimpPaletteFile:
|
class GimpPaletteFile(object):
|
||||||
|
|
||||||
rawmode = "RGB"
|
rawmode = "RGB"
|
||||||
|
|
||||||
|
@ -56,7 +57,6 @@ class GimpPaletteFile:
|
||||||
|
|
||||||
self.palette = b"".join(self.palette)
|
self.palette = b"".join(self.palette)
|
||||||
|
|
||||||
|
|
||||||
def getpalette(self):
|
def getpalette(self):
|
||||||
|
|
||||||
return self.palette, self.rawmode
|
return self.palette, self.rawmode
|
||||||
|
|
|
@ -13,6 +13,7 @@ from PIL import Image, ImageFile
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Install application-specific GRIB image handler.
|
# Install application-specific GRIB image handler.
|
||||||
#
|
#
|
||||||
|
@ -22,12 +23,14 @@ def register_handler(handler):
|
||||||
global _handler
|
global _handler
|
||||||
_handler = handler
|
_handler = handler
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Image adapter
|
# Image adapter
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01'
|
return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01'
|
||||||
|
|
||||||
|
|
||||||
class GribStubImageFile(ImageFile.StubImageFile):
|
class GribStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
||||||
format = "GRIB"
|
format = "GRIB"
|
||||||
|
@ -53,6 +56,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
|
||||||
def _load(self):
|
def _load(self):
|
||||||
return _handler
|
return _handler
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
if _handler is None or not hasattr("_handler", "save"):
|
if _handler is None or not hasattr("_handler", "save"):
|
||||||
raise IOError("GRIB save handler not installed")
|
raise IOError("GRIB save handler not installed")
|
||||||
|
|
|
@ -13,6 +13,7 @@ from PIL import Image, ImageFile
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Install application-specific HDF5 image handler.
|
# Install application-specific HDF5 image handler.
|
||||||
#
|
#
|
||||||
|
@ -22,12 +23,14 @@ def register_handler(handler):
|
||||||
global _handler
|
global _handler
|
||||||
_handler = handler
|
_handler = handler
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Image adapter
|
# Image adapter
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:8] == b"\x89HDF\r\n\x1a\n"
|
return prefix[:8] == b"\x89HDF\r\n\x1a\n"
|
||||||
|
|
||||||
|
|
||||||
class HDF5StubImageFile(ImageFile.StubImageFile):
|
class HDF5StubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
||||||
format = "HDF5"
|
format = "HDF5"
|
||||||
|
|
|
@ -16,7 +16,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageFile, PngImagePlugin, _binary
|
from PIL import Image, ImageFile, PngImagePlugin, _binary
|
||||||
import struct, io
|
import io
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
|
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
|
||||||
if enable_jpeg2k:
|
if enable_jpeg2k:
|
||||||
|
@ -26,9 +31,11 @@ i8 = _binary.i8
|
||||||
|
|
||||||
HEADERSIZE = 8
|
HEADERSIZE = 8
|
||||||
|
|
||||||
|
|
||||||
def nextheader(fobj):
|
def nextheader(fobj):
|
||||||
return struct.unpack('>4sI', fobj.read(HEADERSIZE))
|
return struct.unpack('>4sI', fobj.read(HEADERSIZE))
|
||||||
|
|
||||||
|
|
||||||
def read_32t(fobj, start_length, size):
|
def read_32t(fobj, start_length, size):
|
||||||
# The 128x128 icon seems to have an extra header for some reason.
|
# The 128x128 icon seems to have an extra header for some reason.
|
||||||
(start, length) = start_length
|
(start, length) = start_length
|
||||||
|
@ -38,6 +45,7 @@ def read_32t(fobj, start_length, size):
|
||||||
raise SyntaxError('Unknown signature, expecting 0x00000000')
|
raise SyntaxError('Unknown signature, expecting 0x00000000')
|
||||||
return read_32(fobj, (start + 4, length - 4), size)
|
return read_32(fobj, (start + 4, length - 4), size)
|
||||||
|
|
||||||
|
|
||||||
def read_32(fobj, start_length, size):
|
def read_32(fobj, start_length, size):
|
||||||
"""
|
"""
|
||||||
Read a 32bit RGB icon resource. Seems to be either uncompressed or
|
Read a 32bit RGB icon resource. Seems to be either uncompressed or
|
||||||
|
@ -83,9 +91,10 @@ def read_32(fobj, start_length, size):
|
||||||
im.im.putband(band.im, band_ix)
|
im.im.putband(band.im, band_ix)
|
||||||
return {"RGB": im}
|
return {"RGB": im}
|
||||||
|
|
||||||
|
|
||||||
def read_mk(fobj, start_length, size):
|
def read_mk(fobj, start_length, size):
|
||||||
# Alpha masks seem to be uncompressed
|
# Alpha masks seem to be uncompressed
|
||||||
(start, length) = start_length
|
start = start_length[0]
|
||||||
fobj.seek(start)
|
fobj.seek(start)
|
||||||
pixel_size = (size[0] * size[2], size[1] * size[2])
|
pixel_size = (size[0] * size[2], size[1] * size[2])
|
||||||
sizesq = pixel_size[0] * pixel_size[1]
|
sizesq = pixel_size[0] * pixel_size[1]
|
||||||
|
@ -94,6 +103,7 @@ def read_mk(fobj, start_length, size):
|
||||||
)
|
)
|
||||||
return {"A": band}
|
return {"A": band}
|
||||||
|
|
||||||
|
|
||||||
def read_png_or_jpeg2000(fobj, start_length, size):
|
def read_png_or_jpeg2000(fobj, start_length, size):
|
||||||
(start, length) = start_length
|
(start, length) = start_length
|
||||||
fobj.seek(start)
|
fobj.seek(start)
|
||||||
|
@ -103,10 +113,11 @@ def read_png_or_jpeg2000(fobj, start_length, size):
|
||||||
im = PngImagePlugin.PngImageFile(fobj)
|
im = PngImagePlugin.PngImageFile(fobj)
|
||||||
return {"RGBA": im}
|
return {"RGBA": im}
|
||||||
elif sig[:4] == b'\xff\x4f\xff\x51' \
|
elif sig[:4] == b'\xff\x4f\xff\x51' \
|
||||||
or sig[:4] == b'\x0d\x0a\x87\x0a' \
|
or sig[:4] == b'\x0d\x0a\x87\x0a' \
|
||||||
or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
|
or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
|
||||||
if not enable_jpeg2k:
|
if not enable_jpeg2k:
|
||||||
raise ValueError('Unsupported icon subimage format (rebuild PIL with JPEG 2000 support to fix this)')
|
raise ValueError('Unsupported icon subimage format (rebuild PIL '
|
||||||
|
'with JPEG 2000 support to fix this)')
|
||||||
# j2k, jpc or j2c
|
# j2k, jpc or j2c
|
||||||
fobj.seek(start)
|
fobj.seek(start)
|
||||||
jp2kstream = fobj.read(length)
|
jp2kstream = fobj.read(length)
|
||||||
|
@ -118,7 +129,8 @@ def read_png_or_jpeg2000(fobj, start_length, size):
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unsupported icon subimage format')
|
raise ValueError('Unsupported icon subimage format')
|
||||||
|
|
||||||
class IcnsFile:
|
|
||||||
|
class IcnsFile(object):
|
||||||
|
|
||||||
SIZES = {
|
SIZES = {
|
||||||
(512, 512, 2): [
|
(512, 512, 2): [
|
||||||
|
@ -233,12 +245,13 @@ class IcnsFile:
|
||||||
pass
|
pass
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Mac OS icons.
|
# Image plugin for Mac OS icons.
|
||||||
|
|
||||||
class IcnsImageFile(ImageFile.ImageFile):
|
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
|
Chooses the best resolution, but will possibly load
|
||||||
a different size image if you mutate the size attribute
|
a different size image if you mutate the size attribute
|
||||||
before calling 'load'.
|
before calling 'load'.
|
||||||
|
@ -284,11 +297,64 @@ class IcnsImageFile(ImageFile.ImageFile):
|
||||||
self.tile = ()
|
self.tile = ()
|
||||||
self.load_end()
|
self.load_end()
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
fp.flush()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 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("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
|
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
|
||||||
Image.register_extension("ICNS", '.icns')
|
Image.register_extension("ICNS", '.icns')
|
||||||
|
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
Image.register_save("ICNS", _save)
|
||||||
|
|
||||||
|
Image.register_mime("ICNS", "image/icns")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import os, sys
|
|
||||||
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
|
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
|
||||||
for size in imf.info['sizes']:
|
for size in imf.info['sizes']:
|
||||||
imf.size = size
|
imf.size = size
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis <casadebender@gmail.com>.
|
# 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/p/casadebender/wiki/Win32IconImagePlugin
|
||||||
#
|
#
|
||||||
# Icon format references:
|
# Icon format references:
|
||||||
|
@ -23,6 +24,9 @@
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
import struct
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
||||||
from math import log, ceil
|
from math import log, ceil
|
||||||
|
|
||||||
|
@ -35,11 +39,47 @@ i32 = _binary.i32le
|
||||||
|
|
||||||
_MAGIC = b"\0\0\1\0"
|
_MAGIC = b"\0\0\1\0"
|
||||||
|
|
||||||
|
|
||||||
|
def _save(im, fp, filename):
|
||||||
|
fp.write(_MAGIC) # (2+2)
|
||||||
|
sizes = im.encoderinfo.get("sizes",
|
||||||
|
[(16, 16), (24, 24), (32, 32), (48, 48),
|
||||||
|
(64, 64), (128, 128), (255, 255)])
|
||||||
|
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)
|
||||||
|
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
|
||||||
|
offset = fp.tell() + len(sizes)*16
|
||||||
|
for size in sizes:
|
||||||
|
width, height = size
|
||||||
|
fp.write(struct.pack("B", width)) # bWidth(1)
|
||||||
|
fp.write(struct.pack("B", height)) # bHeight(1)
|
||||||
|
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)
|
||||||
|
|
||||||
|
image_io = BytesIO()
|
||||||
|
tmp = im.copy()
|
||||||
|
tmp.thumbnail(size, Image.LANCZOS)
|
||||||
|
tmp.save(image_io, "png")
|
||||||
|
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)
|
||||||
|
current = fp.tell()
|
||||||
|
fp.seek(offset)
|
||||||
|
fp.write(image_bytes)
|
||||||
|
offset = offset + bytes_len
|
||||||
|
fp.seek(current)
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] == _MAGIC
|
return prefix[:4] == _MAGIC
|
||||||
|
|
||||||
|
|
||||||
class IcoFile:
|
class IcoFile(object):
|
||||||
def __init__(self, buf):
|
def __init__(self, buf):
|
||||||
"""
|
"""
|
||||||
Parse image from file-like object containing ico file data
|
Parse image from file-like object containing ico file data
|
||||||
|
@ -63,7 +103,7 @@ class IcoFile:
|
||||||
icon_header = {
|
icon_header = {
|
||||||
'width': i8(s[0]),
|
'width': i8(s[0]),
|
||||||
'height': i8(s[1]),
|
'height': i8(s[1]),
|
||||||
'nb_color': i8(s[2]), # Number of colors in image (0 if >=8bpp)
|
'nb_color': i8(s[2]), # No. of colors in image (0 if >=8bpp)
|
||||||
'reserved': i8(s[3]),
|
'reserved': i8(s[3]),
|
||||||
'planes': i16(s[4:]),
|
'planes': i16(s[4:]),
|
||||||
'bpp': i16(s[6:]),
|
'bpp': i16(s[6:]),
|
||||||
|
@ -78,10 +118,14 @@ class IcoFile:
|
||||||
|
|
||||||
# See Wikipedia notes about color depth.
|
# See Wikipedia notes about color depth.
|
||||||
# We need this just to differ images with equal sizes
|
# We need this just to differ images with equal sizes
|
||||||
icon_header['color_depth'] = (icon_header['bpp'] or (icon_header['nb_color'] != 0 and ceil(log(icon_header['nb_color'],2))) or 256)
|
icon_header['color_depth'] = (icon_header['bpp'] or
|
||||||
|
(icon_header['nb_color'] != 0 and
|
||||||
|
ceil(log(icon_header['nb_color'],
|
||||||
|
2))) or 256)
|
||||||
|
|
||||||
icon_header['dim'] = (icon_header['width'], icon_header['height'])
|
icon_header['dim'] = (icon_header['width'], icon_header['height'])
|
||||||
icon_header['square'] = icon_header['width'] * icon_header['height']
|
icon_header['square'] = (icon_header['width'] *
|
||||||
|
icon_header['height'])
|
||||||
|
|
||||||
self.entry.append(icon_header)
|
self.entry.append(icon_header)
|
||||||
|
|
||||||
|
@ -102,7 +146,7 @@ class IcoFile:
|
||||||
Get an image from the icon
|
Get an image from the icon
|
||||||
"""
|
"""
|
||||||
for (i, h) in enumerate(self.entry):
|
for (i, h) in enumerate(self.entry):
|
||||||
if size == h['dim'] and (bpp == False or bpp == h['color_depth']):
|
if size == h['dim'] and (bpp is False or bpp == h['color_depth']):
|
||||||
return self.frame(i)
|
return self.frame(i)
|
||||||
return self.frame(0)
|
return self.frame(0)
|
||||||
|
|
||||||
|
@ -127,7 +171,7 @@ class IcoFile:
|
||||||
# change tile dimension to only encompass XOR image
|
# change tile dimension to only encompass XOR image
|
||||||
im.size = (im.size[0], int(im.size[1] / 2))
|
im.size = (im.size[0], int(im.size[1] / 2))
|
||||||
d, e, o, a = im.tile[0]
|
d, e, o, a = im.tile[0]
|
||||||
im.tile[0] = d, (0,0) + im.size, o, a
|
im.tile[0] = d, (0, 0) + im.size, o, a
|
||||||
|
|
||||||
# figure out where AND mask image starts
|
# figure out where AND mask image starts
|
||||||
mode = a[0]
|
mode = a[0]
|
||||||
|
@ -139,8 +183,9 @@ class IcoFile:
|
||||||
|
|
||||||
if 32 == bpp:
|
if 32 == bpp:
|
||||||
# 32-bit color depth icon image allows semitransparent areas
|
# 32-bit color depth icon image allows semitransparent areas
|
||||||
# PIL's DIB format ignores transparency bits, recover them
|
# PIL's DIB format ignores transparency bits, recover them.
|
||||||
# The DIB is packed in BGRX byte order where X is the alpha channel
|
# The DIB is packed in BGRX byte order where X is the alpha
|
||||||
|
# channel.
|
||||||
|
|
||||||
# Back up to start of bmp data
|
# Back up to start of bmp data
|
||||||
self.buf.seek(o)
|
self.buf.seek(o)
|
||||||
|
@ -162,9 +207,11 @@ class IcoFile:
|
||||||
# bitmap row data is aligned to word boundaries
|
# bitmap row data is aligned to word boundaries
|
||||||
w += 32 - (im.size[0] % 32)
|
w += 32 - (im.size[0] % 32)
|
||||||
|
|
||||||
# the total mask data is padded row size * height / bits per char
|
# the total mask data is
|
||||||
|
# padded row size * height / bits per char
|
||||||
|
|
||||||
and_mask_offset = o + int(im.size[0] * im.size[1] * (bpp / 8.0))
|
and_mask_offset = o + int(im.size[0] * im.size[1] *
|
||||||
|
(bpp / 8.0))
|
||||||
total_bytes = int((w * im.size[1]) / 8)
|
total_bytes = int((w * im.size[1]) / 8)
|
||||||
|
|
||||||
self.buf.seek(and_mask_offset)
|
self.buf.seek(and_mask_offset)
|
||||||
|
@ -187,6 +234,7 @@ class IcoFile:
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Windows Icon files.
|
# Image plugin for Windows Icon files.
|
||||||
|
|
||||||
|
@ -194,15 +242,16 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
"""
|
"""
|
||||||
PIL read-only image support for Microsoft Windows .ico files.
|
PIL read-only image support for Microsoft Windows .ico files.
|
||||||
|
|
||||||
By default the largest resolution image in the file will be loaded. This can
|
By default the largest resolution image in the file will be loaded. This
|
||||||
be changed by altering the 'size' attribute before calling 'load'.
|
can be changed by altering the 'size' attribute before calling 'load'.
|
||||||
|
|
||||||
The info dictionary has a key 'sizes' that is a list of the sizes available
|
The info dictionary has a key 'sizes' that is a list of the sizes available
|
||||||
in the icon file.
|
in the icon file.
|
||||||
|
|
||||||
Handles classic, XP and Vista icon formats.
|
Handles classic, XP and Vista icon formats.
|
||||||
|
|
||||||
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis <casadebender@gmail.com>.
|
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/p/casadebender/wiki/Win32IconImagePlugin
|
||||||
"""
|
"""
|
||||||
format = "ICO"
|
format = "ICO"
|
||||||
|
@ -222,12 +271,13 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
self.mode = im.mode
|
self.mode = im.mode
|
||||||
self.size = im.size
|
self.size = im.size
|
||||||
|
|
||||||
|
|
||||||
def load_seek(self):
|
def load_seek(self):
|
||||||
# Flage the ImageFile.Parser so that it just does all the decode at the end.
|
# Flag the ImageFile.Parser so that it
|
||||||
|
# just does all the decode at the end.
|
||||||
pass
|
pass
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
Image.register_open("ICO", IcoImageFile, _accept)
|
Image.register_open("ICO", IcoImageFile, _accept)
|
||||||
|
Image.register_save("ICO", _save)
|
||||||
Image.register_extension("ICO", ".ico")
|
Image.register_extension("ICO", ".ico")
|
||||||
|
|
|
@ -30,7 +30,7 @@ __version__ = "0.7"
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from PIL import Image, ImageFile, ImagePalette
|
from PIL import Image, ImageFile, ImagePalette
|
||||||
from PIL._binary import i8, o8
|
from PIL._binary import i8
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -46,8 +46,8 @@ SCALE = "Scale (x,y)"
|
||||||
SIZE = "Image size (x*y)"
|
SIZE = "Image size (x*y)"
|
||||||
MODE = "Image type"
|
MODE = "Image type"
|
||||||
|
|
||||||
TAGS = { COMMENT:0, DATE:0, EQUIPMENT:0, FRAMES:0, LUT:0, NAME:0,
|
TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0,
|
||||||
SCALE:0, SIZE:0, MODE:0 }
|
SCALE: 0, SIZE: 0, MODE: 0}
|
||||||
|
|
||||||
OPEN = {
|
OPEN = {
|
||||||
# ifunc93/p3cfunc formats
|
# ifunc93/p3cfunc formats
|
||||||
|
@ -94,12 +94,14 @@ for i in range(2, 33):
|
||||||
|
|
||||||
split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
|
split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
|
||||||
|
|
||||||
|
|
||||||
def number(s):
|
def number(s):
|
||||||
try:
|
try:
|
||||||
return int(s)
|
return int(s)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return float(s)
|
return float(s)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for the IFUNC IM file format.
|
# Image plugin for the IFUNC IM file format.
|
||||||
|
|
||||||
|
@ -113,7 +115,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
# Quick rejection: if there's not an LF among the first
|
# Quick rejection: if there's not an LF among the first
|
||||||
# 100 bytes, this is (probably) not a text header.
|
# 100 bytes, this is (probably) not a text header.
|
||||||
|
|
||||||
if not b"\n" in self.fp.read(100):
|
if b"\n" not in self.fp.read(100):
|
||||||
raise SyntaxError("not an IM file")
|
raise SyntaxError("not an IM file")
|
||||||
self.fp.seek(0)
|
self.fp.seek(0)
|
||||||
|
|
||||||
|
@ -155,10 +157,10 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
if m:
|
if m:
|
||||||
|
|
||||||
k, v = m.group(1,2)
|
k, v = m.group(1, 2)
|
||||||
|
|
||||||
# Don't know if this is the correct encoding, but a decent guess
|
# Don't know if this is the correct encoding,
|
||||||
# (I guess)
|
# but a decent guess (I guess)
|
||||||
k = k.decode('latin-1', 'replace')
|
k = k.decode('latin-1', 'replace')
|
||||||
v = v.decode('latin-1', 'replace')
|
v = v.decode('latin-1', 'replace')
|
||||||
|
|
||||||
|
@ -186,7 +188,8 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
raise SyntaxError("Syntax error in IM header: " + s.decode('ascii', 'replace'))
|
raise SyntaxError("Syntax error in IM header: " +
|
||||||
|
s.decode('ascii', 'replace'))
|
||||||
|
|
||||||
if not n:
|
if not n:
|
||||||
raise SyntaxError("Not an IM file")
|
raise SyntaxError("Not an IM file")
|
||||||
|
@ -204,8 +207,8 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
if LUT in self.info:
|
if LUT in self.info:
|
||||||
# convert lookup table to palette or lut attribute
|
# convert lookup table to palette or lut attribute
|
||||||
palette = self.fp.read(768)
|
palette = self.fp.read(768)
|
||||||
greyscale = 1 # greyscale palette
|
greyscale = 1 # greyscale palette
|
||||||
linear = 1 # linear greyscale palette
|
linear = 1 # linear greyscale palette
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
if palette[i] == palette[i+256] == palette[i+512]:
|
if palette[i] == palette[i+256] == palette[i+512]:
|
||||||
if i8(palette[i]) != i:
|
if i8(palette[i]) != i:
|
||||||
|
@ -230,7 +233,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.__offset = offs = self.fp.tell()
|
self.__offset = offs = self.fp.tell()
|
||||||
|
|
||||||
self.__fp = self.fp # FIXME: hack
|
self.__fp = self.fp # FIXME: hack
|
||||||
|
|
||||||
if self.rawmode[:2] == "F;":
|
if self.rawmode[:2] == "F;":
|
||||||
|
|
||||||
|
@ -239,7 +242,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
# use bit decoder (if necessary)
|
# use bit decoder (if necessary)
|
||||||
bits = int(self.rawmode[2:])
|
bits = int(self.rawmode[2:])
|
||||||
if bits not in [8, 16, 32]:
|
if bits not in [8, 16, 32]:
|
||||||
self.tile = [("bit", (0,0)+self.size, offs,
|
self.tile = [("bit", (0, 0)+self.size, offs,
|
||||||
(bits, 8, 3, 0, -1))]
|
(bits, 8, 3, 0, -1))]
|
||||||
return
|
return
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -249,12 +252,17 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
# Old LabEye/3PC files. Would be very surprised if anyone
|
# Old LabEye/3PC files. Would be very surprised if anyone
|
||||||
# ever stumbled upon such a file ;-)
|
# ever stumbled upon such a file ;-)
|
||||||
size = self.size[0] * self.size[1]
|
size = self.size[0] * self.size[1]
|
||||||
self.tile = [("raw", (0,0)+self.size, offs, ("G", 0, -1)),
|
self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)),
|
||||||
("raw", (0,0)+self.size, offs+size, ("R", 0, -1)),
|
("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)),
|
||||||
("raw", (0,0)+self.size, offs+2*size, ("B", 0, -1))]
|
("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))]
|
||||||
else:
|
else:
|
||||||
# LabEye/IFUNC files
|
# LabEye/IFUNC files
|
||||||
self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))]
|
self.tile = [("raw", (0, 0)+self.size, offs,
|
||||||
|
(self.rawmode, 0, -1))]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return self.info[FRAMES]
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
|
||||||
|
@ -276,7 +284,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
|
|
||||||
self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))]
|
self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
|
|
||||||
|
@ -305,10 +313,11 @@ SAVE = {
|
||||||
"YCbCr": ("YCC", "YCbCr;L")
|
"YCbCr": ("YCC", "YCbCr;L")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, check=0):
|
def _save(im, fp, filename, check=0):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
type, rawmode = SAVE[im.mode]
|
image_type, rawmode = SAVE[im.mode]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("Cannot save %s images as IM" % im.mode)
|
raise ValueError("Cannot save %s images as IM" % im.mode)
|
||||||
|
|
||||||
|
@ -320,7 +329,7 @@ def _save(im, fp, filename, check=0):
|
||||||
if check:
|
if check:
|
||||||
return 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:
|
if filename:
|
||||||
fp.write(("Name: %s\r\n" % filename).encode('ascii'))
|
fp.write(("Name: %s\r\n" % filename).encode('ascii'))
|
||||||
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
|
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
|
||||||
|
@ -329,8 +338,8 @@ def _save(im, fp, filename, check=0):
|
||||||
fp.write(b"Lut: 1\r\n")
|
fp.write(b"Lut: 1\r\n")
|
||||||
fp.write(b"\000" * (511-fp.tell()) + b"\032")
|
fp.write(b"\000" * (511-fp.tell()) + b"\032")
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
|
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
|
||||||
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, -1))])
|
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))])
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
230
PIL/Image.py
230
PIL/Image.py
|
@ -34,7 +34,8 @@ import warnings
|
||||||
class DecompressionBombWarning(RuntimeWarning):
|
class DecompressionBombWarning(RuntimeWarning):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class _imaging_not_installed:
|
|
||||||
|
class _imaging_not_installed(object):
|
||||||
# module placeholder
|
# module placeholder
|
||||||
def __getattr__(self, id):
|
def __getattr__(self, id):
|
||||||
raise ImportError("The _imaging C module is not installed")
|
raise ImportError("The _imaging C module is not installed")
|
||||||
|
@ -54,10 +55,11 @@ except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# If the _imaging C module is not present, you can still use
|
# If the _imaging C module is not present, Pillow will not load.
|
||||||
# the "open" function to identify files, but you cannot load
|
# Note that other modules should not refer to _imaging directly;
|
||||||
# them. Note that other modules should not refer to _imaging
|
# import Image and use the Image.core variable instead.
|
||||||
# directly; import Image and use the Image.core variable instead.
|
# Also note that Image.core is not a publicly documented interface,
|
||||||
|
# and should be considered private and subject to change.
|
||||||
from PIL import _imaging as core
|
from PIL import _imaging as core
|
||||||
if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
|
if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
|
||||||
raise ImportError("The _imaging extension was built for another "
|
raise ImportError("The _imaging extension was built for another "
|
||||||
|
@ -90,6 +92,7 @@ except ImportError as v:
|
||||||
RuntimeWarning
|
RuntimeWarning
|
||||||
)
|
)
|
||||||
# Fail here anyway. Don't let people run with a mostly broken Pillow.
|
# Fail here anyway. Don't let people run with a mostly broken Pillow.
|
||||||
|
# see docs/porting-pil-to-pillow.rst
|
||||||
raise
|
raise
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -106,6 +109,8 @@ from PIL._util import deferred_error
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import io
|
||||||
|
import struct
|
||||||
|
|
||||||
# type stuff
|
# type stuff
|
||||||
import collections
|
import collections
|
||||||
|
@ -149,6 +154,7 @@ FLIP_TOP_BOTTOM = 1
|
||||||
ROTATE_90 = 2
|
ROTATE_90 = 2
|
||||||
ROTATE_180 = 3
|
ROTATE_180 = 3
|
||||||
ROTATE_270 = 4
|
ROTATE_270 = 4
|
||||||
|
TRANSPOSE = 5
|
||||||
|
|
||||||
# transforms
|
# transforms
|
||||||
AFFINE = 0
|
AFFINE = 0
|
||||||
|
@ -158,11 +164,10 @@ QUAD = 3
|
||||||
MESH = 4
|
MESH = 4
|
||||||
|
|
||||||
# resampling filters
|
# resampling filters
|
||||||
NONE = 0
|
NEAREST = NONE = 0
|
||||||
NEAREST = 0
|
LANCZOS = ANTIALIAS = 1
|
||||||
ANTIALIAS = 1 # 3-lobed lanczos
|
BILINEAR = LINEAR = 2
|
||||||
LINEAR = BILINEAR = 2
|
BICUBIC = CUBIC = 3
|
||||||
CUBIC = BICUBIC = 3
|
|
||||||
|
|
||||||
# dithers
|
# dithers
|
||||||
NONE = 0
|
NONE = 0
|
||||||
|
@ -382,7 +387,7 @@ def init():
|
||||||
for plugin in _plugins:
|
for plugin in _plugins:
|
||||||
try:
|
try:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print ("Importing %s" % plugin)
|
print("Importing %s" % plugin)
|
||||||
__import__("PIL.%s" % plugin, globals(), locals(), [])
|
__import__("PIL.%s" % plugin, globals(), locals(), [])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
@ -438,7 +443,7 @@ def coerce_e(value):
|
||||||
return value if isinstance(value, _E) else _E(value)
|
return value if isinstance(value, _E) else _E(value)
|
||||||
|
|
||||||
|
|
||||||
class _E:
|
class _E(object):
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
|
@ -473,7 +478,7 @@ def _getscaleoffset(expr):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Implementation wrapper
|
# Implementation wrapper
|
||||||
|
|
||||||
class Image:
|
class Image(object):
|
||||||
"""
|
"""
|
||||||
This class represents an image object. To create
|
This class represents an image object. To create
|
||||||
:py:class:`~PIL.Image.Image` objects, use the appropriate factory
|
:py:class:`~PIL.Image.Image` objects, use the appropriate factory
|
||||||
|
@ -530,7 +535,7 @@ class Image:
|
||||||
"""
|
"""
|
||||||
Closes the file pointer, if possible.
|
Closes the file pointer, if possible.
|
||||||
|
|
||||||
This operation will destroy the image core and release it's memory.
|
This operation will destroy the image core and release its memory.
|
||||||
The image data will be unusable afterward.
|
The image data will be unusable afterward.
|
||||||
|
|
||||||
This function is only required to close images that have not
|
This function is only required to close images that have not
|
||||||
|
@ -541,7 +546,7 @@ class Image:
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
except Exception as msg:
|
except Exception as msg:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print ("Error closing: %s" % msg)
|
print("Error closing: %s" % msg)
|
||||||
|
|
||||||
# Instead of simply setting to None, we're setting up a
|
# Instead of simply setting to None, we're setting up a
|
||||||
# deferred error that will better explain that the core image
|
# deferred error that will better explain that the core image
|
||||||
|
@ -595,6 +600,16 @@ class Image:
|
||||||
id(self)
|
id(self)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _repr_png_(self):
|
||||||
|
""" iPython display hook support
|
||||||
|
|
||||||
|
:returns: png version of the image as bytes
|
||||||
|
"""
|
||||||
|
from io import BytesIO
|
||||||
|
b = BytesIO()
|
||||||
|
self.save(b, 'PNG')
|
||||||
|
return b.getvalue()
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name == "__array_interface__":
|
if name == "__array_interface__":
|
||||||
# numpy array interface support
|
# numpy array interface support
|
||||||
|
@ -622,7 +637,7 @@ class Image:
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.size = size
|
self.size = size
|
||||||
self.im = core.new(mode, size)
|
self.im = core.new(mode, size)
|
||||||
if mode in ("L", "P"):
|
if mode in ("L", "P") and palette:
|
||||||
self.putpalette(palette)
|
self.putpalette(palette)
|
||||||
self.frombytes(data)
|
self.frombytes(data)
|
||||||
|
|
||||||
|
@ -664,6 +679,10 @@ class Image:
|
||||||
|
|
||||||
# Declare tostring as alias to tobytes
|
# Declare tostring as alias to tobytes
|
||||||
def tostring(self, *args, **kw):
|
def tostring(self, *args, **kw):
|
||||||
|
"""Deprecated alias to tobytes.
|
||||||
|
|
||||||
|
.. deprecated:: 2.0
|
||||||
|
"""
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'tostring() is deprecated. Please call tobytes() instead.',
|
'tostring() is deprecated. Please call tobytes() instead.',
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
|
@ -737,6 +756,7 @@ class Image:
|
||||||
associated with the image.
|
associated with the image.
|
||||||
|
|
||||||
:returns: An image access object.
|
:returns: An image access object.
|
||||||
|
:rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
|
||||||
"""
|
"""
|
||||||
if self.im and self.palette and self.palette.dirty:
|
if self.im and self.palette and self.palette.dirty:
|
||||||
# realize palette
|
# realize palette
|
||||||
|
@ -796,7 +816,7 @@ class Image:
|
||||||
use other thresholds, use the :py:meth:`~PIL.Image.Image.point`
|
use other thresholds, use the :py:meth:`~PIL.Image.Image.point`
|
||||||
method.
|
method.
|
||||||
|
|
||||||
:param mode: The requested mode.
|
:param mode: The requested mode. See: :ref:`concept-modes`.
|
||||||
:param matrix: An optional conversion matrix. If given, this
|
:param matrix: An optional conversion matrix. If given, this
|
||||||
should be 4- or 16-tuple containing floating point values.
|
should be 4- or 16-tuple containing floating point values.
|
||||||
:param dither: Dithering method, used when converting from
|
:param dither: Dithering method, used when converting from
|
||||||
|
@ -847,8 +867,9 @@ class Image:
|
||||||
t = self.info['transparency']
|
t = self.info['transparency']
|
||||||
if isinstance(t, bytes):
|
if isinstance(t, bytes):
|
||||||
# Dragons. This can't be represented by a single color
|
# Dragons. This can't be represented by a single color
|
||||||
warnings.warn('Palette images with Transparency expressed ' +
|
warnings.warn('Palette images with Transparency ' +
|
||||||
' in bytes should be converted to RGBA images')
|
' expressed in bytes should be converted ' +
|
||||||
|
'to RGBA images')
|
||||||
delete_trns = True
|
delete_trns = True
|
||||||
else:
|
else:
|
||||||
# get the new transparency color.
|
# get the new transparency color.
|
||||||
|
@ -864,11 +885,20 @@ class Image:
|
||||||
# can't just retrieve the palette number, got to do it
|
# can't just retrieve the palette number, got to do it
|
||||||
# after quantization.
|
# after quantization.
|
||||||
trns_im = trns_im.convert('RGB')
|
trns_im = trns_im.convert('RGB')
|
||||||
trns = trns_im.getpixel((0,0))
|
trns = trns_im.getpixel((0, 0))
|
||||||
|
|
||||||
elif self.mode == 'P' and mode == 'RGBA':
|
elif self.mode == 'P' and mode == 'RGBA':
|
||||||
|
t = self.info['transparency']
|
||||||
delete_trns = True
|
delete_trns = True
|
||||||
|
|
||||||
|
if isinstance(t, bytes):
|
||||||
|
self.im.putpalettealphas(t)
|
||||||
|
elif isinstance(t, int):
|
||||||
|
self.im.putpalettealpha(t, 0)
|
||||||
|
else:
|
||||||
|
raise ValueError("Transparency for P mode should" +
|
||||||
|
" be bytes or int")
|
||||||
|
|
||||||
if mode == "P" and palette == ADAPTIVE:
|
if mode == "P" and palette == ADAPTIVE:
|
||||||
im = self.im.quantize(colors)
|
im = self.im.quantize(colors)
|
||||||
new = self._new(im)
|
new = self._new(im)
|
||||||
|
@ -920,14 +950,19 @@ class Image:
|
||||||
return new_im
|
return new_im
|
||||||
|
|
||||||
def quantize(self, colors=256, method=None, kmeans=0, palette=None):
|
def quantize(self, colors=256, method=None, kmeans=0, palette=None):
|
||||||
|
"""
|
||||||
|
Convert the image to 'P' mode with the specified number
|
||||||
|
of colors.
|
||||||
|
|
||||||
# methods:
|
:param colors: The desired number of colors, <= 256
|
||||||
# 0 = median cut
|
:param method: 0 = median cut
|
||||||
# 1 = maximum coverage
|
1 = maximum coverage
|
||||||
# 2 = fast octree
|
2 = fast octree
|
||||||
|
:param kmeans: Integer
|
||||||
|
:param palette: Quantize to the :py:class:`PIL.ImagingPalette` palette.
|
||||||
|
:returns: A new image
|
||||||
|
|
||||||
# NOTE: this functionality will be moved to the extended
|
"""
|
||||||
# quantizer interface in a later version of PIL.
|
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
|
@ -994,8 +1029,6 @@ class Image:
|
||||||
|
|
||||||
def draft(self, mode, size):
|
def draft(self, mode, size):
|
||||||
"""
|
"""
|
||||||
NYI
|
|
||||||
|
|
||||||
Configures the image file loader so it returns a version of the
|
Configures the image file loader so it returns a version of the
|
||||||
image that as closely as possible matches the given mode and
|
image that as closely as possible matches the given mode and
|
||||||
size. For example, you can use this method to convert a color
|
size. For example, you can use this method to convert a color
|
||||||
|
@ -1255,11 +1288,11 @@ class Image:
|
||||||
images (in the latter case, the alpha band is used as mask).
|
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
|
Where the mask is 255, the given image is copied as is. Where
|
||||||
the mask is 0, the current value is preserved. Intermediate
|
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
|
See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
|
||||||
ignored. You can work around this by using the same image as
|
combine images with respect to their alpha channels.
|
||||||
both source image and mask.
|
|
||||||
|
|
||||||
:param im: Source image or pixel value (integer or tuple).
|
:param im: Source image or pixel value (integer or tuple).
|
||||||
:param box: An optional 4-tuple giving the region to paste into.
|
:param box: An optional 4-tuple giving the region to paste into.
|
||||||
|
@ -1500,36 +1533,30 @@ class Image:
|
||||||
(width, height).
|
(width, height).
|
||||||
:param resample: An optional resampling filter. This can be
|
:param resample: An optional resampling filter. This can be
|
||||||
one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
|
one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
|
||||||
:py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
|
:py:attr:`PIL.Image.BILINEAR` (linear interpolation),
|
||||||
environment), :py:attr:`PIL.Image.BICUBIC` (cubic spline
|
:py:attr:`PIL.Image.BICUBIC` (cubic spline interpolation), or
|
||||||
interpolation in a 4x4 environment), or
|
:py:attr:`PIL.Image.LANCZOS` (a high-quality downsampling filter).
|
||||||
:py:attr:`PIL.Image.ANTIALIAS` (a high-quality downsampling filter).
|
|
||||||
If omitted, or if the image has mode "1" or "P", it is
|
If omitted, or if the image has mode "1" or "P", it is
|
||||||
set :py:attr:`PIL.Image.NEAREST`.
|
set :py:attr:`PIL.Image.NEAREST`.
|
||||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if resample not in (NEAREST, BILINEAR, BICUBIC, ANTIALIAS):
|
if resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS):
|
||||||
raise ValueError("unknown resampling filter")
|
raise ValueError("unknown resampling filter")
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
|
size = tuple(size)
|
||||||
|
if self.size == size:
|
||||||
|
return self._new(self.im)
|
||||||
|
|
||||||
if self.mode in ("1", "P"):
|
if self.mode in ("1", "P"):
|
||||||
resample = NEAREST
|
resample = NEAREST
|
||||||
|
|
||||||
if self.mode == 'RGBA':
|
if self.mode == 'RGBA':
|
||||||
return self.convert('RGBa').resize(size, resample).convert('RGBA')
|
return self.convert('RGBa').resize(size, resample).convert('RGBA')
|
||||||
|
|
||||||
if resample == ANTIALIAS:
|
return self._new(self.im.resize(size, resample))
|
||||||
# requires stretch support (imToolkit & PIL 1.1.3)
|
|
||||||
try:
|
|
||||||
im = self.im.stretch(size, resample)
|
|
||||||
except AttributeError:
|
|
||||||
raise ValueError("unsupported resampling filter")
|
|
||||||
else:
|
|
||||||
im = self.im.resize(size, resample)
|
|
||||||
|
|
||||||
return self._new(im)
|
|
||||||
|
|
||||||
def rotate(self, angle, resample=NEAREST, expand=0):
|
def rotate(self, angle, resample=NEAREST, expand=0):
|
||||||
"""
|
"""
|
||||||
|
@ -1600,15 +1627,16 @@ class Image:
|
||||||
|
|
||||||
Keyword options can be used to provide additional instructions
|
Keyword options can be used to provide additional instructions
|
||||||
to the writer. If a writer doesn't recognise an option, it is
|
to the writer. If a writer doesn't recognise an option, it is
|
||||||
silently ignored. The available options are described later in
|
silently ignored. The available options are described in the
|
||||||
this handbook.
|
:doc:`image format documentation
|
||||||
|
<../handbook/image-file-formats>` for each writer.
|
||||||
|
|
||||||
You can use a file object instead of a filename. In this case,
|
You can use a file object instead of a filename. In this case,
|
||||||
you must always specify the format. The file object must
|
you must always specify the format. The file object must
|
||||||
implement the **seek**, **tell**, and **write**
|
implement the ``seek``, ``tell``, and ``write``
|
||||||
methods, and be opened in binary mode.
|
methods, and be opened in binary mode.
|
||||||
|
|
||||||
:param file: File name or file object.
|
:param fp: File name or file object.
|
||||||
:param format: Optional format override. If omitted, the
|
:param format: Optional format override. If omitted, the
|
||||||
format to use is determined from the filename extension.
|
format to use is determined from the filename extension.
|
||||||
If a file object was used instead of a filename, this
|
If a file object was used instead of a filename, this
|
||||||
|
@ -1735,7 +1763,7 @@ class Image:
|
||||||
"""
|
"""
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def thumbnail(self, size, resample=ANTIALIAS):
|
def thumbnail(self, size, resample=BICUBIC):
|
||||||
"""
|
"""
|
||||||
Make this image into a thumbnail. This method modifies the
|
Make this image into a thumbnail. This method modifies the
|
||||||
image to contain a thumbnail version of itself, no larger than
|
image to contain a thumbnail version of itself, no larger than
|
||||||
|
@ -1744,12 +1772,7 @@ class Image:
|
||||||
:py:meth:`~PIL.Image.Image.draft` method to configure the file reader
|
:py:meth:`~PIL.Image.Image.draft` method to configure the file reader
|
||||||
(where applicable), and finally resizes the image.
|
(where applicable), and finally resizes the image.
|
||||||
|
|
||||||
Note that the bilinear and bicubic filters in the current
|
Note that this function modifies the :py:class:`~PIL.Image.Image`
|
||||||
version of PIL are not well-suited for thumbnail generation.
|
|
||||||
You should use :py:attr:`PIL.Image.ANTIALIAS` unless speed is much more
|
|
||||||
important than quality.
|
|
||||||
|
|
||||||
Also note that this function modifies the :py:class:`~PIL.Image.Image`
|
|
||||||
object in place. If you need to use the full resolution image as well,
|
object in place. If you need to use the full resolution image as well,
|
||||||
apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
|
apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
|
||||||
image.
|
image.
|
||||||
|
@ -1757,10 +1780,9 @@ class Image:
|
||||||
:param size: Requested size.
|
:param size: Requested size.
|
||||||
:param resample: Optional resampling filter. This can be one
|
:param resample: Optional resampling filter. This can be one
|
||||||
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
|
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
|
||||||
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS`
|
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.LANCZOS`.
|
||||||
(best quality). If omitted, it defaults to
|
If omitted, it defaults to :py:attr:`PIL.Image.BICUBIC`.
|
||||||
:py:attr:`PIL.Image.ANTIALIAS`. (was :py:attr:`PIL.Image.NEAREST`
|
(was :py:attr:`PIL.Image.NEAREST` prior to version 2.5.0)
|
||||||
prior to version 2.5.0)
|
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -1779,14 +1801,7 @@ class Image:
|
||||||
|
|
||||||
self.draft(None, size)
|
self.draft(None, size)
|
||||||
|
|
||||||
self.load()
|
im = self.resize(size, resample)
|
||||||
|
|
||||||
try:
|
|
||||||
im = self.resize(size, resample)
|
|
||||||
except ValueError:
|
|
||||||
if resample != ANTIALIAS:
|
|
||||||
raise
|
|
||||||
im = self.resize(size, NEAREST) # fallback
|
|
||||||
|
|
||||||
self.im = im.im
|
self.im = im.im
|
||||||
self.mode = im.mode
|
self.mode = im.mode
|
||||||
|
@ -1795,7 +1810,7 @@ class Image:
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
self.pyaccess = None
|
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.
|
# instead of bloating the method docs, add a separate chapter.
|
||||||
def transform(self, size, method, data=None, resample=NEAREST, fill=1):
|
def transform(self, size, method, data=None, resample=NEAREST, fill=1):
|
||||||
"""
|
"""
|
||||||
|
@ -1902,12 +1917,22 @@ class Image:
|
||||||
|
|
||||||
:param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`,
|
:param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`,
|
||||||
:py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`,
|
:py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`,
|
||||||
:py:attr:`PIL.Image.ROTATE_180`, or :py:attr:`PIL.Image.ROTATE_270`.
|
:py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270` or
|
||||||
|
:py:attr:`PIL.Image.TRANSPOSE`.
|
||||||
:returns: Returns a flipped or rotated copy of this image.
|
:returns: Returns a flipped or rotated copy of this image.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
im = self.im.transpose(method)
|
return self._new(self.im.transpose(method))
|
||||||
|
|
||||||
|
def effect_spread(self, distance):
|
||||||
|
"""
|
||||||
|
Randomly spread pixels in an image.
|
||||||
|
|
||||||
|
:param distance: Distance to spread pixels.
|
||||||
|
"""
|
||||||
|
self.load()
|
||||||
|
im = self.im.effect_spread(distance)
|
||||||
return self._new(im)
|
return self._new(im)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1950,12 +1975,12 @@ class _ImageCrop(Image):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Abstract handlers.
|
# Abstract handlers.
|
||||||
|
|
||||||
class ImagePointHandler:
|
class ImagePointHandler(object):
|
||||||
# used as a mixin by point transforms (for use with im.point)
|
# used as a mixin by point transforms (for use with im.point)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ImageTransformHandler:
|
class ImageTransformHandler(object):
|
||||||
# used as a mixin by geometry transforms (for use with im.transform)
|
# used as a mixin by geometry transforms (for use with im.transform)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1976,7 +2001,8 @@ def new(mode, size, color=0):
|
||||||
"""
|
"""
|
||||||
Creates a new image with the given mode and size.
|
Creates a new image with the given mode and size.
|
||||||
|
|
||||||
:param mode: The mode to use for the new image.
|
:param mode: The mode to use for the new image. See:
|
||||||
|
:ref:`concept-modes`.
|
||||||
:param size: A 2-tuple, containing (width, height) in pixels.
|
:param size: A 2-tuple, containing (width, height) in pixels.
|
||||||
:param color: What color to use for the image. Default is black.
|
:param color: What color to use for the image. Default is black.
|
||||||
If given, this should be a single integer or floating point value
|
If given, this should be a single integer or floating point value
|
||||||
|
@ -2009,14 +2035,14 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
|
||||||
|
|
||||||
You can also use any pixel decoder supported by PIL. For more
|
You can also use any pixel decoder supported by PIL. For more
|
||||||
information on available decoders, see the section
|
information on available decoders, see the section
|
||||||
**Writing Your Own File Decoder**.
|
:ref:`Writing Your Own File Decoder <file-decoders>`.
|
||||||
|
|
||||||
Note that this function decodes pixel data only, not entire images.
|
Note that this function decodes pixel data only, not entire images.
|
||||||
If you have an entire image in a string, wrap it in a
|
If you have an entire image in a string, wrap it in a
|
||||||
:py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load
|
:py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load
|
||||||
it.
|
it.
|
||||||
|
|
||||||
:param mode: The image mode.
|
:param mode: The image mode. See: :ref:`concept-modes`.
|
||||||
:param size: The image size.
|
:param size: The image size.
|
||||||
:param data: A byte buffer containing raw data for the given mode.
|
:param data: A byte buffer containing raw data for the given mode.
|
||||||
:param decoder_name: What decoder to use.
|
:param decoder_name: What decoder to use.
|
||||||
|
@ -2068,7 +2094,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
|
||||||
issues a warning if you do this; to disable the warning, you should provide
|
issues a warning if you do this; to disable the warning, you should provide
|
||||||
the full set of parameters. See below for details.
|
the full set of parameters. See below for details.
|
||||||
|
|
||||||
:param mode: The image mode.
|
:param mode: The image mode. See: :ref:`concept-modes`.
|
||||||
:param size: The image size.
|
:param size: The image size.
|
||||||
:param data: A bytes or other buffer object containing raw
|
:param data: A bytes or other buffer object containing raw
|
||||||
data for the given mode.
|
data for the given mode.
|
||||||
|
@ -2119,7 +2145,8 @@ def fromarray(obj, mode=None):
|
||||||
|
|
||||||
:param obj: Object with array interface
|
:param obj: Object with array interface
|
||||||
:param mode: Mode to use (will be determined from type if None)
|
:param mode: Mode to use (will be determined from type if None)
|
||||||
:returns: An image memory.
|
See: :ref:`concept-modes`.
|
||||||
|
:returns: An image object.
|
||||||
|
|
||||||
.. versionadded:: 1.1.6
|
.. versionadded:: 1.1.6
|
||||||
"""
|
"""
|
||||||
|
@ -2222,6 +2249,11 @@ def open(fp, mode="r"):
|
||||||
else:
|
else:
|
||||||
filename = ""
|
filename = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
fp.seek(0)
|
||||||
|
except (AttributeError, io.UnsupportedOperation):
|
||||||
|
fp = io.BytesIO(fp.read())
|
||||||
|
|
||||||
prefix = fp.read(16)
|
prefix = fp.read(16)
|
||||||
|
|
||||||
preinit()
|
preinit()
|
||||||
|
@ -2234,7 +2266,7 @@ def open(fp, mode="r"):
|
||||||
im = factory(fp, filename)
|
im = factory(fp, filename)
|
||||||
_decompression_bomb_check(im.size)
|
_decompression_bomb_check(im.size)
|
||||||
return im
|
return im
|
||||||
except (SyntaxError, IndexError, TypeError):
|
except (SyntaxError, IndexError, TypeError, struct.error):
|
||||||
# import traceback
|
# import traceback
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
@ -2249,7 +2281,7 @@ def open(fp, mode="r"):
|
||||||
im = factory(fp, filename)
|
im = factory(fp, filename)
|
||||||
_decompression_bomb_check(im.size)
|
_decompression_bomb_check(im.size)
|
||||||
return im
|
return im
|
||||||
except (SyntaxError, IndexError, TypeError):
|
except (SyntaxError, IndexError, TypeError, struct.error):
|
||||||
# import traceback
|
# import traceback
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
@ -2306,7 +2338,7 @@ def composite(image1, image2, mask):
|
||||||
:param image1: The first image.
|
:param image1: The first image.
|
||||||
:param image2: The second image. Must have the same mode and
|
:param image2: The second image. Must have the same mode and
|
||||||
size as the first image.
|
size as the first image.
|
||||||
:param mask: A mask image. This image can can have mode
|
:param mask: A mask image. This image can have mode
|
||||||
"1", "L", or "RGBA", and must have the same size as the
|
"1", "L", or "RGBA", and must have the same size as the
|
||||||
other two images.
|
other two images.
|
||||||
"""
|
"""
|
||||||
|
@ -2336,7 +2368,8 @@ def merge(mode, bands):
|
||||||
"""
|
"""
|
||||||
Merge a set of single band images into a new multiband image.
|
Merge a set of single band images into a new multiband image.
|
||||||
|
|
||||||
:param mode: The mode to use for the output image.
|
:param mode: The mode to use for the output image. See:
|
||||||
|
:ref:`concept-modes`.
|
||||||
:param bands: A sequence containing one single-band image for
|
:param bands: A sequence containing one single-band image for
|
||||||
each band in the output image. All bands must have the
|
each band in the output image. All bands must have the
|
||||||
same size.
|
same size.
|
||||||
|
@ -2419,3 +2452,32 @@ def _show(image, **options):
|
||||||
def _showxv(image, title=None, **options):
|
def _showxv(image, title=None, **options):
|
||||||
from PIL import ImageShow
|
from PIL import ImageShow
|
||||||
ImageShow.show(image, title, **options)
|
ImageShow.show(image, title, **options)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Effects
|
||||||
|
|
||||||
|
def effect_mandelbrot(size, extent, quality):
|
||||||
|
"""
|
||||||
|
Generate a Mandelbrot set covering the given extent.
|
||||||
|
|
||||||
|
:param size: The requested size in pixels, as a 2-tuple:
|
||||||
|
(width, height).
|
||||||
|
:param extent: The extent to cover, as a 4-tuple:
|
||||||
|
(x0, y0, x1, y2).
|
||||||
|
:param quality: Quality.
|
||||||
|
"""
|
||||||
|
return Image()._new(core.effect_mandelbrot(size, extent, quality))
|
||||||
|
|
||||||
|
|
||||||
|
def effect_noise(size, sigma):
|
||||||
|
"""
|
||||||
|
Generate Gaussian noise centered around 128.
|
||||||
|
|
||||||
|
:param size: The requested size in pixels, as a 2-tuple:
|
||||||
|
(width, height).
|
||||||
|
:param sigma: Standard deviation of noise.
|
||||||
|
"""
|
||||||
|
return Image()._new(core.effect_noise(size, sigma))
|
||||||
|
|
||||||
|
# End of file
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
## The Python Imaging Library.
|
# The Python Imaging Library.
|
||||||
## $Id$
|
# $Id$
|
||||||
|
|
||||||
## Optional color managment support, based on Kevin Cazabon's PyCMS
|
# Optional color managment support, based on Kevin Cazabon's PyCMS
|
||||||
## library.
|
# library.
|
||||||
|
|
||||||
## History:
|
# History:
|
||||||
|
|
||||||
## 2009-03-08 fl Added to PIL.
|
# 2009-03-08 fl Added to PIL.
|
||||||
|
|
||||||
## Copyright (C) 2002-2003 Kevin Cazabon
|
# Copyright (C) 2002-2003 Kevin Cazabon
|
||||||
## Copyright (c) 2009 by Fredrik Lundh
|
# Copyright (c) 2009 by Fredrik Lundh
|
||||||
## Copyright (c) 2013 by Eric Soroos
|
# Copyright (c) 2013 by Eric Soroos
|
||||||
|
|
||||||
## See the README file for information on usage and redistribution. See
|
# See the README file for information on usage and redistribution. See
|
||||||
## below for the original description.
|
# below for the original description.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ pyCMS
|
||||||
|
|
||||||
0.0.2 alpha Jan 6, 2002
|
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()
|
potential CObjects... Python won't let you use type()
|
||||||
on them, and raises a TypeError (stupid, if you ask
|
on them, and raises a TypeError (stupid, if you ask
|
||||||
me!)
|
me!)
|
||||||
|
@ -90,8 +90,8 @@ try:
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
# Allow error import for doc purposes, but error out when accessing
|
# Allow error import for doc purposes, but error out when accessing
|
||||||
# anything in core.
|
# anything in core.
|
||||||
from _util import import_err
|
from _util import deferred_error
|
||||||
_imagingcms = import_err(ex)
|
_imagingcms = deferred_error(ex)
|
||||||
from PIL._util import isStringType
|
from PIL._util import isStringType
|
||||||
|
|
||||||
core = _imagingcms
|
core = _imagingcms
|
||||||
|
@ -123,8 +123,8 @@ FLAGS = {
|
||||||
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
||||||
"NOTPRECALC": 256,
|
"NOTPRECALC": 256,
|
||||||
"NULLTRANSFORM": 512, # Don't transform anyway
|
"NULLTRANSFORM": 512, # Don't transform anyway
|
||||||
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
|
"HIGHRESPRECALC": 1024, # Use more memory to give better accuracy
|
||||||
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces
|
"LOWRESPRECALC": 2048, # Use less memory to minimize resources
|
||||||
"WHITEBLACKCOMPENSATION": 8192,
|
"WHITEBLACKCOMPENSATION": 8192,
|
||||||
"BLACKPOINTCOMPENSATION": 8192,
|
"BLACKPOINTCOMPENSATION": 8192,
|
||||||
"GAMUTCHECK": 4096, # Out of Gamut alarm
|
"GAMUTCHECK": 4096, # Out of Gamut alarm
|
||||||
|
@ -147,7 +147,7 @@ for flag in FLAGS.values():
|
||||||
##
|
##
|
||||||
# Profile.
|
# Profile.
|
||||||
|
|
||||||
class ImageCmsProfile:
|
class ImageCmsProfile(object):
|
||||||
|
|
||||||
def __init__(self, profile):
|
def __init__(self, profile):
|
||||||
"""
|
"""
|
||||||
|
@ -184,6 +184,7 @@ class ImageCmsProfile:
|
||||||
|
|
||||||
return core.profile_tobytes(self.profile)
|
return core.profile_tobytes(self.profile)
|
||||||
|
|
||||||
|
|
||||||
class ImageCmsTransform(Image.ImagePointHandler):
|
class ImageCmsTransform(Image.ImagePointHandler):
|
||||||
|
|
||||||
# Transform. This can be used with the procedural API, or with the
|
# Transform. This can be used with the procedural API, or with the
|
||||||
|
@ -191,7 +192,6 @@ class ImageCmsTransform(Image.ImagePointHandler):
|
||||||
#
|
#
|
||||||
# Will return the output profile in the output.info['icc_profile'].
|
# Will return the output profile in the output.info['icc_profile'].
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, input, output, input_mode, output_mode,
|
def __init__(self, input, output, input_mode, output_mode,
|
||||||
intent=INTENT_PERCEPTUAL, proof=None,
|
intent=INTENT_PERCEPTUAL, proof=None,
|
||||||
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
|
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
|
||||||
|
@ -573,7 +573,7 @@ def applyTransform(im, transform, inPlace=0):
|
||||||
This function applies a pre-calculated transform (from
|
This function applies a pre-calculated transform (from
|
||||||
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
|
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
|
||||||
to an image. The transform can be used for multiple images, saving
|
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
|
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
|
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
|
If an error occurs while trying to obtain the default intent, a
|
||||||
PyCMSError is raised.
|
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 intent for this profile. Most profiles support multiple
|
||||||
rendering intents, but are intended mostly for one type of conversion.
|
rendering intents, but are intended mostly for one type of conversion.
|
||||||
If you wish to use a different intent than returned, use
|
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
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
they do.
|
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
|
output, or proof
|
||||||
|
|
||||||
INPUT = 0 (or use ImageCms.DIRECTION_INPUT)
|
INPUT = 0 (or use ImageCms.DIRECTION_INPUT)
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
def getrgb(color):
|
def getrgb(color):
|
||||||
"""
|
"""
|
||||||
Convert a color string to an RGB tuple. If the string cannot be parsed,
|
Convert a color string to an RGB tuple. If the string cannot be parsed,
|
||||||
|
@ -86,7 +87,8 @@ def getrgb(color):
|
||||||
int(rgb[1] * 255 + 0.5),
|
int(rgb[1] * 255 + 0.5),
|
||||||
int(rgb[2] * 255 + 0.5)
|
int(rgb[2] * 255 + 0.5)
|
||||||
)
|
)
|
||||||
m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$",
|
||||||
|
color)
|
||||||
if m:
|
if m:
|
||||||
return (
|
return (
|
||||||
int(m.group(1)),
|
int(m.group(1)),
|
||||||
|
@ -96,6 +98,7 @@ def getrgb(color):
|
||||||
)
|
)
|
||||||
raise ValueError("unknown color specifier: %r" % color)
|
raise ValueError("unknown color specifier: %r" % color)
|
||||||
|
|
||||||
|
|
||||||
def getcolor(color, mode):
|
def getcolor(color, mode):
|
||||||
"""
|
"""
|
||||||
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
|
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
|
||||||
|
|
|
@ -40,13 +40,14 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
warnings = None
|
warnings = None
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# A simple 2D drawing interface for PIL images.
|
# A simple 2D drawing interface for PIL images.
|
||||||
# <p>
|
# <p>
|
||||||
# Application code should use the <b>Draw</b> factory, instead of
|
# Application code should use the <b>Draw</b> factory, instead of
|
||||||
# directly.
|
# directly.
|
||||||
|
|
||||||
class ImageDraw:
|
class ImageDraw(object):
|
||||||
|
|
||||||
##
|
##
|
||||||
# Create a drawing instance.
|
# Create a drawing instance.
|
||||||
|
@ -61,7 +62,7 @@ class ImageDraw:
|
||||||
def __init__(self, im, mode=None):
|
def __init__(self, im, mode=None):
|
||||||
im.load()
|
im.load()
|
||||||
if im.readonly:
|
if im.readonly:
|
||||||
im._copy() # make it writable
|
im._copy() # make it writeable
|
||||||
blend = 0
|
blend = 0
|
||||||
if mode is None:
|
if mode is None:
|
||||||
mode = im.mode
|
mode = im.mode
|
||||||
|
@ -85,7 +86,7 @@ class ImageDraw:
|
||||||
# FIXME: fix Fill2 to properly support matte for I+F images
|
# FIXME: fix Fill2 to properly support matte for I+F images
|
||||||
self.fontmode = "1"
|
self.fontmode = "1"
|
||||||
else:
|
else:
|
||||||
self.fontmode = "L" # aliasing is okay for other modes
|
self.fontmode = "L" # aliasing is okay for other modes
|
||||||
self.fill = 0
|
self.fill = 0
|
||||||
self.font = None
|
self.font = None
|
||||||
|
|
||||||
|
@ -280,6 +281,7 @@ class ImageDraw:
|
||||||
font = self.getfont()
|
font = self.getfont()
|
||||||
return font.getsize(text)
|
return font.getsize(text)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# A simple 2D drawing interface for PIL images.
|
# A simple 2D drawing interface for PIL images.
|
||||||
#
|
#
|
||||||
|
@ -302,6 +304,7 @@ try:
|
||||||
except:
|
except:
|
||||||
Outline = None
|
Outline = None
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# (Experimental) A more advanced 2D drawing interface for PIL images,
|
# (Experimental) A more advanced 2D drawing interface for PIL images,
|
||||||
# based on the WCK interface.
|
# based on the WCK interface.
|
||||||
|
@ -325,6 +328,7 @@ def getdraw(im=None, hints=None):
|
||||||
im = handler.Draw(im)
|
im = handler.Draw(im)
|
||||||
return im, handler
|
return im, handler
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# (experimental) Fills a bounded region with a given color.
|
# (experimental) Fills a bounded region with a given color.
|
||||||
#
|
#
|
||||||
|
@ -344,10 +348,10 @@ def floodfill(image, xy, value, border=None):
|
||||||
try:
|
try:
|
||||||
background = pixel[x, y]
|
background = pixel[x, y]
|
||||||
if background == value:
|
if background == value:
|
||||||
return # seed point already has fill color
|
return # seed point already has fill color
|
||||||
pixel[x, y] = value
|
pixel[x, y] = value
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return # seed point outside image
|
return # seed point outside image
|
||||||
edge = [(x, y)]
|
edge = [(x, y)]
|
||||||
if border is None:
|
if border is None:
|
||||||
while edge:
|
while edge:
|
||||||
|
|
|
@ -18,22 +18,26 @@
|
||||||
|
|
||||||
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||||
|
|
||||||
class Pen:
|
|
||||||
|
class Pen(object):
|
||||||
def __init__(self, color, width=1, opacity=255):
|
def __init__(self, color, width=1, opacity=255):
|
||||||
self.color = ImageColor.getrgb(color)
|
self.color = ImageColor.getrgb(color)
|
||||||
self.width = width
|
self.width = width
|
||||||
|
|
||||||
class Brush:
|
|
||||||
|
class Brush(object):
|
||||||
def __init__(self, color, opacity=255):
|
def __init__(self, color, opacity=255):
|
||||||
self.color = ImageColor.getrgb(color)
|
self.color = ImageColor.getrgb(color)
|
||||||
|
|
||||||
class Font:
|
|
||||||
|
class Font(object):
|
||||||
def __init__(self, color, file, size=12):
|
def __init__(self, color, file, size=12):
|
||||||
# FIXME: add support for bitmap fonts
|
# FIXME: add support for bitmap fonts
|
||||||
self.color = ImageColor.getrgb(color)
|
self.color = ImageColor.getrgb(color)
|
||||||
self.font = ImageFont.truetype(file, size)
|
self.font = ImageFont.truetype(file, size)
|
||||||
|
|
||||||
class Draw:
|
|
||||||
|
class Draw(object):
|
||||||
|
|
||||||
def __init__(self, image, size=None, color=None):
|
def __init__(self, image, size=None, color=None):
|
||||||
if not hasattr(image, "im"):
|
if not hasattr(image, "im"):
|
||||||
|
@ -47,7 +51,8 @@ class Draw:
|
||||||
|
|
||||||
def render(self, op, xy, pen, brush=None):
|
def render(self, op, xy, pen, brush=None):
|
||||||
# handle color arguments
|
# handle color arguments
|
||||||
outline = fill = None; width = 1
|
outline = fill = None
|
||||||
|
width = 1
|
||||||
if isinstance(pen, Pen):
|
if isinstance(pen, Pen):
|
||||||
outline = pen.color
|
outline = pen.color
|
||||||
width = pen.width
|
width = pen.width
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
from PIL import Image, ImageFilter, ImageStat
|
from PIL import Image, ImageFilter, ImageStat
|
||||||
|
|
||||||
|
|
||||||
class _Enhance:
|
class _Enhance(object):
|
||||||
|
|
||||||
def enhance(self, factor):
|
def enhance(self, factor):
|
||||||
"""
|
"""
|
||||||
|
@ -47,7 +47,11 @@ class Color(_Enhance):
|
||||||
"""
|
"""
|
||||||
def __init__(self, image):
|
def __init__(self, image):
|
||||||
self.image = image
|
self.image = image
|
||||||
self.degenerate = image.convert("L").convert(image.mode)
|
self.intermediate_mode = 'L'
|
||||||
|
if 'A' in image.getbands():
|
||||||
|
self.intermediate_mode = 'LA'
|
||||||
|
|
||||||
|
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
|
||||||
|
|
||||||
|
|
||||||
class Contrast(_Enhance):
|
class Contrast(_Enhance):
|
||||||
|
@ -62,11 +66,14 @@ class Contrast(_Enhance):
|
||||||
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
|
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
|
||||||
self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
|
self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
|
||||||
|
|
||||||
|
if 'A' in image.getbands():
|
||||||
|
self.degenerate.putalpha(image.split()[-1])
|
||||||
|
|
||||||
|
|
||||||
class Brightness(_Enhance):
|
class Brightness(_Enhance):
|
||||||
"""Adjust image brightness.
|
"""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
|
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
|
||||||
original image.
|
original image.
|
||||||
"""
|
"""
|
||||||
|
@ -74,6 +81,9 @@ class Brightness(_Enhance):
|
||||||
self.image = image
|
self.image = image
|
||||||
self.degenerate = Image.new(image.mode, image.size, 0)
|
self.degenerate = Image.new(image.mode, image.size, 0)
|
||||||
|
|
||||||
|
if 'A' in image.getbands():
|
||||||
|
self.degenerate.putalpha(image.split()[-1])
|
||||||
|
|
||||||
|
|
||||||
class Sharpness(_Enhance):
|
class Sharpness(_Enhance):
|
||||||
"""Adjust image sharpness.
|
"""Adjust image sharpness.
|
||||||
|
@ -85,3 +95,6 @@ class Sharpness(_Enhance):
|
||||||
def __init__(self, image):
|
def __init__(self, image):
|
||||||
self.image = image
|
self.image = image
|
||||||
self.degenerate = image.filter(ImageFilter.SMOOTH)
|
self.degenerate = image.filter(ImageFilter.SMOOTH)
|
||||||
|
|
||||||
|
if 'A' in image.getbands():
|
||||||
|
self.degenerate.putalpha(image.split()[-1])
|
||||||
|
|
|
@ -29,8 +29,10 @@
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL._util import isPath
|
from PIL._util import isPath
|
||||||
import traceback, os, sys
|
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
MAXBLOCK = 65536
|
MAXBLOCK = 65536
|
||||||
|
|
||||||
|
@ -46,6 +48,7 @@ ERRORS = {
|
||||||
-9: "out of memory error"
|
-9: "out of memory error"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def raise_ioerror(error):
|
def raise_ioerror(error):
|
||||||
try:
|
try:
|
||||||
message = Image.core.getcodecstatus(error)
|
message = Image.core.getcodecstatus(error)
|
||||||
|
@ -55,6 +58,7 @@ def raise_ioerror(error):
|
||||||
message = "decoder error %d" % error
|
message = "decoder error %d" % error
|
||||||
raise IOError(message + " when reading image file")
|
raise IOError(message + " when reading image file")
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Helpers
|
# Helpers
|
||||||
|
@ -63,6 +67,7 @@ def _tilesort(t):
|
||||||
# sort on offset
|
# sort on offset
|
||||||
return t[2]
|
return t[2]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# ImageFile base class
|
# ImageFile base class
|
||||||
|
@ -74,7 +79,7 @@ class ImageFile(Image.Image):
|
||||||
Image.Image.__init__(self)
|
Image.Image.__init__(self)
|
||||||
|
|
||||||
self.tile = None
|
self.tile = None
|
||||||
self.readonly = 1 # until we know better
|
self.readonly = 1 # until we know better
|
||||||
|
|
||||||
self.decoderconfig = ()
|
self.decoderconfig = ()
|
||||||
self.decodermaxblock = MAXBLOCK
|
self.decodermaxblock = MAXBLOCK
|
||||||
|
@ -90,19 +95,19 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._open()
|
self._open()
|
||||||
except IndexError as v: # end of data
|
except IndexError as v: # end of data
|
||||||
if Image.DEBUG > 1:
|
if Image.DEBUG > 1:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise SyntaxError(v)
|
raise SyntaxError(v)
|
||||||
except TypeError as v: # end of data (ord)
|
except TypeError as v: # end of data (ord)
|
||||||
if Image.DEBUG > 1:
|
if Image.DEBUG > 1:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise SyntaxError(v)
|
raise SyntaxError(v)
|
||||||
except KeyError as v: # unsupported mode
|
except KeyError as v: # unsupported mode
|
||||||
if Image.DEBUG > 1:
|
if Image.DEBUG > 1:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise SyntaxError(v)
|
raise SyntaxError(v)
|
||||||
except EOFError as v: # got header but not the first frame
|
except EOFError as v: # got header but not the first frame
|
||||||
if Image.DEBUG > 1:
|
if Image.DEBUG > 1:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise SyntaxError(v)
|
raise SyntaxError(v)
|
||||||
|
@ -191,6 +196,9 @@ class ImageFile(Image.Image):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
prefix = b""
|
prefix = b""
|
||||||
|
|
||||||
|
# Buffer length read; assign a default value
|
||||||
|
t = 0
|
||||||
|
|
||||||
for d, e, o, a in self.tile:
|
for d, e, o, a in self.tile:
|
||||||
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
|
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
|
||||||
seek(o)
|
seek(o)
|
||||||
|
@ -203,23 +211,25 @@ class ImageFile(Image.Image):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
s = read(self.decodermaxblock)
|
s = read(self.decodermaxblock)
|
||||||
except IndexError as ie: # truncated png/gif
|
except IndexError as ie: # truncated png/gif
|
||||||
if LOAD_TRUNCATED_IMAGES:
|
if LOAD_TRUNCATED_IMAGES:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise IndexError(ie)
|
raise IndexError(ie)
|
||||||
|
|
||||||
if not s and not d.handles_eof: # truncated jpeg
|
if not s and not d.handles_eof: # truncated jpeg
|
||||||
self.tile = []
|
self.tile = []
|
||||||
|
|
||||||
# JpegDecode needs to clean things up here either way
|
# JpegDecode needs to clean things up here either way
|
||||||
# If we don't destroy the decompressor, we have a memory leak.
|
# If we don't destroy the decompressor,
|
||||||
|
# we have a memory leak.
|
||||||
d.cleanup()
|
d.cleanup()
|
||||||
|
|
||||||
if LOAD_TRUNCATED_IMAGES:
|
if LOAD_TRUNCATED_IMAGES:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise IOError("image file is truncated (%d bytes not processed)" % len(b))
|
raise IOError("image file is truncated "
|
||||||
|
"(%d bytes not processed)" % len(b))
|
||||||
|
|
||||||
b = b + s
|
b = b + s
|
||||||
n, e = d.decode(b)
|
n, e = d.decode(b)
|
||||||
|
@ -227,11 +237,13 @@ class ImageFile(Image.Image):
|
||||||
break
|
break
|
||||||
b = b[n:]
|
b = b[n:]
|
||||||
t = t + n
|
t = t + n
|
||||||
|
# Need to cleanup here to prevent leaks in PyPy
|
||||||
|
d.cleanup()
|
||||||
|
|
||||||
self.tile = []
|
self.tile = []
|
||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
|
|
||||||
self.fp = None # might be shared
|
self.fp = None # might be shared
|
||||||
|
|
||||||
if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0:
|
if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0:
|
||||||
# still raised if decoder fails to return anything
|
# still raised if decoder fails to return anything
|
||||||
|
@ -299,7 +311,7 @@ class StubImageFile(ImageFile):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Parser:
|
class Parser(object):
|
||||||
"""
|
"""
|
||||||
Incremental image parser. This class implements the standard
|
Incremental image parser. This class implements the standard
|
||||||
feed/close consumer interface.
|
feed/close consumer interface.
|
||||||
|
@ -310,6 +322,7 @@ class Parser:
|
||||||
image = None
|
image = None
|
||||||
data = None
|
data = None
|
||||||
decoder = None
|
decoder = None
|
||||||
|
offset = 0
|
||||||
finished = 0
|
finished = 0
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
|
@ -378,10 +391,10 @@ class Parser:
|
||||||
fp = io.BytesIO(self.data)
|
fp = io.BytesIO(self.data)
|
||||||
im = Image.open(fp)
|
im = Image.open(fp)
|
||||||
finally:
|
finally:
|
||||||
fp.close() # explicitly close the virtual file
|
fp.close() # explicitly close the virtual file
|
||||||
except IOError:
|
except IOError:
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass # not enough data
|
pass # not enough data
|
||||||
else:
|
else:
|
||||||
flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
|
flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
|
||||||
if flag or len(im.tile) != 1:
|
if flag or len(im.tile) != 1:
|
||||||
|
@ -431,9 +444,10 @@ class Parser:
|
||||||
self.image = Image.open(fp)
|
self.image = Image.open(fp)
|
||||||
finally:
|
finally:
|
||||||
self.image.load()
|
self.image.load()
|
||||||
fp.close() # explicitly close the virtual file
|
fp.close() # explicitly close the virtual file
|
||||||
return self.image
|
return self.image
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
def _save(im, fp, tile, bufsize=0):
|
def _save(im, fp, tile, bufsize=0):
|
||||||
|
@ -450,10 +464,10 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
im.encoderconfig = ()
|
im.encoderconfig = ()
|
||||||
tile.sort(key=_tilesort)
|
tile.sort(key=_tilesort)
|
||||||
# FIXME: make MAXBLOCK a configuration parameter
|
# FIXME: make MAXBLOCK a configuration parameter
|
||||||
# It would be great if we could have the encoder specifiy what it needs
|
# It would be great if we could have the encoder specify what it needs
|
||||||
# But, it would need at least the image size in most cases. RawEncode is
|
# But, it would need at least the image size in most cases. RawEncode is
|
||||||
# a tricky case.
|
# a tricky case.
|
||||||
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
|
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
|
||||||
try:
|
try:
|
||||||
fh = fp.fileno()
|
fh = fp.fileno()
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
@ -471,6 +485,7 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
break
|
break
|
||||||
if s < 0:
|
if s < 0:
|
||||||
raise IOError("encoder error %d when writing image file" % s)
|
raise IOError("encoder error %d when writing image file" % s)
|
||||||
|
e.cleanup()
|
||||||
else:
|
else:
|
||||||
# slight speedup: compress to real file object
|
# slight speedup: compress to real file object
|
||||||
for e, b, o, a in tile:
|
for e, b, o, a in tile:
|
||||||
|
@ -481,9 +496,11 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
s = e.encode_to_file(fh, bufsize)
|
s = e.encode_to_file(fh, bufsize)
|
||||||
if s < 0:
|
if s < 0:
|
||||||
raise IOError("encoder error %d when writing image file" % s)
|
raise IOError("encoder error %d when writing image file" % s)
|
||||||
|
e.cleanup()
|
||||||
try:
|
try:
|
||||||
fp.flush()
|
fp.flush()
|
||||||
except: pass
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _safe_read(fp, size):
|
def _safe_read(fp, size):
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from functools import reduce
|
import functools
|
||||||
|
|
||||||
|
|
||||||
class Filter(object):
|
class Filter(object):
|
||||||
|
@ -43,7 +43,7 @@ class Kernel(Filter):
|
||||||
def __init__(self, size, kernel, scale=None, offset=0):
|
def __init__(self, size, kernel, scale=None, offset=0):
|
||||||
if scale is None:
|
if scale is None:
|
||||||
# default scale is sum of kernel
|
# 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):
|
if size[0] * size[1] != len(kernel):
|
||||||
raise ValueError("not enough coefficients in kernel")
|
raise ValueError("not enough coefficients in kernel")
|
||||||
self.filterargs = size, scale, offset, kernel
|
self.filterargs = size, scale, offset, kernel
|
||||||
|
@ -162,7 +162,13 @@ class UnsharpMask(Filter):
|
||||||
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
|
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
|
||||||
the parameters.
|
the parameters.
|
||||||
|
|
||||||
|
:param radius: Blur Radius
|
||||||
|
:param percent: Unsharp strength, in percent
|
||||||
|
:param threshold: Threshold controls the minimum brightness change that
|
||||||
|
will be sharpened
|
||||||
|
|
||||||
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
|
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
|
||||||
|
|
||||||
"""
|
"""
|
||||||
name = "UnsharpMask"
|
name = "UnsharpMask"
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,6 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL._util import isDirectory, isPath
|
from PIL._util import isDirectory, isPath
|
||||||
import os
|
import os
|
||||||
|
@ -38,7 +36,7 @@ except ImportError:
|
||||||
warnings = None
|
warnings = None
|
||||||
|
|
||||||
|
|
||||||
class _imagingft_not_installed:
|
class _imagingft_not_installed(object):
|
||||||
# module placeholder
|
# module placeholder
|
||||||
def __getattr__(self, id):
|
def __getattr__(self, id):
|
||||||
raise ImportError("The _imagingft C module is not installed")
|
raise ImportError("The _imagingft C module is not installed")
|
||||||
|
@ -64,7 +62,7 @@ except ImportError:
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class ImageFont:
|
class ImageFont(object):
|
||||||
"PIL font wrapper"
|
"PIL font wrapper"
|
||||||
|
|
||||||
def _load_pilfont(self, filename):
|
def _load_pilfont(self, filename):
|
||||||
|
@ -120,7 +118,7 @@ class ImageFont:
|
||||||
# Wrapper for FreeType fonts. Application code should use the
|
# Wrapper for FreeType fonts. Application code should use the
|
||||||
# <b>truetype</b> factory function to create font objects.
|
# <b>truetype</b> factory function to create font objects.
|
||||||
|
|
||||||
class FreeTypeFont:
|
class FreeTypeFont(object):
|
||||||
"FreeType font wrapper (requires _imagingft service)"
|
"FreeType font wrapper (requires _imagingft service)"
|
||||||
|
|
||||||
def __init__(self, font=None, size=10, index=0, encoding="", file=None):
|
def __init__(self, font=None, size=10, index=0, encoding="", file=None):
|
||||||
|
@ -133,6 +131,11 @@ class FreeTypeFont:
|
||||||
DeprecationWarning)
|
DeprecationWarning)
|
||||||
font = file
|
font = file
|
||||||
|
|
||||||
|
self.path = font
|
||||||
|
self.size = size
|
||||||
|
self.index = index
|
||||||
|
self.encoding = encoding
|
||||||
|
|
||||||
if isPath(font):
|
if isPath(font):
|
||||||
self.font = core.getfont(font, size, index, encoding)
|
self.font = core.getfont(font, size, index, encoding)
|
||||||
else:
|
else:
|
||||||
|
@ -162,6 +165,22 @@ class FreeTypeFont:
|
||||||
self.font.render(text, im.id, mode == "1")
|
self.font.render(text, im.id, mode == "1")
|
||||||
return im, offset
|
return im, offset
|
||||||
|
|
||||||
|
def font_variant(self, font=None, size=None, index=None, encoding=None):
|
||||||
|
"""
|
||||||
|
Create a copy of this FreeTypeFont object,
|
||||||
|
using any specified arguments to override the settings.
|
||||||
|
|
||||||
|
Parameters are identical to the parameters used to initialize this
|
||||||
|
object, minus the deprecated 'file' argument.
|
||||||
|
|
||||||
|
:return: A FreeTypeFont object.
|
||||||
|
"""
|
||||||
|
return FreeTypeFont(font=self.path if font is None else font,
|
||||||
|
size=self.size if size is None else size,
|
||||||
|
index=self.index if index is None else index,
|
||||||
|
encoding=self.encoding if encoding is None else
|
||||||
|
encoding)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Wrapper that creates a transposed font from any existing font
|
# Wrapper that creates a transposed font from any existing font
|
||||||
# object.
|
# object.
|
||||||
|
@ -172,7 +191,7 @@ class FreeTypeFont:
|
||||||
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
||||||
|
|
||||||
|
|
||||||
class TransposedFont:
|
class TransposedFont(object):
|
||||||
"Wrapper for writing rotated or mirrored text"
|
"Wrapper for writing rotated or mirrored text"
|
||||||
|
|
||||||
def __init__(self, font, orientation=None):
|
def __init__(self, font, orientation=None):
|
||||||
|
@ -214,7 +233,7 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
||||||
|
|
||||||
This function requires the _imagingft service.
|
This function requires the _imagingft service.
|
||||||
|
|
||||||
:param filename: A truetype font file. Under Windows, if the file
|
:param font: A truetype font file. Under Windows, if the file
|
||||||
is not found in this filename, the loader also looks in
|
is not found in this filename, the loader also looks in
|
||||||
Windows :file:`fonts/` directory.
|
Windows :file:`fonts/` directory.
|
||||||
:param size: The requested size, in points.
|
:param size: The requested size, in points.
|
||||||
|
@ -224,6 +243,7 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
||||||
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
||||||
and "armn" (Apple Roman). See the FreeType documentation
|
and "armn" (Apple Roman). See the FreeType documentation
|
||||||
for more information.
|
for more information.
|
||||||
|
:param filename: Deprecated. Please use font instead.
|
||||||
:return: A font object.
|
:return: A font object.
|
||||||
:exception IOError: If the file could not be read.
|
:exception IOError: If the file could not be read.
|
||||||
"""
|
"""
|
||||||
|
@ -239,14 +259,44 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
||||||
try:
|
try:
|
||||||
return FreeTypeFont(font, size, index, encoding)
|
return FreeTypeFont(font, size, index, encoding)
|
||||||
except IOError:
|
except IOError:
|
||||||
|
ttf_filename = os.path.basename(font)
|
||||||
|
|
||||||
|
dirs = []
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
# check the windows font repository
|
# check the windows font repository
|
||||||
# NOTE: must use uppercase WINDIR, to work around bugs in
|
# NOTE: must use uppercase WINDIR, to work around bugs in
|
||||||
# 1.5.2's os.environ.get()
|
# 1.5.2's os.environ.get()
|
||||||
windir = os.environ.get("WINDIR")
|
windir = os.environ.get("WINDIR")
|
||||||
if windir:
|
if windir:
|
||||||
filename = os.path.join(windir, "fonts", font)
|
dirs.append(os.path.join(windir, "fonts"))
|
||||||
return FreeTypeFont(filename, size, index, encoding)
|
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'
|
||||||
|
dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
dirs += ['/Library/Fonts', '/System/Library/Fonts',
|
||||||
|
os.path.expanduser('~/Library/Fonts')]
|
||||||
|
|
||||||
|
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
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@ -259,15 +309,15 @@ def load_path(filename):
|
||||||
:return: A font object.
|
:return: A font object.
|
||||||
:exception IOError: If the file could not be read.
|
:exception IOError: If the file could not be read.
|
||||||
"""
|
"""
|
||||||
for dir in sys.path:
|
for directory in sys.path:
|
||||||
if isDirectory(dir):
|
if isDirectory(directory):
|
||||||
if not isinstance(filename, str):
|
if not isinstance(filename, str):
|
||||||
if bytes is str:
|
if bytes is str:
|
||||||
filename = filename.encode("utf-8")
|
filename = filename.encode("utf-8")
|
||||||
else:
|
else:
|
||||||
filename = filename.decode("utf-8")
|
filename = filename.decode("utf-8")
|
||||||
try:
|
try:
|
||||||
return load(os.path.join(dir, filename))
|
return load(os.path.join(directory, filename))
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
raise IOError("cannot find font file")
|
raise IOError("cannot find font file")
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
import sys
|
||||||
|
if sys.platform != "win32":
|
||||||
|
raise ImportError("ImageGrab is Windows only")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# built-in driver (1.1.3 and later)
|
# built-in driver (1.1.3 and later)
|
||||||
|
@ -40,7 +43,7 @@ def grab(bbox=None):
|
||||||
|
|
||||||
|
|
||||||
def grabclipboard():
|
def grabclipboard():
|
||||||
debug = 0 # temporary interface
|
debug = 0 # temporary interface
|
||||||
data = Image.core.grabclipboard(debug)
|
data = Image.core.grabclipboard(debug)
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
from PIL import BmpImagePlugin
|
from PIL import BmpImagePlugin
|
||||||
|
|
|
@ -31,7 +31,7 @@ def _isconstant(v):
|
||||||
return isinstance(v, int) or isinstance(v, float)
|
return isinstance(v, int) or isinstance(v, float)
|
||||||
|
|
||||||
|
|
||||||
class _Operand:
|
class _Operand(object):
|
||||||
# wraps an image operand, providing standard operators
|
# wraps an image operand, providing standard operators
|
||||||
|
|
||||||
def __init__(self, im):
|
def __init__(self, im):
|
||||||
|
|
|
@ -16,10 +16,11 @@
|
||||||
# mode descriptor cache
|
# mode descriptor cache
|
||||||
_modes = {}
|
_modes = {}
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Wrapper for mode strings.
|
# Wrapper for mode strings.
|
||||||
|
|
||||||
class ModeDescriptor:
|
class ModeDescriptor(object):
|
||||||
|
|
||||||
def __init__(self, mode, bands, basemode, basetype):
|
def __init__(self, mode, bands, basemode, basetype):
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -30,6 +31,7 @@ class ModeDescriptor:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.mode
|
return self.mode
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Gets a mode descriptor for the given mode.
|
# Gets a mode descriptor for the given mode.
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import re
|
||||||
LUT_SIZE = 1 << 9
|
LUT_SIZE = 1 << 9
|
||||||
|
|
||||||
|
|
||||||
class LutBuilder:
|
class LutBuilder(object):
|
||||||
"""A class for building a MorphLut from a descriptive language
|
"""A class for building a MorphLut from a descriptive language
|
||||||
|
|
||||||
The input patterns is a list of a strings sequences like these::
|
The input patterns is a list of a strings sequences like these::
|
||||||
|
@ -176,7 +176,7 @@ class LutBuilder:
|
||||||
return self.lut
|
return self.lut
|
||||||
|
|
||||||
|
|
||||||
class MorphOp:
|
class MorphOp(object):
|
||||||
"""A class for binary morphological operators"""
|
"""A class for binary morphological operators"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL._util import isStringType
|
from PIL._util import isStringType
|
||||||
import operator
|
import operator
|
||||||
from functools import reduce
|
import functools
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# helpers
|
# helpers
|
||||||
|
@ -35,12 +36,14 @@ def _border(border):
|
||||||
left = top = right = bottom = border
|
left = top = right = bottom = border
|
||||||
return left, top, right, bottom
|
return left, top, right, bottom
|
||||||
|
|
||||||
|
|
||||||
def _color(color, mode):
|
def _color(color, mode):
|
||||||
if isStringType(color):
|
if isStringType(color):
|
||||||
from PIL import ImageColor
|
from PIL import ImageColor
|
||||||
color = ImageColor.getcolor(color, mode)
|
color = ImageColor.getcolor(color, mode)
|
||||||
return color
|
return color
|
||||||
|
|
||||||
|
|
||||||
def _lut(image, lut):
|
def _lut(image, lut):
|
||||||
if image.mode == "P":
|
if image.mode == "P":
|
||||||
# FIXME: apply to lookup table, not image data
|
# FIXME: apply to lookup table, not image data
|
||||||
|
@ -147,7 +150,9 @@ def colorize(image, black, white):
|
||||||
assert image.mode == "L"
|
assert image.mode == "L"
|
||||||
black = _color(black, "RGB")
|
black = _color(black, "RGB")
|
||||||
white = _color(white, "RGB")
|
white = _color(white, "RGB")
|
||||||
red = []; green = []; blue = []
|
red = []
|
||||||
|
green = []
|
||||||
|
blue = []
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
red.append(black[0]+i*(white[0]-black[0])//255)
|
red.append(black[0]+i*(white[0]-black[0])//255)
|
||||||
green.append(black[1]+i*(white[1]-black[1])//255)
|
green.append(black[1]+i*(white[1]-black[1])//255)
|
||||||
|
@ -208,7 +213,7 @@ def equalize(image, mask=None):
|
||||||
if len(histo) <= 1:
|
if len(histo) <= 1:
|
||||||
lut.extend(list(range(256)))
|
lut.extend(list(range(256)))
|
||||||
else:
|
else:
|
||||||
step = (reduce(operator.add, histo) - histo[-1]) // 255
|
step = (functools.reduce(operator.add, histo) - histo[-1]) // 255
|
||||||
if not step:
|
if not step:
|
||||||
lut.extend(list(range(256)))
|
lut.extend(list(range(256)))
|
||||||
else:
|
else:
|
||||||
|
@ -228,7 +233,6 @@ def expand(image, border=0, fill=0):
|
||||||
:param fill: Pixel fill value (a color value). Default is 0 (black).
|
:param fill: Pixel fill value (a color value). Default is 0 (black).
|
||||||
:return: An image.
|
:return: An image.
|
||||||
"""
|
"""
|
||||||
"Add border to image"
|
|
||||||
left, top, right, bottom = _border(border)
|
left, top, right, bottom = _border(border)
|
||||||
width = left + image.size[0] + right
|
width = left + image.size[0] + right
|
||||||
height = top + image.size[1] + bottom
|
height = top + image.size[1] + bottom
|
||||||
|
@ -273,7 +277,7 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
|
||||||
centering = [centering[0], centering[1]]
|
centering = [centering[0], centering[1]]
|
||||||
|
|
||||||
if centering[0] > 1.0 or centering[0] < 0.0:
|
if centering[0] > 1.0 or centering[0] < 0.0:
|
||||||
centering [0] = 0.50
|
centering[0] = 0.50
|
||||||
if centering[1] > 1.0 or centering[1] < 0.0:
|
if centering[1] > 1.0 or centering[1] < 0.0:
|
||||||
centering[1] = 0.50
|
centering[1] = 0.50
|
||||||
|
|
||||||
|
@ -404,6 +408,7 @@ def solarize(image, threshold=128):
|
||||||
lut.append(255-i)
|
lut.append(255-i)
|
||||||
return _lut(image, lut)
|
return _lut(image, lut)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# PIL USM components, from Kevin Cazabon.
|
# PIL USM components, from Kevin Cazabon.
|
||||||
|
|
||||||
|
@ -419,6 +424,7 @@ def gaussian_blur(im, radius=None):
|
||||||
|
|
||||||
gblur = gaussian_blur
|
gblur = gaussian_blur
|
||||||
|
|
||||||
|
|
||||||
def unsharp_mask(im, radius=None, percent=None, threshold=None):
|
def unsharp_mask(im, radius=None, percent=None, threshold=None):
|
||||||
""" PIL_usm.usm(im, [radius, percent, threshold])"""
|
""" PIL_usm.usm(im, [radius, percent, threshold])"""
|
||||||
|
|
||||||
|
@ -434,3 +440,22 @@ def unsharp_mask(im, radius=None, percent=None, threshold=None):
|
||||||
return im.im.unsharp_mask(radius, percent, threshold)
|
return im.im.unsharp_mask(radius, percent, threshold)
|
||||||
|
|
||||||
usm = unsharp_mask
|
usm = unsharp_mask
|
||||||
|
|
||||||
|
|
||||||
|
def box_blur(image, radius):
|
||||||
|
"""
|
||||||
|
Blur the image by setting each pixel to the average value of the pixels
|
||||||
|
in a square box extending radius pixels in each direction.
|
||||||
|
Supports float radius of arbitrary size. Uses an optimized implementation
|
||||||
|
which runs in linear time relative to the size of the image
|
||||||
|
for any radius value.
|
||||||
|
|
||||||
|
:param image: The image to blur.
|
||||||
|
:param radius: Size of the box in one direction. Radius 0 does not blur,
|
||||||
|
returns an identical image. Radius 1 takes 1 pixel
|
||||||
|
in each direction, i.e. 9 pixels in total.
|
||||||
|
:return: An image.
|
||||||
|
"""
|
||||||
|
image.load()
|
||||||
|
|
||||||
|
return image._new(image.im.box_blur(radius))
|
||||||
|
|
|
@ -21,7 +21,7 @@ import warnings
|
||||||
from PIL import ImageColor
|
from PIL import ImageColor
|
||||||
|
|
||||||
|
|
||||||
class ImagePalette:
|
class ImagePalette(object):
|
||||||
"Color palette for palette mapped images"
|
"Color palette for palette mapped images"
|
||||||
|
|
||||||
def __init__(self, mode="RGB", palette=None, size=0):
|
def __init__(self, mode="RGB", palette=None, size=0):
|
||||||
|
@ -225,8 +225,8 @@ def load(filename):
|
||||||
p = PaletteFile.PaletteFile(fp)
|
p = PaletteFile.PaletteFile(fp)
|
||||||
lut = p.getpalette()
|
lut = p.getpalette()
|
||||||
except (SyntaxError, ValueError):
|
except (SyntaxError, ValueError):
|
||||||
import traceback
|
#import traceback
|
||||||
traceback.print_exc()
|
#traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not lut:
|
if not lut:
|
||||||
|
|
|
@ -20,7 +20,7 @@ from PIL import Image
|
||||||
# the Python class below is overridden by the C implementation.
|
# the Python class below is overridden by the C implementation.
|
||||||
|
|
||||||
|
|
||||||
class Path:
|
class Path(object):
|
||||||
|
|
||||||
def __init__(self, xy):
|
def __init__(self, xy):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -18,20 +18,30 @@
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL._util import isPath
|
from PIL._util import isPath
|
||||||
|
import sys
|
||||||
|
|
||||||
try:
|
if 'PyQt4.QtGui' not in sys.modules:
|
||||||
from PyQt5.QtGui import QImage, qRgba
|
try:
|
||||||
except:
|
from PyQt5.QtGui import QImage, qRgba
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
from PyQt4.QtGui import QImage, qRgba
|
||||||
|
except:
|
||||||
|
from PySide.QtGui import QImage, qRgba
|
||||||
|
|
||||||
|
else: #PyQt4 is used
|
||||||
from PyQt4.QtGui import QImage, qRgba
|
from PyQt4.QtGui import QImage, qRgba
|
||||||
|
|
||||||
##
|
##
|
||||||
# (Internal) Turns an RGB color into a Qt compatible color integer.
|
# (Internal) Turns an RGB color into a Qt compatible color integer.
|
||||||
|
|
||||||
|
|
||||||
def rgb(r, g, b, a=255):
|
def rgb(r, g, b, a=255):
|
||||||
# use qRgb to pack the colors, and then turn the resulting long
|
# use qRgb to pack the colors, and then turn the resulting long
|
||||||
# into a negative integer with the same bitpattern.
|
# into a negative integer with the same bitpattern.
|
||||||
return (qRgba(r, g, b, a) & 0xffffffff)
|
return (qRgba(r, g, b, a) & 0xffffffff)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# 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 PyQt4's QImage
|
||||||
# class.
|
# class.
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
||||||
class Iterator:
|
|
||||||
|
class Iterator(object):
|
||||||
"""
|
"""
|
||||||
This class implements an iterator object that can be used to loop
|
This class implements an iterator object that can be used to loop
|
||||||
over an image sequence.
|
over an image sequence.
|
||||||
|
@ -38,4 +39,4 @@ class Iterator:
|
||||||
self.im.seek(ix)
|
self.im.seek(ix)
|
||||||
return self.im
|
return self.im
|
||||||
except EOFError:
|
except EOFError:
|
||||||
raise IndexError # end of sequence
|
raise IndexError # end of sequence
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import os, sys
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
if sys.version_info >= (3, 3):
|
if sys.version_info >= (3, 3):
|
||||||
from shlex import quote
|
from shlex import quote
|
||||||
|
@ -24,17 +25,19 @@ else:
|
||||||
|
|
||||||
_viewers = []
|
_viewers = []
|
||||||
|
|
||||||
|
|
||||||
def register(viewer, order=1):
|
def register(viewer, order=1):
|
||||||
try:
|
try:
|
||||||
if issubclass(viewer, Viewer):
|
if issubclass(viewer, Viewer):
|
||||||
viewer = viewer()
|
viewer = viewer()
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass # raised if viewer wasn't a class
|
pass # raised if viewer wasn't a class
|
||||||
if order > 0:
|
if order > 0:
|
||||||
_viewers.append(viewer)
|
_viewers.append(viewer)
|
||||||
elif order < 0:
|
elif order < 0:
|
||||||
_viewers.insert(0, viewer)
|
_viewers.insert(0, viewer)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Displays a given image.
|
# Displays a given image.
|
||||||
#
|
#
|
||||||
|
@ -49,10 +52,11 @@ def show(image, title=None, **options):
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Base class for viewers.
|
# Base class for viewers.
|
||||||
|
|
||||||
class Viewer:
|
class Viewer(object):
|
||||||
|
|
||||||
# main api
|
# main api
|
||||||
|
|
||||||
|
@ -102,6 +106,7 @@ if sys.platform == "win32":
|
||||||
|
|
||||||
class WindowsViewer(Viewer):
|
class WindowsViewer(Viewer):
|
||||||
format = "BMP"
|
format = "BMP"
|
||||||
|
|
||||||
def get_command(self, file, **options):
|
def get_command(self, file, **options):
|
||||||
return ('start "Pillow" /WAIT "%s" '
|
return ('start "Pillow" /WAIT "%s" '
|
||||||
'&& ping -n 2 127.0.0.1 >NUL '
|
'&& ping -n 2 127.0.0.1 >NUL '
|
||||||
|
@ -113,11 +118,13 @@ elif sys.platform == "darwin":
|
||||||
|
|
||||||
class MacViewer(Viewer):
|
class MacViewer(Viewer):
|
||||||
format = "BMP"
|
format = "BMP"
|
||||||
|
|
||||||
def get_command(self, file, **options):
|
def get_command(self, file, **options):
|
||||||
# on darwin open returns immediately resulting in the temp
|
# on darwin open returns immediately resulting in the temp
|
||||||
# file removal while app is opening
|
# file removal while app is opening
|
||||||
command = "open -a /Applications/Preview.app"
|
command = "open -a /Applications/Preview.app"
|
||||||
command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file), quote(file))
|
command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file),
|
||||||
|
quote(file))
|
||||||
return command
|
return command
|
||||||
|
|
||||||
register(MacViewer)
|
register(MacViewer)
|
||||||
|
@ -140,7 +147,8 @@ else:
|
||||||
class UnixViewer(Viewer):
|
class UnixViewer(Viewer):
|
||||||
def show_file(self, file, **options):
|
def show_file(self, file, **options):
|
||||||
command, executable = self.get_command_ex(file, **options)
|
command, executable = self.get_command_ex(file, **options)
|
||||||
command = "(%s %s; rm -f %s)&" % (command, quote(file), quote(file))
|
command = "(%s %s; rm -f %s)&" % (command, quote(file),
|
||||||
|
quote(file))
|
||||||
os.system(command)
|
os.system(command)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
|
@ -21,20 +21,21 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
import operator, math
|
import math
|
||||||
from functools import reduce
|
import operator
|
||||||
|
import functools
|
||||||
|
|
||||||
|
|
||||||
class Stat:
|
class Stat(object):
|
||||||
|
|
||||||
def __init__(self, image_or_list, mask = None):
|
def __init__(self, image_or_list, mask=None):
|
||||||
try:
|
try:
|
||||||
if mask:
|
if mask:
|
||||||
self.h = image_or_list.histogram(mask)
|
self.h = image_or_list.histogram(mask)
|
||||||
else:
|
else:
|
||||||
self.h = image_or_list.histogram()
|
self.h = image_or_list.histogram()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.h = image_or_list # assume it to be a histogram list
|
self.h = image_or_list # assume it to be a histogram list
|
||||||
if not isinstance(self.h, list):
|
if not isinstance(self.h, list):
|
||||||
raise TypeError("first argument must be image or list")
|
raise TypeError("first argument must be image or list")
|
||||||
self.bands = list(range(len(self.h) // 256))
|
self.bands = list(range(len(self.h) // 256))
|
||||||
|
@ -58,7 +59,7 @@ class Stat:
|
||||||
if histogram[i]:
|
if histogram[i]:
|
||||||
n = min(n, i)
|
n = min(n, i)
|
||||||
x = max(x, i)
|
x = max(x, i)
|
||||||
return n, x # returns (255, 0) if there's no data in the histogram
|
return n, x # returns (255, 0) if there's no data in the histogram
|
||||||
|
|
||||||
v = []
|
v = []
|
||||||
for i in range(0, len(self.h), 256):
|
for i in range(0, len(self.h), 256):
|
||||||
|
@ -70,7 +71,7 @@ class Stat:
|
||||||
|
|
||||||
v = []
|
v = []
|
||||||
for i in range(0, len(self.h), 256):
|
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
|
return v
|
||||||
|
|
||||||
def _getsum(self):
|
def _getsum(self):
|
||||||
|
@ -78,10 +79,10 @@ class Stat:
|
||||||
|
|
||||||
v = []
|
v = []
|
||||||
for i in range(0, len(self.h), 256):
|
for i in range(0, len(self.h), 256):
|
||||||
sum = 0.0
|
layerSum = 0.0
|
||||||
for j in range(256):
|
for j in range(256):
|
||||||
sum += j * self.h[i + j]
|
layerSum += j * self.h[i + j]
|
||||||
v.append(sum)
|
v.append(layerSum)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
def _getsum2(self):
|
def _getsum2(self):
|
||||||
|
@ -126,7 +127,6 @@ class Stat:
|
||||||
v.append(math.sqrt(self.sum2[i] / self.count[i]))
|
v.append(math.sqrt(self.sum2[i] / self.count[i]))
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
def _getvar(self):
|
def _getvar(self):
|
||||||
"Get variance for each layer"
|
"Get variance for each layer"
|
||||||
|
|
||||||
|
@ -144,4 +144,4 @@ class Stat:
|
||||||
v.append(math.sqrt(self.var[i]))
|
v.append(math.sqrt(self.var[i]))
|
||||||
return v
|
return v
|
||||||
|
|
||||||
Global = Stat # compatibility
|
Global = Stat # compatibility
|
||||||
|
|
|
@ -40,21 +40,23 @@ from PIL import Image
|
||||||
|
|
||||||
_pilbitmap_ok = None
|
_pilbitmap_ok = None
|
||||||
|
|
||||||
|
|
||||||
def _pilbitmap_check():
|
def _pilbitmap_check():
|
||||||
global _pilbitmap_ok
|
global _pilbitmap_ok
|
||||||
if _pilbitmap_ok is None:
|
if _pilbitmap_ok is None:
|
||||||
try:
|
try:
|
||||||
im = Image.new("1", (1,1))
|
im = Image.new("1", (1, 1))
|
||||||
tkinter.BitmapImage(data="PIL:%d" % im.im.id)
|
tkinter.BitmapImage(data="PIL:%d" % im.im.id)
|
||||||
_pilbitmap_ok = 1
|
_pilbitmap_ok = 1
|
||||||
except tkinter.TclError:
|
except tkinter.TclError:
|
||||||
_pilbitmap_ok = 0
|
_pilbitmap_ok = 0
|
||||||
return _pilbitmap_ok
|
return _pilbitmap_ok
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# PhotoImage
|
# PhotoImage
|
||||||
|
|
||||||
class PhotoImage:
|
class PhotoImage(object):
|
||||||
"""
|
"""
|
||||||
A Tkinter-compatible photo image. This can be used
|
A Tkinter-compatible photo image. This can be used
|
||||||
everywhere Tkinter expects an image object. If the image is an RGBA
|
everywhere Tkinter expects an image object. If the image is an RGBA
|
||||||
|
@ -95,7 +97,7 @@ class PhotoImage:
|
||||||
try:
|
try:
|
||||||
mode = image.palette.mode
|
mode = image.palette.mode
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
mode = "RGB" # default
|
mode = "RGB" # default
|
||||||
size = image.size
|
size = image.size
|
||||||
kw["width"], kw["height"] = size
|
kw["width"], kw["height"] = size
|
||||||
else:
|
else:
|
||||||
|
@ -118,8 +120,7 @@ class PhotoImage:
|
||||||
try:
|
try:
|
||||||
self.__photo.tk.call("image", "delete", name)
|
self.__photo.tk.call("image", "delete", name)
|
||||||
except:
|
except:
|
||||||
pass # ignore internal errors
|
pass # ignore internal errors
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
|
@ -131,7 +132,6 @@ class PhotoImage:
|
||||||
"""
|
"""
|
||||||
return str(self.__photo)
|
return str(self.__photo)
|
||||||
|
|
||||||
|
|
||||||
def width(self):
|
def width(self):
|
||||||
"""
|
"""
|
||||||
Get the width of the image.
|
Get the width of the image.
|
||||||
|
@ -140,7 +140,6 @@ class PhotoImage:
|
||||||
"""
|
"""
|
||||||
return self.__size[0]
|
return self.__size[0]
|
||||||
|
|
||||||
|
|
||||||
def height(self):
|
def height(self):
|
||||||
"""
|
"""
|
||||||
Get the height of the image.
|
Get the height of the image.
|
||||||
|
@ -149,7 +148,6 @@ class PhotoImage:
|
||||||
"""
|
"""
|
||||||
return self.__size[1]
|
return self.__size[1]
|
||||||
|
|
||||||
|
|
||||||
def paste(self, im, box=None):
|
def paste(self, im, box=None):
|
||||||
"""
|
"""
|
||||||
Paste a PIL image into the photo image. Note that this can
|
Paste a PIL image into the photo image. Note that this can
|
||||||
|
@ -170,13 +168,13 @@ class PhotoImage:
|
||||||
block = image
|
block = image
|
||||||
else:
|
else:
|
||||||
block = image.new_block(self.__mode, im.size)
|
block = image.new_block(self.__mode, im.size)
|
||||||
image.convert2(block, image) # convert directly between buffers
|
image.convert2(block, image) # convert directly between buffers
|
||||||
|
|
||||||
tk = self.__photo.tk
|
tk = self.__photo.tk
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tk.call("PyImagingPhoto", self.__photo, block.id)
|
tk.call("PyImagingPhoto", self.__photo, block.id)
|
||||||
except tkinter.TclError as v:
|
except tkinter.TclError:
|
||||||
# activate Tkinter hook
|
# activate Tkinter hook
|
||||||
try:
|
try:
|
||||||
from PIL import _imagingtk
|
from PIL import _imagingtk
|
||||||
|
@ -186,13 +184,13 @@ class PhotoImage:
|
||||||
_imagingtk.tkinit(id(tk), 0)
|
_imagingtk.tkinit(id(tk), 0)
|
||||||
tk.call("PyImagingPhoto", self.__photo, block.id)
|
tk.call("PyImagingPhoto", self.__photo, block.id)
|
||||||
except (ImportError, AttributeError, tkinter.TclError):
|
except (ImportError, AttributeError, tkinter.TclError):
|
||||||
raise # configuration problem; cannot attach to Tkinter
|
raise # configuration problem; cannot attach to Tkinter
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# BitmapImage
|
# BitmapImage
|
||||||
|
|
||||||
|
|
||||||
class BitmapImage:
|
class BitmapImage(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
|
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
|
||||||
|
@ -226,7 +224,7 @@ class BitmapImage:
|
||||||
# fast way (requires the pilbitmap booster patch)
|
# fast way (requires the pilbitmap booster patch)
|
||||||
image.load()
|
image.load()
|
||||||
kw["data"] = "PIL:%d" % image.im.id
|
kw["data"] = "PIL:%d" % image.im.id
|
||||||
self.__im = image # must keep a reference
|
self.__im = image # must keep a reference
|
||||||
else:
|
else:
|
||||||
# slow but safe way
|
# slow but safe way
|
||||||
kw["data"] = image.tobitmap()
|
kw["data"] = image.tobitmap()
|
||||||
|
@ -238,8 +236,7 @@ class BitmapImage:
|
||||||
try:
|
try:
|
||||||
self.__photo.tk.call("image", "delete", name)
|
self.__photo.tk.call("image", "delete", name)
|
||||||
except:
|
except:
|
||||||
pass # ignore internal errors
|
pass # ignore internal errors
|
||||||
|
|
||||||
|
|
||||||
def width(self):
|
def width(self):
|
||||||
"""
|
"""
|
||||||
|
@ -249,7 +246,6 @@ class BitmapImage:
|
||||||
"""
|
"""
|
||||||
return self.__size[0]
|
return self.__size[0]
|
||||||
|
|
||||||
|
|
||||||
def height(self):
|
def height(self):
|
||||||
"""
|
"""
|
||||||
Get the height of the image.
|
Get the height of the image.
|
||||||
|
@ -258,7 +254,6 @@ class BitmapImage:
|
||||||
"""
|
"""
|
||||||
return self.__size[1]
|
return self.__size[1]
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
Get the Tkinter bitmap image identifier. This method is automatically
|
Get the Tkinter bitmap image identifier. This method is automatically
|
||||||
|
@ -274,6 +269,7 @@ def getimage(photo):
|
||||||
"""Copies the contents of a PhotoImage to a PIL image memory."""
|
"""Copies the contents of a PhotoImage to a PIL image memory."""
|
||||||
photo.tk.call("PyImagingPhotoGet", photo)
|
photo.tk.call("PyImagingPhotoGet", photo)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Helper for the Image.show method.
|
# Helper for the Image.show method.
|
||||||
|
|
||||||
|
@ -286,7 +282,7 @@ def _show(image, title):
|
||||||
else:
|
else:
|
||||||
self.image = PhotoImage(im, master=master)
|
self.image = PhotoImage(im, master=master)
|
||||||
tkinter.Label.__init__(self, master, image=self.image,
|
tkinter.Label.__init__(self, master, image=self.image,
|
||||||
bg="black", bd=0)
|
bg="black", bd=0)
|
||||||
|
|
||||||
if not tkinter._default_root:
|
if not tkinter._default_root:
|
||||||
raise IOError("tkinter not initialized")
|
raise IOError("tkinter not initialized")
|
||||||
|
|
|
@ -15,16 +15,20 @@
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
class Transform(Image.ImageTransformHandler):
|
class Transform(Image.ImageTransformHandler):
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def getdata(self):
|
def getdata(self):
|
||||||
return self.method, self.data
|
return self.method, self.data
|
||||||
|
|
||||||
def transform(self, size, image, **options):
|
def transform(self, size, image, **options):
|
||||||
# can be overridden
|
# can be overridden
|
||||||
method, data = self.getdata()
|
method, data = self.getdata()
|
||||||
return image.transform(size, method, data, **options)
|
return image.transform(size, method, data, **options)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Define an affine image transform.
|
# Define an affine image transform.
|
||||||
# <p>
|
# <p>
|
||||||
|
@ -43,9 +47,11 @@ class Transform(Image.ImageTransformHandler):
|
||||||
# the first two rows from an affine transform matrix.
|
# the first two rows from an affine transform matrix.
|
||||||
# @see Image#Image.transform
|
# @see Image#Image.transform
|
||||||
|
|
||||||
|
|
||||||
class AffineTransform(Transform):
|
class AffineTransform(Transform):
|
||||||
method = Image.AFFINE
|
method = Image.AFFINE
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Define a transform to extract a subregion from an image.
|
# Define a transform to extract a subregion from an image.
|
||||||
# <p>
|
# <p>
|
||||||
|
@ -68,6 +74,7 @@ class AffineTransform(Transform):
|
||||||
class ExtentTransform(Transform):
|
class ExtentTransform(Transform):
|
||||||
method = Image.EXTENT
|
method = Image.EXTENT
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Define an quad image transform.
|
# Define an quad image transform.
|
||||||
# <p>
|
# <p>
|
||||||
|
@ -83,6 +90,7 @@ class ExtentTransform(Transform):
|
||||||
class QuadTransform(Transform):
|
class QuadTransform(Transform):
|
||||||
method = Image.QUAD
|
method = Image.QUAD
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Define an mesh image transform. A mesh transform consists of one
|
# Define an mesh image transform. A mesh transform consists of one
|
||||||
# or more individual quad transforms.
|
# or more individual quad transforms.
|
||||||
|
|
|
@ -21,30 +21,33 @@ import warnings
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
class HDC:
|
class HDC(object):
|
||||||
"""
|
"""
|
||||||
Wraps a HDC integer. The resulting object can be passed to the
|
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`
|
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
|
||||||
methods.
|
methods.
|
||||||
"""
|
"""
|
||||||
def __init__(self, dc):
|
def __init__(self, dc):
|
||||||
self.dc = dc
|
self.dc = dc
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.dc
|
return self.dc
|
||||||
|
|
||||||
class HWND:
|
|
||||||
|
class HWND(object):
|
||||||
"""
|
"""
|
||||||
Wraps a HWND integer. The resulting object can be passed to the
|
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`
|
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
|
||||||
methods, instead of a DC.
|
methods, instead of a DC.
|
||||||
"""
|
"""
|
||||||
def __init__(self, wnd):
|
def __init__(self, wnd):
|
||||||
self.wnd = wnd
|
self.wnd = wnd
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.wnd
|
return self.wnd
|
||||||
|
|
||||||
|
|
||||||
class Dib:
|
class Dib(object):
|
||||||
"""
|
"""
|
||||||
A Windows bitmap with the given mode and size. The mode can be one of "1",
|
A Windows bitmap with the given mode and size. The mode can be one of "1",
|
||||||
"L", "P", or "RGB".
|
"L", "P", or "RGB".
|
||||||
|
@ -79,13 +82,12 @@ class Dib:
|
||||||
if image:
|
if image:
|
||||||
self.paste(image)
|
self.paste(image)
|
||||||
|
|
||||||
|
|
||||||
def expose(self, handle):
|
def expose(self, handle):
|
||||||
"""
|
"""
|
||||||
Copy the bitmap contents to a device context.
|
Copy the bitmap contents to a device context.
|
||||||
|
|
||||||
:param handle: Device context (HDC), cast to a Python integer, or a HDC
|
:param handle: Device context (HDC), cast to a Python integer, or an
|
||||||
or HWND instance. In PythonWin, you can use the
|
HDC or HWND instance. In PythonWin, you can use the
|
||||||
:py:meth:`CDC.GetHandleAttrib` to get a suitable handle.
|
:py:meth:`CDC.GetHandleAttrib` to get a suitable handle.
|
||||||
"""
|
"""
|
||||||
if isinstance(handle, HWND):
|
if isinstance(handle, HWND):
|
||||||
|
@ -109,7 +111,7 @@ class Dib:
|
||||||
necessary.
|
necessary.
|
||||||
"""
|
"""
|
||||||
if not src:
|
if not src:
|
||||||
src = (0,0) + self.size
|
src = (0, 0) + self.size
|
||||||
if isinstance(handle, HWND):
|
if isinstance(handle, HWND):
|
||||||
dc = self.image.getdc(handle)
|
dc = self.image.getdc(handle)
|
||||||
try:
|
try:
|
||||||
|
@ -120,7 +122,6 @@ class Dib:
|
||||||
result = self.image.draw(handle, dst, src)
|
result = self.image.draw(handle, dst, src)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def query_palette(self, handle):
|
def query_palette(self, handle):
|
||||||
"""
|
"""
|
||||||
Installs the palette associated with the image in the given device
|
Installs the palette associated with the image in the given device
|
||||||
|
@ -146,7 +147,6 @@ class Dib:
|
||||||
result = self.image.query_palette(handle)
|
result = self.image.query_palette(handle)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def paste(self, im, box=None):
|
def paste(self, im, box=None):
|
||||||
"""
|
"""
|
||||||
Paste a PIL image into the bitmap image.
|
Paste a PIL image into the bitmap image.
|
||||||
|
@ -166,7 +166,6 @@ class Dib:
|
||||||
else:
|
else:
|
||||||
self.image.paste(im.im)
|
self.image.paste(im.im)
|
||||||
|
|
||||||
|
|
||||||
def frombytes(self, buffer):
|
def frombytes(self, buffer):
|
||||||
"""
|
"""
|
||||||
Load display memory contents from byte data.
|
Load display memory contents from byte data.
|
||||||
|
@ -176,7 +175,6 @@ class Dib:
|
||||||
"""
|
"""
|
||||||
return self.image.frombytes(buffer)
|
return self.image.frombytes(buffer)
|
||||||
|
|
||||||
|
|
||||||
def tobytes(self):
|
def tobytes(self):
|
||||||
"""
|
"""
|
||||||
Copy display memory contents to bytes object.
|
Copy display memory contents to bytes object.
|
||||||
|
@ -204,10 +202,11 @@ class Dib:
|
||||||
)
|
)
|
||||||
return self.tobytes()
|
return self.tobytes()
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Create a Window with the given title size.
|
# Create a Window with the given title size.
|
||||||
|
|
||||||
class Window:
|
class Window(object):
|
||||||
|
|
||||||
def __init__(self, title="PIL", width=None, height=None):
|
def __init__(self, title="PIL", width=None, height=None):
|
||||||
self.hwnd = Image.core.createwindow(
|
self.hwnd = Image.core.createwindow(
|
||||||
|
@ -235,6 +234,7 @@ class Window:
|
||||||
def mainloop(self):
|
def mainloop(self):
|
||||||
Image.core.eventloop()
|
Image.core.eventloop()
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Create an image window which displays the given image.
|
# Create an image window which displays the given image.
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ from PIL import Image, ImageFile
|
||||||
|
|
||||||
field = re.compile(br"([a-z]*) ([^ \r\n]*)")
|
field = re.compile(br"([a-z]*) ([^ \r\n]*)")
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for IM Tools images.
|
# Image plugin for IM Tools images.
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
# Quick rejection: if there's not a LF among the first
|
# Quick rejection: if there's not a LF among the first
|
||||||
# 100 bytes, this is (probably) not a text header.
|
# 100 bytes, this is (probably) not a text header.
|
||||||
|
|
||||||
if not b"\n" in self.fp.read(100):
|
if b"\n" not in self.fp.read(100):
|
||||||
raise SyntaxError("not an IM file")
|
raise SyntaxError("not an IM file")
|
||||||
self.fp.seek(0)
|
self.fp.seek(0)
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
if s == b'\x0C':
|
if s == b'\x0C':
|
||||||
|
|
||||||
# image data begins
|
# image data begins
|
||||||
self.tile = [("raw", (0,0)+self.size,
|
self.tile = [("raw", (0, 0)+self.size,
|
||||||
self.fp.tell(),
|
self.fp.tell(),
|
||||||
(self.mode, 0, 1))]
|
(self.mode, 0, 1))]
|
||||||
|
|
||||||
|
@ -68,12 +69,12 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
if len(s) == 1 or len(s) > 100:
|
if len(s) == 1 or len(s) > 100:
|
||||||
break
|
break
|
||||||
if s[0] == b"*":
|
if s[0] == b"*":
|
||||||
continue # comment
|
continue # comment
|
||||||
|
|
||||||
m = field.match(s)
|
m = field.match(s)
|
||||||
if not m:
|
if not m:
|
||||||
break
|
break
|
||||||
k, v = m.group(1,2)
|
k, v = m.group(1, 2)
|
||||||
if k == "width":
|
if k == "width":
|
||||||
xsize = int(v)
|
xsize = int(v)
|
||||||
self.size = xsize, ysize
|
self.size = xsize, ysize
|
||||||
|
|
|
@ -222,7 +222,7 @@ def getiptcinfo(im):
|
||||||
offset += 2
|
offset += 2
|
||||||
# resource name (usually empty)
|
# resource name (usually empty)
|
||||||
name_len = i8(app[offset])
|
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
|
offset = 1 + offset + name_len
|
||||||
if offset & 1:
|
if offset & 1:
|
||||||
offset += 1
|
offset += 1
|
||||||
|
@ -251,7 +251,7 @@ def getiptcinfo(im):
|
||||||
return None # no properties
|
return None # no properties
|
||||||
|
|
||||||
# create an IptcImagePlugin object without initializing it
|
# create an IptcImagePlugin object without initializing it
|
||||||
class FakeImage:
|
class FakeImage(object):
|
||||||
pass
|
pass
|
||||||
im = FakeImage()
|
im = FakeImage()
|
||||||
im.__class__ = IptcImageFile
|
im.__class__ = IptcImageFile
|
||||||
|
|
|
@ -12,14 +12,13 @@
|
||||||
#
|
#
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
import struct
|
import struct
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
def _parse_codestream(fp):
|
def _parse_codestream(fp):
|
||||||
"""Parse the JPEG 2000 codestream to extract the size and component
|
"""Parse the JPEG 2000 codestream to extract the size and component
|
||||||
|
@ -208,8 +207,8 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return (prefix[:4] == b'\xff\x4f\xff\x51'
|
return (prefix[:4] == b'\xff\x4f\xff\x51' or
|
||||||
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# JPEG (JFIF) file handling
|
# 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)
|
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
|
||||||
#
|
#
|
||||||
# History:
|
# History:
|
||||||
|
@ -115,7 +115,8 @@ def APP(self, marker):
|
||||||
elif marker == 0xFFE2 and s[:4] == b"MPF\0":
|
elif marker == 0xFFE2 and s[:4] == b"MPF\0":
|
||||||
# extract MPO information
|
# extract MPO information
|
||||||
self.info["mp"] = s[4:]
|
self.info["mp"] = s[4:]
|
||||||
# offset is current location minus buffer size plus constant header size
|
# offset is current location minus buffer size
|
||||||
|
# plus constant header size
|
||||||
self.info["mpoffset"] = self.fp.tell() - n + 4
|
self.info["mpoffset"] = self.fp.tell() - n + 4
|
||||||
|
|
||||||
|
|
||||||
|
@ -321,7 +322,8 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
rawmode = self.mode
|
rawmode = self.mode
|
||||||
if self.mode == "CMYK":
|
if self.mode == "CMYK":
|
||||||
rawmode = "CMYK;I" # assume adobe conventions
|
rawmode = "CMYK;I" # assume adobe conventions
|
||||||
self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
|
self.tile = [("jpeg", (0, 0) + self.size, 0,
|
||||||
|
(rawmode, ""))]
|
||||||
# self.__offset = self.fp.tell()
|
# self.__offset = self.fp.tell()
|
||||||
break
|
break
|
||||||
s = self.fp.read(1)
|
s = self.fp.read(1)
|
||||||
|
@ -353,7 +355,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
scale = s
|
scale = s
|
||||||
|
|
||||||
self.tile = [(d, e, o, a)]
|
self.tile = [(d, e, o, a)]
|
||||||
self.decoderconfig = (scale, 1)
|
self.decoderconfig = (scale, 0)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -452,13 +454,13 @@ def _getmp(self):
|
||||||
data = self.info["mp"]
|
data = self.info["mp"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
file = io.BytesIO(data)
|
file_contents = io.BytesIO(data)
|
||||||
head = file.read(8)
|
head = file_contents.read(8)
|
||||||
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
||||||
mp = {}
|
mp = {}
|
||||||
# process dictionary
|
# process dictionary
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
info.load(file)
|
info.load(file_contents)
|
||||||
for key, value in info.items():
|
for key, value in info.items():
|
||||||
mp[key] = _fixup(value)
|
mp[key] = _fixup(value)
|
||||||
# it's an error not to have a number of images
|
# it's an error not to have a number of images
|
||||||
|
@ -472,14 +474,18 @@ def _getmp(self):
|
||||||
for entrynum in range(0, quant):
|
for entrynum in range(0, quant):
|
||||||
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
|
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
|
||||||
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
|
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
|
||||||
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2')
|
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
||||||
|
'EntryNo2')
|
||||||
mpentry = dict(zip(labels, unpackedentry))
|
mpentry = dict(zip(labels, unpackedentry))
|
||||||
mpentryattr = {
|
mpentryattr = {
|
||||||
'DependentParentImageFlag': bool(mpentry['Attribute'] & (1<<31)),
|
'DependentParentImageFlag': bool(mpentry['Attribute'] &
|
||||||
'DependentChildImageFlag': bool(mpentry['Attribute'] & (1<<30)),
|
(1 << 31)),
|
||||||
'RepresentativeImageFlag': bool(mpentry['Attribute'] & (1<<29)),
|
'DependentChildImageFlag': bool(mpentry['Attribute'] &
|
||||||
'Reserved': (mpentry['Attribute'] & (3<<27)) >> 27,
|
(1 << 30)),
|
||||||
'ImageDataFormat': (mpentry['Attribute'] & (7<<24)) >> 24,
|
'RepresentativeImageFlag': bool(mpentry['Attribute'] &
|
||||||
|
(1 << 29)),
|
||||||
|
'Reserved': (mpentry['Attribute'] & (3 << 27)) >> 27,
|
||||||
|
'ImageDataFormat': (mpentry['Attribute'] & (7 << 24)) >> 24,
|
||||||
'MPType': mpentry['Attribute'] & 0x00FFFFFF
|
'MPType': mpentry['Attribute'] & 0x00FFFFFF
|
||||||
}
|
}
|
||||||
if mpentryattr['ImageDataFormat'] == 0:
|
if mpentryattr['ImageDataFormat'] == 0:
|
||||||
|
@ -496,7 +502,7 @@ def _getmp(self):
|
||||||
0x030000: 'Baseline MP Primary Image'
|
0x030000: 'Baseline MP Primary Image'
|
||||||
}
|
}
|
||||||
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
|
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
|
||||||
'Unknown')
|
'Unknown')
|
||||||
mpentry['Attribute'] = mpentryattr
|
mpentry['Attribute'] = mpentryattr
|
||||||
mpentries.append(mpentry)
|
mpentries.append(mpentry)
|
||||||
mp[0xB002] = mpentries
|
mp[0xB002] = mpentries
|
||||||
|
@ -530,11 +536,10 @@ zigzag_index = ( 0, 1, 5, 6, 14, 15, 27, 28,
|
||||||
21, 34, 37, 47, 50, 56, 59, 61,
|
21, 34, 37, 47, 50, 56, 59, 61,
|
||||||
35, 36, 48, 49, 57, 58, 62, 63)
|
35, 36, 48, 49, 57, 58, 62, 63)
|
||||||
|
|
||||||
samplings = {
|
samplings = {(1, 1, 1, 1, 1, 1): 0,
|
||||||
(1, 1, 1, 1, 1, 1): 0,
|
|
||||||
(2, 1, 1, 1, 1, 1): 1,
|
(2, 1, 1, 1, 1, 1): 1,
|
||||||
(2, 2, 1, 1, 1, 1): 2,
|
(2, 2, 1, 1, 1, 1): 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def convert_dict_qtables(qtables):
|
def convert_dict_qtables(qtables):
|
||||||
|
@ -545,6 +550,15 @@ def convert_dict_qtables(qtables):
|
||||||
|
|
||||||
|
|
||||||
def get_sampling(im):
|
def get_sampling(im):
|
||||||
|
# There's no subsampling when image have only 1 layer
|
||||||
|
# (grayscale images) or when they are CMYK (4 layers),
|
||||||
|
# so set subsampling to default value.
|
||||||
|
#
|
||||||
|
# NOTE: currently Pillow can't encode JPEG to YCCK format.
|
||||||
|
# If YCCK support is added in the future, subsampling code will have
|
||||||
|
# to be updated (here and in JpegEncode.c) to deal with 4 layers.
|
||||||
|
if not hasattr(im, 'layers') or im.layers in (1, 4):
|
||||||
|
return -1
|
||||||
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
|
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
|
||||||
return samplings.get(sampling, -1)
|
return samplings.get(sampling, -1)
|
||||||
|
|
||||||
|
@ -589,7 +603,8 @@ def _save(im, fp, filename):
|
||||||
subsampling = 2
|
subsampling = 2
|
||||||
elif subsampling == "keep":
|
elif subsampling == "keep":
|
||||||
if im.format != "JPEG":
|
if im.format != "JPEG":
|
||||||
raise ValueError("Cannot use 'keep' when original image is not a JPEG")
|
raise ValueError(
|
||||||
|
"Cannot use 'keep' when original image is not a JPEG")
|
||||||
subsampling = get_sampling(im)
|
subsampling = get_sampling(im)
|
||||||
|
|
||||||
def validate_qtables(qtables):
|
def validate_qtables(qtables):
|
||||||
|
@ -623,7 +638,8 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
if qtables == "keep":
|
if qtables == "keep":
|
||||||
if im.format != "JPEG":
|
if im.format != "JPEG":
|
||||||
raise ValueError("Cannot use 'keep' when original image is not a JPEG")
|
raise ValueError(
|
||||||
|
"Cannot use 'keep' when original image is not a JPEG")
|
||||||
qtables = getattr(im, "quantization", None)
|
qtables = getattr(im, "quantization", None)
|
||||||
qtables = validate_qtables(qtables)
|
qtables = validate_qtables(qtables)
|
||||||
|
|
||||||
|
@ -641,7 +657,8 @@ def _save(im, fp, filename):
|
||||||
i = 1
|
i = 1
|
||||||
for marker in markers:
|
for marker in markers:
|
||||||
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
|
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
|
||||||
extra += b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker
|
extra += (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) +
|
||||||
|
o8(len(markers)) + marker)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
# get keyword arguments
|
# get keyword arguments
|
||||||
|
@ -667,7 +684,8 @@ def _save(im, fp, filename):
|
||||||
# https://github.com/jdriscoll/django-imagekit/issues/50
|
# https://github.com/jdriscoll/django-imagekit/issues/50
|
||||||
bufsize = 0
|
bufsize = 0
|
||||||
if "optimize" in info or "progressive" in info or "progression" in info:
|
if "optimize" in info or "progressive" in info or "progression" in info:
|
||||||
if quality >= 95:
|
# 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]
|
bufsize = 2 * im.size[0] * im.size[1]
|
||||||
else:
|
else:
|
||||||
bufsize = im.size[0] * im.size[1]
|
bufsize = im.size[0] * im.size[1]
|
||||||
|
@ -686,7 +704,7 @@ def _save_cjpeg(im, fp, filename):
|
||||||
tempfile = im._dump()
|
tempfile = im._dump()
|
||||||
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
||||||
try:
|
try:
|
||||||
os.unlink(file)
|
os.unlink(tempfile)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,8 @@ You can get the quantization tables of a JPEG with::
|
||||||
|
|
||||||
im.quantization
|
im.quantization
|
||||||
|
|
||||||
This will return a dict with a number of arrays. You can pass this dict directly
|
This will return a dict with a number of arrays. You can pass this dict
|
||||||
as the qtables argument when saving a JPEG.
|
directly as the qtables argument when saving a JPEG.
|
||||||
|
|
||||||
The tables format between im.quantization and quantization in presets differ in
|
The tables format between im.quantization and quantization in presets differ in
|
||||||
3 ways:
|
3 ways:
|
||||||
|
@ -67,7 +67,7 @@ Libjpeg ref.: http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
||||||
"""
|
"""
|
||||||
|
|
||||||
presets = {
|
presets = {
|
||||||
'web_low': {'subsampling': 2, # "4:1:1"
|
'web_low': {'subsampling': 2, # "4:1:1"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[20, 16, 25, 39, 50, 46, 62, 68,
|
[20, 16, 25, 39, 50, 46, 62, 68,
|
||||||
16, 18, 23, 38, 38, 53, 65, 68,
|
16, 18, 23, 38, 38, 53, 65, 68,
|
||||||
|
@ -86,7 +86,7 @@ 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]
|
68, 68, 68, 68, 68, 68, 68, 68]
|
||||||
]},
|
]},
|
||||||
'web_medium': {'subsampling': 2, # "4:1:1"
|
'web_medium': {'subsampling': 2, # "4:1:1"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[16, 11, 11, 16, 23, 27, 31, 30,
|
[16, 11, 11, 16, 23, 27, 31, 30,
|
||||||
11, 12, 12, 15, 20, 23, 23, 30,
|
11, 12, 12, 15, 20, 23, 23, 30,
|
||||||
|
@ -105,7 +105,7 @@ presets = {
|
||||||
38, 35, 46, 53, 64, 64, 64, 64,
|
38, 35, 46, 53, 64, 64, 64, 64,
|
||||||
48, 43, 53, 64, 64, 64, 64, 64]
|
48, 43, 53, 64, 64, 64, 64, 64]
|
||||||
]},
|
]},
|
||||||
'web_high': {'subsampling': 0, # "4:4:4"
|
'web_high': {'subsampling': 0, # "4:4:4"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[ 6, 4, 4, 6, 9, 11, 12, 16,
|
[ 6, 4, 4, 6, 9, 11, 12, 16,
|
||||||
4, 5, 5, 6, 8, 10, 12, 12,
|
4, 5, 5, 6, 8, 10, 12, 12,
|
||||||
|
@ -124,7 +124,7 @@ 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]
|
31, 31, 31, 31, 31, 31, 31, 31]
|
||||||
]},
|
]},
|
||||||
'web_very_high': {'subsampling': 0, # "4:4:4"
|
'web_very_high': {'subsampling': 0, # "4:4:4"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[ 2, 2, 2, 2, 3, 4, 5, 6,
|
[ 2, 2, 2, 2, 3, 4, 5, 6,
|
||||||
2, 2, 2, 2, 3, 4, 5, 6,
|
2, 2, 2, 2, 3, 4, 5, 6,
|
||||||
|
@ -143,7 +143,7 @@ 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]
|
15, 12, 12, 12, 12, 12, 12, 12]
|
||||||
]},
|
]},
|
||||||
'web_maximum': {'subsampling': 0, # "4:4:4"
|
'web_maximum': {'subsampling': 0, # "4:4:4"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[ 1, 1, 1, 1, 1, 1, 1, 1,
|
[ 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
@ -162,7 +162,7 @@ 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]
|
3, 3, 3, 3, 3, 3, 3, 3]
|
||||||
]},
|
]},
|
||||||
'low': {'subsampling': 2, # "4:1:1"
|
'low': {'subsampling': 2, # "4:1:1"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[18, 14, 14, 21, 30, 35, 34, 17,
|
[18, 14, 14, 21, 30, 35, 34, 17,
|
||||||
14, 16, 16, 19, 26, 23, 12, 12,
|
14, 16, 16, 19, 26, 23, 12, 12,
|
||||||
|
@ -181,7 +181,7 @@ presets = {
|
||||||
17, 12, 12, 12, 12, 12, 12, 12,
|
17, 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"
|
'medium': {'subsampling': 2, # "4:1:1"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[12, 8, 8, 12, 17, 21, 24, 17,
|
[12, 8, 8, 12, 17, 21, 24, 17,
|
||||||
8, 9, 9, 11, 15, 19, 12, 12,
|
8, 9, 9, 11, 15, 19, 12, 12,
|
||||||
|
@ -200,7 +200,7 @@ presets = {
|
||||||
17, 12, 12, 12, 12, 12, 12, 12,
|
17, 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"
|
'high': {'subsampling': 0, # "4:4:4"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[ 6, 4, 4, 6, 9, 11, 12, 16,
|
[ 6, 4, 4, 6, 9, 11, 12, 16,
|
||||||
4, 5, 5, 6, 8, 10, 12, 12,
|
4, 5, 5, 6, 8, 10, 12, 12,
|
||||||
|
@ -219,7 +219,7 @@ presets = {
|
||||||
17, 12, 12, 12, 12, 12, 12, 12,
|
17, 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"
|
'maximum': {'subsampling': 0, # "4:4:4"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[ 2, 2, 2, 2, 3, 4, 5, 6,
|
[ 2, 2, 2, 2, 3, 4, 5, 6,
|
||||||
2, 2, 2, 2, 3, 4, 5, 6,
|
2, 2, 2, 2, 3, 4, 5, 6,
|
||||||
|
|
|
@ -21,9 +21,11 @@ __version__ = "0.2"
|
||||||
import struct
|
import struct
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
|
|
||||||
|
|
||||||
def _accept(s):
|
def _accept(s):
|
||||||
return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04"
|
return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for McIdas area images.
|
# Image plugin for McIdas area images.
|
||||||
|
|
||||||
|
@ -47,10 +49,12 @@ class McIdasImageFile(ImageFile.ImageFile):
|
||||||
mode = rawmode = "L"
|
mode = rawmode = "L"
|
||||||
elif w[11] == 2:
|
elif w[11] == 2:
|
||||||
# FIXME: add memory map support
|
# FIXME: add memory map support
|
||||||
mode = "I"; rawmode = "I;16B"
|
mode = "I"
|
||||||
|
rawmode = "I;16B"
|
||||||
elif w[11] == 4:
|
elif w[11] == 4:
|
||||||
# FIXME: add memory map support
|
# FIXME: add memory map support
|
||||||
mode = "I"; rawmode = "I;32B"
|
mode = "I"
|
||||||
|
rawmode = "I;32B"
|
||||||
else:
|
else:
|
||||||
raise SyntaxError("unsupported McIdas format")
|
raise SyntaxError("unsupported McIdas format")
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ __version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
from PIL import Image, TiffImagePlugin
|
||||||
from PIL.OleFileIO import *
|
from PIL.OleFileIO import MAGIC, OleFileIO
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -31,6 +31,7 @@ from PIL.OleFileIO import *
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:8] == MAGIC
|
return prefix[:8] == MAGIC
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Microsoft's Image Composer file format.
|
# Image plugin for Microsoft's Image Composer file format.
|
||||||
|
|
||||||
|
@ -53,9 +54,9 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
# best way to identify MIC files, but what the... ;-)
|
# best way to identify MIC files, but what the... ;-)
|
||||||
|
|
||||||
self.images = []
|
self.images = []
|
||||||
for file in self.ole.listdir():
|
for path in self.ole.listdir():
|
||||||
if file[1:] and file[0][-4:] == ".ACI" and file[1] == "Image":
|
if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image":
|
||||||
self.images.append(file)
|
self.images.append(path)
|
||||||
|
|
||||||
# if we didn't find any images, this is probably not
|
# if we didn't find any images, this is probably not
|
||||||
# an MIC file.
|
# an MIC file.
|
||||||
|
@ -70,6 +71,10 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
|
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return len(self.images)
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -18,10 +18,11 @@ __version__ = "0.1"
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
from PIL._binary import i8
|
from PIL._binary import i8
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Bitstream parser
|
# Bitstream parser
|
||||||
|
|
||||||
class BitStream:
|
class BitStream(object):
|
||||||
|
|
||||||
def __init__(self, fp):
|
def __init__(self, fp):
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
|
@ -52,6 +53,7 @@ class BitStream:
|
||||||
self.bits = self.bits - bits
|
self.bits = self.bits - bits
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for MPEG streams. This plugin can identify a stream,
|
# Image plugin for MPEG streams. This plugin can identify a stream,
|
||||||
# but it cannot read it.
|
# but it cannot read it.
|
||||||
|
|
|
@ -22,13 +22,16 @@ __version__ = "0.1"
|
||||||
|
|
||||||
from PIL import Image, JpegImagePlugin
|
from PIL import Image, JpegImagePlugin
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return JpegImagePlugin._accept(prefix)
|
return JpegImagePlugin._accept(prefix)
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
# Note that we can only save the current frame at present
|
# Note that we can only save the current frame at present
|
||||||
return JpegImagePlugin._save(im, fp, filename)
|
return JpegImagePlugin._save(im, fp, filename)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for MPO images.
|
# Image plugin for MPO images.
|
||||||
|
|
||||||
|
@ -38,19 +41,19 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
format_description = "MPO (CIPA DC-007)"
|
format_description = "MPO (CIPA DC-007)"
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
self.fp.seek(0) # prep the fp in order to pass the JPEG test
|
self.fp.seek(0) # prep the fp in order to pass the JPEG test
|
||||||
JpegImagePlugin.JpegImageFile._open(self)
|
JpegImagePlugin.JpegImageFile._open(self)
|
||||||
self.mpinfo = self._getmp()
|
self.mpinfo = self._getmp()
|
||||||
self.__framecount = self.mpinfo[0xB001]
|
self.__framecount = self.mpinfo[0xB001]
|
||||||
self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] \
|
self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset']
|
||||||
for mpent in self.mpinfo[0xB002]]
|
for mpent in self.mpinfo[0xB002]]
|
||||||
self.__mpoffsets[0] = 0
|
self.__mpoffsets[0] = 0
|
||||||
# Note that the following assertion will only be invalid if something
|
# Note that the following assertion will only be invalid if something
|
||||||
# gets broken within JpegImagePlugin.
|
# gets broken within JpegImagePlugin.
|
||||||
assert self.__framecount == len(self.__mpoffsets)
|
assert self.__framecount == len(self.__mpoffsets)
|
||||||
del self.info['mpoffset'] # no longer needed
|
del self.info['mpoffset'] # no longer needed
|
||||||
self.__fp = self.fp # FIXME: hack
|
self.__fp = self.fp # FIXME: hack
|
||||||
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
|
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
|
||||||
self.__frame = 0
|
self.__frame = 0
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
# for now we can only handle reading and individual frame extraction
|
# for now we can only handle reading and individual frame extraction
|
||||||
|
@ -59,6 +62,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
def load_seek(self, pos):
|
def load_seek(self, pos):
|
||||||
self.__fp.seek(pos)
|
self.__fp.seek(pos)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return self.__framecount
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame < 0 or frame >= self.__framecount:
|
if frame < 0 or frame >= self.__framecount:
|
||||||
raise EOFError("no more images in MPO file")
|
raise EOFError("no more images in MPO file")
|
||||||
|
@ -79,7 +86,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
|
|
||||||
# Note that since MPO shares a factory with JPEG, we do not need to do a
|
# Note that since MPO shares a factory with JPEG, we do not need to do a
|
||||||
# separate registration for it here.
|
# separate registration for it here.
|
||||||
#Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
|
# Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
|
||||||
Image.register_save("MPO", _save)
|
Image.register_save("MPO", _save)
|
||||||
|
|
||||||
Image.register_extension("MPO", ".mpo")
|
Image.register_extension("MPO", ".mpo")
|
||||||
|
|
|
@ -27,9 +27,11 @@ from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] in [b"DanM", b"LinS"]
|
return prefix[:4] in [b"DanM", b"LinS"]
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Windows MSP images. This plugin supports both
|
# Image plugin for Windows MSP images. This plugin supports both
|
||||||
# uncompressed (Windows 1.0).
|
# uncompressed (Windows 1.0).
|
||||||
|
@ -47,25 +49,26 @@ class MspImageFile(ImageFile.ImageFile):
|
||||||
raise SyntaxError("not an MSP file")
|
raise SyntaxError("not an MSP file")
|
||||||
|
|
||||||
# Header checksum
|
# Header checksum
|
||||||
sum = 0
|
checksum = 0
|
||||||
for i in range(0, 32, 2):
|
for i in range(0, 32, 2):
|
||||||
sum = sum ^ i16(s[i:i+2])
|
checksum = checksum ^ i16(s[i:i+2])
|
||||||
if sum != 0:
|
if checksum != 0:
|
||||||
raise SyntaxError("bad MSP checksum")
|
raise SyntaxError("bad MSP checksum")
|
||||||
|
|
||||||
self.mode = "1"
|
self.mode = "1"
|
||||||
self.size = i16(s[4:]), i16(s[6:])
|
self.size = i16(s[4:]), i16(s[6:])
|
||||||
|
|
||||||
if s[:4] == b"DanM":
|
if s[:4] == b"DanM":
|
||||||
self.tile = [("raw", (0,0)+self.size, 32, ("1", 0, 1))]
|
self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
|
||||||
else:
|
else:
|
||||||
self.tile = [("msp", (0,0)+self.size, 32+2*self.size[1], None)]
|
self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)]
|
||||||
|
|
||||||
#
|
#
|
||||||
# write MSP files (uncompressed only)
|
# write MSP files (uncompressed only)
|
||||||
|
|
||||||
o16 = _binary.o16le
|
o16 = _binary.o16le
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
|
|
||||||
if im.mode != "1":
|
if im.mode != "1":
|
||||||
|
@ -74,23 +77,23 @@ def _save(im, fp, filename):
|
||||||
# create MSP header
|
# create MSP header
|
||||||
header = [0] * 16
|
header = [0] * 16
|
||||||
|
|
||||||
header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1
|
header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1
|
||||||
header[2], header[3] = im.size
|
header[2], header[3] = im.size
|
||||||
header[4], header[5] = 1, 1
|
header[4], header[5] = 1, 1
|
||||||
header[6], header[7] = 1, 1
|
header[6], header[7] = 1, 1
|
||||||
header[8], header[9] = im.size
|
header[8], header[9] = im.size
|
||||||
|
|
||||||
sum = 0
|
checksum = 0
|
||||||
for h in header:
|
for h in header:
|
||||||
sum = sum ^ h
|
checksum = checksum ^ h
|
||||||
header[12] = sum # FIXME: is this the right field?
|
header[12] = checksum # FIXME: is this the right field?
|
||||||
|
|
||||||
# header
|
# header
|
||||||
for h in header:
|
for h in header:
|
||||||
fp.write(o16(h))
|
fp.write(o16(h))
|
||||||
|
|
||||||
# image body
|
# image body
|
||||||
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 32, ("1", 0, 1))])
|
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
|
||||||
|
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
1125
PIL/OleFileIO.py
Normal file → Executable file
1125
PIL/OleFileIO.py
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
|
@ -15,14 +15,13 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from PIL import EpsImagePlugin
|
from PIL import EpsImagePlugin
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Simple Postscript graphics interface.
|
# Simple Postscript graphics interface.
|
||||||
|
|
||||||
class PSDraw:
|
class PSDraw(object):
|
||||||
"""
|
"""
|
||||||
Sets up printing to the given file. If **file** is omitted,
|
Sets up printing to the given file. If **file** is omitted,
|
||||||
:py:attr:`sys.stdout` is assumed.
|
:py:attr:`sys.stdout` is assumed.
|
||||||
|
@ -34,25 +33,31 @@ class PSDraw:
|
||||||
fp = sys.stdout
|
fp = sys.stdout
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
|
|
||||||
def begin_document(self, id = None):
|
def _fp_write(self, to_write):
|
||||||
|
if bytes is str:
|
||||||
|
self.fp.write(to_write)
|
||||||
|
else:
|
||||||
|
self.fp.write(bytes(to_write, 'UTF-8'))
|
||||||
|
|
||||||
|
def begin_document(self, id=None):
|
||||||
"""Set up printing of a document. (Write Postscript DSC header.)"""
|
"""Set up printing of a document. (Write Postscript DSC header.)"""
|
||||||
# FIXME: incomplete
|
# FIXME: incomplete
|
||||||
self.fp.write("%!PS-Adobe-3.0\n"
|
self._fp_write("%!PS-Adobe-3.0\n"
|
||||||
"save\n"
|
"save\n"
|
||||||
"/showpage { } def\n"
|
"/showpage { } def\n"
|
||||||
"%%EndComments\n"
|
"%%EndComments\n"
|
||||||
"%%BeginDocument\n")
|
"%%BeginDocument\n")
|
||||||
#self.fp.write(ERROR_PS) # debugging!
|
# self.fp_write(ERROR_PS) # debugging!
|
||||||
self.fp.write(EDROFF_PS)
|
self._fp_write(EDROFF_PS)
|
||||||
self.fp.write(VDI_PS)
|
self._fp_write(VDI_PS)
|
||||||
self.fp.write("%%EndProlog\n")
|
self._fp_write("%%EndProlog\n")
|
||||||
self.isofont = {}
|
self.isofont = {}
|
||||||
|
|
||||||
def end_document(self):
|
def end_document(self):
|
||||||
"""Ends printing. (Write Postscript DSC footer.)"""
|
"""Ends printing. (Write Postscript DSC footer.)"""
|
||||||
self.fp.write("%%EndDocument\n"
|
self._fp_write("%%EndDocument\n"
|
||||||
"restore showpage\n"
|
"restore showpage\n"
|
||||||
"%%End\n")
|
"%%End\n")
|
||||||
if hasattr(self.fp, "flush"):
|
if hasattr(self.fp, "flush"):
|
||||||
self.fp.flush()
|
self.fp.flush()
|
||||||
|
|
||||||
|
@ -65,18 +70,11 @@ class PSDraw:
|
||||||
"""
|
"""
|
||||||
if font not in self.isofont:
|
if font not in self.isofont:
|
||||||
# reencode font
|
# reencode font
|
||||||
self.fp.write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %\
|
self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %
|
||||||
(font, font))
|
(font, font))
|
||||||
self.isofont[font] = 1
|
self.isofont[font] = 1
|
||||||
# rough
|
# rough
|
||||||
self.fp.write("/F0 %d /PSDraw-%s F\n" % (size, font))
|
self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font))
|
||||||
|
|
||||||
def setink(self, ink):
|
|
||||||
"""
|
|
||||||
.. warning:: This has been in the PIL API for ages but was never implemented.
|
|
||||||
|
|
||||||
"""
|
|
||||||
print("*** NOT YET IMPLEMENTED ***")
|
|
||||||
|
|
||||||
def line(self, xy0, xy1):
|
def line(self, xy0, xy1):
|
||||||
"""
|
"""
|
||||||
|
@ -85,7 +83,7 @@ class PSDraw:
|
||||||
left corner of the page).
|
left corner of the page).
|
||||||
"""
|
"""
|
||||||
xy = xy0 + xy1
|
xy = xy0 + xy1
|
||||||
self.fp.write("%d %d %d %d Vl\n" % xy)
|
self._fp_write("%d %d %d %d Vl\n" % xy)
|
||||||
|
|
||||||
def rectangle(self, box):
|
def rectangle(self, box):
|
||||||
"""
|
"""
|
||||||
|
@ -100,7 +98,7 @@ class PSDraw:
|
||||||
|
|
||||||
%d %d M %d %d 0 Vr\n
|
%d %d M %d %d 0 Vr\n
|
||||||
"""
|
"""
|
||||||
self.fp.write("%d %d M %d %d 0 Vr\n" % box)
|
self._fp_write("%d %d M %d %d 0 Vr\n" % box)
|
||||||
|
|
||||||
def text(self, xy, text):
|
def text(self, xy, text):
|
||||||
"""
|
"""
|
||||||
|
@ -110,16 +108,16 @@ class PSDraw:
|
||||||
text = "\\(".join(text.split("("))
|
text = "\\(".join(text.split("("))
|
||||||
text = "\\)".join(text.split(")"))
|
text = "\\)".join(text.split(")"))
|
||||||
xy = xy + (text,)
|
xy = xy + (text,)
|
||||||
self.fp.write("%d %d M (%s) S\n" % xy)
|
self._fp_write("%d %d M (%s) S\n" % xy)
|
||||||
|
|
||||||
def image(self, box, im, dpi = None):
|
def image(self, box, im, dpi=None):
|
||||||
"""Draw a PIL image, centered in the given box."""
|
"""Draw a PIL image, centered in the given box."""
|
||||||
# default resolution depends on mode
|
# default resolution depends on mode
|
||||||
if not dpi:
|
if not dpi:
|
||||||
if im.mode == "1":
|
if im.mode == "1":
|
||||||
dpi = 200 # fax
|
dpi = 200 # fax
|
||||||
else:
|
else:
|
||||||
dpi = 100 # greyscale
|
dpi = 100 # greyscale
|
||||||
# image size (on paper)
|
# image size (on paper)
|
||||||
x = float(im.size[0] * 72) / dpi
|
x = float(im.size[0] * 72) / dpi
|
||||||
y = float(im.size[1] * 72) / dpi
|
y = float(im.size[1] * 72) / dpi
|
||||||
|
@ -127,19 +125,21 @@ class PSDraw:
|
||||||
xmax = float(box[2] - box[0])
|
xmax = float(box[2] - box[0])
|
||||||
ymax = float(box[3] - box[1])
|
ymax = float(box[3] - box[1])
|
||||||
if x > xmax:
|
if x > xmax:
|
||||||
y = y * xmax / x; x = xmax
|
y = y * xmax / x
|
||||||
|
x = xmax
|
||||||
if y > ymax:
|
if y > ymax:
|
||||||
x = x * ymax / y; y = ymax
|
x = x * ymax / y
|
||||||
|
y = ymax
|
||||||
dx = (xmax - x) / 2 + box[0]
|
dx = (xmax - x) / 2 + box[0]
|
||||||
dy = (ymax - y) / 2 + box[1]
|
dy = (ymax - y) / 2 + box[1]
|
||||||
self.fp.write("gsave\n%f %f translate\n" % (dx, dy))
|
self._fp_write("gsave\n%f %f translate\n" % (dx, dy))
|
||||||
if (x, y) != im.size:
|
if (x, y) != im.size:
|
||||||
# EpsImagePlugin._save prints the image at (0,0,xsize,ysize)
|
# EpsImagePlugin._save prints the image at (0,0,xsize,ysize)
|
||||||
sx = x / im.size[0]
|
sx = x / im.size[0]
|
||||||
sy = y / im.size[1]
|
sy = y / im.size[1]
|
||||||
self.fp.write("%f %f scale\n" % (sx, sy))
|
self._fp_write("%f %f scale\n" % (sx, sy))
|
||||||
EpsImagePlugin._save(im, self.fp, None, 0)
|
EpsImagePlugin._save(im, self.fp, None, 0)
|
||||||
self.fp.write("\ngrestore\n")
|
self._fp_write("\ngrestore\n")
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Postscript driver
|
# Postscript driver
|
||||||
|
|
|
@ -15,10 +15,11 @@
|
||||||
|
|
||||||
from PIL._binary import o8
|
from PIL._binary import o8
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# File handler for Teragon-style palette files.
|
# File handler for Teragon-style palette files.
|
||||||
|
|
||||||
class PaletteFile:
|
class PaletteFile(object):
|
||||||
|
|
||||||
rawmode = "RGB"
|
rawmode = "RGB"
|
||||||
|
|
||||||
|
@ -49,7 +50,6 @@ class PaletteFile:
|
||||||
|
|
||||||
self.palette = b"".join(self.palette)
|
self.palette = b"".join(self.palette)
|
||||||
|
|
||||||
|
|
||||||
def getpalette(self):
|
def getpalette(self):
|
||||||
|
|
||||||
return self.palette, self.rawmode
|
return self.palette, self.rawmode
|
||||||
|
|
|
@ -22,6 +22,7 @@ from PIL import Image, ImageFile, _binary
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for PhotoCD images. This plugin only reads the 768x512
|
# Image plugin for PhotoCD images. This plugin only reads the 768x512
|
||||||
# image from the file; higher resolutions are encoded in a proprietary
|
# image from the file; higher resolutions are encoded in a proprietary
|
||||||
|
@ -43,32 +44,13 @@ class PcdImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
orientation = i8(s[1538]) & 3
|
orientation = i8(s[1538]) & 3
|
||||||
if orientation == 1:
|
if orientation == 1:
|
||||||
self.tile_post_rotate = 90 # hack
|
self.tile_post_rotate = 90 # hack
|
||||||
elif orientation == 3:
|
elif orientation == 3:
|
||||||
self.tile_post_rotate = -90
|
self.tile_post_rotate = -90
|
||||||
|
|
||||||
self.mode = "RGB"
|
self.mode = "RGB"
|
||||||
self.size = 768, 512 # FIXME: not correct for rotated images!
|
self.size = 768, 512 # FIXME: not correct for rotated images!
|
||||||
self.tile = [("pcd", (0,0)+self.size, 96*2048, None)]
|
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
|
# registry
|
||||||
|
|
|
@ -23,20 +23,20 @@ from PIL import _binary
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# declarations
|
# declarations
|
||||||
|
|
||||||
PCF_MAGIC = 0x70636601 # "\x01fcp"
|
PCF_MAGIC = 0x70636601 # "\x01fcp"
|
||||||
|
|
||||||
PCF_PROPERTIES = (1<<0)
|
PCF_PROPERTIES = (1 << 0)
|
||||||
PCF_ACCELERATORS = (1<<1)
|
PCF_ACCELERATORS = (1 << 1)
|
||||||
PCF_METRICS = (1<<2)
|
PCF_METRICS = (1 << 2)
|
||||||
PCF_BITMAPS = (1<<3)
|
PCF_BITMAPS = (1 << 3)
|
||||||
PCF_INK_METRICS = (1<<4)
|
PCF_INK_METRICS = (1 << 4)
|
||||||
PCF_BDF_ENCODINGS = (1<<5)
|
PCF_BDF_ENCODINGS = (1 << 5)
|
||||||
PCF_SWIDTHS = (1<<6)
|
PCF_SWIDTHS = (1 << 6)
|
||||||
PCF_GLYPH_NAMES = (1<<7)
|
PCF_GLYPH_NAMES = (1 << 7)
|
||||||
PCF_BDF_ACCELERATORS = (1<<8)
|
PCF_BDF_ACCELERATORS = (1 << 8)
|
||||||
|
|
||||||
BYTES_PER_ROW = [
|
BYTES_PER_ROW = [
|
||||||
lambda bits: ((bits+7) >> 3),
|
lambda bits: ((bits+7) >> 3),
|
||||||
lambda bits: ((bits+15) >> 3) & ~1,
|
lambda bits: ((bits+15) >> 3) & ~1,
|
||||||
lambda bits: ((bits+31) >> 3) & ~3,
|
lambda bits: ((bits+31) >> 3) & ~3,
|
||||||
lambda bits: ((bits+63) >> 3) & ~7,
|
lambda bits: ((bits+63) >> 3) & ~7,
|
||||||
|
@ -48,9 +48,11 @@ l32 = _binary.i32le
|
||||||
b16 = _binary.i16be
|
b16 = _binary.i16be
|
||||||
b32 = _binary.i32be
|
b32 = _binary.i32be
|
||||||
|
|
||||||
|
|
||||||
def sz(s, o):
|
def sz(s, o):
|
||||||
return s[o:s.index(b"\0", o)]
|
return s[o:s.index(b"\0", o)]
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Font file plugin for the X11 PCF format.
|
# Font file plugin for the X11 PCF format.
|
||||||
|
|
||||||
|
@ -122,7 +124,7 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
for i in range(nprops):
|
for i in range(nprops):
|
||||||
p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
|
p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
|
||||||
if nprops & 3:
|
if nprops & 3:
|
||||||
fp.seek(4 - (nprops & 3), 1) # pad
|
fp.seek(4 - (nprops & 3), 1) # pad
|
||||||
|
|
||||||
data = fp.read(i32(fp.read(4)))
|
data = fp.read(i32(fp.read(4)))
|
||||||
|
|
||||||
|
@ -202,16 +204,16 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
bitmapSizes.append(i32(fp.read(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
|
bitorder = format & 8 # non-zero => MSB
|
||||||
padindex = format & 3
|
padindex = format & 3
|
||||||
|
|
||||||
bitmapsize = bitmapSizes[padindex]
|
bitmapsize = bitmapSizes[padindex]
|
||||||
offsets.append(bitmapsize)
|
offsets.append(bitmapsize)
|
||||||
|
|
||||||
data = fp.read(bitmapsize)
|
data = fp.read(bitmapsize)
|
||||||
|
|
||||||
pad = BYTES_PER_ROW[padindex]
|
pad = BYTES_PER_ROW[padindex]
|
||||||
mode = "1;R"
|
mode = "1;R"
|
||||||
if bitorder:
|
if bitorder:
|
||||||
mode = "1"
|
mode = "1"
|
||||||
|
@ -245,6 +247,6 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
try:
|
try:
|
||||||
encoding[i+firstCol] = encodingOffset
|
encoding[i+firstCol] = encodingOffset
|
||||||
except IndexError:
|
except IndexError:
|
||||||
break # only load ISO-8859-1 glyphs
|
break # only load ISO-8859-1 glyphs
|
||||||
|
|
||||||
return encoding
|
return encoding
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "0.6"
|
from __future__ import print_function
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
|
||||||
|
@ -33,9 +33,13 @@ i8 = _binary.i8
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
o8 = _binary.o8
|
o8 = _binary.o8
|
||||||
|
|
||||||
|
__version__ = "0.6"
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
|
return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Paintbrush images.
|
# Image plugin for Paintbrush images.
|
||||||
|
|
||||||
|
@ -52,23 +56,22 @@ class PcxImageFile(ImageFile.ImageFile):
|
||||||
raise SyntaxError("not a PCX file")
|
raise SyntaxError("not a PCX file")
|
||||||
|
|
||||||
# image
|
# image
|
||||||
bbox = i16(s,4), i16(s,6), i16(s,8)+1, i16(s,10)+1
|
bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1
|
||||||
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
|
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
|
||||||
raise SyntaxError("bad PCX image size")
|
raise SyntaxError("bad PCX image size")
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("BBox: %s %s %s %s" % bbox)
|
print("BBox: %s %s %s %s" % bbox)
|
||||||
|
|
||||||
|
|
||||||
# format
|
# format
|
||||||
version = i8(s[1])
|
version = i8(s[1])
|
||||||
bits = i8(s[3])
|
bits = i8(s[3])
|
||||||
planes = i8(s[65])
|
planes = i8(s[65])
|
||||||
stride = i16(s,66)
|
stride = i16(s, 66)
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("PCX version %s, bits %s, planes %s, stride %s" %
|
print("PCX version %s, bits %s, planes %s, stride %s" %
|
||||||
(version, bits, planes, stride))
|
(version, bits, planes, stride))
|
||||||
|
|
||||||
self.info["dpi"] = i16(s,12), i16(s,14)
|
self.info["dpi"] = i16(s, 12), i16(s, 14)
|
||||||
|
|
||||||
if bits == 1 and planes == 1:
|
if bits == 1 and planes == 1:
|
||||||
mode = rawmode = "1"
|
mode = rawmode = "1"
|
||||||
|
@ -105,7 +108,7 @@ class PcxImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
bbox = (0, 0) + self.size
|
bbox = (0, 0) + self.size
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("size: %sx%s" % self.size)
|
print("size: %sx%s" % self.size)
|
||||||
|
|
||||||
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
|
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
|
||||||
|
|
||||||
|
@ -122,6 +125,7 @@ SAVE = {
|
||||||
|
|
||||||
o16 = _binary.o16le
|
o16 = _binary.o16le
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, check=0):
|
def _save(im, fp, filename, check=0):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -140,9 +144,8 @@ def _save(im, fp, filename, check=0):
|
||||||
# Ideally it should be passed in in the state, but the bytes value
|
# Ideally it should be passed in in the state, but the bytes value
|
||||||
# gets overwritten.
|
# gets overwritten.
|
||||||
|
|
||||||
|
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
|
print("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
|
||||||
im.size[0], bits, stride))
|
im.size[0], bits, stride))
|
||||||
|
|
||||||
# under windows, we could determine the current screen size with
|
# under windows, we could determine the current screen size with
|
||||||
|
@ -163,13 +166,13 @@ def _save(im, fp, filename, check=0):
|
||||||
|
|
||||||
assert fp.tell() == 128
|
assert fp.tell() == 128
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("pcx", (0,0)+im.size, 0,
|
ImageFile._save(im, fp, [("pcx", (0, 0)+im.size, 0,
|
||||||
(rawmode, bits*planes))])
|
(rawmode, bits*planes))])
|
||||||
|
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
# colour palette
|
# colour palette
|
||||||
fp.write(o8(12))
|
fp.write(o8(12))
|
||||||
fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
|
fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
|
||||||
elif im.mode == "L":
|
elif im.mode == "L":
|
||||||
# greyscale palette
|
# greyscale palette
|
||||||
fp.write(o8(12))
|
fp.write(o8(12))
|
||||||
|
|
|
@ -63,7 +63,7 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
xref = [0]*(5+1) # placeholders
|
xref = [0]*(5+1) # placeholders
|
||||||
|
|
||||||
class TextWriter:
|
class TextWriter(object):
|
||||||
def __init__(self, fp):
|
def __init__(self, fp):
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ from PIL import Image, ImageFile, _binary
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
i32 = _binary.i32le
|
i32 = _binary.i32le
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for PIXAR raster images.
|
# Image plugin for PIXAR raster images.
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ class PixarImageFile(ImageFile.ImageFile):
|
||||||
# FIXME: to be continued...
|
# FIXME: to be continued...
|
||||||
|
|
||||||
# create tile descriptor (assuming "dumped")
|
# create tile descriptor (assuming "dumped")
|
||||||
self.tile = [("raw", (0,0)+self.size, 1024, (self.mode, 0, 1))]
|
self.tile = [("raw", (0, 0)+self.size, 1024, (self.mode, 0, 1))]
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -52,30 +52,46 @@ _MAGIC = b"\211PNG\r\n\032\n"
|
||||||
|
|
||||||
_MODES = {
|
_MODES = {
|
||||||
# supported bits/color combinations, and corresponding modes/rawmodes
|
# supported bits/color combinations, and corresponding modes/rawmodes
|
||||||
(1, 0): ("1", "1"),
|
(1, 0): ("1", "1"),
|
||||||
(2, 0): ("L", "L;2"),
|
(2, 0): ("L", "L;2"),
|
||||||
(4, 0): ("L", "L;4"),
|
(4, 0): ("L", "L;4"),
|
||||||
(8, 0): ("L", "L"),
|
(8, 0): ("L", "L"),
|
||||||
(16,0): ("I", "I;16B"),
|
(16, 0): ("I", "I;16B"),
|
||||||
(8, 2): ("RGB", "RGB"),
|
(8, 2): ("RGB", "RGB"),
|
||||||
(16,2): ("RGB", "RGB;16B"),
|
(16, 2): ("RGB", "RGB;16B"),
|
||||||
(1, 3): ("P", "P;1"),
|
(1, 3): ("P", "P;1"),
|
||||||
(2, 3): ("P", "P;2"),
|
(2, 3): ("P", "P;2"),
|
||||||
(4, 3): ("P", "P;4"),
|
(4, 3): ("P", "P;4"),
|
||||||
(8, 3): ("P", "P"),
|
(8, 3): ("P", "P"),
|
||||||
(8, 4): ("LA", "LA"),
|
(8, 4): ("LA", "LA"),
|
||||||
(16,4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
|
(16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
|
||||||
(8, 6): ("RGBA", "RGBA"),
|
(8, 6): ("RGBA", "RGBA"),
|
||||||
(16,6): ("RGBA", "RGBA;16B"),
|
(16, 6): ("RGBA", "RGBA;16B"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_simple_palette = re.compile(b'^\xff+\x00\xff*$')
|
_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
|
||||||
|
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)
|
||||||
|
if dobj.unconsumed_tail:
|
||||||
|
raise ValueError("Decompressed Data Too Large")
|
||||||
|
return plaintext
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Support classes. Suitable for PNG and related formats like MNG etc.
|
# Support classes. Suitable for PNG and related formats like MNG etc.
|
||||||
|
|
||||||
class ChunkStream:
|
class ChunkStream(object):
|
||||||
|
|
||||||
def __init__(self, fp):
|
def __init__(self, fp):
|
||||||
|
|
||||||
|
@ -123,15 +139,15 @@ class ChunkStream:
|
||||||
crc1 = Image.core.crc32(data, Image.core.crc32(cid))
|
crc1 = Image.core.crc32(data, Image.core.crc32(cid))
|
||||||
crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
|
crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
|
||||||
if crc1 != crc2:
|
if crc1 != crc2:
|
||||||
raise SyntaxError("broken PNG file"\
|
raise SyntaxError("broken PNG file"
|
||||||
"(bad header checksum in %s)" % cid)
|
"(bad header checksum in %s)" % cid)
|
||||||
|
|
||||||
def crc_skip(self, cid, data):
|
def crc_skip(self, cid, data):
|
||||||
"Read checksum. Used if the C module is not present"
|
"Read checksum. Used if the C module is not present"
|
||||||
|
|
||||||
self.fp.read(4)
|
self.fp.read(4)
|
||||||
|
|
||||||
def verify(self, endchunk = b"IEND"):
|
def verify(self, endchunk=b"IEND"):
|
||||||
|
|
||||||
# Simple approach; just calculate checksum for all remaining
|
# Simple approach; just calculate checksum for all remaining
|
||||||
# blocks. Must be called directly after open.
|
# blocks. Must be called directly after open.
|
||||||
|
@ -147,30 +163,57 @@ class ChunkStream:
|
||||||
|
|
||||||
return cids
|
return cids
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# Subclass of string to allow iTXt chunks to look like strings while
|
|
||||||
# keeping their extra information
|
|
||||||
|
|
||||||
class iTXt(str):
|
class iTXt(str):
|
||||||
|
"""
|
||||||
|
Subclass of string to allow iTXt chunks to look like strings while
|
||||||
|
keeping their extra information
|
||||||
|
|
||||||
|
"""
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __new__(cls, text, lang, tkey):
|
def __new__(cls, text, lang, tkey):
|
||||||
|
"""
|
||||||
|
:param value: value for this key
|
||||||
|
:param lang: language code
|
||||||
|
:param tkey: UTF-8 version of the key name
|
||||||
|
"""
|
||||||
|
|
||||||
self = str.__new__(cls, text)
|
self = str.__new__(cls, text)
|
||||||
self.lang = lang
|
self.lang = lang
|
||||||
self.tkey = tkey
|
self.tkey = tkey
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# PNG chunk container (for use with save(pnginfo=))
|
|
||||||
|
|
||||||
class PngInfo:
|
class PngInfo(object):
|
||||||
|
"""
|
||||||
|
PNG chunk container (for use with save(pnginfo=))
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.chunks = []
|
self.chunks = []
|
||||||
|
|
||||||
def add(self, cid, data):
|
def add(self, cid, data):
|
||||||
|
"""Appends an arbitrary chunk. Use with caution.
|
||||||
|
|
||||||
|
:param cid: a byte string, 4 bytes long.
|
||||||
|
:param data: a byte string of the encoded data
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
self.chunks.append((cid, data))
|
self.chunks.append((cid, data))
|
||||||
|
|
||||||
def add_itxt(self, key, value, lang="", tkey="", zip=False):
|
def add_itxt(self, key, value, lang="", tkey="", zip=False):
|
||||||
|
"""Appends an iTXt chunk.
|
||||||
|
|
||||||
|
:param key: latin-1 encodable text key name
|
||||||
|
:param value: value for this key
|
||||||
|
:param lang: language code
|
||||||
|
:param tkey: UTF-8 version of the key name
|
||||||
|
:param zip: compression flag
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
if not isinstance(key, bytes):
|
if not isinstance(key, bytes):
|
||||||
key = key.encode("latin-1", "strict")
|
key = key.encode("latin-1", "strict")
|
||||||
if not isinstance(value, bytes):
|
if not isinstance(value, bytes):
|
||||||
|
@ -181,12 +224,21 @@ class PngInfo:
|
||||||
tkey = tkey.encode("utf-8", "strict")
|
tkey = tkey.encode("utf-8", "strict")
|
||||||
|
|
||||||
if zip:
|
if zip:
|
||||||
import zlib
|
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" +
|
||||||
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value))
|
zlib.compress(value))
|
||||||
else:
|
else:
|
||||||
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)
|
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" +
|
||||||
|
value)
|
||||||
|
|
||||||
def add_text(self, key, value, zip=0):
|
def add_text(self, key, value, zip=0):
|
||||||
|
"""Appends a text chunk.
|
||||||
|
|
||||||
|
:param key: latin-1 encodable text key name
|
||||||
|
:param value: value for this key, text or an
|
||||||
|
:py:class:`PIL.PngImagePlugin.iTXt` instance
|
||||||
|
:param zip: compression flag
|
||||||
|
|
||||||
|
"""
|
||||||
if isinstance(value, iTXt):
|
if isinstance(value, iTXt):
|
||||||
return self.add_itxt(key, value, value.lang, value.tkey, bool(zip))
|
return self.add_itxt(key, value, value.lang, value.tkey, bool(zip))
|
||||||
|
|
||||||
|
@ -201,11 +253,11 @@ class PngInfo:
|
||||||
key = key.encode('latin-1', 'strict')
|
key = key.encode('latin-1', 'strict')
|
||||||
|
|
||||||
if zip:
|
if zip:
|
||||||
import zlib
|
|
||||||
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
|
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
|
||||||
else:
|
else:
|
||||||
self.add(b"tEXt", key + b"\0" + value)
|
self.add(b"tEXt", key + b"\0" + value)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# PNG image stream (IHDR/IEND)
|
# PNG image stream (IHDR/IEND)
|
||||||
|
|
||||||
|
@ -218,11 +270,19 @@ class PngStream(ChunkStream):
|
||||||
# local copies of Image attributes
|
# local copies of Image attributes
|
||||||
self.im_info = {}
|
self.im_info = {}
|
||||||
self.im_text = {}
|
self.im_text = {}
|
||||||
self.im_size = (0,0)
|
self.im_size = (0, 0)
|
||||||
self.im_mode = None
|
self.im_mode = None
|
||||||
self.im_tile = None
|
self.im_tile = None
|
||||||
self.im_palette = None
|
self.im_palette = None
|
||||||
|
|
||||||
|
self.text_memory = 0
|
||||||
|
|
||||||
|
def check_text_memory(self, chunklen):
|
||||||
|
self.text_memory += chunklen
|
||||||
|
if self.text_memory > MAX_TEXT_MEMORY:
|
||||||
|
raise ValueError("Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" %
|
||||||
|
self.text_memory)
|
||||||
|
|
||||||
def chunk_iCCP(self, pos, length):
|
def chunk_iCCP(self, pos, length):
|
||||||
|
|
||||||
# ICC profile
|
# ICC profile
|
||||||
|
@ -238,11 +298,12 @@ class PngStream(ChunkStream):
|
||||||
print("Compression method", i8(s[i]))
|
print("Compression method", i8(s[i]))
|
||||||
comp_method = i8(s[i])
|
comp_method = i8(s[i])
|
||||||
if comp_method != 0:
|
if comp_method != 0:
|
||||||
raise SyntaxError("Unknown compression method %s in iCCP chunk" % comp_method)
|
raise SyntaxError("Unknown compression method %s in iCCP chunk" %
|
||||||
|
comp_method)
|
||||||
try:
|
try:
|
||||||
icc_profile = zlib.decompress(s[i+2:])
|
icc_profile = _safe_zlib_decompress(s[i+2:])
|
||||||
except zlib.error:
|
except zlib.error:
|
||||||
icc_profile = None # FIXME
|
icc_profile = None # FIXME
|
||||||
self.im_info["icc_profile"] = icc_profile
|
self.im_info["icc_profile"] = icc_profile
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -264,7 +325,7 @@ class PngStream(ChunkStream):
|
||||||
def chunk_IDAT(self, pos, length):
|
def chunk_IDAT(self, pos, length):
|
||||||
|
|
||||||
# image data
|
# image data
|
||||||
self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
|
self.im_tile = [("zip", (0, 0)+self.im_size, pos, self.im_rawmode)]
|
||||||
self.im_idat = length
|
self.im_idat = length
|
||||||
raise EOFError
|
raise EOFError
|
||||||
|
|
||||||
|
@ -290,6 +351,8 @@ class PngStream(ChunkStream):
|
||||||
i = s.find(b"\0")
|
i = s.find(b"\0")
|
||||||
if i >= 0:
|
if i >= 0:
|
||||||
self.im_info["transparency"] = i
|
self.im_info["transparency"] = i
|
||||||
|
elif _null_palette.match(s):
|
||||||
|
self.im_info["transparency"] = 0
|
||||||
else:
|
else:
|
||||||
self.im_info["transparency"] = s
|
self.im_info["transparency"] = s
|
||||||
elif self.im_mode == "L":
|
elif self.im_mode == "L":
|
||||||
|
@ -311,7 +374,7 @@ class PngStream(ChunkStream):
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
px, py = i32(s), i32(s[4:])
|
px, py = i32(s), i32(s[4:])
|
||||||
unit = i8(s[8])
|
unit = i8(s[8])
|
||||||
if unit == 1: # meter
|
if unit == 1: # meter
|
||||||
dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5)
|
dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5)
|
||||||
self.im_info["dpi"] = dpi
|
self.im_info["dpi"] = dpi
|
||||||
elif unit == 0:
|
elif unit == 0:
|
||||||
|
@ -325,13 +388,17 @@ class PngStream(ChunkStream):
|
||||||
try:
|
try:
|
||||||
k, v = s.split(b"\0", 1)
|
k, v = s.split(b"\0", 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
k = s; v = b"" # fallback for broken tEXt tags
|
# fallback for broken tEXt tags
|
||||||
|
k = s
|
||||||
|
v = b""
|
||||||
if k:
|
if k:
|
||||||
if bytes is not str:
|
if bytes is not str:
|
||||||
k = k.decode('latin-1', 'strict')
|
k = k.decode('latin-1', 'strict')
|
||||||
v = v.decode('latin-1', 'replace')
|
v = v.decode('latin-1', 'replace')
|
||||||
|
|
||||||
self.im_info[k] = self.im_text[k] = v
|
self.im_info[k] = self.im_text[k] = v
|
||||||
|
self.check_text_memory(len(v))
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def chunk_zTXt(self, pos, length):
|
def chunk_zTXt(self, pos, length):
|
||||||
|
@ -341,16 +408,17 @@ class PngStream(ChunkStream):
|
||||||
try:
|
try:
|
||||||
k, v = s.split(b"\0", 1)
|
k, v = s.split(b"\0", 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
k = s; v = b""
|
k = s
|
||||||
|
v = b""
|
||||||
if v:
|
if v:
|
||||||
comp_method = i8(v[0])
|
comp_method = i8(v[0])
|
||||||
else:
|
else:
|
||||||
comp_method = 0
|
comp_method = 0
|
||||||
if comp_method != 0:
|
if comp_method != 0:
|
||||||
raise SyntaxError("Unknown compression method %s in zTXt chunk" % comp_method)
|
raise SyntaxError("Unknown compression method %s in zTXt chunk" %
|
||||||
import zlib
|
comp_method)
|
||||||
try:
|
try:
|
||||||
v = zlib.decompress(v[1:])
|
v = _safe_zlib_decompress(v[1:])
|
||||||
except zlib.error:
|
except zlib.error:
|
||||||
v = b""
|
v = b""
|
||||||
|
|
||||||
|
@ -360,6 +428,8 @@ class PngStream(ChunkStream):
|
||||||
v = v.decode('latin-1', 'replace')
|
v = v.decode('latin-1', 'replace')
|
||||||
|
|
||||||
self.im_info[k] = self.im_text[k] = v
|
self.im_info[k] = self.im_text[k] = v
|
||||||
|
self.check_text_memory(len(v))
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def chunk_iTXt(self, pos, length):
|
def chunk_iTXt(self, pos, length):
|
||||||
|
@ -379,9 +449,8 @@ class PngStream(ChunkStream):
|
||||||
return s
|
return s
|
||||||
if cf != 0:
|
if cf != 0:
|
||||||
if cm == 0:
|
if cm == 0:
|
||||||
import zlib
|
|
||||||
try:
|
try:
|
||||||
v = zlib.decompress(v)
|
v = _safe_zlib_decompress(v)
|
||||||
except zlib.error:
|
except zlib.error:
|
||||||
return s
|
return s
|
||||||
else:
|
else:
|
||||||
|
@ -396,15 +465,18 @@ class PngStream(ChunkStream):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
|
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
|
||||||
|
self.check_text_memory(len(v))
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# PNG reader
|
# PNG reader
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:8] == _MAGIC
|
return prefix[:8] == _MAGIC
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for PNG images.
|
# Image plugin for PNG images.
|
||||||
|
|
||||||
|
@ -451,7 +523,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.mode = self.png.im_mode
|
self.mode = self.png.im_mode
|
||||||
self.size = self.png.im_size
|
self.size = self.png.im_size
|
||||||
self.info = self.png.im_info
|
self.info = self.png.im_info
|
||||||
self.text = self.png.im_text # experimental
|
self.text = self.png.im_text # experimental
|
||||||
self.tile = self.png.im_tile
|
self.tile = self.png.im_tile
|
||||||
|
|
||||||
if self.png.im_palette:
|
if self.png.im_palette:
|
||||||
|
@ -460,7 +532,6 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.__idat = length # used by load_read()
|
self.__idat = length # used by load_read()
|
||||||
|
|
||||||
|
|
||||||
def verify(self):
|
def verify(self):
|
||||||
"Verify PNG file"
|
"Verify PNG file"
|
||||||
|
|
||||||
|
@ -489,7 +560,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
while self.__idat == 0:
|
while self.__idat == 0:
|
||||||
# end of chunk, skip forward to next one
|
# end of chunk, skip forward to next one
|
||||||
|
|
||||||
self.fp.read(4) # CRC
|
self.fp.read(4) # CRC
|
||||||
|
|
||||||
cid, pos, length = self.png.read()
|
cid, pos, length = self.png.read()
|
||||||
|
|
||||||
|
@ -509,7 +580,6 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
return self.fp.read(read_bytes)
|
return self.fp.read(read_bytes)
|
||||||
|
|
||||||
|
|
||||||
def load_end(self):
|
def load_end(self):
|
||||||
"internal: finished reading image data"
|
"internal: finished reading image data"
|
||||||
|
|
||||||
|
@ -526,21 +596,22 @@ o32 = _binary.o32be
|
||||||
|
|
||||||
_OUTMODES = {
|
_OUTMODES = {
|
||||||
# supported PIL modes, and corresponding rawmodes/bits/color combinations
|
# supported PIL modes, and corresponding rawmodes/bits/color combinations
|
||||||
"1": ("1", b'\x01\x00'),
|
"1": ("1", b'\x01\x00'),
|
||||||
"L;1": ("L;1", b'\x01\x00'),
|
"L;1": ("L;1", b'\x01\x00'),
|
||||||
"L;2": ("L;2", b'\x02\x00'),
|
"L;2": ("L;2", b'\x02\x00'),
|
||||||
"L;4": ("L;4", b'\x04\x00'),
|
"L;4": ("L;4", b'\x04\x00'),
|
||||||
"L": ("L", b'\x08\x00'),
|
"L": ("L", b'\x08\x00'),
|
||||||
"LA": ("LA", b'\x08\x04'),
|
"LA": ("LA", b'\x08\x04'),
|
||||||
"I": ("I;16B", b'\x10\x00'),
|
"I": ("I;16B", b'\x10\x00'),
|
||||||
"P;1": ("P;1", b'\x01\x03'),
|
"P;1": ("P;1", b'\x01\x03'),
|
||||||
"P;2": ("P;2", b'\x02\x03'),
|
"P;2": ("P;2", b'\x02\x03'),
|
||||||
"P;4": ("P;4", b'\x04\x03'),
|
"P;4": ("P;4", b'\x04\x03'),
|
||||||
"P": ("P", b'\x08\x03'),
|
"P": ("P", b'\x08\x03'),
|
||||||
"RGB": ("RGB", b'\x08\x02'),
|
"RGB": ("RGB", b'\x08\x02'),
|
||||||
"RGBA":("RGBA", b'\x08\x06'),
|
"RGBA": ("RGBA", b'\x08\x06'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def putchunk(fp, cid, *data):
|
def putchunk(fp, cid, *data):
|
||||||
"Write a PNG chunk (including CRC field)"
|
"Write a PNG chunk (including CRC field)"
|
||||||
|
|
||||||
|
@ -551,15 +622,18 @@ def putchunk(fp, cid, *data):
|
||||||
hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
|
hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
|
||||||
fp.write(o16(hi) + o16(lo))
|
fp.write(o16(hi) + o16(lo))
|
||||||
|
|
||||||
class _idat:
|
|
||||||
|
class _idat(object):
|
||||||
# wrap output from the encoder in IDAT chunks
|
# wrap output from the encoder in IDAT chunks
|
||||||
|
|
||||||
def __init__(self, fp, chunk):
|
def __init__(self, fp, chunk):
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
self.chunk = chunk
|
self.chunk = chunk
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
self.chunk(self.fp, b"IDAT", data)
|
self.chunk(self.fp, b"IDAT", data)
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, chunk=putchunk, check=0):
|
def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
# save an image to disk (called by the save method)
|
# save an image to disk (called by the save method)
|
||||||
|
|
||||||
|
@ -597,9 +671,9 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
dictionary = b""
|
dictionary = b""
|
||||||
|
|
||||||
im.encoderconfig = ("optimize" in im.encoderinfo,
|
im.encoderconfig = ("optimize" in im.encoderinfo,
|
||||||
im.encoderinfo.get("compress_level", -1),
|
im.encoderinfo.get("compress_level", -1),
|
||||||
im.encoderinfo.get("compress_type", -1),
|
im.encoderinfo.get("compress_type", -1),
|
||||||
dictionary)
|
dictionary)
|
||||||
|
|
||||||
# get the corresponding PNG mode
|
# get the corresponding PNG mode
|
||||||
try:
|
try:
|
||||||
|
@ -616,8 +690,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
fp.write(_MAGIC)
|
fp.write(_MAGIC)
|
||||||
|
|
||||||
chunk(fp, b"IHDR",
|
chunk(fp, b"IHDR",
|
||||||
o32(im.size[0]), o32(im.size[1]), # 0: size
|
o32(im.size[0]), o32(im.size[1]), # 0: size
|
||||||
mode, # 8: depth/type
|
mode, # 8: depth/type
|
||||||
b'\0', # 10: compression
|
b'\0', # 10: compression
|
||||||
b'\0', # 11: filter category
|
b'\0', # 11: filter category
|
||||||
b'\0') # 12: interlace flag
|
b'\0') # 12: interlace flag
|
||||||
|
@ -629,7 +703,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
palette_bytes += b'\0'
|
palette_bytes += b'\0'
|
||||||
chunk(fp, b"PLTE", palette_bytes)
|
chunk(fp, b"PLTE", palette_bytes)
|
||||||
|
|
||||||
transparency = im.encoderinfo.get('transparency',im.info.get('transparency', None))
|
transparency = im.encoderinfo.get('transparency',
|
||||||
|
im.info.get('transparency', None))
|
||||||
|
|
||||||
if transparency or transparency == 0:
|
if transparency or transparency == 0:
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
|
@ -658,10 +733,6 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
alpha_bytes = 2**bits
|
alpha_bytes = 2**bits
|
||||||
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
||||||
|
|
||||||
if 0:
|
|
||||||
# FIXME: to be supported some day
|
|
||||||
chunk(fp, b"gAMA", o32(int(gamma * 100000.0)))
|
|
||||||
|
|
||||||
dpi = im.encoderinfo.get("dpi")
|
dpi = im.encoderinfo.get("dpi")
|
||||||
if dpi:
|
if dpi:
|
||||||
chunk(fp, b"pHYs",
|
chunk(fp, b"pHYs",
|
||||||
|
@ -686,7 +757,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
data = name + b"\0\0" + zlib.compress(im.info["icc_profile"])
|
data = name + b"\0\0" + zlib.compress(im.info["icc_profile"])
|
||||||
chunk(fp, b"iCCP", data)
|
chunk(fp, b"iCCP", data)
|
||||||
|
|
||||||
ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)])
|
ImageFile._save(im, _idat(fp, chunk),
|
||||||
|
[("zip", (0, 0)+im.size, 0, rawmode)])
|
||||||
|
|
||||||
chunk(fp, b"IEND", b"")
|
chunk(fp, b"IEND", b"")
|
||||||
|
|
||||||
|
@ -702,10 +774,12 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
def getchunks(im, **params):
|
def getchunks(im, **params):
|
||||||
"""Return a list of PNG chunks representing this image."""
|
"""Return a list of PNG chunks representing this image."""
|
||||||
|
|
||||||
class collector:
|
class collector(object):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def append(self, chunk):
|
def append(self, chunk):
|
||||||
self.data.append(chunk)
|
self.data.append(chunk)
|
||||||
|
|
||||||
|
|
|
@ -27,12 +27,13 @@ from PIL import Image, ImageFile
|
||||||
b_whitespace = string.whitespace
|
b_whitespace = string.whitespace
|
||||||
try:
|
try:
|
||||||
import locale
|
import locale
|
||||||
locale_lang,locale_enc = locale.getlocale()
|
locale_lang, locale_enc = locale.getlocale()
|
||||||
if locale_enc is None:
|
if locale_enc is None:
|
||||||
locale_lang,locale_enc = locale.getdefaultlocale()
|
locale_lang, locale_enc = locale.getdefaultlocale()
|
||||||
b_whitespace = b_whitespace.decode(locale_enc)
|
b_whitespace = b_whitespace.decode(locale_enc)
|
||||||
except: pass
|
except:
|
||||||
b_whitespace = b_whitespace.encode('ascii','ignore')
|
pass
|
||||||
|
b_whitespace = b_whitespace.encode('ascii', 'ignore')
|
||||||
|
|
||||||
MODES = {
|
MODES = {
|
||||||
# standard
|
# standard
|
||||||
|
@ -47,9 +48,11 @@ MODES = {
|
||||||
b"PyCMYK": "CMYK"
|
b"PyCMYK": "CMYK"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[0:1] == b"P" and prefix[1] in b"0456y"
|
return prefix[0:1] == b"P" and prefix[1] in b"0456y"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for PBM, PGM, and PPM images.
|
# Image plugin for PBM, PGM, and PPM images.
|
||||||
|
|
||||||
|
@ -58,8 +61,8 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
format = "PPM"
|
format = "PPM"
|
||||||
format_description = "Pbmplus image"
|
format_description = "Pbmplus image"
|
||||||
|
|
||||||
def _token(self, s = b""):
|
def _token(self, s=b""):
|
||||||
while True: # read until next whitespace
|
while True: # read until next whitespace
|
||||||
c = self.fp.read(1)
|
c = self.fp.read(1)
|
||||||
if not c or c in b_whitespace:
|
if not c or c in b_whitespace:
|
||||||
break
|
break
|
||||||
|
@ -104,12 +107,12 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
# maxgrey
|
# maxgrey
|
||||||
if s > 255:
|
if s > 255:
|
||||||
if not mode == 'L':
|
if not mode == 'L':
|
||||||
raise ValueError("Too many colors for band: %s" %s)
|
raise ValueError("Too many colors for band: %s" % s)
|
||||||
if s < 2**16:
|
if s < 2**16:
|
||||||
self.mode = 'I'
|
self.mode = 'I'
|
||||||
rawmode = 'I;16B'
|
rawmode = 'I;16B'
|
||||||
else:
|
else:
|
||||||
self.mode = 'I';
|
self.mode = 'I'
|
||||||
rawmode = 'I;32B'
|
rawmode = 'I;32B'
|
||||||
|
|
||||||
self.size = xsize, ysize
|
self.size = xsize, ysize
|
||||||
|
@ -123,6 +126,7 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
# self.mode = self.im.mode
|
# self.mode = self.im.mode
|
||||||
# self.size = self.im.size
|
# self.size = self.im.size
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -152,7 +156,7 @@ def _save(im, fp, filename):
|
||||||
fp.write(b"65535\n")
|
fp.write(b"65535\n")
|
||||||
elif rawmode == "I;32B":
|
elif rawmode == "I;32B":
|
||||||
fp.write(b"2147483648\n")
|
fp.write(b"2147483648\n")
|
||||||
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))])
|
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
|
||||||
|
|
||||||
# ALTERNATIVE: save via builtin debug function
|
# ALTERNATIVE: save via builtin debug function
|
||||||
# im._dump(filename)
|
# im._dump(filename)
|
||||||
|
|
|
@ -28,8 +28,8 @@ MODES = {
|
||||||
(2, 8): ("P", 1),
|
(2, 8): ("P", 1),
|
||||||
(3, 8): ("RGB", 3),
|
(3, 8): ("RGB", 3),
|
||||||
(4, 8): ("CMYK", 4),
|
(4, 8): ("CMYK", 4),
|
||||||
(7, 8): ("L", 1), # FIXME: multilayer
|
(7, 8): ("L", 1), # FIXME: multilayer
|
||||||
(8, 8): ("L", 1), # duotone
|
(8, 8): ("L", 1), # duotone
|
||||||
(9, 8): ("LAB", 3)
|
(9, 8): ("LAB", 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +40,14 @@ i8 = _binary.i8
|
||||||
i16 = _binary.i16be
|
i16 = _binary.i16be
|
||||||
i32 = _binary.i32be
|
i32 = _binary.i32be
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
# read PSD images
|
# read PSD images
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] == b"8BPS"
|
return prefix[:4] == b"8BPS"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Photoshop images.
|
# Image plugin for Photoshop images.
|
||||||
|
|
||||||
|
@ -100,12 +102,12 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
id = i16(read(2))
|
id = i16(read(2))
|
||||||
name = read(i8(read(1)))
|
name = read(i8(read(1)))
|
||||||
if not (len(name) & 1):
|
if not (len(name) & 1):
|
||||||
read(1) # padding
|
read(1) # padding
|
||||||
data = read(i32(read(4)))
|
data = read(i32(read(4)))
|
||||||
if (len(data) & 1):
|
if (len(data) & 1):
|
||||||
read(1) # padding
|
read(1) # padding
|
||||||
self.resources.append((id, name, data))
|
self.resources.append((id, name, data))
|
||||||
if id == 1039: # ICC profile
|
if id == 1039: # ICC profile
|
||||||
self.info["icc_profile"] = data
|
self.info["icc_profile"] = data
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -130,6 +132,10 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
self._fp = self.fp
|
self._fp = self.fp
|
||||||
self.frame = 0
|
self.frame = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return len(self.layers)
|
||||||
|
|
||||||
def seek(self, layer):
|
def seek(self, layer):
|
||||||
# seek to given layer (1..max)
|
# seek to given layer (1..max)
|
||||||
if layer == self.frame:
|
if layer == self.frame:
|
||||||
|
@ -159,6 +165,7 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
if self.mode == "P":
|
if self.mode == "P":
|
||||||
Image.Image.load(self)
|
Image.Image.load(self)
|
||||||
|
|
||||||
|
|
||||||
def _layerinfo(file):
|
def _layerinfo(file):
|
||||||
# read layerinfo block
|
# read layerinfo block
|
||||||
layers = []
|
layers = []
|
||||||
|
@ -166,8 +173,10 @@ def _layerinfo(file):
|
||||||
for i in range(abs(i16(read(2)))):
|
for i in range(abs(i16(read(2)))):
|
||||||
|
|
||||||
# bounding box
|
# bounding box
|
||||||
y0 = i32(read(4)); x0 = i32(read(4))
|
y0 = i32(read(4))
|
||||||
y1 = i32(read(4)); x1 = i32(read(4))
|
x0 = i32(read(4))
|
||||||
|
y1 = i32(read(4))
|
||||||
|
x1 = i32(read(4))
|
||||||
|
|
||||||
# image info
|
# image info
|
||||||
info = []
|
info = []
|
||||||
|
@ -197,7 +206,7 @@ def _layerinfo(file):
|
||||||
elif mode == ["A", "B", "G", "R"]:
|
elif mode == ["A", "B", "G", "R"]:
|
||||||
mode = "RGBA"
|
mode = "RGBA"
|
||||||
else:
|
else:
|
||||||
mode = None # unknown
|
mode = None # unknown
|
||||||
|
|
||||||
# skip over blend flags and extra information
|
# skip over blend flags and extra information
|
||||||
filler = read(12)
|
filler = read(12)
|
||||||
|
@ -207,8 +216,10 @@ def _layerinfo(file):
|
||||||
if size:
|
if size:
|
||||||
length = i32(read(4))
|
length = i32(read(4))
|
||||||
if length:
|
if length:
|
||||||
mask_y = i32(read(4)); mask_x = i32(read(4))
|
mask_y = i32(read(4))
|
||||||
mask_h = i32(read(4)) - mask_y; mask_w = i32(read(4)) - mask_x
|
mask_x = i32(read(4))
|
||||||
|
mask_h = i32(read(4)) - mask_y
|
||||||
|
mask_w = i32(read(4)) - mask_x
|
||||||
file.seek(length - 16, 1)
|
file.seek(length - 16, 1)
|
||||||
combined += length + 4
|
combined += length + 4
|
||||||
|
|
||||||
|
@ -219,7 +230,8 @@ def _layerinfo(file):
|
||||||
|
|
||||||
length = i8(read(1))
|
length = i8(read(1))
|
||||||
if length:
|
if length:
|
||||||
# Don't know the proper encoding, Latin-1 should be a good guess
|
# Don't know the proper encoding,
|
||||||
|
# Latin-1 should be a good guess
|
||||||
name = read(length).decode('latin-1', 'replace')
|
name = read(length).decode('latin-1', 'replace')
|
||||||
combined += length + 1
|
combined += length + 1
|
||||||
|
|
||||||
|
@ -239,6 +251,7 @@ def _layerinfo(file):
|
||||||
|
|
||||||
return layers
|
return layers
|
||||||
|
|
||||||
|
|
||||||
def _maketile(file, mode, bbox, channels):
|
def _maketile(file, mode, bbox, channels):
|
||||||
|
|
||||||
tile = None
|
tile = None
|
||||||
|
@ -283,7 +296,7 @@ def _maketile(file, mode, bbox, channels):
|
||||||
file.seek(offset)
|
file.seek(offset)
|
||||||
|
|
||||||
if offset & 1:
|
if offset & 1:
|
||||||
read(1) # padding
|
read(1) # padding
|
||||||
|
|
||||||
return tile
|
return tile
|
||||||
|
|
||||||
|
|
113
PIL/PyAccess.py
113
PIL/PyAccess.py
|
@ -16,7 +16,8 @@
|
||||||
# * Implements the pixel access object following Access.
|
# * Implements the pixel access object following Access.
|
||||||
# * Does not implement the line functions, as they don't appear to be used
|
# * Does not implement the line functions, as they don't appear to be used
|
||||||
# * Taking only the tuple form, which is used from python.
|
# * Taking only the tuple form, which is used from python.
|
||||||
# * Fill.c uses the integer form, but it's still going to use the old Access.c implementation.
|
# * Fill.c uses the integer form, but it's still going to use the old
|
||||||
|
# Access.c implementation.
|
||||||
#
|
#
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
@ -40,7 +41,7 @@ ffi.cdef(defs)
|
||||||
|
|
||||||
class PyAccess(object):
|
class PyAccess(object):
|
||||||
|
|
||||||
def __init__(self, img, readonly = False):
|
def __init__(self, img, readonly=False):
|
||||||
vals = dict(img.im.unsafe_ptrs)
|
vals = dict(img.im.unsafe_ptrs)
|
||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
self.image8 = ffi.cast('unsigned char **', vals['image8'])
|
self.image8 = ffi.cast('unsigned char **', vals['image8'])
|
||||||
|
@ -50,10 +51,11 @@ class PyAccess(object):
|
||||||
self.ysize = vals['ysize']
|
self.ysize = vals['ysize']
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print (vals)
|
print(vals)
|
||||||
self._post_init()
|
self._post_init()
|
||||||
|
|
||||||
def _post_init(): pass
|
def _post_init(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def __setitem__(self, xy, color):
|
def __setitem__(self, xy, color):
|
||||||
"""
|
"""
|
||||||
|
@ -64,9 +66,10 @@ class PyAccess(object):
|
||||||
:param xy: The pixel coordinate, given as (x, y).
|
:param xy: The pixel coordinate, given as (x, y).
|
||||||
:param value: The pixel value.
|
:param value: The pixel value.
|
||||||
"""
|
"""
|
||||||
if self.readonly: raise ValueError('Attempt to putpixel a read only image')
|
if self.readonly:
|
||||||
(x,y) = self.check_xy(xy)
|
raise ValueError('Attempt to putpixel a read only image')
|
||||||
return self.set_pixel(x,y,color)
|
(x, y) = self.check_xy(xy)
|
||||||
|
return self.set_pixel(x, y, color)
|
||||||
|
|
||||||
def __getitem__(self, xy):
|
def __getitem__(self, xy):
|
||||||
"""
|
"""
|
||||||
|
@ -75,34 +78,38 @@ class PyAccess(object):
|
||||||
images
|
images
|
||||||
|
|
||||||
:param xy: The pixel coordinate, given as (x, y).
|
:param xy: The pixel coordinate, given as (x, y).
|
||||||
|
:returns: a pixel value for single band images, a tuple of
|
||||||
|
pixel values for multiband images.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
(x,y) = self.check_xy(xy)
|
(x, y) = self.check_xy(xy)
|
||||||
return self.get_pixel(x,y)
|
return self.get_pixel(x, y)
|
||||||
|
|
||||||
putpixel = __setitem__
|
putpixel = __setitem__
|
||||||
getpixel = __getitem__
|
getpixel = __getitem__
|
||||||
|
|
||||||
def check_xy(self, xy):
|
def check_xy(self, xy):
|
||||||
(x,y) = xy
|
(x, y) = xy
|
||||||
if not (0 <= x < self.xsize and 0 <= y < self.ysize):
|
if not (0 <= x < self.xsize and 0 <= y < self.ysize):
|
||||||
raise ValueError('pixel location out of range')
|
raise ValueError('pixel location out of range')
|
||||||
return xy
|
return xy
|
||||||
|
|
||||||
|
|
||||||
class _PyAccess32_2(PyAccess):
|
class _PyAccess32_2(PyAccess):
|
||||||
""" PA, LA, stored in first and last bytes of a 32 bit word """
|
""" PA, LA, stored in first and last bytes of a 32 bit word """
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
return (pixel.r, pixel.a)
|
return (pixel.r, pixel.a)
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
# tuple
|
# tuple
|
||||||
pixel.r = min(color[0],255)
|
pixel.r = min(color[0], 255)
|
||||||
pixel.a = min(color[1],255)
|
pixel.a = min(color[1], 255)
|
||||||
|
|
||||||
|
|
||||||
class _PyAccess32_3(PyAccess):
|
class _PyAccess32_3(PyAccess):
|
||||||
""" RGB and friends, stored in the first three bytes of a 32 bit word """
|
""" RGB and friends, stored in the first three bytes of a 32 bit word """
|
||||||
|
@ -110,33 +117,34 @@ class _PyAccess32_3(PyAccess):
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
return (pixel.r, pixel.g, pixel.b)
|
return (pixel.r, pixel.g, pixel.b)
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
# tuple
|
# tuple
|
||||||
pixel.r = min(color[0],255)
|
pixel.r = min(color[0], 255)
|
||||||
pixel.g = min(color[1],255)
|
pixel.g = min(color[1], 255)
|
||||||
pixel.b = min(color[2],255)
|
pixel.b = min(color[2], 255)
|
||||||
|
|
||||||
|
|
||||||
class _PyAccess32_4(PyAccess):
|
class _PyAccess32_4(PyAccess):
|
||||||
""" RGBA etc, all 4 bytes of a 32 bit word """
|
""" RGBA etc, all 4 bytes of a 32 bit word """
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
return (pixel.r, pixel.g, pixel.b, pixel.a)
|
return (pixel.r, pixel.g, pixel.b, pixel.a)
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
# tuple
|
# tuple
|
||||||
pixel.r = min(color[0],255)
|
pixel.r = min(color[0], 255)
|
||||||
pixel.g = min(color[1],255)
|
pixel.g = min(color[1], 255)
|
||||||
pixel.b = min(color[2],255)
|
pixel.b = min(color[2], 255)
|
||||||
pixel.a = min(color[3],255)
|
pixel.a = min(color[3], 255)
|
||||||
|
|
||||||
|
|
||||||
class _PyAccess8(PyAccess):
|
class _PyAccess8(PyAccess):
|
||||||
|
@ -144,26 +152,27 @@ class _PyAccess8(PyAccess):
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
self.pixels = self.image8
|
self.pixels = self.image8
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
return self.pixels[y][x]
|
return self.pixels[y][x]
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
try:
|
try:
|
||||||
# integer
|
# integer
|
||||||
self.pixels[y][x] = min(color,255)
|
self.pixels[y][x] = min(color, 255)
|
||||||
except:
|
except:
|
||||||
# tuple
|
# tuple
|
||||||
self.pixels[y][x] = min(color[0],255)
|
self.pixels[y][x] = min(color[0], 255)
|
||||||
|
|
||||||
|
|
||||||
class _PyAccessI16_N(PyAccess):
|
class _PyAccessI16_N(PyAccess):
|
||||||
""" I;16 access, native bitendian without conversion """
|
""" I;16 access, native bitendian without conversion """
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
self.pixels = ffi.cast('unsigned short **', self.image)
|
self.pixels = ffi.cast('unsigned short **', self.image)
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
return self.pixels[y][x]
|
return self.pixels[y][x]
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
try:
|
try:
|
||||||
# integer
|
# integer
|
||||||
self.pixels[y][x] = min(color, 65535)
|
self.pixels[y][x] = min(color, 65535)
|
||||||
|
@ -171,35 +180,37 @@ class _PyAccessI16_N(PyAccess):
|
||||||
# tuple
|
# tuple
|
||||||
self.pixels[y][x] = min(color[0], 65535)
|
self.pixels[y][x] = min(color[0], 65535)
|
||||||
|
|
||||||
|
|
||||||
class _PyAccessI16_L(PyAccess):
|
class _PyAccessI16_L(PyAccess):
|
||||||
""" I;16L access, with conversion """
|
""" I;16L access, with conversion """
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
|
self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
return pixel.l + pixel.r * 256
|
return pixel.l + pixel.r * 256
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
try:
|
try:
|
||||||
color = min(color, 65535)
|
color = min(color, 65535)
|
||||||
except:
|
except TypeError:
|
||||||
color = min(color[0], 65535)
|
color = min(color[0], 65535)
|
||||||
|
|
||||||
pixel.l = color & 0xFF
|
pixel.l = color & 0xFF
|
||||||
pixel.r = color >> 8
|
pixel.r = color >> 8
|
||||||
|
|
||||||
|
|
||||||
class _PyAccessI16_B(PyAccess):
|
class _PyAccessI16_B(PyAccess):
|
||||||
""" I;16B access, with conversion """
|
""" I;16B access, with conversion """
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
|
self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
return pixel.l *256 + pixel.r
|
return pixel.l * 256 + pixel.r
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
try:
|
try:
|
||||||
color = min(color, 65535)
|
color = min(color, 65535)
|
||||||
|
@ -209,17 +220,19 @@ class _PyAccessI16_B(PyAccess):
|
||||||
pixel.l = color >> 8
|
pixel.l = color >> 8
|
||||||
pixel.r = color & 0xFF
|
pixel.r = color & 0xFF
|
||||||
|
|
||||||
|
|
||||||
class _PyAccessI32_N(PyAccess):
|
class _PyAccessI32_N(PyAccess):
|
||||||
""" Signed Int32 access, native endian """
|
""" Signed Int32 access, native endian """
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
self.pixels = self.image32
|
self.pixels = self.image32
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
return self.pixels[y][x]
|
return self.pixels[y][x]
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
self.pixels[y][x] = color
|
self.pixels[y][x] = color
|
||||||
|
|
||||||
|
|
||||||
class _PyAccessI32_Swap(PyAccess):
|
class _PyAccessI32_Swap(PyAccess):
|
||||||
""" I;32L/B access, with byteswapping conversion """
|
""" I;32L/B access, with byteswapping conversion """
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
|
@ -228,24 +241,26 @@ class _PyAccessI32_Swap(PyAccess):
|
||||||
def reverse(self, i):
|
def reverse(self, i):
|
||||||
orig = ffi.new('int *', i)
|
orig = ffi.new('int *', i)
|
||||||
chars = ffi.cast('unsigned char *', orig)
|
chars = ffi.cast('unsigned char *', orig)
|
||||||
chars[0],chars[1],chars[2],chars[3] = chars[3], chars[2],chars[1],chars[0]
|
chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], \
|
||||||
|
chars[1], chars[0]
|
||||||
return ffi.cast('int *', chars)[0]
|
return ffi.cast('int *', chars)[0]
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
return self.reverse(self.pixels[y][x])
|
return self.reverse(self.pixels[y][x])
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
self.pixels[y][x] = self.reverse(color)
|
self.pixels[y][x] = self.reverse(color)
|
||||||
|
|
||||||
|
|
||||||
class _PyAccessF(PyAccess):
|
class _PyAccessF(PyAccess):
|
||||||
""" 32 bit float access """
|
""" 32 bit float access """
|
||||||
def _post_init(self, *args, **kwargs):
|
def _post_init(self, *args, **kwargs):
|
||||||
self.pixels = ffi.cast('float **', self.image32)
|
self.pixels = ffi.cast('float **', self.image32)
|
||||||
|
|
||||||
def get_pixel(self, x,y):
|
def get_pixel(self, x, y):
|
||||||
return self.pixels[y][x]
|
return self.pixels[y][x]
|
||||||
|
|
||||||
def set_pixel(self, x,y, color):
|
def set_pixel(self, x, y, color):
|
||||||
try:
|
try:
|
||||||
# not a tuple
|
# not a tuple
|
||||||
self.pixels[y][x] = color
|
self.pixels[y][x] = color
|
||||||
|
@ -286,13 +301,15 @@ else:
|
||||||
mode_map['I;32L'] = _PyAccessI32_Swap
|
mode_map['I;32L'] = _PyAccessI32_Swap
|
||||||
mode_map['I;32B'] = _PyAccessI32_N
|
mode_map['I;32B'] = _PyAccessI32_N
|
||||||
|
|
||||||
def new(img, readonly=False):
|
|
||||||
|
|
||||||
|
def new(img, readonly=False):
|
||||||
access_type = mode_map.get(img.mode, None)
|
access_type = mode_map.get(img.mode, None)
|
||||||
if not access_type:
|
if not access_type:
|
||||||
if DEBUG: print ("PyAccess Not Implemented: %s" % img.mode)
|
if DEBUG:
|
||||||
|
print("PyAccess Not Implemented: %s" % img.mode)
|
||||||
return None
|
return None
|
||||||
if DEBUG: print ("New PyAccess: %s" % img.mode)
|
if DEBUG:
|
||||||
|
print("New PyAccess: %s" % img.mode)
|
||||||
return access_type(img, readonly)
|
return access_type(img, readonly)
|
||||||
|
|
||||||
|
# End of file
|
||||||
|
|
|
@ -48,7 +48,7 @@ def isInt(f):
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
except:
|
except ValueError:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
iforms = [1, 3, -11, -12, -21, -22]
|
iforms = [1, 3, -11, -12, -21, -22]
|
||||||
|
@ -127,12 +127,12 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
if self.istack == 0 and self.imgnumber == 0:
|
if self.istack == 0 and self.imgnumber == 0:
|
||||||
# stk=0, img=0: a regular 2D image
|
# stk=0, img=0: a regular 2D image
|
||||||
offset = hdrlen
|
offset = hdrlen
|
||||||
self.nimages = 1
|
self._nimages = 1
|
||||||
elif self.istack > 0 and self.imgnumber == 0:
|
elif self.istack > 0 and self.imgnumber == 0:
|
||||||
# stk>0, img=0: Opening the stack for the first time
|
# stk>0, img=0: Opening the stack for the first time
|
||||||
self.imgbytes = int(h[12]) * int(h[2]) * 4
|
self.imgbytes = int(h[12]) * int(h[2]) * 4
|
||||||
self.hdrlen = hdrlen
|
self.hdrlen = hdrlen
|
||||||
self.nimages = int(h[26])
|
self._nimages = int(h[26])
|
||||||
# Point to the first image in the stack
|
# Point to the first image in the stack
|
||||||
offset = hdrlen * 2
|
offset = hdrlen * 2
|
||||||
self.imgnumber = 1
|
self.imgnumber = 1
|
||||||
|
@ -154,6 +154,10 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
(self.rawmode, 0, 1))]
|
(self.rawmode, 0, 1))]
|
||||||
self.__fp = self.fp # FIXME: hack
|
self.__fp = self.fp # FIXME: hack
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return self._nimages
|
||||||
|
|
||||||
# 1st image index is zero (although SPIDER imgnumber starts at 1)
|
# 1st image index is zero (although SPIDER imgnumber starts at 1)
|
||||||
def tell(self):
|
def tell(self):
|
||||||
if self.imgnumber < 1:
|
if self.imgnumber < 1:
|
||||||
|
@ -164,7 +168,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if self.istack == 0:
|
if self.istack == 0:
|
||||||
return
|
return
|
||||||
if frame >= self.nimages:
|
if frame >= self._nimages:
|
||||||
raise EOFError("attempt to seek past end of file")
|
raise EOFError("attempt to seek past end of file")
|
||||||
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
|
@ -173,11 +177,11 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# returns a byte image after rescaling to 0..255
|
# returns a byte image after rescaling to 0..255
|
||||||
def convert2byte(self, depth=255):
|
def convert2byte(self, depth=255):
|
||||||
(min, max) = self.getextrema()
|
(minimum, maximum) = self.getextrema()
|
||||||
m = 1
|
m = 1
|
||||||
if max != min:
|
if maximum != minimum:
|
||||||
m = depth / (max-min)
|
m = depth / (maximum-minimum)
|
||||||
b = -m * min
|
b = -m * minimum
|
||||||
return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
|
return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
|
||||||
|
|
||||||
# returns a ImageTk.PhotoImage object, after rescaling to 0..255
|
# returns a ImageTk.PhotoImage object, after rescaling to 0..255
|
||||||
|
@ -271,7 +275,7 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
def _save_spider(im, fp, filename):
|
def _save_spider(im, fp, filename):
|
||||||
# get the filename extension and register it with Image
|
# 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)
|
Image.register_extension("SPIDER", ext)
|
||||||
_save(im, fp, filename)
|
_save(im, fp, filename)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
from PIL import ContainerIO
|
from PIL import ContainerIO
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# A file object that provides read access to a given member of a TAR
|
# A file object that provides read access to a given member of a TAR
|
||||||
# file.
|
# file.
|
||||||
|
|
|
@ -42,9 +42,6 @@ MODES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
|
||||||
return prefix[0:1] == b"\0"
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Targa files.
|
# Image plugin for Targa files.
|
||||||
|
|
||||||
|
@ -58,7 +55,7 @@ class TgaImageFile(ImageFile.ImageFile):
|
||||||
# process header
|
# process header
|
||||||
s = self.fp.read(18)
|
s = self.fp.read(18)
|
||||||
|
|
||||||
id = i8(s[0])
|
idlen = i8(s[0])
|
||||||
|
|
||||||
colormaptype = i8(s[1])
|
colormaptype = i8(s[1])
|
||||||
imagetype = i8(s[2])
|
imagetype = i8(s[2])
|
||||||
|
@ -70,7 +67,7 @@ class TgaImageFile(ImageFile.ImageFile):
|
||||||
self.size = i16(s[12:]), i16(s[14:])
|
self.size = i16(s[12:]), i16(s[14:])
|
||||||
|
|
||||||
# validate header fields
|
# validate header fields
|
||||||
if id != 0 or colormaptype not in (0, 1) or\
|
if colormaptype not in (0, 1) or\
|
||||||
self.size[0] <= 0 or self.size[1] <= 0 or\
|
self.size[0] <= 0 or self.size[1] <= 0 or\
|
||||||
depth not in (1, 8, 16, 24, 32):
|
depth not in (1, 8, 16, 24, 32):
|
||||||
raise SyntaxError("not a TGA file")
|
raise SyntaxError("not a TGA file")
|
||||||
|
@ -79,7 +76,7 @@ class TgaImageFile(ImageFile.ImageFile):
|
||||||
if imagetype in (3, 11):
|
if imagetype in (3, 11):
|
||||||
self.mode = "L"
|
self.mode = "L"
|
||||||
if depth == 1:
|
if depth == 1:
|
||||||
self.mode = "1" # ???
|
self.mode = "1" # ???
|
||||||
elif imagetype in (1, 9):
|
elif imagetype in (1, 9):
|
||||||
self.mode = "P"
|
self.mode = "P"
|
||||||
elif imagetype in (2, 10):
|
elif imagetype in (2, 10):
|
||||||
|
@ -103,22 +100,25 @@ class TgaImageFile(ImageFile.ImageFile):
|
||||||
if imagetype & 8:
|
if imagetype & 8:
|
||||||
self.info["compression"] = "tga_rle"
|
self.info["compression"] = "tga_rle"
|
||||||
|
|
||||||
|
if idlen:
|
||||||
|
self.info["id_section"] = self.fp.read(idlen)
|
||||||
|
|
||||||
if colormaptype:
|
if colormaptype:
|
||||||
# read palette
|
# read palette
|
||||||
start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:])
|
start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:])
|
||||||
if mapdepth == 16:
|
if mapdepth == 16:
|
||||||
self.palette = ImagePalette.raw("BGR;16",
|
self.palette = ImagePalette.raw(
|
||||||
b"\0"*2*start + self.fp.read(2*size))
|
"BGR;16", b"\0"*2*start + self.fp.read(2*size))
|
||||||
elif mapdepth == 24:
|
elif mapdepth == 24:
|
||||||
self.palette = ImagePalette.raw("BGR",
|
self.palette = ImagePalette.raw(
|
||||||
b"\0"*3*start + self.fp.read(3*size))
|
"BGR", b"\0"*3*start + self.fp.read(3*size))
|
||||||
elif mapdepth == 32:
|
elif mapdepth == 32:
|
||||||
self.palette = ImagePalette.raw("BGRA",
|
self.palette = ImagePalette.raw(
|
||||||
b"\0"*4*start + self.fp.read(4*size))
|
"BGRA", b"\0"*4*start + self.fp.read(4*size))
|
||||||
|
|
||||||
# setup tile descriptor
|
# setup tile descriptor
|
||||||
try:
|
try:
|
||||||
rawmode = MODES[(imagetype&7, depth)]
|
rawmode = MODES[(imagetype & 7, depth)]
|
||||||
if imagetype & 8:
|
if imagetype & 8:
|
||||||
# compressed
|
# compressed
|
||||||
self.tile = [("tga_rle", (0, 0)+self.size,
|
self.tile = [("tga_rle", (0, 0)+self.size,
|
||||||
|
@ -127,7 +127,7 @@ class TgaImageFile(ImageFile.ImageFile):
|
||||||
self.tile = [("raw", (0, 0)+self.size,
|
self.tile = [("raw", (0, 0)+self.size,
|
||||||
self.fp.tell(), (rawmode, 0, orientation))]
|
self.fp.tell(), (rawmode, 0, orientation))]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass # cannot decode
|
pass # cannot decode
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -145,6 +145,7 @@ SAVE = {
|
||||||
"RGBA": ("BGRA", 32, 0, 2),
|
"RGBA": ("BGRA", 32, 0, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, check=0):
|
def _save(im, fp, filename, check=0):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -185,13 +186,14 @@ def _save(im, fp, filename, check=0):
|
||||||
if colormaptype:
|
if colormaptype:
|
||||||
fp.write(im.im.getpalette("RGB", "BGR"))
|
fp.write(im.im.getpalette("RGB", "BGR"))
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, orientation))])
|
ImageFile._save(
|
||||||
|
im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))])
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Registry
|
# Registry
|
||||||
|
|
||||||
Image.register_open("TGA", TgaImageFile, _accept)
|
Image.register_open("TGA", TgaImageFile)
|
||||||
Image.register_save("TGA", _save)
|
Image.register_save("TGA", _save)
|
||||||
|
|
||||||
Image.register_extension("TGA", ".tga")
|
Image.register_extension("TGA", ".tga")
|
||||||
|
|
|
@ -54,6 +54,7 @@ import sys
|
||||||
import collections
|
import collections
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
|
import io
|
||||||
|
|
||||||
# Set these to true to force use of libtiff for reading or writing.
|
# Set these to true to force use of libtiff for reading or writing.
|
||||||
READ_LIBTIFF = False
|
READ_LIBTIFF = False
|
||||||
|
@ -149,6 +150,7 @@ OPEN_INFO = {
|
||||||
(II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
|
(II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
|
||||||
(II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
|
(II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
|
||||||
(II, 1, 1, 1, (1,), ()): ("1", "1"),
|
(II, 1, 1, 1, (1,), ()): ("1", "1"),
|
||||||
|
(II, 1, 1, 1, (4,), ()): ("L", "L;4"),
|
||||||
(II, 1, 1, 2, (1,), ()): ("1", "1;R"),
|
(II, 1, 1, 2, (1,), ()): ("1", "1;R"),
|
||||||
(II, 1, 1, 1, (8,), ()): ("L", "L"),
|
(II, 1, 1, 1, (8,), ()): ("L", "L"),
|
||||||
(II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
|
(II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
|
||||||
|
@ -281,6 +283,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
self.tagdata = {}
|
self.tagdata = {}
|
||||||
self.tagtype = {} # added 2008-06-05 by Florian Hoech
|
self.tagtype = {} # added 2008-06-05 by Florian Hoech
|
||||||
self.next = None
|
self.next = None
|
||||||
|
self.offset = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.as_dict())
|
return str(self.as_dict())
|
||||||
|
@ -291,7 +294,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
|
|
||||||
def named(self):
|
def named(self):
|
||||||
"""
|
"""
|
||||||
Returns the complete tag dictionary, with named tags where posible.
|
Returns the complete tag dictionary, with named tags where possible.
|
||||||
"""
|
"""
|
||||||
from PIL import TiffTags
|
from PIL import TiffTags
|
||||||
result = {}
|
result = {}
|
||||||
|
@ -415,6 +418,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
# load tag dictionary
|
# load tag dictionary
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
self.offset = fp.tell()
|
||||||
|
|
||||||
i16 = self.i16
|
i16 = self.i16
|
||||||
i32 = self.i32
|
i32 = self.i32
|
||||||
|
@ -422,6 +426,11 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
for i in range(i16(fp.read(2))):
|
for i in range(i16(fp.read(2))):
|
||||||
|
|
||||||
ifd = fp.read(12)
|
ifd = fp.read(12)
|
||||||
|
if len(ifd) != 12:
|
||||||
|
warnings.warn("Possibly corrupt EXIF data. "
|
||||||
|
"Expecting to read 12 bytes but only got %d."
|
||||||
|
% (len(ifd)))
|
||||||
|
continue
|
||||||
|
|
||||||
tag, typ = i16(ifd), i16(ifd, 2)
|
tag, typ = i16(ifd), i16(ifd, 2)
|
||||||
|
|
||||||
|
@ -446,7 +455,11 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
# Get and expand tag value
|
# Get and expand tag value
|
||||||
if size > 4:
|
if size > 4:
|
||||||
here = fp.tell()
|
here = fp.tell()
|
||||||
|
if Image.DEBUG:
|
||||||
|
print("Tag Location: %s" % here)
|
||||||
fp.seek(i32(ifd, 8))
|
fp.seek(i32(ifd, 8))
|
||||||
|
if Image.DEBUG:
|
||||||
|
print("Data Location: %s" % fp.tell())
|
||||||
data = ImageFile._safe_read(fp, size)
|
data = ImageFile._safe_read(fp, size)
|
||||||
fp.seek(here)
|
fp.seek(here)
|
||||||
else:
|
else:
|
||||||
|
@ -468,7 +481,14 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
else:
|
else:
|
||||||
print("- value:", self[tag])
|
print("- value:", self[tag])
|
||||||
|
|
||||||
self.next = i32(fp.read(4))
|
ifd = fp.read(4)
|
||||||
|
if len(ifd) != 4:
|
||||||
|
warnings.warn("Possibly corrupt EXIF data. "
|
||||||
|
"Expecting to read 4 bytes but only got %d."
|
||||||
|
% (len(ifd)))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.next = i32(ifd)
|
||||||
|
|
||||||
# save primitives
|
# save primitives
|
||||||
|
|
||||||
|
@ -498,7 +518,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
typ = self.tagtype[tag]
|
typ = self.tagtype[tag]
|
||||||
|
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
|
print("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
|
||||||
|
|
||||||
if typ == 1:
|
if typ == 1:
|
||||||
# byte data
|
# byte data
|
||||||
|
@ -509,6 +529,15 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
elif typ == 7:
|
elif typ == 7:
|
||||||
# untyped data
|
# untyped data
|
||||||
data = value = b"".join(value)
|
data = value = b"".join(value)
|
||||||
|
elif typ in (11, 12):
|
||||||
|
# float value
|
||||||
|
tmap = {11: 'f', 12: 'd'}
|
||||||
|
if not isinstance(value, tuple):
|
||||||
|
value = (value,)
|
||||||
|
a = array.array(tmap[typ], value)
|
||||||
|
if self.prefix != native_prefix:
|
||||||
|
a.byteswap()
|
||||||
|
data = a.tostring()
|
||||||
elif isStringType(value[0]):
|
elif isStringType(value[0]):
|
||||||
# string data
|
# string data
|
||||||
if isinstance(value, tuple):
|
if isinstance(value, tuple):
|
||||||
|
@ -619,45 +648,63 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.__first = self.__next = self.ifd.i32(ifh, 4)
|
self.__first = self.__next = self.ifd.i32(ifh, 4)
|
||||||
self.__frame = -1
|
self.__frame = -1
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
|
self._frame_pos = []
|
||||||
|
self._n_frames = None
|
||||||
|
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("*** TiffImageFile._open ***")
|
print("*** TiffImageFile._open ***")
|
||||||
print ("- __first:", self.__first)
|
print("- __first:", self.__first)
|
||||||
print ("- ifh: ", ifh)
|
print("- ifh: ", ifh)
|
||||||
|
|
||||||
# and load the first frame
|
# and load the first frame
|
||||||
self._seek(0)
|
self._seek(0)
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
"Select a given frame as current image"
|
"Select a given frame as current image"
|
||||||
|
self._seek(max(frame, 0)) # Questionable backwards compatibility.
|
||||||
if frame < 0:
|
# Create a new core image object on second and
|
||||||
frame = 0
|
# subsequent frames in the image. Image may be
|
||||||
self._seek(frame)
|
# different size/mode.
|
||||||
|
Image._decompression_bomb_check(self.size)
|
||||||
def tell(self):
|
self.im = Image.core.new(self.mode, self.size)
|
||||||
"Return the current frame number"
|
|
||||||
|
|
||||||
return self._tell()
|
|
||||||
|
|
||||||
def _seek(self, frame):
|
def _seek(self, frame):
|
||||||
|
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
if frame < self.__frame:
|
while len(self._frame_pos) <= frame:
|
||||||
# rewind file
|
|
||||||
self.__frame = -1
|
|
||||||
self.__next = self.__first
|
|
||||||
while self.__frame < frame:
|
|
||||||
if not self.__next:
|
if not self.__next:
|
||||||
raise EOFError("no more images in TIFF file")
|
raise EOFError("no more images in TIFF file")
|
||||||
|
if Image.DEBUG:
|
||||||
|
print("Seeking to frame %s, on frame %s, __next %s, location: %s" %
|
||||||
|
(frame, self.__frame, self.__next, self.fp.tell()))
|
||||||
|
# reset python3 buffered io handle in case fp
|
||||||
|
# was passed to libtiff, invalidating the buffer
|
||||||
|
self.fp.tell()
|
||||||
self.fp.seek(self.__next)
|
self.fp.seek(self.__next)
|
||||||
|
self._frame_pos.append(self.__next)
|
||||||
|
if Image.DEBUG:
|
||||||
|
print("Loading tags, location: %s" % self.fp.tell())
|
||||||
self.tag.load(self.fp)
|
self.tag.load(self.fp)
|
||||||
self.__next = self.tag.next
|
self.__next = self.tag.next
|
||||||
self.__frame += 1
|
self.__frame += 1
|
||||||
|
self.fp.seek(self._frame_pos[frame])
|
||||||
|
self.tag.load(self.fp)
|
||||||
|
self.__frame = frame
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
def _tell(self):
|
def tell(self):
|
||||||
|
"Return the current frame number"
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
def _decoder(self, rawmode, layer, tile=None):
|
def _decoder(self, rawmode, layer, tile=None):
|
||||||
|
@ -705,7 +752,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# (self._compression, (extents tuple),
|
# (self._compression, (extents tuple),
|
||||||
# 0, (rawmode, self._compression, fp))
|
# 0, (rawmode, self._compression, fp))
|
||||||
ignored, extents, ignored_2, args = self.tile[0]
|
extents = self.tile[0][1]
|
||||||
|
args = self.tile[0][3] + (self.ifd.offset,)
|
||||||
decoder = Image._getdecoder(self.mode, 'libtiff', args,
|
decoder = Image._getdecoder(self.mode, 'libtiff', args,
|
||||||
self.decoderconfig)
|
self.decoderconfig)
|
||||||
try:
|
try:
|
||||||
|
@ -722,21 +770,21 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# Rearranging for supporting byteio items, since they have a fileno
|
# Rearranging for supporting byteio items, since they have a fileno
|
||||||
# that returns an IOError if there's no underlying fp. Easier to
|
# that returns an IOError if there's no underlying fp. Easier to
|
||||||
# dea. with here by reordering.
|
# deal with here by reordering.
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("have getvalue. just sending in a string from getvalue")
|
print("have getvalue. just sending in a string from getvalue")
|
||||||
n, err = decoder.decode(self.fp.getvalue())
|
n, err = decoder.decode(self.fp.getvalue())
|
||||||
elif hasattr(self.fp, "fileno"):
|
elif hasattr(self.fp, "fileno"):
|
||||||
# we've got a actual file on disk, pass in the fp.
|
# we've got a actual file on disk, pass in the fp.
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("have fileno, calling fileno version of the decoder.")
|
print("have fileno, calling fileno version of the decoder.")
|
||||||
self.fp.seek(0)
|
self.fp.seek(0)
|
||||||
# 4 bytes, otherwise the trace might error out
|
# 4 bytes, otherwise the trace might error out
|
||||||
n, err = decoder.decode(b"fpfp")
|
n, err = decoder.decode(b"fpfp")
|
||||||
else:
|
else:
|
||||||
# we have something else.
|
# we have something else.
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("don't have fileno or getvalue. just reading")
|
print("don't have fileno or getvalue. just reading")
|
||||||
# UNDONE -- so much for that buffer size thing.
|
# UNDONE -- so much for that buffer size thing.
|
||||||
n, err = decoder.decode(self.fp.read())
|
n, err = decoder.decode(self.fp.read())
|
||||||
|
|
||||||
|
@ -744,7 +792,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
# libtiff closed the fp in a, we need to close self.fp, if possible
|
# libtiff closed the fp in a, we need to close self.fp, if possible
|
||||||
if hasattr(self.fp, 'close'):
|
if hasattr(self.fp, 'close'):
|
||||||
self.fp.close()
|
if not self.__next:
|
||||||
|
self.fp.close()
|
||||||
self.fp = None # might be shared
|
self.fp = None # might be shared
|
||||||
|
|
||||||
if err < 0:
|
if err < 0:
|
||||||
|
@ -866,6 +915,10 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
try:
|
try:
|
||||||
fp = hasattr(self.fp, "fileno") and \
|
fp = hasattr(self.fp, "fileno") and \
|
||||||
os.dup(self.fp.fileno())
|
os.dup(self.fp.fileno())
|
||||||
|
# flush the file descriptor, prevents error on pypy 2.4+
|
||||||
|
# should also eliminate the need for fp.tell for py3
|
||||||
|
# in _seek
|
||||||
|
self.fp.flush()
|
||||||
except IOError:
|
except IOError:
|
||||||
# io.BytesIO have a fileno, but returns an IOError if
|
# io.BytesIO have a fileno, but returns an IOError if
|
||||||
# it doesn't use a file descriptor.
|
# it doesn't use a file descriptor.
|
||||||
|
@ -911,7 +964,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
(0, min(y, ysize), w, min(y+h, ysize)),
|
(0, min(y, ysize), w, min(y+h, ysize)),
|
||||||
offsets[i], a))
|
offsets[i], a))
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("tiles: ", self.tile)
|
print("tiles: ", self.tile)
|
||||||
y = y + h
|
y = y + h
|
||||||
if y >= self.size[1]:
|
if y >= self.size[1]:
|
||||||
x = y = 0
|
x = y = 0
|
||||||
|
@ -946,14 +999,14 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# fixup palette descriptor
|
# fixup palette descriptor
|
||||||
|
|
||||||
if self.mode == "P":
|
if self.mode == "P":
|
||||||
palette = [o8(a // 256) for a in self.tag[COLORMAP]]
|
palette = [o8(b // 256) for b in self.tag[COLORMAP]]
|
||||||
self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
|
self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Write TIFF files
|
# Write TIFF files
|
||||||
|
|
||||||
# little endian is default except for image modes with
|
# little endian is default except for image modes with
|
||||||
# explict big endian byte-order
|
# explicit big endian byte-order
|
||||||
|
|
||||||
SAVE_INFO = {
|
SAVE_INFO = {
|
||||||
# mode => rawmode, byteorder, photometrics,
|
# mode => rawmode, byteorder, photometrics,
|
||||||
|
@ -1045,31 +1098,25 @@ def _save(im, fp, filename):
|
||||||
if "icc_profile" in im.info:
|
if "icc_profile" in im.info:
|
||||||
ifd[ICCPROFILE] = im.info["icc_profile"]
|
ifd[ICCPROFILE] = im.info["icc_profile"]
|
||||||
|
|
||||||
if "description" in im.encoderinfo:
|
for key, name, cvt in [
|
||||||
ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"]
|
(IMAGEDESCRIPTION, "description", lambda x: x),
|
||||||
if "resolution" in im.encoderinfo:
|
(X_RESOLUTION, "resolution", _cvt_res),
|
||||||
ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \
|
(Y_RESOLUTION, "resolution", _cvt_res),
|
||||||
= _cvt_res(im.encoderinfo["resolution"])
|
(X_RESOLUTION, "x_resolution", _cvt_res),
|
||||||
if "x resolution" in im.encoderinfo:
|
(Y_RESOLUTION, "y_resolution", _cvt_res),
|
||||||
ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"])
|
(RESOLUTION_UNIT, "resolution_unit",
|
||||||
if "y resolution" in im.encoderinfo:
|
lambda x: {"inch": 2, "cm": 3, "centimeter": 3}.get(x, 1)),
|
||||||
ifd[Y_RESOLUTION] = _cvt_res(im.encoderinfo["y resolution"])
|
(SOFTWARE, "software", lambda x: x),
|
||||||
if "resolution unit" in im.encoderinfo:
|
(DATE_TIME, "date_time", lambda x: x),
|
||||||
unit = im.encoderinfo["resolution unit"]
|
(ARTIST, "artist", lambda x: x),
|
||||||
if unit == "inch":
|
(COPYRIGHT, "copyright", lambda x: x)]:
|
||||||
ifd[RESOLUTION_UNIT] = 2
|
name_with_spaces = name.replace("_", " ")
|
||||||
elif unit == "cm" or unit == "centimeter":
|
if "_" in name and name_with_spaces in im.encoderinfo:
|
||||||
ifd[RESOLUTION_UNIT] = 3
|
warnings.warn("%r is deprecated; use %r instead" %
|
||||||
else:
|
(name_with_spaces, name), DeprecationWarning)
|
||||||
ifd[RESOLUTION_UNIT] = 1
|
ifd[key] = cvt(im.encoderinfo[name.replace("_", " ")])
|
||||||
if "software" in im.encoderinfo:
|
if name in im.encoderinfo:
|
||||||
ifd[SOFTWARE] = im.encoderinfo["software"]
|
ifd[key] = cvt(im.encoderinfo[name])
|
||||||
if "date time" in im.encoderinfo:
|
|
||||||
ifd[DATE_TIME] = im.encoderinfo["date time"]
|
|
||||||
if "artist" in im.encoderinfo:
|
|
||||||
ifd[ARTIST] = im.encoderinfo["artist"]
|
|
||||||
if "copyright" in im.encoderinfo:
|
|
||||||
ifd[COPYRIGHT] = im.encoderinfo["copyright"]
|
|
||||||
|
|
||||||
dpi = im.encoderinfo.get("dpi")
|
dpi = im.encoderinfo.get("dpi")
|
||||||
if dpi:
|
if dpi:
|
||||||
|
@ -1102,12 +1149,15 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
if libtiff:
|
if libtiff:
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("Saving using libtiff encoder")
|
print("Saving using libtiff encoder")
|
||||||
print (ifd.items())
|
print(ifd.items())
|
||||||
_fp = 0
|
_fp = 0
|
||||||
if hasattr(fp, "fileno"):
|
if hasattr(fp, "fileno"):
|
||||||
fp.seek(0)
|
try:
|
||||||
_fp = os.dup(fp.fileno())
|
fp.seek(0)
|
||||||
|
_fp = os.dup(fp.fileno())
|
||||||
|
except io.UnsupportedOperation:
|
||||||
|
pass
|
||||||
|
|
||||||
# ICC Profile crashes.
|
# ICC Profile crashes.
|
||||||
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE]
|
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE]
|
||||||
|
@ -1132,8 +1182,11 @@ def _save(im, fp, filename):
|
||||||
# following tiffcp.c->cpTag->TIFF_RATIONAL
|
# following tiffcp.c->cpTag->TIFF_RATIONAL
|
||||||
atts[k] = float(v[0][0])/float(v[0][1])
|
atts[k] = float(v[0][0])/float(v[0][1])
|
||||||
continue
|
continue
|
||||||
if type(v) == tuple and len(v) > 2:
|
if (type(v) == tuple and
|
||||||
|
(len(v) > 2 or
|
||||||
|
(len(v) == 2 and v[1] == 0))):
|
||||||
# List of ints?
|
# List of ints?
|
||||||
|
# Avoid divide by zero in next if-clause
|
||||||
if type(v[0]) in (int, float):
|
if type(v[0]) in (int, float):
|
||||||
atts[k] = list(v)
|
atts[k] = list(v)
|
||||||
continue
|
continue
|
||||||
|
@ -1154,7 +1207,7 @@ def _save(im, fp, filename):
|
||||||
atts[k] = v
|
atts[k] = v
|
||||||
|
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print (atts)
|
print(atts)
|
||||||
|
|
||||||
# libtiff always expects the bytes in native order.
|
# libtiff always expects the bytes in native order.
|
||||||
# we're storing image byte order. So, if the rawmode
|
# we're storing image byte order. So, if the rawmode
|
||||||
|
|
|
@ -46,8 +46,8 @@ TAGS = {
|
||||||
(262, 5): "CMYK",
|
(262, 5): "CMYK",
|
||||||
(262, 6): "YCbCr",
|
(262, 6): "YCbCr",
|
||||||
(262, 8): "CieLAB",
|
(262, 8): "CieLAB",
|
||||||
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
|
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
|
||||||
(262, 32892): "LinearRaw", # Adobe DNG
|
(262, 32892): "LinearRaw", # Adobe DNG
|
||||||
|
|
||||||
263: "Thresholding",
|
263: "Thresholding",
|
||||||
264: "CellWidth",
|
264: "CellWidth",
|
||||||
|
@ -255,7 +255,6 @@ TAGS = {
|
||||||
50716: "BlackLevelDeltaV",
|
50716: "BlackLevelDeltaV",
|
||||||
50717: "WhiteLevel",
|
50717: "WhiteLevel",
|
||||||
50718: "DefaultScale",
|
50718: "DefaultScale",
|
||||||
50741: "BestQualityScale", # FIXME! Dictionary contains duplicate keys 50741
|
|
||||||
50719: "DefaultCropOrigin",
|
50719: "DefaultCropOrigin",
|
||||||
50720: "DefaultCropSize",
|
50720: "DefaultCropSize",
|
||||||
50778: "CalibrationIlluminant1",
|
50778: "CalibrationIlluminant1",
|
||||||
|
@ -279,11 +278,12 @@ TAGS = {
|
||||||
50737: "ChromaBlurRadius",
|
50737: "ChromaBlurRadius",
|
||||||
50738: "AntiAliasStrength",
|
50738: "AntiAliasStrength",
|
||||||
50740: "DNGPrivateData",
|
50740: "DNGPrivateData",
|
||||||
50741: "MakerNoteSafety", # FIXME! Dictionary contains duplicate keys 50741
|
50741: "MakerNoteSafety",
|
||||||
|
50780: "BestQualityScale",
|
||||||
|
|
||||||
#ImageJ
|
# ImageJ
|
||||||
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
|
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
|
||||||
50839: "ImageJMetaData", # private tag registered with Adobe
|
50839: "ImageJMetaData", # private tag registered with Adobe
|
||||||
}
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
# -*- coding: iso-8859-1 -*-
|
|
||||||
#
|
|
||||||
# The Python Imaging Library.
|
# The Python Imaging Library.
|
||||||
# $Id$
|
# $Id$
|
||||||
#
|
#
|
||||||
|
@ -33,6 +31,7 @@ except ImportError:
|
||||||
|
|
||||||
i32 = _binary.i32le
|
i32 = _binary.i32le
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Load texture from a Quake2 WAL texture file.
|
# Load texture from a Quake2 WAL texture file.
|
||||||
# <p>
|
# <p>
|
||||||
|
@ -75,7 +74,7 @@ def open(filename):
|
||||||
|
|
||||||
|
|
||||||
quake2palette = (
|
quake2palette = (
|
||||||
# default palette taken from piffo 0.93 by Hans Häggström
|
# default palette taken from piffo 0.93 by Hans Häggström
|
||||||
b"\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e"
|
b"\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e"
|
||||||
b"\x1e\x1e\x22\x22\x22\x26\x26\x26\x29\x29\x29\x2c\x2c\x2c\x2f\x2f"
|
b"\x1e\x1e\x22\x22\x22\x26\x26\x26\x29\x29\x29\x2c\x2c\x2c\x2f\x2f"
|
||||||
b"\x2f\x32\x32\x32\x35\x35\x35\x37\x37\x37\x3a\x3a\x3a\x3c\x3c\x3c"
|
b"\x2f\x32\x32\x32\x35\x35\x35\x37\x37\x37\x3a\x3a\x3a\x3c\x3c\x3c"
|
||||||
|
|
|
@ -12,7 +12,7 @@ _VALID_WEBP_MODES = {
|
||||||
_VP8_MODES_BY_IDENTIFIER = {
|
_VP8_MODES_BY_IDENTIFIER = {
|
||||||
b"VP8 ": "RGB",
|
b"VP8 ": "RGB",
|
||||||
b"VP8X": "RGBA",
|
b"VP8X": "RGBA",
|
||||||
b"VP8L": "RGBA", # lossless
|
b"VP8L": "RGBA", # lossless
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
format_description = "WebP image"
|
format_description = "WebP image"
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(self.fp.read())
|
data, width, height, self.mode, icc_profile, exif = \
|
||||||
|
_webp.WebPDecode(self.fp.read())
|
||||||
|
|
||||||
if icc_profile:
|
if icc_profile:
|
||||||
self.info["icc_profile"] = icc_profile
|
self.info["icc_profile"] = icc_profile
|
||||||
|
|
|
@ -24,6 +24,7 @@ _handler = None
|
||||||
if str != bytes:
|
if str != bytes:
|
||||||
long = int
|
long = int
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Install application-specific WMF image handler.
|
# Install application-specific WMF image handler.
|
||||||
#
|
#
|
||||||
|
@ -36,14 +37,14 @@ def register_handler(handler):
|
||||||
if hasattr(Image.core, "drawwmf"):
|
if hasattr(Image.core, "drawwmf"):
|
||||||
# install default handler (windows only)
|
# install default handler (windows only)
|
||||||
|
|
||||||
class WmfHandler:
|
class WmfHandler(object):
|
||||||
|
|
||||||
def open(self, im):
|
def open(self, im):
|
||||||
im.mode = "RGB"
|
im.mode = "RGB"
|
||||||
self.bbox = im.info["wmf_bbox"]
|
self.bbox = im.info["wmf_bbox"]
|
||||||
|
|
||||||
def load(self, im):
|
def load(self, im):
|
||||||
im.fp.seek(0) # rewind
|
im.fp.seek(0) # rewind
|
||||||
return Image.frombytes(
|
return Image.frombytes(
|
||||||
"RGB", im.size,
|
"RGB", im.size,
|
||||||
Image.core.drawwmf(im.fp.read(), im.size, self.bbox),
|
Image.core.drawwmf(im.fp.read(), im.size, self.bbox),
|
||||||
|
@ -56,6 +57,7 @@ if hasattr(Image.core, "drawwmf"):
|
||||||
|
|
||||||
word = _binary.i16le
|
word = _binary.i16le
|
||||||
|
|
||||||
|
|
||||||
def short(c, o=0):
|
def short(c, o=0):
|
||||||
v = word(c, o)
|
v = word(c, o)
|
||||||
if v >= 32768:
|
if v >= 32768:
|
||||||
|
@ -64,6 +66,7 @@ def short(c, o=0):
|
||||||
|
|
||||||
dword = _binary.i32le
|
dword = _binary.i32le
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Read WMF file
|
# Read WMF file
|
||||||
|
@ -74,6 +77,7 @@ def _accept(prefix):
|
||||||
prefix[:4] == b"\x01\x00\x00\x00"
|
prefix[:4] == b"\x01\x00\x00\x00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Windows metafiles.
|
# Image plugin for Windows metafiles.
|
||||||
|
|
||||||
|
@ -95,8 +99,10 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
inch = word(s, 14)
|
inch = word(s, 14)
|
||||||
|
|
||||||
# get bounding box
|
# get bounding box
|
||||||
x0 = short(s, 6); y0 = short(s, 8)
|
x0 = short(s, 6)
|
||||||
x1 = short(s, 10); y1 = short(s, 12)
|
y0 = short(s, 8)
|
||||||
|
x1 = short(s, 10)
|
||||||
|
y1 = short(s, 12)
|
||||||
|
|
||||||
# normalize size to 72 dots per inch
|
# normalize size to 72 dots per inch
|
||||||
size = (x1 - x0) * 72 // inch, (y1 - y0) * 72 // inch
|
size = (x1 - x0) * 72 // inch, (y1 - y0) * 72 // inch
|
||||||
|
@ -115,8 +121,10 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
# enhanced metafile
|
# enhanced metafile
|
||||||
|
|
||||||
# get bounding box
|
# get bounding box
|
||||||
x0 = dword(s, 8); y0 = dword(s, 12)
|
x0 = dword(s, 8)
|
||||||
x1 = dword(s, 16); y1 = dword(s, 20)
|
y0 = dword(s, 12)
|
||||||
|
x1 = dword(s, 16)
|
||||||
|
y1 = dword(s, 20)
|
||||||
|
|
||||||
# get frame (in 0.01 millimeter units)
|
# get frame (in 0.01 millimeter units)
|
||||||
frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36)
|
frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36)
|
||||||
|
|
|
@ -30,6 +30,7 @@ for r in range(8):
|
||||||
for b in range(4):
|
for b in range(4):
|
||||||
PALETTE = PALETTE + (o8((r*255)//7)+o8((g*255)//7)+o8((b*255)//3))
|
PALETTE = PALETTE + (o8((r*255)//7)+o8((g*255)//7)+o8((b*255)//3))
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for XV thumbnail images.
|
# Image plugin for XV thumbnail images.
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,11 @@ xbm_head = re.compile(
|
||||||
b"[\\000-\\377]*_bits\\[\\]"
|
b"[\\000-\\377]*_bits\\[\\]"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix.lstrip()[:7] == b"#define"
|
return prefix.lstrip()[:7] == b"#define"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for X11 bitmaps.
|
# Image plugin for X11 bitmaps.
|
||||||
|
|
||||||
|
@ -81,7 +83,7 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
fp.write(b"static char im_bits[] = {\n")
|
fp.write(b"static char im_bits[] = {\n")
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("xbm", (0,0)+im.size, 0, None)])
|
ImageFile._save(im, fp, [("xbm", (0, 0)+im.size, 0, None)])
|
||||||
|
|
||||||
fp.write(b"};\n")
|
fp.write(b"};\n")
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
# ;-)
|
# ;-)
|
||||||
|
|
||||||
VERSION = '1.1.7' # PIL version
|
VERSION = '1.1.7' # PIL version
|
||||||
PILLOW_VERSION = '2.5.3' # Pillow
|
PILLOW_VERSION = '2.9.0.dev0' # Pillow
|
||||||
|
|
||||||
_plugins = ['BmpImagePlugin',
|
_plugins = ['BmpImagePlugin',
|
||||||
'BufrStubImagePlugin',
|
'BufrStubImagePlugin',
|
||||||
|
|
|
@ -11,21 +11,24 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from struct import unpack, pack
|
||||||
|
|
||||||
if bytes is str:
|
if bytes is str:
|
||||||
def i8(c):
|
def i8(c):
|
||||||
return ord(c)
|
return ord(c)
|
||||||
|
|
||||||
def o8(i):
|
def o8(i):
|
||||||
return chr(i&255)
|
return chr(i & 255)
|
||||||
else:
|
else:
|
||||||
def i8(c):
|
def i8(c):
|
||||||
return c if c.__class__ is int else c[0]
|
return c if c.__class__ is int else c[0]
|
||||||
|
|
||||||
def o8(i):
|
def o8(i):
|
||||||
return bytes((i&255,))
|
return bytes((i & 255,))
|
||||||
|
|
||||||
|
|
||||||
# Input, le = little endian, be = big endian
|
# Input, le = little endian, be = big endian
|
||||||
#TODO: replace with more readable struct.unpack equivalent
|
# TODO: replace with more readable struct.unpack equivalent
|
||||||
def i16le(c, o=0):
|
def i16le(c, o=0):
|
||||||
"""
|
"""
|
||||||
Converts a 2-bytes (16 bits) string to an integer.
|
Converts a 2-bytes (16 bits) string to an integer.
|
||||||
|
@ -33,7 +36,8 @@ def i16le(c, o=0):
|
||||||
c: string containing bytes to convert
|
c: string containing bytes to convert
|
||||||
o: offset of bytes to convert in string
|
o: offset of bytes to convert in string
|
||||||
"""
|
"""
|
||||||
return i8(c[o]) | (i8(c[o+1])<<8)
|
return unpack("<H", c[o:o+2])[0]
|
||||||
|
|
||||||
|
|
||||||
def i32le(c, o=0):
|
def i32le(c, o=0):
|
||||||
"""
|
"""
|
||||||
|
@ -42,24 +46,31 @@ def i32le(c, o=0):
|
||||||
c: string containing bytes to convert
|
c: string containing bytes to convert
|
||||||
o: offset of bytes to convert in string
|
o: offset of bytes to convert in string
|
||||||
"""
|
"""
|
||||||
return i8(c[o]) | (i8(c[o+1])<<8) | (i8(c[o+2])<<16) | (i8(c[o+3])<<24)
|
return unpack("<I", c[o:o+4])[0]
|
||||||
|
|
||||||
|
|
||||||
def i16be(c, o=0):
|
def i16be(c, o=0):
|
||||||
return (i8(c[o])<<8) | i8(c[o+1])
|
return unpack(">H", c[o:o+2])[0]
|
||||||
|
|
||||||
|
|
||||||
def i32be(c, o=0):
|
def i32be(c, o=0):
|
||||||
return (i8(c[o])<<24) | (i8(c[o+1])<<16) | (i8(c[o+2])<<8) | i8(c[o+3])
|
return unpack(">I", c[o:o+4])[0]
|
||||||
|
|
||||||
|
|
||||||
# Output, le = little endian, be = big endian
|
# Output, le = little endian, be = big endian
|
||||||
def o16le(i):
|
def o16le(i):
|
||||||
return o8(i) + o8(i>>8)
|
return pack("<H", i)
|
||||||
|
|
||||||
|
|
||||||
def o32le(i):
|
def o32le(i):
|
||||||
return o8(i) + o8(i>>8) + o8(i>>16) + o8(i>>24)
|
return pack("<I", i)
|
||||||
|
|
||||||
|
|
||||||
def o16be(i):
|
def o16be(i):
|
||||||
return o8(i>>8) + o8(i)
|
return pack(">H", i)
|
||||||
|
|
||||||
|
|
||||||
def o32be(i):
|
def o32be(i):
|
||||||
return o8(i>>24) + o8(i>>16) + o8(i>>8) + o8(i)
|
return pack(">I", i)
|
||||||
|
|
||||||
|
# End of file
|
||||||
|
|
67
PIL/features.py
Normal file
67
PIL/features.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
modules = {
|
||||||
|
"pil": "PIL._imaging",
|
||||||
|
"tkinter": "PIL._imagingtk",
|
||||||
|
"freetype2": "PIL._imagingft",
|
||||||
|
"littlecms2": "PIL._imagingcms",
|
||||||
|
"webp": "PIL._webp",
|
||||||
|
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_module(feature):
|
||||||
|
if feature not in modules:
|
||||||
|
raise ValueError("Unknown module %s" % feature)
|
||||||
|
|
||||||
|
module = modules[feature]
|
||||||
|
|
||||||
|
method_to_call = None
|
||||||
|
if type(module) is tuple:
|
||||||
|
module, method_to_call = module
|
||||||
|
|
||||||
|
try:
|
||||||
|
imported_module = __import__(module)
|
||||||
|
except ImportError:
|
||||||
|
# If a method is being checked, None means that
|
||||||
|
# rather than the method failing, the module required for the method
|
||||||
|
# failed to be imported first
|
||||||
|
return None if method_to_call else False
|
||||||
|
|
||||||
|
if method_to_call:
|
||||||
|
method = getattr(imported_module, method_to_call)
|
||||||
|
return method() is True
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_supported_modules():
|
||||||
|
supported_modules = []
|
||||||
|
for feature in modules:
|
||||||
|
if check_module(feature):
|
||||||
|
supported_modules.append(feature)
|
||||||
|
return supported_modules
|
||||||
|
|
||||||
|
codecs = {
|
||||||
|
"jpg": "jpeg",
|
||||||
|
"jpg_2000": "jpeg2k",
|
||||||
|
"zlib": "zip",
|
||||||
|
"libtiff": "libtiff"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_codec(feature):
|
||||||
|
if feature not in codecs:
|
||||||
|
raise ValueError("Unknown codec %s" % feature)
|
||||||
|
|
||||||
|
codec = codecs[feature]
|
||||||
|
|
||||||
|
return codec + "_encoder" in dir(Image.core)
|
||||||
|
|
||||||
|
|
||||||
|
def get_supported_codecs():
|
||||||
|
supported_codecs = []
|
||||||
|
for feature in codecs:
|
||||||
|
if check_codec(feature):
|
||||||
|
supported_codecs.append(feature)
|
||||||
|
return supported_codecs
|
38
README.rst
38
README.rst
|
@ -1,22 +1,48 @@
|
||||||
Pillow
|
Pillow
|
||||||
======
|
======
|
||||||
|
|
||||||
*Python Imaging Library (Fork)*
|
Python Imaging Library (Fork)
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
Pillow is the "friendly" PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation <http://pillow.readthedocs.org/>`_, `check the changelog <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst>`_ and `find out how to contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_.
|
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://travis-ci.org/python-pillow/Pillow.svg?branch=master
|
||||||
:target: https://travis-ci.org/python-pillow/Pillow
|
:target: https://travis-ci.org/python-pillow/Pillow
|
||||||
:alt: Travis CI build status
|
:alt: Travis CI build status (Linux)
|
||||||
|
|
||||||
.. image:: https://pypip.in/v/Pillow/badge.png
|
..
|
||||||
|
image:: https://pypip.in/v/Pillow/badge.png
|
||||||
:target: https://pypi.python.org/pypi/Pillow/
|
:target: https://pypi.python.org/pypi/Pillow/
|
||||||
:alt: Latest PyPI version
|
:alt: Latest PyPI version
|
||||||
|
|
||||||
.. image:: https://pypip.in/d/Pillow/badge.png
|
..
|
||||||
|
image:: https://pypip.in/d/Pillow/badge.png
|
||||||
:target: https://pypi.python.org/pypi/Pillow/
|
:target: https://pypi.python.org/pypi/Pillow/
|
||||||
:alt: Number of PyPI downloads
|
:alt: Number of PyPI downloads
|
||||||
|
|
||||||
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
|
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
|
||||||
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
|
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
|
||||||
|
:alt: Code coverage
|
||||||
|
|
||||||
|
.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.png
|
||||||
|
: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>`_
|
||||||
|
|
||||||
|
- `Pre-fork <https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#02b5---117-1995-2010>`_
|
||||||
|
|
||||||
|
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/CONTRIBUTING.md>`_
|
||||||
|
|
||||||
|
- `Issues <https://github.com/python-pillow/Pillow/issues>`_
|
||||||
|
|
||||||
|
- `Documentation <http://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>`_
|
||||||
|
|
105
RELEASING.md
Normal file
105
RELEASING.md
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
# Release Checklist
|
||||||
|
|
||||||
|
## Main Release
|
||||||
|
|
||||||
|
Released quarterly on the first day of January, April, July, October.
|
||||||
|
|
||||||
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174
|
||||||
|
* [ ] Develop and prepare release in ``master`` branch.
|
||||||
|
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
|
||||||
|
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
|
||||||
|
```
|
||||||
|
PIL/__init__.py setup.py _imaging.c
|
||||||
|
```
|
||||||
|
* [ ] Update `CHANGES.rst`.
|
||||||
|
* [ ] Run pre-release check via `make pre`.
|
||||||
|
* [ ] 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 sdistup
|
||||||
|
```
|
||||||
|
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||||
|
* [ ] Manually hide old versions on PyPI as needed, such that only the latest main release is visible when viewing https://pypi.python.org/pypi/Pillow
|
||||||
|
|
||||||
|
## Point Release
|
||||||
|
|
||||||
|
Released as needed for security, installation or critical bug fixes.
|
||||||
|
|
||||||
|
* [ ] 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
|
||||||
|
```
|
||||||
|
* [ ] Run pre-release check via `make pre`.
|
||||||
|
* [ ] 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
|
||||||
|
|
||||||
|
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`
|
||||||
|
* [ ] 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
|
||||||
|
```
|
||||||
|
* [ ] Create and upload source distributions e.g.:
|
||||||
|
```
|
||||||
|
$ make sdistup
|
||||||
|
```
|
||||||
|
* [ ] 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.
|
34
Sane/CHANGES
34
Sane/CHANGES
|
@ -1,34 +0,0 @@
|
||||||
|
|
||||||
from V1.0 to V2.0
|
|
||||||
|
|
||||||
_sane.c:
|
|
||||||
- Values for option constraints are correctly translated to floats
|
|
||||||
if value type is TYPE_FIXED for SANE_CONSTRAINT_RANGE and
|
|
||||||
SANE_CONSTRAINT_WORD_LIST
|
|
||||||
- added constants INFO_INEXACT, INFO_RELOAD_OPTIONS,
|
|
||||||
INFO_RELOAD_PARAMS (possible return values of set_option())
|
|
||||||
to module dictionnary.
|
|
||||||
- removed additional return variable 'i' from SaneDev_get_option(),
|
|
||||||
because it is only set when SANE_ACTION_SET_VALUE is used.
|
|
||||||
- scanDev.get_parameters() now returns the scanner mode as 'format',
|
|
||||||
no more the typical PIL codes. So 'L' became 'gray', 'RGB' is now
|
|
||||||
'color', 'R' is 'red', 'G' is 'green', 'B' is 'red'. This matches
|
|
||||||
the way scanDev.mode is set.
|
|
||||||
This should be the only incompatibility vs. version 1.0.
|
|
||||||
|
|
||||||
sane.py
|
|
||||||
- ScanDev got new method __load_option_dict() called from __init__()
|
|
||||||
and from __setattr__() if backend reported that the frontend should
|
|
||||||
reload the options.
|
|
||||||
- Nice human-readable __repr__() method added for class Option
|
|
||||||
- if __setattr__ (i.e. set_option) reports that all other options
|
|
||||||
have to be reloaded due to a change in the backend then they are reloaded.
|
|
||||||
- due to the change in SaneDev_get_option() only the 'value' is
|
|
||||||
returned from get_option().
|
|
||||||
- in __setattr__ integer values are automatically converted to floats
|
|
||||||
if SANE backend expects SANE_FIXED (i.e. fix-point float)
|
|
||||||
- The scanner options can now directly be accessed via scanDev[optionName]
|
|
||||||
instead scanDev.opt[optionName]. (The old way still works).
|
|
||||||
|
|
||||||
V1.0:
|
|
||||||
A.M. Kuchling's original pysane package.
|
|
|
@ -1,22 +0,0 @@
|
||||||
Python SANE module V1.1 (30 Sep. 2004)
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
The SANE module provides an interface to the SANE scanner and frame
|
|
||||||
grabber interface for Linux. This module was contributed by Andrew
|
|
||||||
Kuchling and is extended and currently maintained by Ralph Heinkel
|
|
||||||
(rheinkel-at-email.de). If you write to me please make sure to have the
|
|
||||||
word 'SANE' or 'sane' in the subject of your mail, otherwise it might
|
|
||||||
be classified as spam in the future.
|
|
||||||
|
|
||||||
|
|
||||||
To build this module, type (in the Sane directory)::
|
|
||||||
|
|
||||||
python setup.py build
|
|
||||||
|
|
||||||
In order to install the module type::
|
|
||||||
|
|
||||||
python setup.py install
|
|
||||||
|
|
||||||
|
|
||||||
For some basic documentation please look at the file sanedoc.txt
|
|
||||||
The two demo_*.py scripts give basic examples on how to use the software.
|
|
1405
Sane/_sane.c
1405
Sane/_sane.c
File diff suppressed because it is too large
Load Diff
|
@ -1,41 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
#
|
|
||||||
# Shows how to scan a 16 bit grayscale image into a numarray object
|
|
||||||
#
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
# Get the path set up to find PIL modules if not installed yet:
|
|
||||||
import sys ; sys.path.append('../PIL')
|
|
||||||
|
|
||||||
from numarray import *
|
|
||||||
import sane
|
|
||||||
import Image
|
|
||||||
|
|
||||||
def toImage(arr):
|
|
||||||
if arr.type().bytes == 1:
|
|
||||||
# need to swap coordinates btw array and image (with [::-1])
|
|
||||||
im = Image.frombytes('L', arr.shape[::-1], arr.tostring())
|
|
||||||
else:
|
|
||||||
arr_c = arr - arr.min()
|
|
||||||
arr_c *= (255./arr_c.max())
|
|
||||||
arr = arr_c.astype(UInt8)
|
|
||||||
# need to swap coordinates btw array and image (with [::-1])
|
|
||||||
im = Image.frombytes('L', arr.shape[::-1], arr.tostring())
|
|
||||||
return im
|
|
||||||
|
|
||||||
print('SANE version:', sane.init())
|
|
||||||
print('Available devices=', sane.get_devices())
|
|
||||||
|
|
||||||
s = sane.open(sane.get_devices()[0][0])
|
|
||||||
|
|
||||||
# Set scan parameters
|
|
||||||
s.mode = 'gray'
|
|
||||||
s.br_x=320. ; s.br_y=240.
|
|
||||||
|
|
||||||
print('Device parameters:', s.get_parameters())
|
|
||||||
|
|
||||||
s.depth=16
|
|
||||||
arr16 = s.arr_scan()
|
|
||||||
toImage(arr16).show()
|
|
|
@ -1,35 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
#
|
|
||||||
# Shows how to scan a color image into a PIL rgb-image
|
|
||||||
#
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
# Get the path set up to find PIL modules if not installed yet:
|
|
||||||
import sys ; sys.path.append('../PIL')
|
|
||||||
|
|
||||||
import sane
|
|
||||||
print('SANE version:', sane.init())
|
|
||||||
print('Available devices=', sane.get_devices())
|
|
||||||
|
|
||||||
s = sane.open(sane.get_devices()[0][0])
|
|
||||||
|
|
||||||
s.mode = 'color'
|
|
||||||
s.br_x=320. ; s.br_y=240.
|
|
||||||
|
|
||||||
print('Device parameters:', s.get_parameters())
|
|
||||||
|
|
||||||
# Initiate the scan
|
|
||||||
s.start()
|
|
||||||
|
|
||||||
# Get an Image object
|
|
||||||
# (For my B&W QuickCam, this is a grey-scale image. Other scanning devices
|
|
||||||
# may return a
|
|
||||||
im=s.snap()
|
|
||||||
|
|
||||||
# Write the image out as a GIF file
|
|
||||||
#im.save('foo.gif')
|
|
||||||
|
|
||||||
# The show method() simply saves the image to a temporary file and calls "xv".
|
|
||||||
im.show()
|
|
288
Sane/sane.py
288
Sane/sane.py
|
@ -1,288 +0,0 @@
|
||||||
# sane.py
|
|
||||||
#
|
|
||||||
# Python wrapper on top of the _sane module, which is in turn a very
|
|
||||||
# thin wrapper on top of the SANE library. For a complete understanding
|
|
||||||
# of SANE, consult the documentation at the SANE home page:
|
|
||||||
# http://www.mostang.com/sane/ .
|
|
||||||
|
|
||||||
__version__ = '2.0'
|
|
||||||
__author__ = ['Andrew Kuchling', 'Ralph Heinkel']
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
import _sane
|
|
||||||
from _sane import *
|
|
||||||
|
|
||||||
TYPE_STR = { TYPE_BOOL: "TYPE_BOOL", TYPE_INT: "TYPE_INT",
|
|
||||||
TYPE_FIXED: "TYPE_FIXED", TYPE_STRING: "TYPE_STRING",
|
|
||||||
TYPE_BUTTON: "TYPE_BUTTON", TYPE_GROUP: "TYPE_GROUP" }
|
|
||||||
|
|
||||||
UNIT_STR = { UNIT_NONE: "UNIT_NONE",
|
|
||||||
UNIT_PIXEL: "UNIT_PIXEL",
|
|
||||||
UNIT_BIT: "UNIT_BIT",
|
|
||||||
UNIT_MM: "UNIT_MM",
|
|
||||||
UNIT_DPI: "UNIT_DPI",
|
|
||||||
UNIT_PERCENT: "UNIT_PERCENT",
|
|
||||||
UNIT_MICROSECOND: "UNIT_MICROSECOND" }
|
|
||||||
|
|
||||||
|
|
||||||
class Option:
|
|
||||||
"""Class representing a SANE option.
|
|
||||||
Attributes:
|
|
||||||
index -- number from 0 to n, giving the option number
|
|
||||||
name -- a string uniquely identifying the option
|
|
||||||
title -- single-line string containing a title for the option
|
|
||||||
desc -- a long string describing the option; useful as a help message
|
|
||||||
type -- type of this option. Possible values: TYPE_BOOL,
|
|
||||||
TYPE_INT, TYPE_STRING, and so forth.
|
|
||||||
unit -- units of this option. Possible values: UNIT_NONE,
|
|
||||||
UNIT_PIXEL, etc.
|
|
||||||
size -- size of the value in bytes
|
|
||||||
cap -- capabilities available; CAP_EMULATED, CAP_SOFT_SELECT, etc.
|
|
||||||
constraint -- constraint on values. Possible values:
|
|
||||||
None : No constraint
|
|
||||||
(min,max,step) Integer values, from min to max, stepping by
|
|
||||||
list of integers or strings: only the listed values are allowed
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, args, scanDev):
|
|
||||||
self.scanDev = scanDev # needed to get current value of this option
|
|
||||||
self.index, self.name = args[0], args[1]
|
|
||||||
self.title, self.desc = args[2], args[3]
|
|
||||||
self.type, self.unit = args[4], args[5]
|
|
||||||
self.size, self.cap = args[6], args[7]
|
|
||||||
self.constraint = args[8]
|
|
||||||
def f(x):
|
|
||||||
if x=='-': return '_'
|
|
||||||
else: return x
|
|
||||||
if not isinstance(self.name, str): self.py_name=str(self.name)
|
|
||||||
else: self.py_name=''.join(map(f, self.name))
|
|
||||||
|
|
||||||
def is_active(self):
|
|
||||||
return _sane.OPTION_IS_ACTIVE(self.cap)
|
|
||||||
def is_settable(self):
|
|
||||||
return _sane.OPTION_IS_SETTABLE(self.cap)
|
|
||||||
def __repr__(self):
|
|
||||||
if self.is_settable():
|
|
||||||
settable = 'yes'
|
|
||||||
else:
|
|
||||||
settable = 'no'
|
|
||||||
if self.is_active():
|
|
||||||
active = 'yes'
|
|
||||||
curValue = repr(getattr(self.scanDev, self.py_name))
|
|
||||||
else:
|
|
||||||
active = 'no'
|
|
||||||
curValue = '<not available, inactive option>'
|
|
||||||
s = """\nName: %s
|
|
||||||
Cur value: %s
|
|
||||||
Index: %d
|
|
||||||
Title: %s
|
|
||||||
Desc: %s
|
|
||||||
Type: %s
|
|
||||||
Unit: %s
|
|
||||||
Constr: %s
|
|
||||||
active: %s
|
|
||||||
settable: %s\n""" % (self.py_name, curValue,
|
|
||||||
self.index, self.title, self.desc,
|
|
||||||
TYPE_STR[self.type], UNIT_STR[self.unit],
|
|
||||||
repr(self.constraint), active, settable)
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
class _SaneIterator:
|
|
||||||
""" intended for ADF scans.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, device):
|
|
||||||
self.device = device
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.device.cancel()
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
try:
|
|
||||||
self.device.start()
|
|
||||||
except error as v:
|
|
||||||
if v == 'Document feeder out of documents':
|
|
||||||
raise StopIteration
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
return self.device.snap(1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SaneDev:
|
|
||||||
"""Class representing a SANE device.
|
|
||||||
Methods:
|
|
||||||
start() -- initiate a scan, using the current settings
|
|
||||||
snap() -- snap a picture, returning an Image object
|
|
||||||
arr_snap() -- snap a picture, returning a numarray object
|
|
||||||
cancel() -- cancel an in-progress scanning operation
|
|
||||||
fileno() -- return the file descriptor for the scanner (handy for select)
|
|
||||||
|
|
||||||
Also available, but rather low-level:
|
|
||||||
get_parameters() -- get the current parameter settings of the device
|
|
||||||
get_options() -- return a list of tuples describing all the options.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
optlist -- list of option names
|
|
||||||
|
|
||||||
You can also access an option name to retrieve its value, and to
|
|
||||||
set it. For example, if one option has a .name attribute of
|
|
||||||
imagemode, and scanner is a SaneDev object, you can do:
|
|
||||||
print scanner.imagemode
|
|
||||||
scanner.imagemode = 'Full frame'
|
|
||||||
scanner.['imagemode'] returns the corresponding Option object.
|
|
||||||
"""
|
|
||||||
def __init__(self, devname):
|
|
||||||
d=self.__dict__
|
|
||||||
d['sane_signature'] = self._getSaneSignature(devname)
|
|
||||||
d['scanner_model'] = d['sane_signature'][1:3]
|
|
||||||
d['dev'] = _sane._open(devname)
|
|
||||||
self.__load_option_dict()
|
|
||||||
|
|
||||||
def _getSaneSignature(self, devname):
|
|
||||||
devices = get_devices()
|
|
||||||
if not devices:
|
|
||||||
raise RuntimeError('no scanner available')
|
|
||||||
for dev in devices:
|
|
||||||
if devname == dev[0]:
|
|
||||||
return dev
|
|
||||||
raise RuntimeError('no such scan device "%s"' % devname)
|
|
||||||
|
|
||||||
def __load_option_dict(self):
|
|
||||||
d=self.__dict__
|
|
||||||
d['opt']={}
|
|
||||||
optlist=d['dev'].get_options()
|
|
||||||
for t in optlist:
|
|
||||||
o=Option(t, self)
|
|
||||||
if o.type!=TYPE_GROUP:
|
|
||||||
d['opt'][o.py_name]=o
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
|
||||||
dev=self.__dict__['dev']
|
|
||||||
optdict=self.__dict__['opt']
|
|
||||||
if key not in optdict:
|
|
||||||
self.__dict__[key]=value ; return
|
|
||||||
opt=optdict[key]
|
|
||||||
if opt.type==TYPE_GROUP:
|
|
||||||
raise AttributeError("Groups can't be set: "+key)
|
|
||||||
if not _sane.OPTION_IS_ACTIVE(opt.cap):
|
|
||||||
raise AttributeError('Inactive option: '+key)
|
|
||||||
if not _sane.OPTION_IS_SETTABLE(opt.cap):
|
|
||||||
raise AttributeError("Option can't be set by software: "+key)
|
|
||||||
if isinstance(value, int) and opt.type == TYPE_FIXED:
|
|
||||||
# avoid annoying errors of backend if int is given instead float:
|
|
||||||
value = float(value)
|
|
||||||
self.last_opt = dev.set_option(opt.index, value)
|
|
||||||
# do binary AND to find if we have to reload options:
|
|
||||||
if self.last_opt & INFO_RELOAD_OPTIONS:
|
|
||||||
self.__load_option_dict()
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
|
||||||
dev=self.__dict__['dev']
|
|
||||||
optdict=self.__dict__['opt']
|
|
||||||
if key=='optlist':
|
|
||||||
return list(self.opt.keys())
|
|
||||||
if key=='area':
|
|
||||||
return (self.tl_x, self.tl_y),(self.br_x, self.br_y)
|
|
||||||
if key not in optdict:
|
|
||||||
raise AttributeError('No such attribute: '+key)
|
|
||||||
opt=optdict[key]
|
|
||||||
if opt.type==TYPE_BUTTON:
|
|
||||||
raise AttributeError("Buttons don't have values: "+key)
|
|
||||||
if opt.type==TYPE_GROUP:
|
|
||||||
raise AttributeError("Groups don't have values: "+key)
|
|
||||||
if not _sane.OPTION_IS_ACTIVE(opt.cap):
|
|
||||||
raise AttributeError('Inactive option: '+key)
|
|
||||||
value = dev.get_option(opt.index)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self.opt[key]
|
|
||||||
|
|
||||||
def get_parameters(self):
|
|
||||||
"""Return a 5-tuple holding all the current device settings:
|
|
||||||
(format, last_frame, (pixels_per_line, lines), depth, bytes_per_line)
|
|
||||||
|
|
||||||
- format is one of 'L' (grey), 'RGB', 'R' (red), 'G' (green), 'B' (blue).
|
|
||||||
- last_frame [bool] indicates if this is the last frame of a multi frame image
|
|
||||||
- (pixels_per_line, lines) specifies the size of the scanned image (x,y)
|
|
||||||
- lines denotes the number of scanlines per frame
|
|
||||||
- depth gives number of pixels per sample
|
|
||||||
"""
|
|
||||||
return self.dev.get_parameters()
|
|
||||||
|
|
||||||
def get_options(self):
|
|
||||||
"Return a list of tuples describing all the available options"
|
|
||||||
return self.dev.get_options()
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"Initiate a scanning operation"
|
|
||||||
return self.dev.start()
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
"Cancel an in-progress scanning operation"
|
|
||||||
return self.dev.cancel()
|
|
||||||
|
|
||||||
def snap(self, no_cancel=0):
|
|
||||||
"Snap a picture, returning a PIL image object with the results"
|
|
||||||
(mode, last_frame,
|
|
||||||
(xsize, ysize), depth, bytes_per_line) = self.get_parameters()
|
|
||||||
if mode in ['gray', 'red', 'green', 'blue']:
|
|
||||||
format = 'L'
|
|
||||||
elif mode == 'color':
|
|
||||||
format = 'RGB'
|
|
||||||
else:
|
|
||||||
raise ValueError('got unknown "mode" from self.get_parameters()')
|
|
||||||
im=Image.new(format, (xsize,ysize))
|
|
||||||
self.dev.snap( im.im.id, no_cancel )
|
|
||||||
return im
|
|
||||||
|
|
||||||
def scan(self):
|
|
||||||
self.start()
|
|
||||||
return self.snap()
|
|
||||||
|
|
||||||
def multi_scan(self):
|
|
||||||
return _SaneIterator(self)
|
|
||||||
|
|
||||||
def arr_snap(self, multipleOf=1):
|
|
||||||
"""Snap a picture, returning a numarray object with the results.
|
|
||||||
By default the resulting array has the same number of pixels per
|
|
||||||
line as specified in self.get_parameters()[2][0]
|
|
||||||
However sometimes it is necessary to obtain arrays where
|
|
||||||
the number of pixels per line is e.g. a multiple of 4. This can then
|
|
||||||
be achieved with the option 'multipleOf=4'. So if the scanner
|
|
||||||
scanned 34 pixels per line, you will obtain an array with 32 pixels
|
|
||||||
per line.
|
|
||||||
"""
|
|
||||||
(mode, last_frame, (xsize, ysize), depth, bpl) = self.get_parameters()
|
|
||||||
if not mode in ['gray', 'red', 'green', 'blue']:
|
|
||||||
raise RuntimeError('arr_snap() only works with monochrome images')
|
|
||||||
if multipleOf < 1:
|
|
||||||
raise ValueError('option "multipleOf" must be a positive number')
|
|
||||||
elif multipleOf > 1:
|
|
||||||
pixels_per_line = xsize - divmod(xsize, 4)[1]
|
|
||||||
else:
|
|
||||||
pixels_per_line = xsize
|
|
||||||
return self.dev.arr_snap(pixels_per_line)
|
|
||||||
|
|
||||||
def arr_scan(self, multipleOf=1):
|
|
||||||
self.start()
|
|
||||||
return self.arr_snap(multipleOf=multipleOf)
|
|
||||||
|
|
||||||
def fileno(self):
|
|
||||||
"Return the file descriptor for the scanning device"
|
|
||||||
return self.dev.fileno()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.dev.close()
|
|
||||||
|
|
||||||
|
|
||||||
def open(devname):
|
|
||||||
"Open a device for scanning"
|
|
||||||
new=SaneDev(devname)
|
|
||||||
return new
|
|
294
Sane/sanedoc.txt
294
Sane/sanedoc.txt
|
@ -1,294 +0,0 @@
|
||||||
The _sane_ module is an Python interface to the SANE (Scanning is Now
|
|
||||||
Easy) library, which provides access to various raster scanning
|
|
||||||
devices such as flatbed scanners and digital cameras. For more
|
|
||||||
information about SANE, consult the SANE Web site at
|
|
||||||
http://www.mostang.com/sane/ . Note that this
|
|
||||||
documentation doesn't duplicate all the information in the SANE
|
|
||||||
documentation, which you must also consult to get a complete
|
|
||||||
understanding.
|
|
||||||
|
|
||||||
This module has been originally developed by A.M. Kuchling (amk1@erols.com),
|
|
||||||
now development has been taken over by Ralph Heinkel (rheinkel-at-email.de).
|
|
||||||
If you write to me please make sure to have the word 'SANE' or 'sane' in
|
|
||||||
the subject of your mail, otherwise it might be classified as spam in the
|
|
||||||
future.
|
|
||||||
|
|
||||||
|
|
||||||
The module exports two object types, a bunch of constants, and two
|
|
||||||
functions.
|
|
||||||
|
|
||||||
get_devices()
|
|
||||||
Return a list of 4-tuples containing the available scanning
|
|
||||||
devices. Each tuple contains 4 strings: the device name, suitable for
|
|
||||||
passing to _open()_; the device's vendor; the model; and the type of
|
|
||||||
device, such as 'virtual device' or 'video camera'.
|
|
||||||
|
|
||||||
>>> import sane ; sane.get_devices()
|
|
||||||
[('epson:libusb:001:004', 'Epson', 'GT-8300', 'flatbed scanner')]
|
|
||||||
|
|
||||||
open(devicename)
|
|
||||||
Open a device, given a string containing its name. SANE
|
|
||||||
devices have names like 'epson:libusb:001:004'. If the attempt
|
|
||||||
to open the device fails, a _sane.error_ exception will be raised. If
|
|
||||||
there are no problems, a SaneDev object will be returned.
|
|
||||||
As an easy way to open the scanner (if only one is available) just type
|
|
||||||
>>> sane.open(sane.get_devices()[0][0])
|
|
||||||
|
|
||||||
|
|
||||||
SaneDev objects
|
|
||||||
===============
|
|
||||||
|
|
||||||
The basic process of scanning an image consists of getting a SaneDev
|
|
||||||
object for the device, setting various parameters, starting the scan,
|
|
||||||
and then reading the image data. Images are composed of one or more
|
|
||||||
frames; greyscale and one-pass colour scanners return a single frame
|
|
||||||
containing all the image data, but 3-pass scanners will usually return
|
|
||||||
3 frames, one for each of the red, green, blue channels.
|
|
||||||
|
|
||||||
Methods:
|
|
||||||
--------
|
|
||||||
fileno()
|
|
||||||
Returns a file descriptor for the scanning device. This
|
|
||||||
method's existence means that SaneDev objects can be used by the
|
|
||||||
select module.
|
|
||||||
|
|
||||||
get_parameters()
|
|
||||||
Return a tuple containing information about the current settings of
|
|
||||||
the device and the current frame: (format, last_frame,
|
|
||||||
pixels_per_line, lines, depth, bytes_per_line).
|
|
||||||
|
|
||||||
mode -- 'gray' for greyscale image, 'color' for RGB image, or
|
|
||||||
one of 'red', 'green', 'blue' if the image is a single
|
|
||||||
channel of an RGB image (from PIL's point of view,
|
|
||||||
this is equivalent to 'L').
|
|
||||||
last_frame -- A Boolean value, which is true if this is the
|
|
||||||
last frame of the image, and false otherwise.
|
|
||||||
pixels_per_line -- Width of the frame.
|
|
||||||
lines -- Height of the frame.
|
|
||||||
depth -- Depth of the image, measured in bits. SANE will only
|
|
||||||
allow using 8, 16, or 24-bit depths.
|
|
||||||
bytes_per_line -- Bytes required to store a single line of
|
|
||||||
data, as computed from pixels_per_line and depth.
|
|
||||||
|
|
||||||
start()
|
|
||||||
Start a scan. This function must be called before the
|
|
||||||
_snap()_ method can be used.
|
|
||||||
|
|
||||||
cancel()
|
|
||||||
Cancel a scan already in progress.
|
|
||||||
|
|
||||||
snap(no_cancel=0)
|
|
||||||
Snap a single frame of data, returning a PIL Image object
|
|
||||||
containing the data. If no_cancel is false, the Sane library function
|
|
||||||
sane_cancel is called after the scan. This is reasonable in most cases,
|
|
||||||
but may cause backends for duplex ADF scanners to drop the backside image,
|
|
||||||
when snap() is called for the front side image. If no_cancel is true,
|
|
||||||
cancel() should be called manually, after all scans are finished.
|
|
||||||
|
|
||||||
scan()
|
|
||||||
This is just a shortcut for s.start(); s.snap()
|
|
||||||
Returns a PIL image
|
|
||||||
|
|
||||||
multi_scan()
|
|
||||||
This method returns an iterator. It is intended to be used for
|
|
||||||
scanning with an automatic document feeder. The next() method of the
|
|
||||||
iterator tries to start a scan. If this is successful, it returns a
|
|
||||||
PIL Image object, like scan(); if the document feeder runs out of
|
|
||||||
paper, it raises StopIteration, thereby signaling that the sequence
|
|
||||||
is ran out of items.
|
|
||||||
|
|
||||||
arr_snap(multipleOf=1)
|
|
||||||
same as snap, but the result is a NumArray object. (Not that
|
|
||||||
num_array must be installed already at compilation time, otherwise
|
|
||||||
this feature will not be activated).
|
|
||||||
By default the resulting array has the same number of pixels per
|
|
||||||
line as specified in self.get_parameters()[2][0]
|
|
||||||
However sometimes it is necessary to obtain arrays where
|
|
||||||
the number of pixels per line is e.g. a multiple of 4. This can then
|
|
||||||
be achieved with the option 'multipleOf=4'. So if the scanner
|
|
||||||
scanned 34 pixels per line, you will obtain an array with 32 pixels
|
|
||||||
per line.
|
|
||||||
Note that this only works with monochrome images (e.g. gray-scales)
|
|
||||||
|
|
||||||
arr_scan(multipleOf=1)
|
|
||||||
This is just a shortcut for s.start(); s.arr_snap(multipleOf=1)
|
|
||||||
Returns a NumArray object
|
|
||||||
|
|
||||||
close()
|
|
||||||
Closes the object.
|
|
||||||
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
-----------
|
|
||||||
SaneDev objects have a few fixed attributes which are always
|
|
||||||
available, and a larger collection of attributes which vary depending
|
|
||||||
on the device. An Epson 1660 photo scanner has attributes like
|
|
||||||
'mode', 'depth', etc.
|
|
||||||
Another (pseudo scanner), the _pnm:0_ device, takes a PNM file and
|
|
||||||
simulates a scanner using the image data; a SaneDev object
|
|
||||||
representing the _pnm:0_ device therefore has a _filename_ attribute
|
|
||||||
which can be changed to specify the filename, _contrast_ and
|
|
||||||
_brightness_ attributes to modify the returned image, and so forth.
|
|
||||||
|
|
||||||
The values of the scanner options may be an integer, floating-point
|
|
||||||
value, or string, depending on the nature of the option.
|
|
||||||
|
|
||||||
sane_signature
|
|
||||||
The tuple for this scandev that is returned by sane.get_devices()
|
|
||||||
e.g. ('epson:libusb:001:006', 'Epson', 'GT-8300', 'flatbed scanner')
|
|
||||||
|
|
||||||
scanner_model
|
|
||||||
same as sane_signature[1:3], i.e. ('Epson', 'GT-8300') for the case above.
|
|
||||||
|
|
||||||
optlist
|
|
||||||
A list containing the all the options supported by this device.
|
|
||||||
|
|
||||||
>>> import sane ; s=sane.open('epson:libusb:001:004') ; s.optlist
|
|
||||||
['focus_position', 'color_correction', 'sharpness', ...., 'br_x']
|
|
||||||
|
|
||||||
A closer look at all options listed in s.optlist can be obtained
|
|
||||||
through the SaneOption objects.
|
|
||||||
|
|
||||||
SaneOption objects
|
|
||||||
==================
|
|
||||||
|
|
||||||
SANE's option handling is its most elaborate subsystem, intended to
|
|
||||||
allow automatically generating dialog boxes and prompts for user
|
|
||||||
configuration of the scanning device. The SaneOption object can be
|
|
||||||
used to get a human-readable name and description for an option, the
|
|
||||||
units to use, and what the legal values are. No information about the
|
|
||||||
current value of the option is available; for that, read the
|
|
||||||
corresponding attribute of a SaneDev object.
|
|
||||||
|
|
||||||
This documentation does not explain all the details of SANE's option
|
|
||||||
handling; consult the SANE documentation for all the details.
|
|
||||||
|
|
||||||
A scandevice option is accessed via __getitem__. For example
|
|
||||||
s['mode'] returns the option descriptor for the mode-option which
|
|
||||||
controls whether the scanner works in color, grayscale, or b/w mode.
|
|
||||||
|
|
||||||
>>> s['mode']
|
|
||||||
Name: mode
|
|
||||||
Cur value: Color
|
|
||||||
Index: 2
|
|
||||||
Title: Scan mode
|
|
||||||
Desc: Selects the scan mode (e.g., lineart, monochrome, or color).
|
|
||||||
Type: TYPE_STRING
|
|
||||||
Unit: UNIT_NONE
|
|
||||||
Constr: ['Binary', 'Gray', 'Color']
|
|
||||||
active: yes
|
|
||||||
settable: yes
|
|
||||||
|
|
||||||
In order to change 'mode' to 'gray', just type:
|
|
||||||
>>> s.mode = 'gray'
|
|
||||||
|
|
||||||
|
|
||||||
With the attributes and methods of sane-option objects it is possible
|
|
||||||
to access individual option values:
|
|
||||||
|
|
||||||
is_active()
|
|
||||||
Returns true if the option is active.
|
|
||||||
|
|
||||||
is_settable()
|
|
||||||
Returns true if the option can be set under software control.
|
|
||||||
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
|
|
||||||
cap
|
|
||||||
An integer containing various flags about the object's
|
|
||||||
capabilities; whether it's active, whether it's settable, etc. Also
|
|
||||||
available as the _capability_ attribute.
|
|
||||||
|
|
||||||
constraint
|
|
||||||
The constraint placed on the value of this option. If it's
|
|
||||||
_None_, there are essentially no constraint of the value. It may also
|
|
||||||
be a list of integers or strings, in which case the value *must* be
|
|
||||||
one of the possibilities in the list. Numeric values may have a
|
|
||||||
3-tuple as the constraint; this 3-tuple contains _(minimum, maximum,
|
|
||||||
increment)_, and the value must be in the defined range.
|
|
||||||
|
|
||||||
desc
|
|
||||||
A lengthy description of what the option does; it may be shown
|
|
||||||
to the user for clarification.
|
|
||||||
|
|
||||||
index
|
|
||||||
An integer giving the option's index in the option list.
|
|
||||||
|
|
||||||
name
|
|
||||||
A short name for the option, as it comes from the sane-backend.
|
|
||||||
|
|
||||||
py_name
|
|
||||||
The option's name, as a legal Python identifier. The name
|
|
||||||
attribute may contain the '-' character, so it will be converted to
|
|
||||||
'_' for the py_name attribute.
|
|
||||||
|
|
||||||
size
|
|
||||||
For a string-valued option, this is the maximum length allowed.
|
|
||||||
|
|
||||||
title
|
|
||||||
A single-line string that can be used as a title string.
|
|
||||||
|
|
||||||
type
|
|
||||||
A constant giving the type of this option: will be one of the following
|
|
||||||
constants found in the SANE module:
|
|
||||||
TYPE_BOOL
|
|
||||||
TYPE_INT
|
|
||||||
TYPE_FIXED
|
|
||||||
TYPE_STRING
|
|
||||||
TYPE_BUTTON
|
|
||||||
TYPE_GROUP
|
|
||||||
|
|
||||||
unit
|
|
||||||
For numeric-valued options, this is a constant representing
|
|
||||||
the unit used for this option. It will be one of the following
|
|
||||||
constants found in the SANE module:
|
|
||||||
UNIT_NONE
|
|
||||||
UNIT_PIXEL
|
|
||||||
UNIT_BIT
|
|
||||||
UNIT_MM
|
|
||||||
UNIT_DPI
|
|
||||||
UNIT_PERCENT
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Example us usage:
|
|
||||||
=================
|
|
||||||
>>> import sane
|
|
||||||
>>> print 'SANE version:', sane.init()
|
|
||||||
>>> print 'Available devices=', sane.get_devices()
|
|
||||||
SANE version: (16777230, 1, 0, 14)
|
|
||||||
>>> s = sane.open(sane.get_devices()[0][0])
|
|
||||||
>>> print 'Device parameters:', s.get_parameters()
|
|
||||||
Device parameters: ('L', 1, (424, 585), 1, 53)
|
|
||||||
>>> print s.resolution
|
|
||||||
50
|
|
||||||
|
|
||||||
## In order to scan a color image into a PIL object:
|
|
||||||
>>> s.mode = 'color'
|
|
||||||
>>> s.start()
|
|
||||||
>>> img = s.snap()
|
|
||||||
>>> img.show()
|
|
||||||
|
|
||||||
|
|
||||||
## In order to obtain a 16-bit grayscale image at 100DPI in a numarray object
|
|
||||||
## with bottom-right coordinates set to (160, 120) [in millimeter] :
|
|
||||||
>>> s.mode = 'gray'
|
|
||||||
>>> s.br_x=160. ; s.br_y=120.
|
|
||||||
>>> s.resolution = 100
|
|
||||||
>>> s.depth=16
|
|
||||||
>>> s.start()
|
|
||||||
>>> s.get_parameters()[2] # just check the size
|
|
||||||
(624, 472)
|
|
||||||
>>> arr16 = s.arr_snap()
|
|
||||||
>>> arr16
|
|
||||||
array([[63957, 64721, 65067, ..., 65535, 65535, 65535],
|
|
||||||
[63892, 64342, 64236, ..., 65535, 65535, 65535],
|
|
||||||
[64286, 64248, 64705, ..., 65535, 65535, 65535],
|
|
||||||
...,
|
|
||||||
[65518, 65249, 65058, ..., 65535, 65535, 65535],
|
|
||||||
[64435, 65047, 65081, ..., 65535, 65535, 65535],
|
|
||||||
[65309, 65438, 65535, ..., 65535, 65535, 65535]], type=UInt16)
|
|
||||||
>>> arr16.shape # inverse order of coordinates, first y, then x!
|
|
||||||
(472, 624)
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
from distutils.core import setup, Extension
|
|
||||||
|
|
||||||
PIL_BUILD_DIR = '..'
|
|
||||||
PIL_IMAGING_DIR = PIL_BUILD_DIR+'/libImaging'
|
|
||||||
|
|
||||||
defs = []
|
|
||||||
try:
|
|
||||||
import numarray
|
|
||||||
defs.append(('WITH_NUMARRAY',None))
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
sane = Extension('_sane',
|
|
||||||
include_dirs = [PIL_IMAGING_DIR],
|
|
||||||
libraries = ['sane'],
|
|
||||||
library_dirs = [PIL_IMAGING_DIR],
|
|
||||||
define_macros = defs,
|
|
||||||
sources = ['_sane.c'])
|
|
||||||
|
|
||||||
setup (name = 'pysane',
|
|
||||||
version = '2.0',
|
|
||||||
description = 'This is the pysane package',
|
|
||||||
py_modules = ['sane'],
|
|
||||||
ext_modules = [sane])
|
|
|
@ -63,20 +63,3 @@ explode.py
|
||||||
--------------------------------------------------------------------
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
Split a sequence file into individual frames.
|
Split a sequence file into individual frames.
|
||||||
|
|
||||||
image2py.py
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
Convert an image to a Python module containing an IMAGE variable.
|
|
||||||
Note that the module using the module must include JPEG and ZIP
|
|
||||||
decoders, unless the -u option is used.
|
|
||||||
|
|
||||||
olesummary.py
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
Uses the OleFileIO module to dump the summary information from an OLE
|
|
||||||
structured storage file. This works with most OLE files, including
|
|
||||||
Word documents, FlashPix images, etc.
|
|
||||||
|
|
||||||
Note that datetime fields currently show the number of seconds since
|
|
||||||
January 1st, 1601.
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import print_function
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from tkinter import *
|
from tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from Tkinter import *
|
from Tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
|
||||||
|
|
||||||
from PIL import Image, ImageTk, ImageEnhance
|
from PIL import Image, ImageTk, ImageEnhance
|
||||||
import sys
|
import sys
|
||||||
|
@ -18,6 +18,7 @@ import sys
|
||||||
#
|
#
|
||||||
# enhancer widget
|
# enhancer widget
|
||||||
|
|
||||||
|
|
||||||
class Enhance(Frame):
|
class Enhance(Frame):
|
||||||
def __init__(self, master, image, name, enhancer, lo, hi):
|
def __init__(self, master, image, name, enhancer, lo, hi):
|
||||||
Frame.__init__(self, master)
|
Frame.__init__(self, master)
|
||||||
|
@ -25,7 +26,7 @@ class Enhance(Frame):
|
||||||
# set up the image
|
# set up the image
|
||||||
self.tkim = ImageTk.PhotoImage(image.mode, image.size)
|
self.tkim = ImageTk.PhotoImage(image.mode, image.size)
|
||||||
self.enhancer = enhancer(image)
|
self.enhancer = enhancer(image)
|
||||||
self.update("1.0") # normalize
|
self.update("1.0") # normalize
|
||||||
|
|
||||||
# image window
|
# image window
|
||||||
Label(self, image=self.tkim).pack()
|
Label(self, image=self.tkim).pack()
|
||||||
|
|
|
@ -9,11 +9,13 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from PIL import Image
|
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)
|
self.setinterval(interval)
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user