mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 17:24:31 +03:00
Merge branch 'master' into resample-roi
This commit is contained in:
commit
09a2e1641b
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +1,2 @@
|
|||
*.ppm binary
|
||||
*.container binary
|
||||
|
|
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
|
@ -6,7 +6,9 @@
|
|||
|
||||
### What versions of Pillow and Python are you using?
|
||||
|
||||
Please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, django, or buildout, try to replicate the issue just using Pillow.
|
||||
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
|
||||
|
||||
The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, Django, or buildout, try to replicate the issue just using Pillow.
|
||||
|
||||
```python
|
||||
code goes here
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -34,6 +34,9 @@ htmlcov/
|
|||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Test files
|
||||
test_images
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
|
|
133
.travis.yml
133
.travis.yml
|
@ -5,46 +5,40 @@ notifications:
|
|||
|
||||
# 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:
|
||||
- "pypy"
|
||||
- "pypy3"
|
||||
- 3.5
|
||||
- 2.7
|
||||
- "2.7_with_system_site_packages" # For PyQt4
|
||||
- 3.3
|
||||
- 3.4
|
||||
- nightly
|
||||
# Then run the remainder, with fastest Docker jobs last.
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: "pypy-5.7.1"
|
||||
- python: "pypy3.3-5.2-alpha1"
|
||||
- python: '3.6'
|
||||
- python: '2.7'
|
||||
- python: "2.7_with_system_site_packages" # For PyQt4
|
||||
- python: '3.5'
|
||||
- python: '3.4'
|
||||
- python: '3.3'
|
||||
- env: DOCKER="alpine"
|
||||
- env: DOCKER="arch" # contains PyQt5
|
||||
- env: DOCKER="ubuntu-trusty-x86"
|
||||
- env: DOCKER="ubuntu-xenial-amd64"
|
||||
- env: DOCKER="ubuntu-precise-amd64"
|
||||
- env: DOCKER="debian-stretch-x86"
|
||||
- env: DOCKER="centos-6-amd64"
|
||||
- env: DOCKER="amazon-amd64"
|
||||
|
||||
dist: trusty
|
||||
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
install:
|
||||
- "travis_retry sudo apt-get update"
|
||||
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
|
||||
- "travis_retry pip install cffi"
|
||||
- "travis_retry pip install nose"
|
||||
- "travis_retry pip install check-manifest"
|
||||
# Pyroma tests sometimes hang on PyPy; skip
|
||||
- if [ $TRAVIS_PYTHON_VERSION != "pypy" ]; then travis_retry pip install pyroma; fi
|
||||
- if [ "$DOCKER" == "" ]; then .travis/install.sh; fi
|
||||
|
||||
- "travis_retry pip install coverage"
|
||||
|
||||
# docs only on python 2.7
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then travis_retry pip install -r requirements.txt ; fi
|
||||
|
||||
# clean checkout for manifest
|
||||
- mkdir /tmp/check-manifest && cp -a . /tmp/check-manifest
|
||||
|
||||
# webp
|
||||
- pushd depends && ./install_webp.sh && popd
|
||||
|
||||
# openjpeg
|
||||
- pushd depends && ./install_openjpeg.sh && popd
|
||||
|
||||
# libimagequant
|
||||
- pushd depends && ./install_imagequant.sh && popd
|
||||
|
||||
# extra test images
|
||||
- pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
||||
before_install:
|
||||
- if [ "$DOCKER" ]; then docker pull pythonpillow/$DOCKER; fi
|
||||
|
||||
before_script:
|
||||
# Qt needs a display for some of the tests, and it's only run on the system site packages install
|
||||
|
@ -52,59 +46,17 @@ before_script:
|
|||
- "sh -e /etc/init.d/xvfb start"
|
||||
|
||||
script:
|
||||
- coverage erase
|
||||
- python setup.py clean
|
||||
- CFLAGS="-coverage" python setup.py build_ext --inplace
|
||||
|
||||
- coverage run --append --include=PIL/* selftest.py
|
||||
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
|
||||
- pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd
|
||||
|
||||
# Docs
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make install && make doccheck; fi
|
||||
- |
|
||||
if [ "$DOCKER" == "" ]; then
|
||||
.travis/script.sh
|
||||
else
|
||||
# the Pillow user in the docker container is UID 1000
|
||||
sudo chown -R 1000 $TRAVIS_BUILD_DIR
|
||||
docker run -v $TRAVIS_BUILD_DIR:/Pillow pythonpillow/$DOCKER
|
||||
fi
|
||||
|
||||
after_success:
|
||||
# gather the coverage data
|
||||
- travis_retry sudo apt-get -qq install lcov
|
||||
- lcov --capture --directory . -b . --output-file coverage.info
|
||||
# filter to remove system headers
|
||||
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
|
||||
# convert to json
|
||||
- travis_retry gem install coveralls-lcov
|
||||
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
|
||||
|
||||
- coverage report
|
||||
- travis_retry pip install coveralls-merge
|
||||
- coveralls-merge coverage.c.json
|
||||
|
||||
- travis_retry pip install pep8 pyflakes
|
||||
- pep8 --statistics --count PIL/*.py
|
||||
- pep8 --statistics --count Tests/*.py
|
||||
- pyflakes *.py | tee >(wc -l)
|
||||
- pyflakes PIL/*.py | tee >(wc -l)
|
||||
- pyflakes Tests/*.py | tee >(wc -l)
|
||||
|
||||
# Coverage and quality reports on just the latest diff.
|
||||
# (Installation is very slow on Py3, so just do it for Py2.)
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-install.sh; fi
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/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 macOS build..."
|
||||
# Trigger a macOS build at the pillow-wheels repo
|
||||
./build_children.sh
|
||||
else
|
||||
echo "Some jobs failed"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
- .travis/after_success.sh
|
||||
|
||||
after_failure:
|
||||
- |
|
||||
|
@ -127,11 +79,6 @@ after_script:
|
|||
echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS
|
||||
fi
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- python: nightly
|
||||
|
||||
env:
|
||||
global:
|
||||
# travis encrypt AUTH_TOKEN=
|
||||
|
|
49
.travis/after_success.sh
Executable file
49
.travis/after_success.sh
Executable file
|
@ -0,0 +1,49 @@
|
|||
#!/bin/bash
|
||||
|
||||
# gather the coverage data
|
||||
sudo apt-get -qq install lcov
|
||||
lcov --capture --directory . -b . --output-file coverage.info
|
||||
# filter to remove system headers
|
||||
lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
|
||||
# convert to json
|
||||
gem install coveralls-lcov
|
||||
coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
|
||||
|
||||
coverage report
|
||||
pip install codecov
|
||||
pip install coveralls-merge
|
||||
coveralls-merge coverage.c.json
|
||||
codecov
|
||||
|
||||
if [ "$DOCKER" == "" ]; then
|
||||
pip install pep8 pyflakes
|
||||
pep8 --statistics --count PIL/*.py
|
||||
pep8 --statistics --count Tests/*.py
|
||||
pyflakes *.py | tee >(wc -l)
|
||||
pyflakes PIL/*.py | tee >(wc -l)
|
||||
pyflakes Tests/*.py | tee >(wc -l)
|
||||
fi
|
||||
|
||||
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then
|
||||
# Coverage and quality reports on just the latest diff.
|
||||
# (Installation is very slow on Py3, so just do it for Py2.)
|
||||
depends/diffcover-install.sh
|
||||
depends/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 succeeded! Triggering macOS build..."
|
||||
# Trigger a macOS build at the pillow-wheels repo
|
||||
./build_children.sh
|
||||
else
|
||||
echo "Some jobs failed"
|
||||
fi
|
||||
fi
|
||||
fi
|
34
.travis/install.sh
Executable file
34
.travis/install.sh
Executable file
|
@ -0,0 +1,34 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk\
|
||||
python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick\
|
||||
libharfbuzz-dev libfribidi-dev
|
||||
|
||||
pip install cffi
|
||||
pip install nose
|
||||
pip install check-manifest
|
||||
pip install olefile
|
||||
pip install pyroma
|
||||
pip install coverage
|
||||
|
||||
# docs only on Python 2.7
|
||||
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi
|
||||
|
||||
# clean checkout for manifest
|
||||
mkdir /tmp/check-manifest && cp -a . /tmp/check-manifest
|
||||
|
||||
# webp
|
||||
pushd depends && ./install_webp.sh && popd
|
||||
|
||||
# openjpeg
|
||||
pushd depends && ./install_openjpeg.sh && popd
|
||||
|
||||
# libimagequant
|
||||
pushd depends && ./install_imagequant.sh && popd
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
14
.travis/script.sh
Executable file
14
.travis/script.sh
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
coverage erase
|
||||
python setup.py clean
|
||||
CFLAGS="-coverage" python setup.py build_ext --inplace
|
||||
|
||||
coverage run --append --include=PIL/* selftest.py
|
||||
coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
|
||||
pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd
|
||||
|
||||
# Docs
|
||||
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make install && make doccheck; fi
|
771
CHANGES.rst
771
CHANGES.rst
File diff suppressed because it is too large
Load Diff
4
LICENSE
4
LICENSE
|
@ -5,9 +5,9 @@ The Python Imaging Library (PIL) is
|
|||
|
||||
Pillow is the friendly PIL fork. It is
|
||||
|
||||
Copyright © 2016 by Alex Clark and contributors
|
||||
Copyright © 2010-2017 by Alex Clark and contributors
|
||||
|
||||
Like PIL, Pillow is licensed under the MIT-like open source PIL Software License:
|
||||
Like PIL, Pillow is licensed under the open source PIL Software License:
|
||||
|
||||
By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions:
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ prune docs/_static
|
|||
exclude .coveragerc
|
||||
exclude .editorconfig
|
||||
exclude .landscape.yaml
|
||||
exclude .travis
|
||||
exclude .travis/*
|
||||
exclude appveyor.yml
|
||||
exclude build_children.sh
|
||||
exclude tox.ini
|
||||
|
|
13
Makefile
13
Makefile
|
@ -58,6 +58,13 @@ install:
|
|||
python setup.py install
|
||||
python selftest.py --installed
|
||||
|
||||
debug:
|
||||
# make a debug version if we don't have a -dbg python. Leaves in symbols
|
||||
# for our stuff, kills optimization, and redirects to dev null so we
|
||||
# see any build failures.
|
||||
make clean > /dev/null
|
||||
CFLAGS='-g -O0' python setup.py build_ext install > /dev/null
|
||||
|
||||
install-req:
|
||||
pip install -r requirements.txt
|
||||
|
||||
|
@ -77,7 +84,7 @@ release-test:
|
|||
viewdoc
|
||||
|
||||
sdist:
|
||||
python setup.py sdist --format=gztar,zip
|
||||
python setup.py sdist --format=gztar
|
||||
|
||||
test:
|
||||
python test-installed.py
|
||||
|
@ -88,10 +95,10 @@ upload-test:
|
|||
# username:
|
||||
# password:
|
||||
# repository = http://test.pythonpackages.com
|
||||
python setup.py sdist --format=gztar,zip upload -r test
|
||||
python setup.py sdist --format=gztar upload -r test
|
||||
|
||||
upload:
|
||||
python setup.py sdist --format=gztar,zip upload
|
||||
python setup.py sdist --format=gztar upload
|
||||
|
||||
readme:
|
||||
viewdoc
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL import FontFile
|
||||
from __future__ import print_function
|
||||
|
||||
from . import Image, FontFile
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -119,9 +120,9 @@ class BdfFontFile(FontFile.FontFile):
|
|||
|
||||
# fontname = ";".join(font[1:])
|
||||
|
||||
# print "#", fontname
|
||||
# print("#", fontname)
|
||||
# for i in comments:
|
||||
# print "#", i
|
||||
# print("#", i)
|
||||
|
||||
while True:
|
||||
c = bdf_char(fp)
|
||||
|
|
|
@ -24,18 +24,13 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, i16le as i16, i32le as i32, \
|
||||
o8, o16le as o16, o32le as o32
|
||||
import math
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
o8 = _binary.o8
|
||||
o16 = _binary.o16le
|
||||
o32 = _binary.o32le
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Read BMP file
|
||||
|
@ -136,7 +131,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
# ----------------- Process BMP with Bitfields compression (not palette)
|
||||
if file_info['compression'] == self.BITFIELDS:
|
||||
SUPPORTED = {
|
||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0), (0xff000000, 0xff0000, 0xff00, 0x0) ],
|
||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0), (0xff000000, 0xff0000, 0xff00, 0x0)],
|
||||
24: [(0xff0000, 0xff00, 0xff)],
|
||||
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
_handler = None
|
||||
|
||||
|
@ -40,7 +40,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
|
|||
|
||||
offset = self.fp.tell()
|
||||
|
||||
if not _accept(self.fp.read(8)):
|
||||
if not _accept(self.fp.read(4)):
|
||||
raise SyntaxError("Not a BUFR file")
|
||||
|
||||
self.fp.seek(offset)
|
||||
|
|
|
@ -16,18 +16,16 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image, BmpImagePlugin, _binary
|
||||
from . import Image, BmpImagePlugin
|
||||
from ._binary import i8, i16le as i16, i32le as i32
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == b"\0\0\2\0"
|
||||
|
@ -58,14 +56,14 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
|||
m = s
|
||||
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
||||
m = s
|
||||
# print "width", i8(s[0])
|
||||
# print "height", i8(s[1])
|
||||
# print "colors", i8(s[2])
|
||||
# print "reserved", i8(s[3])
|
||||
# print "hotspot x", i16(s[4:])
|
||||
# print "hotspot y", i16(s[6:])
|
||||
# print "bytes", i32(s[8:])
|
||||
# print "offset", i32(s[12:])
|
||||
# print("width", i8(s[0]))
|
||||
# print("height", i8(s[1]))
|
||||
# print("colors", i8(s[2]))
|
||||
# print("reserved", i8(s[3]))
|
||||
# print("hotspot x", i16(s[4:]))
|
||||
# print("hotspot y", i16(s[6:]))
|
||||
# print("bytes", i32(s[8:]))
|
||||
# print("offset", i32(s[12:]))
|
||||
if not m:
|
||||
raise TypeError("No cursors were found")
|
||||
|
||||
|
|
|
@ -21,15 +21,14 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, _binary
|
||||
from PIL.PcxImagePlugin import PcxImageFile
|
||||
from . import Image
|
||||
from ._binary import i32le as i32
|
||||
from .PcxImagePlugin import PcxImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
||||
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return len(prefix) >= 4 and i32(prefix) == MAGIC
|
||||
|
@ -42,7 +41,8 @@ class DcxImageFile(PcxImageFile):
|
|||
|
||||
format = "DCX"
|
||||
format_description = "Intel DCX"
|
||||
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
|
||||
# Header
|
||||
|
|
|
@ -12,7 +12,7 @@ Full text of the CC0 license:
|
|||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
|
||||
# Magic ("DDS ")
|
||||
|
|
|
@ -22,17 +22,16 @@
|
|||
|
||||
import re
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32le as i32
|
||||
|
||||
__version__ = "0.5"
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
i32 = _binary.i32le
|
||||
o32 = _binary.o32le
|
||||
|
||||
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
||||
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
||||
|
||||
|
@ -59,8 +58,8 @@ def has_ghostscript():
|
|||
if not sys.platform.startswith('win'):
|
||||
import subprocess
|
||||
try:
|
||||
gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
|
||||
gs.stdout.read()
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
subprocess.check_call(['gs', '--version'], stdout=devnull)
|
||||
return True
|
||||
except OSError:
|
||||
# no ghostscript
|
||||
|
@ -85,7 +84,6 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
|
||||
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
@ -123,6 +121,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
"-q", # quiet mode
|
||||
"-g%dx%d" % size, # set output geometry (pixels)
|
||||
"-r%fx%f" % res, # set input DPI (dots per inch)
|
||||
"-dBATCH", # exit after processing
|
||||
"-dNOPAUSE", # don't pause between pages,
|
||||
"-dSAFER", # safe mode
|
||||
"-sDEVICE=ppmraw", # ppm driver
|
||||
|
@ -139,12 +138,8 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
|
||||
# push data through ghostscript
|
||||
try:
|
||||
gs = subprocess.Popen(command, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
gs.stdin.close()
|
||||
status = gs.wait()
|
||||
if status:
|
||||
raise IOError("gs failed (status %d)" % status)
|
||||
with open(os.devnull, 'w+b') as devnull:
|
||||
subprocess.check_call(command, stdin=devnull, stdout=devnull)
|
||||
im = Image.open(outfile)
|
||||
im.load()
|
||||
finally:
|
||||
|
@ -323,7 +318,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
# 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
|
||||
# https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
||||
offset = i32(s[4:8])
|
||||
length = i32(s[8:12])
|
||||
else:
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
_handler = None
|
||||
|
||||
|
|
|
@ -16,15 +16,11 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, i16le as i16, i32le as i32, o8
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
o8 = _binary.o8
|
||||
|
||||
|
||||
#
|
||||
# decoder
|
||||
|
@ -41,7 +37,8 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
|
||||
format = "FLI"
|
||||
format_description = "Autodesk FLI/FLC Animation"
|
||||
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
|
||||
# HEAD
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
from PIL import Image, _binary
|
||||
from . import Image, _binary
|
||||
|
||||
WIDTH = 800
|
||||
|
||||
|
@ -88,7 +90,7 @@ class FontFile(object):
|
|||
x = xx
|
||||
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
|
||||
self.bitmap.paste(im.crop(src), s)
|
||||
# print chr(i), dst, s
|
||||
# print(chr(i), dst, s)
|
||||
self.metrics[i] = d, dst, s
|
||||
|
||||
def save(self, filename):
|
||||
|
|
|
@ -15,13 +15,15 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32le as i32, i8
|
||||
|
||||
import olefile
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||
MODES = {
|
||||
# opacity
|
||||
|
@ -42,7 +44,7 @@ MODES = {
|
|||
# --------------------------------------------------------------------
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:8] == MAGIC
|
||||
return prefix[:8] == olefile.MAGIC
|
||||
|
||||
|
||||
##
|
||||
|
@ -59,7 +61,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
# to be a FlashPix file
|
||||
|
||||
try:
|
||||
self.ole = OleFileIO(self.fp)
|
||||
self.ole = olefile.OleFileIO(self.fp)
|
||||
except IOError:
|
||||
raise SyntaxError("not an FPX file; invalid OLE file")
|
||||
|
||||
|
@ -112,7 +114,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
if id in prop:
|
||||
self.jpeg[i] = prop[id]
|
||||
|
||||
# print len(self.jpeg), "tables loaded"
|
||||
# print(len(self.jpeg), "tables loaded")
|
||||
|
||||
self._open_subimage(1, self.maxid)
|
||||
|
||||
|
@ -141,7 +143,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
offset = i32(s, 28)
|
||||
length = i32(s, 32)
|
||||
|
||||
# print size, self.mode, self.rawmode
|
||||
# print(size, self.mode, self.rawmode)
|
||||
|
||||
if size != self.size:
|
||||
raise IOError("subimage mismatch")
|
||||
|
|
|
@ -42,7 +42,7 @@ Note: All data is stored in little-Endian (Intel) byte order.
|
|||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
|
||||
MAGIC = b"FTEX"
|
||||
|
|
|
@ -24,9 +24,8 @@
|
|||
# Version 3 files have a format specifier of 18 for 16bit floats in
|
||||
# the color depth field. This is currently unsupported by Pillow.
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
i32 = _binary.i32be
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32be as i32
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
|
|
|
@ -23,8 +23,9 @@
|
|||
# purposes only.
|
||||
|
||||
|
||||
from PIL import ImageFile, ImagePalette, _binary
|
||||
from PIL._util import isPath
|
||||
from . import ImageFile, ImagePalette
|
||||
from ._binary import i16be as i16
|
||||
from ._util import isPath
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
@ -34,8 +35,6 @@ except ImportError:
|
|||
import __builtin__
|
||||
builtins = __builtin__
|
||||
|
||||
i16 = _binary.i16be
|
||||
|
||||
|
||||
##
|
||||
# Image plugin for the GD uncompressed format. Note that this format
|
||||
|
|
|
@ -24,21 +24,14 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, \
|
||||
ImageChops, ImageSequence, _binary
|
||||
from . import Image, ImageFile, ImagePalette, ImageChops, ImageSequence
|
||||
from ._binary import i8, i16le as i16, o8, o16le as o16
|
||||
|
||||
import itertools
|
||||
|
||||
__version__ = "0.9"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
o8 = _binary.o8
|
||||
o16 = _binary.o16le
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Identify/read GIF files
|
||||
|
||||
|
@ -54,6 +47,8 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
|
||||
format = "GIF"
|
||||
format_description = "Compuserve GIF"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
global_palette = None
|
||||
|
||||
def data(self):
|
||||
|
@ -262,7 +257,7 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
|
||||
# only dispose the extent in this frame
|
||||
if self.dispose:
|
||||
self.dispose = self.dispose.crop(self.dispose_extent)
|
||||
self.dispose = self._crop(self.dispose, self.dispose_extent)
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
|
@ -285,7 +280,7 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
if self._prev_im and self.disposal_method == 1:
|
||||
# we do this by pasting the updated area onto the previous
|
||||
# frame which we then use as the current image content
|
||||
updated = self.im.crop(self.dispose_extent)
|
||||
updated = self._crop(self.im, self.dispose_extent)
|
||||
self._prev_im.paste(updated, self.dispose_extent,
|
||||
updated.convert('RGBA'))
|
||||
self.im = self._prev_im
|
||||
|
@ -294,52 +289,168 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
# --------------------------------------------------------------------
|
||||
# Write GIF files
|
||||
|
||||
try:
|
||||
import _imaging_gif
|
||||
except ImportError:
|
||||
_imaging_gif = None
|
||||
|
||||
RAWMODE = {
|
||||
"1": "L",
|
||||
"L": "L",
|
||||
"P": "P",
|
||||
"P": "P"
|
||||
}
|
||||
|
||||
|
||||
def _convert_mode(im, initial_call=False):
|
||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
||||
# should automatically convert images on save...)
|
||||
def _normalize_mode(im, initial_call=False):
|
||||
"""
|
||||
Takes an image (or frame), returns an image in a mode that is appropriate
|
||||
for saving in a Gif.
|
||||
|
||||
It may return the original image, or it may return an image converted to
|
||||
palette or 'L' mode.
|
||||
|
||||
UNDONE: What is the point of mucking with the initial call palette, for
|
||||
an image that shouldn't have a palette, or it would be a mode 'P' and
|
||||
get returned in the RAWMODE clause.
|
||||
|
||||
:param im: Image object
|
||||
:param initial_call: Default false, set to true for a single frame.
|
||||
:returns: Image object
|
||||
"""
|
||||
if im.mode in RAWMODE:
|
||||
im.load()
|
||||
return im
|
||||
if Image.getmodebase(im.mode) == "RGB":
|
||||
if initial_call:
|
||||
palette_size = 256
|
||||
if im.palette:
|
||||
palette_size = len(im.palette.getdata()[1]) // 3
|
||||
return im.convert("P", palette=1, colors=palette_size)
|
||||
return im.convert("P", palette=Image.ADAPTIVE, colors=palette_size)
|
||||
else:
|
||||
return im.convert("P")
|
||||
return im.convert("L")
|
||||
|
||||
|
||||
def _normalize_palette(im, palette, info):
|
||||
"""
|
||||
Normalizes the palette for image.
|
||||
- Sets the palette to the incoming palette, if provided.
|
||||
- Ensures that there's a palette for L mode images
|
||||
- Optimizes the palette if necessary/desired.
|
||||
|
||||
:param im: Image object
|
||||
:param palette: bytes object containing the source palette, or ....
|
||||
:param info: encoderinfo
|
||||
:returns: Image object
|
||||
"""
|
||||
source_palette = None
|
||||
if palette:
|
||||
# a bytes palette
|
||||
if isinstance(palette, (bytes, bytearray, list)):
|
||||
source_palette = bytearray(palette[:768])
|
||||
if isinstance(palette, ImagePalette.ImagePalette):
|
||||
source_palette = bytearray(itertools.chain.from_iterable(
|
||||
zip(palette.palette[:256],
|
||||
palette.palette[256:512],
|
||||
palette.palette[512:768])))
|
||||
|
||||
if im.mode == "P":
|
||||
if not source_palette:
|
||||
source_palette = im.im.getpalette("RGB")[:768]
|
||||
else: # L-mode
|
||||
if not source_palette:
|
||||
source_palette = bytearray(i//3 for i in range(768))
|
||||
im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=source_palette)
|
||||
|
||||
used_palette_colors = _get_optimize(im, info)
|
||||
if used_palette_colors is not None:
|
||||
return im.remap_palette(used_palette_colors, source_palette)
|
||||
|
||||
im.palette.palette = source_palette
|
||||
return im
|
||||
|
||||
|
||||
def _write_single_frame(im, fp, palette):
|
||||
im_out = _normalize_mode(im, True)
|
||||
im_out = _normalize_palette(im_out, palette, im.encoderinfo)
|
||||
|
||||
for s in _get_global_header(im_out, im.encoderinfo):
|
||||
fp.write(s)
|
||||
|
||||
# local image header
|
||||
flags = 0
|
||||
if get_interlace(im):
|
||||
flags = flags | 64
|
||||
_write_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
|
||||
|
||||
|
||||
def _write_multiple_frames(im, fp, palette):
|
||||
|
||||
duration = im.encoderinfo.get("duration", None)
|
||||
|
||||
im_frames = []
|
||||
frame_count = 0
|
||||
for imSequence in [im]+im.encoderinfo.get("append_images", []):
|
||||
for im_frame in ImageSequence.Iterator(imSequence):
|
||||
# a copy is required here since seek can still mutate the image
|
||||
im_frame = _normalize_mode(im_frame.copy())
|
||||
im_frame = _normalize_palette(im_frame, palette, im.encoderinfo)
|
||||
|
||||
encoderinfo = im.encoderinfo.copy()
|
||||
if isinstance(duration, (list, tuple)):
|
||||
encoderinfo['duration'] = duration[frame_count]
|
||||
frame_count += 1
|
||||
|
||||
if im_frames:
|
||||
# delta frame
|
||||
previous = im_frames[-1]
|
||||
if _get_palette_bytes(im_frame) == _get_palette_bytes(previous['im']):
|
||||
delta = ImageChops.subtract_modulo(im_frame,
|
||||
previous['im'])
|
||||
else:
|
||||
delta = ImageChops.subtract_modulo(im_frame.convert('RGB'),
|
||||
previous['im'].convert('RGB'))
|
||||
bbox = delta.getbbox()
|
||||
if not bbox:
|
||||
# This frame is identical to the previous frame
|
||||
if duration:
|
||||
previous['encoderinfo']['duration'] += encoderinfo['duration']
|
||||
continue
|
||||
else:
|
||||
bbox = None
|
||||
im_frames.append({
|
||||
'im': im_frame,
|
||||
'bbox': bbox,
|
||||
'encoderinfo': encoderinfo
|
||||
})
|
||||
|
||||
if len(im_frames) > 1:
|
||||
for frame_data in im_frames:
|
||||
im_frame = frame_data['im']
|
||||
if not frame_data['bbox']:
|
||||
# global header
|
||||
for s in _get_global_header(im_frame,
|
||||
frame_data['encoderinfo']):
|
||||
fp.write(s)
|
||||
offset = (0, 0)
|
||||
else:
|
||||
# compress difference
|
||||
frame_data['encoderinfo']['include_color_table'] = True
|
||||
|
||||
im_frame = im_frame.crop(frame_data['bbox'])
|
||||
offset = frame_data['bbox'][:2]
|
||||
_write_frame_data(fp, im_frame, offset, frame_data['encoderinfo'])
|
||||
return True
|
||||
|
||||
|
||||
def _save_all(im, fp, filename):
|
||||
_save(im, fp, filename, save_all=True)
|
||||
|
||||
|
||||
def _save(im, fp, filename, save_all=False):
|
||||
|
||||
im.encoderinfo.update(im.info)
|
||||
if _imaging_gif:
|
||||
# call external driver
|
||||
try:
|
||||
_imaging_gif.save(im, fp, filename)
|
||||
return
|
||||
except IOError:
|
||||
pass # write uncompressed file
|
||||
|
||||
if im.mode in RAWMODE:
|
||||
im_out = im.copy()
|
||||
else:
|
||||
im_out = _convert_mode(im, True)
|
||||
|
||||
# header
|
||||
try:
|
||||
palette = im.encoderinfo["palette"]
|
||||
|
@ -347,62 +458,8 @@ def _save(im, fp, filename, save_all=False):
|
|||
palette = None
|
||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||
|
||||
if save_all:
|
||||
previous = None
|
||||
|
||||
first_frame = None
|
||||
append_images = im.encoderinfo.get("append_images", [])
|
||||
for imSequence in [im]+append_images:
|
||||
for im_frame in ImageSequence.Iterator(imSequence):
|
||||
encoderinfo = im.encoderinfo.copy()
|
||||
im_frame = _convert_mode(im_frame)
|
||||
|
||||
# To specify duration, add the time in milliseconds to getdata(),
|
||||
# e.g. getdata(im_frame, duration=1000)
|
||||
if not previous:
|
||||
# global header
|
||||
first_frame = getheader(im_frame, palette, encoderinfo)[0]
|
||||
first_frame += getdata(im_frame, (0, 0), **encoderinfo)
|
||||
else:
|
||||
if first_frame:
|
||||
for s in first_frame:
|
||||
fp.write(s)
|
||||
first_frame = None
|
||||
|
||||
# delta frame
|
||||
delta = ImageChops.subtract_modulo(im_frame, previous.copy())
|
||||
bbox = delta.getbbox()
|
||||
|
||||
if bbox:
|
||||
# compress difference
|
||||
encoderinfo['include_color_table'] = True
|
||||
for s in getdata(im_frame.crop(bbox),
|
||||
bbox[:2], **encoderinfo):
|
||||
fp.write(s)
|
||||
else:
|
||||
# FIXME: what should we do in this case?
|
||||
pass
|
||||
previous = im_frame
|
||||
if first_frame:
|
||||
save_all = False
|
||||
if not save_all:
|
||||
header = getheader(im_out, palette, im.encoderinfo)[0]
|
||||
for s in header:
|
||||
fp.write(s)
|
||||
|
||||
flags = 0
|
||||
|
||||
if get_interlace(im):
|
||||
flags = flags | 64
|
||||
|
||||
# local image header
|
||||
_get_local_header(fp, im, (0, 0), flags)
|
||||
|
||||
im_out.encoderconfig = (8, get_interlace(im))
|
||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||
RAWMODE[im_out.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
if not save_all or not _write_multiple_frames(im, fp, palette):
|
||||
_write_single_frame(im, fp, palette)
|
||||
|
||||
fp.write(b";") # end of file
|
||||
|
||||
|
@ -411,10 +468,7 @@ def _save(im, fp, filename, save_all=False):
|
|||
|
||||
|
||||
def get_interlace(im):
|
||||
try:
|
||||
interlace = im.encoderinfo["interlace"]
|
||||
except KeyError:
|
||||
interlace = 1
|
||||
interlace = im.encoderinfo.get("interlace", 1)
|
||||
|
||||
# workaround for @PIL153
|
||||
if min(im.size) < 16:
|
||||
|
@ -423,7 +477,7 @@ def get_interlace(im):
|
|||
return interlace
|
||||
|
||||
|
||||
def _get_local_header(fp, im, offset, flags):
|
||||
def _write_local_header(fp, im, offset, flags):
|
||||
transparent_color_exists = False
|
||||
try:
|
||||
transparency = im.encoderinfo["transparency"]
|
||||
|
@ -434,18 +488,13 @@ def _get_local_header(fp, im, offset, flags):
|
|||
# optimize the block away if transparent color is not used
|
||||
transparent_color_exists = True
|
||||
|
||||
if _get_optimize(im, im.encoderinfo):
|
||||
used_palette_colors = _get_used_palette_colors(im)
|
||||
|
||||
used_palette_colors = _get_optimize(im, im.encoderinfo)
|
||||
if used_palette_colors is not None:
|
||||
# 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
|
||||
try:
|
||||
transparency = used_palette_colors.index(transparency)
|
||||
except ValueError:
|
||||
transparent_color_exists = False
|
||||
|
||||
if "duration" in im.encoderinfo:
|
||||
duration = int(im.encoderinfo["duration"] / 10)
|
||||
|
@ -482,11 +531,8 @@ def _get_local_header(fp, im, offset, flags):
|
|||
o8(0))
|
||||
include_color_table = im.encoderinfo.get('include_color_table')
|
||||
if include_color_table:
|
||||
try:
|
||||
palette = im.encoderinfo["palette"]
|
||||
except KeyError:
|
||||
palette = None
|
||||
palette_bytes = _get_palette_bytes(im, palette, im.encoderinfo)[0]
|
||||
palette = im.encoderinfo.get("palette", None)
|
||||
palette_bytes = _get_palette_bytes(im)
|
||||
color_table_size = _get_color_table_size(palette_bytes)
|
||||
if color_table_size:
|
||||
flags = flags | 128 # local color table flag
|
||||
|
@ -505,6 +551,8 @@ def _get_local_header(fp, im, offset, flags):
|
|||
|
||||
def _save_netpbm(im, fp, filename):
|
||||
|
||||
# Unused by default.
|
||||
# To use, uncomment the register_save call at the end of the file.
|
||||
#
|
||||
# If you need real GIF compression and/or RGB quantization, you
|
||||
# can use the external NETPBM/PBMPLUS utilities. See comments
|
||||
|
@ -512,25 +560,21 @@ def _save_netpbm(im, fp, filename):
|
|||
|
||||
import os
|
||||
from subprocess import Popen, check_call, PIPE, CalledProcessError
|
||||
import tempfile
|
||||
file = im._dump()
|
||||
|
||||
if im.mode != "RGB":
|
||||
with open(filename, 'wb') as f:
|
||||
stderr = tempfile.TemporaryFile()
|
||||
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
|
||||
else:
|
||||
with open(filename, 'wb') as f:
|
||||
|
||||
with open(filename, 'wb') as f:
|
||||
if im.mode != "RGB":
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
check_call(["ppmtogif", file], stdout=f, stderr=devnull)
|
||||
else:
|
||||
# Pipe ppmquant output into ppmtogif
|
||||
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
|
||||
quant_cmd = ["ppmquant", "256", file]
|
||||
togif_cmd = ["ppmtogif"]
|
||||
stderr = tempfile.TemporaryFile()
|
||||
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
|
||||
stderr = tempfile.TemporaryFile()
|
||||
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
|
||||
stderr=stderr)
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
|
||||
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout,
|
||||
stdout=f, stderr=devnull)
|
||||
|
||||
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
||||
quant_proc.stdout.close()
|
||||
|
@ -549,24 +593,45 @@ def _save_netpbm(im, fp, filename):
|
|||
pass
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# GIF utilities
|
||||
# Force optimization so that we can test performance against
|
||||
# cases where it took lots of memory and time previously.
|
||||
_FORCE_OPTIMIZE = False
|
||||
|
||||
|
||||
def _get_optimize(im, info):
|
||||
return im.mode in ("P", "L") and info and info.get("optimize", 0)
|
||||
"""
|
||||
Palette optimization is a potentially expensive operation.
|
||||
|
||||
This function determines if the palette should be optimized using
|
||||
some heuristics, then returns the list of palette entries in use.
|
||||
|
||||
def _get_used_palette_colors(im):
|
||||
used_palette_colors = []
|
||||
:param im: Image object
|
||||
:param info: encoderinfo
|
||||
:returns: list of indexes of palette entries in use, or None
|
||||
"""
|
||||
if im.mode in ("P", "L") and info and info.get("optimize", 0):
|
||||
# Potentially expensive operation.
|
||||
|
||||
# check which colors are used
|
||||
i = 0
|
||||
for count in im.histogram():
|
||||
if count:
|
||||
used_palette_colors.append(i)
|
||||
i += 1
|
||||
# The palette saves 3 bytes per color not used, but palette
|
||||
# lengths are restricted to 3*(2**N) bytes. Max saving would
|
||||
# be 768 -> 6 bytes if we went all the way down to 2 colors.
|
||||
# * If we're over 128 colors, we can't save any space.
|
||||
# * If there aren't any holes, it's not worth collapsing.
|
||||
# * If we have a 'large' image, the palette is in the noise.
|
||||
|
||||
# create the new palette if not every color is used
|
||||
optimise = _FORCE_OPTIMIZE or im.mode == 'L'
|
||||
if optimise or im.width * im.height < 512 * 512:
|
||||
# check which colors are used
|
||||
used_palette_colors = []
|
||||
for i, count in enumerate(im.histogram()):
|
||||
if count:
|
||||
used_palette_colors.append(i)
|
||||
|
||||
if optimise or (len(used_palette_colors) <= 128 and
|
||||
max(used_palette_colors) > len(used_palette_colors)):
|
||||
return used_palette_colors
|
||||
|
||||
return used_palette_colors
|
||||
|
||||
def _get_color_table_size(palette_bytes):
|
||||
# calculate the palette size for the header
|
||||
|
@ -576,7 +641,15 @@ def _get_color_table_size(palette_bytes):
|
|||
color_table_size = 0
|
||||
return color_table_size
|
||||
|
||||
|
||||
def _get_header_palette(palette_bytes):
|
||||
"""
|
||||
Returns the palette, null padded to the next power of 2 (*3) bytes
|
||||
suitable for direct inclusion in the GIF header
|
||||
|
||||
:param palette_bytes: Unpadded palette bytes, in RGBRGB form
|
||||
:returns: Null padded palette
|
||||
"""
|
||||
color_table_size = _get_color_table_size(palette_bytes)
|
||||
|
||||
# add the missing amount of bytes
|
||||
|
@ -586,102 +659,18 @@ def _get_header_palette(palette_bytes):
|
|||
palette_bytes += o8(0) * 3 * actual_target_size_diff
|
||||
return palette_bytes
|
||||
|
||||
# Force optimization so that we can test performance against
|
||||
# cases where it took lots of memory and time previously.
|
||||
_FORCE_OPTIMIZE = False
|
||||
|
||||
def _get_palette_bytes(im, palette, info):
|
||||
if im.mode == "P":
|
||||
if palette and isinstance(palette, bytes):
|
||||
source_palette = palette[:768]
|
||||
else:
|
||||
source_palette = im.im.getpalette("RGB")[:768]
|
||||
else: # L-mode
|
||||
if palette and isinstance(palette, bytes):
|
||||
source_palette = palette[:768]
|
||||
else:
|
||||
source_palette = bytearray(i//3 for i in range(768))
|
||||
def _get_palette_bytes(im):
|
||||
"""
|
||||
Gets the palette for inclusion in the gif header
|
||||
|
||||
used_palette_colors = palette_bytes = None
|
||||
:param im: Image object
|
||||
:returns: Bytes, len<=768 suitable for inclusion in gif header
|
||||
"""
|
||||
return im.palette.palette
|
||||
|
||||
if _get_optimize(im, info):
|
||||
used_palette_colors = _get_used_palette_colors(im)
|
||||
|
||||
# Potentially expensive operation.
|
||||
|
||||
# The palette saves 3 bytes per color not used, but palette
|
||||
# lengths are restricted to 3*(2**N) bytes. Max saving would
|
||||
# be 768 -> 6 bytes if we went all the way down to 2 colors.
|
||||
# * If we're over 128 colors, we can't save any space.
|
||||
# * If there aren't any holes, it's not worth collapsing.
|
||||
# * If we have a 'large' image, the palette is in the noise.
|
||||
|
||||
# create the new palette if not every color is used
|
||||
if _FORCE_OPTIMIZE or im.mode == 'L' or \
|
||||
(len(used_palette_colors) <= 128 and
|
||||
max(used_palette_colors) > len(used_palette_colors) and
|
||||
im.width * im.height < 512 * 512):
|
||||
palette_bytes = b""
|
||||
new_positions = [0]*256
|
||||
|
||||
# pick only the used colors from the palette
|
||||
for i, oldPosition in enumerate(used_palette_colors):
|
||||
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
|
||||
new_positions[oldPosition] = i
|
||||
|
||||
# replace the palette color id of all pixel with the new id
|
||||
|
||||
# Palette images are [0..255], mapped through a 1 or 3
|
||||
# byte/color map. We need to remap the whole image
|
||||
# from palette 1 to palette 2. New_positions is
|
||||
# an array of indexes into palette 1. Palette 2 is
|
||||
# palette 1 with any holes removed.
|
||||
|
||||
# We're going to leverage the convert mechanism to use the
|
||||
# C code to remap the image from palette 1 to palette 2,
|
||||
# by forcing the source image into 'L' mode and adding a
|
||||
# mapping 'L' mode palette, then converting back to 'L'
|
||||
# sans palette thus converting the image bytes, then
|
||||
# assigning the optimized RGB palette.
|
||||
|
||||
# perf reference, 9500x4000 gif, w/~135 colors
|
||||
# 14 sec prepatch, 1 sec postpatch with optimization forced.
|
||||
|
||||
mapping_palette = bytearray(new_positions)
|
||||
|
||||
m_im = im.copy()
|
||||
m_im.mode = 'P'
|
||||
|
||||
m_im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=mapping_palette*3,
|
||||
size=768)
|
||||
#possibly set palette dirty, then
|
||||
#m_im.putpalette(mapping_palette, 'L') # converts to 'P'
|
||||
# or just force it.
|
||||
# UNDONE -- this is part of the general issue with palettes
|
||||
m_im.im.putpalette(*m_im.palette.getdata())
|
||||
|
||||
m_im = m_im.convert('L')
|
||||
|
||||
# Internally, we require 768 bytes for a palette.
|
||||
new_palette_bytes = (palette_bytes +
|
||||
(768 - len(palette_bytes)) * b'\x00')
|
||||
m_im.putpalette(new_palette_bytes)
|
||||
m_im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=palette_bytes,
|
||||
size=len(palette_bytes))
|
||||
|
||||
# oh gawd, this is modifying the image in place so I can pass by ref.
|
||||
# REFACTOR SOONEST
|
||||
im.frombytes(m_im.tobytes())
|
||||
|
||||
if not palette_bytes:
|
||||
palette_bytes = source_palette
|
||||
|
||||
# returning palette, _not_ padded to 768 bytes like our internal ones.
|
||||
return palette_bytes, used_palette_colors
|
||||
|
||||
def getheader(im, palette=None, info=None):
|
||||
def _get_global_header(im, info):
|
||||
"""Return a list of strings representing a GIF header"""
|
||||
|
||||
# Header Block
|
||||
|
@ -691,7 +680,7 @@ def getheader(im, palette=None, info=None):
|
|||
for extensionKey in ["transparency", "duration", "loop", "comment"]:
|
||||
if info and extensionKey in info:
|
||||
if ((extensionKey == "duration" and info[extensionKey] == 0) or
|
||||
(extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))):
|
||||
(extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))):
|
||||
continue
|
||||
version = b"89a"
|
||||
break
|
||||
|
@ -699,42 +688,89 @@ def getheader(im, palette=None, info=None):
|
|||
if im.info.get("version") == b"89a":
|
||||
version = b"89a"
|
||||
|
||||
header = [
|
||||
b"GIF"+version + # signature + version
|
||||
o16(im.size[0]) + # canvas width
|
||||
o16(im.size[1]) # canvas height
|
||||
palette_bytes = _get_palette_bytes(im)
|
||||
color_table_size = _get_color_table_size(palette_bytes)
|
||||
|
||||
background = info["background"] if "background" in info else 0
|
||||
|
||||
return [
|
||||
b"GIF"+version + # signature + version
|
||||
o16(im.size[0]) + # canvas width
|
||||
o16(im.size[1]), # canvas height
|
||||
|
||||
# Logical Screen Descriptor
|
||||
# size of global color table + global color table flag
|
||||
o8(color_table_size + 128), # packed fields
|
||||
# background + reserved/aspect
|
||||
o8(background) + o8(0),
|
||||
|
||||
# Global Color Table
|
||||
_get_header_palette(palette_bytes)
|
||||
]
|
||||
|
||||
palette_bytes, used_palette_colors = _get_palette_bytes(im, palette, info)
|
||||
|
||||
# Logical Screen Descriptor
|
||||
color_table_size = _get_color_table_size(palette_bytes)
|
||||
# size of global color table + global color table flag
|
||||
header.append(o8(color_table_size + 128)) # packed fields
|
||||
# background + reserved/aspect
|
||||
if info and "background" in info:
|
||||
background = info["background"]
|
||||
elif "background" in im.info:
|
||||
# This elif is redundant within GifImagePlugin
|
||||
# since im.info parameters are bundled into the info dictionary
|
||||
# However, external scripts may call getheader directly
|
||||
# So this maintains earlier behaviour
|
||||
background = im.info["background"]
|
||||
else:
|
||||
background = 0
|
||||
header.append(o8(background) + o8(0))
|
||||
# end of Logical Screen Descriptor
|
||||
def _write_frame_data(fp, im_frame, offset, params):
|
||||
try:
|
||||
im_frame.encoderinfo = params
|
||||
|
||||
# local image header
|
||||
_write_local_header(fp, im_frame, offset, 0)
|
||||
|
||||
ImageFile._save(im_frame, fp, [("gif", (0, 0)+im_frame.size, 0,
|
||||
RAWMODE[im_frame.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
finally:
|
||||
del im_frame.encoderinfo
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Legacy GIF utilities
|
||||
|
||||
|
||||
def getheader(im, palette=None, info=None):
|
||||
"""
|
||||
Legacy Method to get Gif data from image.
|
||||
|
||||
Warning:: May modify image data.
|
||||
|
||||
:param im: Image object
|
||||
:param palette: bytes object containing the source palette, or ....
|
||||
:param info: encoderinfo
|
||||
:returns: tuple of(list of header items, optimized palette)
|
||||
|
||||
"""
|
||||
used_palette_colors = _get_optimize(im, info)
|
||||
|
||||
if info is None:
|
||||
info = {}
|
||||
|
||||
if "background" not in info and "background" in im.info:
|
||||
info["background"] = im.info["background"]
|
||||
|
||||
im_mod = _normalize_palette(im, palette, info)
|
||||
im.palette = im_mod.palette
|
||||
im.im = im_mod.im
|
||||
header = _get_global_header(im, info)
|
||||
|
||||
# Header + Logical Screen Descriptor + Global Color Table
|
||||
header.append(_get_header_palette(palette_bytes))
|
||||
return header, used_palette_colors
|
||||
|
||||
|
||||
# To specify duration, add the time in milliseconds to getdata(),
|
||||
# e.g. getdata(im_frame, duration=1000)
|
||||
def getdata(im, offset=(0, 0), **params):
|
||||
"""Return a list of strings representing this image.
|
||||
The first string is a local image header, the rest contains
|
||||
encoded image data."""
|
||||
"""
|
||||
Legacy Method
|
||||
|
||||
Return a list of strings representing this image.
|
||||
The first string is a local image header, the rest contains
|
||||
encoded image data.
|
||||
|
||||
:param im: Image object
|
||||
:param offset: Tuple of (x, y) pixels. Defaults to (0,0)
|
||||
:param **params: E.g. duration or other encoder info parameters
|
||||
:returns: List of Bytes containing gif encoded frame data
|
||||
|
||||
"""
|
||||
class Collector(object):
|
||||
data = []
|
||||
|
||||
|
@ -745,18 +781,7 @@ def getdata(im, offset=(0, 0), **params):
|
|||
|
||||
fp = Collector()
|
||||
|
||||
try:
|
||||
im.encoderinfo = params
|
||||
|
||||
# local image header
|
||||
_get_local_header(fp, im, offset, 0)
|
||||
|
||||
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
|
||||
finally:
|
||||
del im.encoderinfo
|
||||
_write_frame_data(fp, im, offset, params)
|
||||
|
||||
return fp.data
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#
|
||||
|
||||
from math import pi, log, sin, sqrt
|
||||
from PIL._binary import o8
|
||||
from ._binary import o8
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Stuff to translate curve segments to palette values (derived from
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#
|
||||
|
||||
import re
|
||||
from PIL._binary import o8
|
||||
from ._binary import o8
|
||||
|
||||
|
||||
##
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i8
|
||||
|
||||
_handler = None
|
||||
|
||||
|
@ -28,7 +29,7 @@ def register_handler(handler):
|
|||
# Image adapter
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01'
|
||||
return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1
|
||||
|
||||
|
||||
class GribStubImageFile(ImageFile.StubImageFile):
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
_handler = None
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, PngImagePlugin, _binary
|
||||
from PIL import Image, ImageFile, PngImagePlugin
|
||||
from PIL._binary import i8
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
|
@ -27,8 +28,6 @@ enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
|
|||
if enable_jpeg2k:
|
||||
from PIL import Jpeg2KImagePlugin
|
||||
|
||||
i8 = _binary.i8
|
||||
|
||||
HEADERSIZE = 8
|
||||
|
||||
|
||||
|
@ -330,8 +329,8 @@ def _save(im, fp, filename):
|
|||
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)
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=devnull)
|
||||
|
||||
convert_proc.stdout.close()
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
import struct
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
||||
from . import Image, ImageFile, BmpImagePlugin, PngImagePlugin
|
||||
from ._binary import i8, i16le as i16, i32le as i32
|
||||
from math import log, ceil
|
||||
|
||||
__version__ = "0.1"
|
||||
|
@ -33,10 +34,6 @@ __version__ = "0.1"
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
||||
_MAGIC = b"\0\0\1\0"
|
||||
|
||||
|
||||
|
@ -44,16 +41,19 @@ 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)])
|
||||
(64, 64), (128, 128), (256, 256)])
|
||||
width, height = im.size
|
||||
filter(lambda x: False if (x[0] > width or x[1] > height or
|
||||
x[0] > 255 or x[1] > 255) else True, sizes)
|
||||
sizes = filter(lambda x: False if (x[0] > width or x[1] > height or
|
||||
x[0] > 256 or x[1] > 256) else True,
|
||||
sizes)
|
||||
sizes = list(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)
|
||||
# 0 means 256
|
||||
fp.write(struct.pack("B", width if width < 256 else 0)) # bWidth(1)
|
||||
fp.write(struct.pack("B", height if height < 256 else 0)) # bHeight(1)
|
||||
fp.write(b"\0") # bColorCount(1)
|
||||
fp.write(b"\0") # bReserved(1)
|
||||
fp.write(b"\0\0") # wPlanes(2)
|
||||
|
@ -176,8 +176,8 @@ class IcoFile(object):
|
|||
# figure out where AND mask image starts
|
||||
mode = a[0]
|
||||
bpp = 8
|
||||
for k in BmpImagePlugin.BIT2MODE.keys():
|
||||
if mode == BmpImagePlugin.BIT2MODE[k][1]:
|
||||
for k, v in BmpImagePlugin.BIT2MODE.items():
|
||||
if mode == v[1]:
|
||||
bpp = k
|
||||
break
|
||||
|
||||
|
@ -215,13 +215,13 @@ class IcoFile(object):
|
|||
total_bytes = int((w * im.size[1]) / 8)
|
||||
|
||||
self.buf.seek(and_mask_offset)
|
||||
maskData = self.buf.read(total_bytes)
|
||||
mask_data = self.buf.read(total_bytes)
|
||||
|
||||
# convert raw data to image
|
||||
mask = Image.frombuffer(
|
||||
'1', # 1 bpp
|
||||
im.size, # (w, h)
|
||||
maskData, # source chars
|
||||
mask_data, # source chars
|
||||
'raw', # raw decoder
|
||||
('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed
|
||||
)
|
||||
|
@ -278,6 +278,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
|
||||
Image.register_save(IcoImageFile.format, _save)
|
||||
Image.register_extension(IcoImageFile.format, ".ico")
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
|
||||
|
||||
import re
|
||||
from PIL import Image, ImageFile, ImagePalette
|
||||
from PIL._binary import i8
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8
|
||||
|
||||
__version__ = "0.7"
|
||||
|
||||
|
@ -109,6 +109,7 @@ class ImImageFile(ImageFile.ImageFile):
|
|||
|
||||
format = "IM"
|
||||
format_description = "IFUNC Image Memory"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
|
||||
|
@ -325,10 +326,7 @@ def _save(im, fp, filename, check=0):
|
|||
except KeyError:
|
||||
raise ValueError("Cannot save %s images as IM" % im.mode)
|
||||
|
||||
try:
|
||||
frames = im.encoderinfo["frames"]
|
||||
except KeyError:
|
||||
frames = 1
|
||||
frames = im.encoderinfo.get("frames", 1)
|
||||
|
||||
if check:
|
||||
return check
|
||||
|
|
421
PIL/Image.py
421
PIL/Image.py
|
@ -24,9 +24,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import VERSION, PILLOW_VERSION, _plugins
|
||||
from . import VERSION, PILLOW_VERSION, _plugins
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
|
@ -48,15 +46,6 @@ class _imaging_not_installed(object):
|
|||
# Limit to around a quarter gigabyte for a 24 bit (3 bpp) image
|
||||
MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3)
|
||||
|
||||
try:
|
||||
# give Tk a chance to set up the environment, in case we're
|
||||
# using an _imaging module linked against libtcl/libtk (use
|
||||
# __import__ to hide this from naive packagers; we don't really
|
||||
# depend on Tk unless ImageTk is used, and that module already
|
||||
# imports Tkinter)
|
||||
__import__("FixTk")
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
# If the _imaging C module is not present, Pillow will not load.
|
||||
|
@ -64,10 +53,13 @@ try:
|
|||
# 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 . import _imaging as core
|
||||
if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
|
||||
raise ImportError("The _imaging extension was built for another "
|
||||
"version of Pillow or PIL")
|
||||
"version of Pillow or PIL: Core Version: %s"
|
||||
"Pillow Version: %s" %
|
||||
(getattr(core, 'PILLOW_VERSION', None),
|
||||
PILLOW_VERSION))
|
||||
|
||||
except ImportError as v:
|
||||
core = _imaging_not_installed()
|
||||
|
@ -109,11 +101,9 @@ except ImportError:
|
|||
import __builtin__
|
||||
builtins = __builtin__
|
||||
|
||||
from PIL import ImageMode
|
||||
from PIL._binary import i8
|
||||
from PIL._util import isPath
|
||||
from PIL._util import isStringType
|
||||
from PIL._util import deferred_error
|
||||
from . import ImageMode
|
||||
from ._binary import i8
|
||||
from ._util import isPath, isStringType, deferred_error
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -146,6 +136,7 @@ def isImageType(t):
|
|||
"""
|
||||
return hasattr(t, "im")
|
||||
|
||||
|
||||
#
|
||||
# Constants (also defined in _imagingmodule.c!)
|
||||
|
||||
|
@ -211,6 +202,8 @@ MIME = {}
|
|||
SAVE = {}
|
||||
SAVE_ALL = {}
|
||||
EXTENSION = {}
|
||||
DECODERS = {}
|
||||
ENCODERS = {}
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Modes supported by this version
|
||||
|
@ -284,7 +277,7 @@ def _conv_type_shape(im):
|
|||
return shape+(extra,), typ
|
||||
|
||||
|
||||
MODES = sorted(_MODEINFO.keys())
|
||||
MODES = sorted(_MODEINFO)
|
||||
|
||||
# raw modes that may be memory mapped. NOTE: if you change this, you
|
||||
# may have to modify the stride calculation in map.c too!
|
||||
|
@ -341,6 +334,7 @@ def getmodebands(mode):
|
|||
"""
|
||||
return len(ImageMode.getmode(mode).bands)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
|
@ -355,23 +349,23 @@ def preinit():
|
|||
return
|
||||
|
||||
try:
|
||||
from PIL import BmpImagePlugin
|
||||
from . import BmpImagePlugin
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from PIL import GifImagePlugin
|
||||
from . import GifImagePlugin
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from PIL import JpegImagePlugin
|
||||
from . import JpegImagePlugin
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from PIL import PpmImagePlugin
|
||||
from . import PpmImagePlugin
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from PIL import PngImagePlugin
|
||||
from . import PngImagePlugin
|
||||
except ImportError:
|
||||
pass
|
||||
# try:
|
||||
|
@ -415,6 +409,11 @@ def _getdecoder(mode, decoder_name, args, extra=()):
|
|||
elif not isinstance(args, tuple):
|
||||
args = (args,)
|
||||
|
||||
try:
|
||||
decoder = DECODERS[decoder_name]
|
||||
return decoder(mode, *args + extra)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
# get decoder
|
||||
decoder = getattr(core, decoder_name + "_decoder")
|
||||
|
@ -432,6 +431,11 @@ def _getencoder(mode, encoder_name, args, extra=()):
|
|||
elif not isinstance(args, tuple):
|
||||
args = (args,)
|
||||
|
||||
try:
|
||||
encoder = ENCODERS[encoder_name]
|
||||
return encoder(mode, *args + extra)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
# get encoder
|
||||
encoder = getattr(core, encoder_name + "_encoder")
|
||||
|
@ -496,6 +500,7 @@ class Image(object):
|
|||
"""
|
||||
format = None
|
||||
format_description = None
|
||||
_close_exclusive_fp_after_loading = True
|
||||
|
||||
def __init__(self):
|
||||
# FIXME: take "new" parameters / other image?
|
||||
|
@ -525,13 +530,11 @@ class Image(object):
|
|||
if self.palette:
|
||||
new.palette = self.palette.copy()
|
||||
if im.mode == "P" and not new.palette:
|
||||
from PIL import ImagePalette
|
||||
from . import ImagePalette
|
||||
new.palette = ImagePalette.ImagePalette()
|
||||
new.info = self.info.copy()
|
||||
return new
|
||||
|
||||
_makeself = _new # compatibility
|
||||
|
||||
# Context Manager Support
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
@ -552,21 +555,32 @@ class Image(object):
|
|||
"""
|
||||
try:
|
||||
self.fp.close()
|
||||
self.fp = None
|
||||
except Exception as msg:
|
||||
logger.debug("Error closing: %s", msg)
|
||||
|
||||
if getattr(self, 'map', None):
|
||||
self.map = None
|
||||
|
||||
# Instead of simply setting to None, we're setting up a
|
||||
# deferred error that will better explain that the core image
|
||||
# object is gone.
|
||||
self.im = deferred_error(ValueError("Operation on closed image"))
|
||||
|
||||
if sys.version_info >= (3, 4, 0):
|
||||
def __del__(self):
|
||||
if (hasattr(self, 'fp') and hasattr(self, '_exclusive_fp')
|
||||
and self.fp and self._exclusive_fp):
|
||||
self.fp.close()
|
||||
self.fp = None
|
||||
|
||||
def _copy(self):
|
||||
self.load()
|
||||
self.im = self.im.copy()
|
||||
self.pyaccess = None
|
||||
self.readonly = 0
|
||||
|
||||
def _dump(self, file=None, format=None):
|
||||
def _dump(self, file=None, format=None, **options):
|
||||
import tempfile
|
||||
suffix = ''
|
||||
if format:
|
||||
|
@ -581,7 +595,7 @@ class Image(object):
|
|||
else:
|
||||
if not file.endswith(format):
|
||||
file = file + "." + format
|
||||
self.save(file, format)
|
||||
self.save(file, format, **options)
|
||||
return file
|
||||
|
||||
def __eq__(self, other):
|
||||
|
@ -695,8 +709,8 @@ class Image(object):
|
|||
return b"".join(data)
|
||||
|
||||
def tostring(self, *args, **kw):
|
||||
raise NotImplementedError("tostring() has been removed. " +
|
||||
"Please call tobytes() instead.")
|
||||
raise NotImplementedError("tostring() has been removed. "
|
||||
"Please call tobytes() instead.")
|
||||
|
||||
def tobitmap(self, name="image"):
|
||||
"""
|
||||
|
@ -746,8 +760,8 @@ class Image(object):
|
|||
raise ValueError("cannot decode image data")
|
||||
|
||||
def fromstring(self, *args, **kw):
|
||||
raise NotImplementedError("fromstring() has been removed. " +
|
||||
"Please call frombytes() instead.")
|
||||
raise NotImplementedError("fromstring() has been removed. "
|
||||
"Please call frombytes() instead.")
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
|
@ -777,7 +791,7 @@ class Image(object):
|
|||
if HAS_CFFI and USE_CFFI_ACCESS:
|
||||
if self.pyaccess:
|
||||
return self.pyaccess
|
||||
from PIL import PyAccess
|
||||
from . import PyAccess
|
||||
self.pyaccess = PyAccess.new(self, self.readonly)
|
||||
if self.pyaccess:
|
||||
return self.pyaccess
|
||||
|
@ -883,7 +897,7 @@ class Image(object):
|
|||
try:
|
||||
t = trns_im.palette.getcolor(t)
|
||||
except:
|
||||
raise ValueError("Couldn't allocate a palette " +
|
||||
raise ValueError("Couldn't allocate a palette "
|
||||
"color for transparency")
|
||||
trns_im.putpixel((0, 0), t)
|
||||
|
||||
|
@ -910,7 +924,7 @@ class Image(object):
|
|||
if mode == "P" and palette == ADAPTIVE:
|
||||
im = self.im.quantize(colors)
|
||||
new = self._new(im)
|
||||
from PIL import ImagePalette
|
||||
from . import ImagePalette
|
||||
new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB"))
|
||||
if delete_trns:
|
||||
# This could possibly happen if we requantize to fewer colors.
|
||||
|
@ -997,7 +1011,7 @@ class Image(object):
|
|||
"only RGB or L mode images can be quantized to a palette"
|
||||
)
|
||||
im = self.im.convert("P", 1, palette.im)
|
||||
return self._makeself(im)
|
||||
return self._new(im)
|
||||
|
||||
return self._new(self.im.quantize(colors, method, kmeans))
|
||||
|
||||
|
@ -1031,6 +1045,20 @@ class Image(object):
|
|||
if box is None:
|
||||
return self.copy()
|
||||
|
||||
return self._new(self._crop(self.im, box))
|
||||
|
||||
def _crop(self, im, box):
|
||||
"""
|
||||
Returns a rectangular region from the core image object im.
|
||||
|
||||
This is equivalent to calling im.crop((x0, y0, x1, y1)), but
|
||||
includes additional sanity checks.
|
||||
|
||||
:param im: a core image object
|
||||
:param box: The crop rectangle, as a (left, upper, right, lower)-tuple.
|
||||
:returns: A core image object.
|
||||
"""
|
||||
|
||||
x0, y0, x1, y1 = map(int, map(round, box))
|
||||
|
||||
if x1 < x0:
|
||||
|
@ -1038,8 +1066,9 @@ class Image(object):
|
|||
if y1 < y0:
|
||||
y1 = y0
|
||||
|
||||
return self._new(self.im.crop(( x0, y0, x1, y1)))
|
||||
_decompression_bomb_check((x1, y1))
|
||||
|
||||
return im.crop((x0, y0, x1, y1))
|
||||
|
||||
def draft(self, mode, size):
|
||||
"""
|
||||
|
@ -1053,6 +1082,9 @@ class Image(object):
|
|||
in place. If the image has already been loaded, this method has no
|
||||
effect.
|
||||
|
||||
Note: This method is not implemented for most images. It is
|
||||
currently implemented only for JPEG and PCD images.
|
||||
|
||||
:param mode: The requested mode.
|
||||
:param size: The requested size.
|
||||
"""
|
||||
|
@ -1258,8 +1290,8 @@ class Image(object):
|
|||
return self.im.histogram()
|
||||
|
||||
def offset(self, xoffset, yoffset=None):
|
||||
raise NotImplementedError("offset() has been removed. " +
|
||||
"Please call ImageChops.offset() instead.")
|
||||
raise NotImplementedError("offset() has been removed. "
|
||||
"Please call ImageChops.offset() instead.")
|
||||
|
||||
def paste(self, im, box=None, mask=None):
|
||||
"""
|
||||
|
@ -1323,7 +1355,7 @@ class Image(object):
|
|||
box += (box[0]+size[0], box[1]+size[1])
|
||||
|
||||
if isStringType(im):
|
||||
from PIL import ImageColor
|
||||
from . import ImageColor
|
||||
im = ImageColor.getcolor(im, self.mode)
|
||||
|
||||
elif isImageType(im):
|
||||
|
@ -1344,6 +1376,54 @@ class Image(object):
|
|||
else:
|
||||
self.im.paste(im, box)
|
||||
|
||||
def alpha_composite(self, im, dest=(0,0), source=(0,0)):
|
||||
""" 'In-place' analog of Image.alpha_composite. Composites an image
|
||||
onto this image.
|
||||
|
||||
:param im: image to composite over this one
|
||||
:param dest: Optional 2 tuple (left, top) specifying the upper
|
||||
left corner in this (destination) image.
|
||||
:param source: Optional 2 (left, top) tuple for the upper left
|
||||
corner in the overlay source image, or 4 tuple (left, top, right,
|
||||
bottom) for the bounds of the source rectangle
|
||||
|
||||
Performance Note: Not currently implemented in-place in the core layer.
|
||||
"""
|
||||
|
||||
if not isinstance(source, tuple):
|
||||
raise ValueError("Source must be a tuple")
|
||||
if not isinstance(dest, tuple):
|
||||
raise ValueError("Destination must be a tuple")
|
||||
if not len(source) in (2, 4):
|
||||
raise ValueError("Source must be a 2 or 4-tuple")
|
||||
if not len(dest) == 2:
|
||||
raise ValueError("Destination must be a 2-tuple")
|
||||
if min(source) < 0:
|
||||
raise ValueError("Source must be non-negative")
|
||||
if min(dest) < 0:
|
||||
raise ValueError("Destination must be non-negative")
|
||||
|
||||
if len(source) == 2:
|
||||
source = source + im.size
|
||||
|
||||
# over image, crop if it's not the whole thing.
|
||||
if source == (0,0) + im.size:
|
||||
overlay = im
|
||||
else:
|
||||
overlay = im.crop(source)
|
||||
|
||||
# target for the paste
|
||||
box = dest + (dest[0] + overlay.width, dest[1] + overlay.height)
|
||||
|
||||
# destination image. don't copy if we're using the whole image.
|
||||
if dest == (0,0) + self.size:
|
||||
background = self
|
||||
else:
|
||||
background = self.crop(box)
|
||||
|
||||
result = alpha_composite(background, overlay)
|
||||
self.paste(result, box)
|
||||
|
||||
def point(self, lut, mode=None):
|
||||
"""
|
||||
Maps this image through a lookup table or function.
|
||||
|
@ -1470,7 +1550,7 @@ class Image(object):
|
|||
|
||||
:param data: A palette sequence (either a list or a string).
|
||||
"""
|
||||
from PIL import ImagePalette
|
||||
from . import ImagePalette
|
||||
|
||||
if self.mode not in ("L", "P"):
|
||||
raise ValueError("illegal image mode")
|
||||
|
@ -1519,6 +1599,80 @@ class Image(object):
|
|||
return self.pyaccess.putpixel(xy, value)
|
||||
return self.im.putpixel(xy, value)
|
||||
|
||||
def remap_palette(self, dest_map, source_palette=None):
|
||||
"""
|
||||
Rewrites the image to reorder the palette.
|
||||
|
||||
:param dest_map: A list of indexes into the original palette.
|
||||
e.g. [1,0] would swap a two item palette, and list(range(255))
|
||||
is the identity transform.
|
||||
:param source_palette: Bytes or None.
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
|
||||
"""
|
||||
from . import ImagePalette
|
||||
|
||||
if self.mode not in ("L", "P"):
|
||||
raise ValueError("illegal image mode")
|
||||
|
||||
if source_palette is None:
|
||||
if self.mode == "P":
|
||||
source_palette = self.im.getpalette("RGB")[:768]
|
||||
else: # L-mode
|
||||
source_palette = bytearray(i//3 for i in range(768))
|
||||
|
||||
palette_bytes = b""
|
||||
new_positions = [0]*256
|
||||
|
||||
# pick only the used colors from the palette
|
||||
for i, oldPosition in enumerate(dest_map):
|
||||
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
|
||||
new_positions[oldPosition] = i
|
||||
|
||||
# replace the palette color id of all pixel with the new id
|
||||
|
||||
# Palette images are [0..255], mapped through a 1 or 3
|
||||
# byte/color map. We need to remap the whole image
|
||||
# from palette 1 to palette 2. New_positions is
|
||||
# an array of indexes into palette 1. Palette 2 is
|
||||
# palette 1 with any holes removed.
|
||||
|
||||
# We're going to leverage the convert mechanism to use the
|
||||
# C code to remap the image from palette 1 to palette 2,
|
||||
# by forcing the source image into 'L' mode and adding a
|
||||
# mapping 'L' mode palette, then converting back to 'L'
|
||||
# sans palette thus converting the image bytes, then
|
||||
# assigning the optimized RGB palette.
|
||||
|
||||
# perf reference, 9500x4000 gif, w/~135 colors
|
||||
# 14 sec prepatch, 1 sec postpatch with optimization forced.
|
||||
|
||||
mapping_palette = bytearray(new_positions)
|
||||
|
||||
m_im = self.copy()
|
||||
m_im.mode = 'P'
|
||||
|
||||
m_im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=mapping_palette*3,
|
||||
size=768)
|
||||
# possibly set palette dirty, then
|
||||
# m_im.putpalette(mapping_palette, 'L') # converts to 'P'
|
||||
# or just force it.
|
||||
# UNDONE -- this is part of the general issue with palettes
|
||||
m_im.im.putpalette(*m_im.palette.getdata())
|
||||
|
||||
m_im = m_im.convert('L')
|
||||
|
||||
# Internally, we require 768 bytes for a palette.
|
||||
new_palette_bytes = (palette_bytes +
|
||||
(768 - len(palette_bytes)) * b'\x00')
|
||||
m_im.putpalette(new_palette_bytes)
|
||||
m_im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=palette_bytes,
|
||||
size=len(palette_bytes))
|
||||
|
||||
return m_im
|
||||
|
||||
def resize(self, size, resample=NEAREST, box=None):
|
||||
"""
|
||||
Returns a resized copy of this image.
|
||||
|
@ -1567,7 +1721,8 @@ class Image(object):
|
|||
|
||||
return self._new(self.im.resize(size, resample, box))
|
||||
|
||||
def rotate(self, angle, resample=NEAREST, expand=0):
|
||||
def rotate(self, angle, resample=NEAREST, expand=0, center=None,
|
||||
translate=None):
|
||||
"""
|
||||
Returns a rotated copy of this image. This method returns a
|
||||
copy of this image, rotated the given number of degrees counter
|
||||
|
@ -1584,48 +1739,86 @@ class Image(object):
|
|||
:param expand: Optional expansion flag. If true, expands the output
|
||||
image to make it large enough to hold the entire rotated image.
|
||||
If false or omitted, make the output image the same size as the
|
||||
input image.
|
||||
input image. Note that the expand flag assumes rotation around
|
||||
the center and no translation.
|
||||
:param center: Optional center of rotation (a 2-tuple). Origin is
|
||||
the upper left corner. Default is the center of the image.
|
||||
:param translate: An optional post-rotate translation (a 2-tuple).
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
"""
|
||||
|
||||
angle = angle % 360.0
|
||||
|
||||
# Fast paths regardless of filter
|
||||
if angle == 0:
|
||||
return self.copy()
|
||||
if angle == 180:
|
||||
return self.transpose(ROTATE_180)
|
||||
if angle == 90 and expand:
|
||||
return self.transpose(ROTATE_90)
|
||||
if angle == 270 and expand:
|
||||
return self.transpose(ROTATE_270)
|
||||
# Fast paths regardless of filter, as long as we're not
|
||||
# translating or changing the center.
|
||||
if not (center or translate):
|
||||
if angle == 0:
|
||||
return self.copy()
|
||||
if angle == 180:
|
||||
return self.transpose(ROTATE_180)
|
||||
if angle == 90 and expand:
|
||||
return self.transpose(ROTATE_90)
|
||||
if angle == 270 and expand:
|
||||
return self.transpose(ROTATE_270)
|
||||
|
||||
# Calculate the affine matrix. Note that this is the reverse
|
||||
# transformation (from destination image to source) because we
|
||||
# want to interpolate the (discrete) destination pixel from
|
||||
# the local area around the (floating) source pixel.
|
||||
|
||||
# The matrix we actually want (note that it operates from the right):
|
||||
# (1, 0, tx) (1, 0, cx) ( cos a, sin a, 0) (1, 0, -cx)
|
||||
# (0, 1, ty) * (0, 1, cy) * (-sin a, cos a, 0) * (0, 1, -cy)
|
||||
# (0, 0, 1) (0, 0, 1) ( 0, 0, 1) (0, 0, 1)
|
||||
|
||||
# The reverse matrix is thus:
|
||||
# (1, 0, cx) ( cos -a, sin -a, 0) (1, 0, -cx) (1, 0, -tx)
|
||||
# (0, 1, cy) * (-sin -a, cos -a, 0) * (0, 1, -cy) * (0, 1, -ty)
|
||||
# (0, 0, 1) ( 0, 0, 1) (0, 0, 1) (0, 0, 1)
|
||||
|
||||
# In any case, the final translation may be updated at the end to
|
||||
# compensate for the expand flag.
|
||||
|
||||
w, h = self.size
|
||||
|
||||
if translate is None:
|
||||
translate = [0, 0]
|
||||
if center is None:
|
||||
center = [w / 2.0, h / 2.0]
|
||||
|
||||
angle = - math.radians(angle)
|
||||
matrix = [
|
||||
round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0,
|
||||
round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0
|
||||
]
|
||||
]
|
||||
|
||||
def transform(x, y, matrix=matrix):
|
||||
def transform(x, y, matrix):
|
||||
(a, b, c, d, e, f) = matrix
|
||||
return a*x + b*y + c, d*x + e*y + f
|
||||
|
||||
w, h = self.size
|
||||
matrix[2], matrix[5] = transform(-center[0] - translate[0],
|
||||
-center[1] - translate[1], matrix)
|
||||
matrix[2] += center[0]
|
||||
matrix[5] += center[1]
|
||||
|
||||
if expand:
|
||||
# calculate output size
|
||||
xx = []
|
||||
yy = []
|
||||
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
|
||||
x, y = transform(x, y)
|
||||
x, y = transform(x, y, matrix)
|
||||
xx.append(x)
|
||||
yy.append(y)
|
||||
w = int(math.ceil(max(xx)) - math.floor(min(xx)))
|
||||
h = int(math.ceil(max(yy)) - math.floor(min(yy)))
|
||||
nw = int(math.ceil(max(xx)) - math.floor(min(xx)))
|
||||
nh = int(math.ceil(max(yy)) - math.floor(min(yy)))
|
||||
|
||||
# adjust center
|
||||
x, y = transform(w / 2.0, h / 2.0)
|
||||
matrix[2] = self.size[0] / 2.0 - x
|
||||
matrix[5] = self.size[1] / 2.0 - y
|
||||
# We multiply a translation matrix from the right. Because of its
|
||||
# special form, this is the same as taking the image of the
|
||||
# translation vector as new translation vector.
|
||||
matrix[2], matrix[5] = transform(-(nw - w) / 2.0,
|
||||
-(nh - h) / 2.0,
|
||||
matrix)
|
||||
w, h = nw, nh
|
||||
|
||||
return self.transform((w, h), AFFINE, matrix, resample)
|
||||
|
||||
|
@ -1689,7 +1882,10 @@ class Image(object):
|
|||
if not format:
|
||||
if ext not in EXTENSION:
|
||||
init()
|
||||
format = EXTENSION[ext]
|
||||
try:
|
||||
format = EXTENSION[ext]
|
||||
except KeyError:
|
||||
raise ValueError('unknown file extension: {}'.format(ext))
|
||||
|
||||
if format.upper() not in SAVE:
|
||||
init()
|
||||
|
@ -1740,8 +1936,8 @@ class Image(object):
|
|||
PPM file, and calls either the **xv** utility or the **display**
|
||||
utility, depending on which one can be found.
|
||||
|
||||
On macOS, this method saves the image to a temporary BMP file, and opens
|
||||
it with the native Preview application.
|
||||
On macOS, this method saves the image to a temporary BMP file, and
|
||||
opens it with the native Preview application.
|
||||
|
||||
On Windows, it saves the image to a temporary BMP file, and uses
|
||||
the standard BMP display utility to show it (usually Paint).
|
||||
|
@ -1956,20 +2152,19 @@ class Image(object):
|
|||
|
||||
def toqimage(self):
|
||||
"""Returns a QImage copy of this image"""
|
||||
from PIL import ImageQt
|
||||
from . import ImageQt
|
||||
if not ImageQt.qt_is_installed:
|
||||
raise ImportError("Qt bindings are not installed")
|
||||
return ImageQt.toqimage(self)
|
||||
|
||||
def toqpixmap(self):
|
||||
"""Returns a QPixmap copy of this image"""
|
||||
from PIL import ImageQt
|
||||
from . import ImageQt
|
||||
if not ImageQt.qt_is_installed:
|
||||
raise ImportError("Qt bindings are not installed")
|
||||
return ImageQt.toqpixmap(self)
|
||||
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Abstract handlers.
|
||||
|
||||
|
@ -1994,6 +2189,7 @@ def _wedge():
|
|||
|
||||
return Image()._new(core.wedge("L"))
|
||||
|
||||
|
||||
def _check_size(size):
|
||||
"""
|
||||
Common check to enforce type and sanity check on size tuples
|
||||
|
@ -2006,11 +2202,12 @@ def _check_size(size):
|
|||
raise ValueError("Size must be a tuple")
|
||||
if len(size) != 2:
|
||||
raise ValueError("Size must be a tuple of length 2")
|
||||
if size[0] <= 0 or size[1] <= 0:
|
||||
raise ValueError("Width and Height must be > 0")
|
||||
if size[0] < 0 or size[1] < 0:
|
||||
raise ValueError("Width and height must be >= 0")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def new(mode, size, color=0):
|
||||
"""
|
||||
Creates a new image with the given mode and size.
|
||||
|
@ -2036,7 +2233,7 @@ def new(mode, size, color=0):
|
|||
if isStringType(color):
|
||||
# css3-style specifier
|
||||
|
||||
from PIL import ImageColor
|
||||
from . import ImageColor
|
||||
color = ImageColor.getcolor(color, mode)
|
||||
|
||||
return Image()._new(core.fill(mode, size, color))
|
||||
|
@ -2082,7 +2279,7 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
|
|||
|
||||
def fromstring(*args, **kw):
|
||||
raise NotImplementedError("fromstring() has been removed. " +
|
||||
"Please call frombytes() instead.")
|
||||
"Please call frombytes() instead.")
|
||||
|
||||
|
||||
def frombuffer(mode, size, data, decoder_name="raw", *args):
|
||||
|
@ -2164,16 +2361,13 @@ def fromarray(obj, mode=None):
|
|||
arr = obj.__array_interface__
|
||||
shape = arr['shape']
|
||||
ndim = len(shape)
|
||||
try:
|
||||
strides = arr['strides']
|
||||
except KeyError:
|
||||
strides = None
|
||||
strides = arr.get('strides', None)
|
||||
if mode is None:
|
||||
try:
|
||||
typekey = (1, 1) + shape[2:], arr['typestr']
|
||||
mode, rawmode = _fromarray_typemap[typekey]
|
||||
except KeyError:
|
||||
# print typekey
|
||||
# print(typekey)
|
||||
raise TypeError("Cannot handle this data type")
|
||||
else:
|
||||
rawmode = mode
|
||||
|
@ -2198,7 +2392,7 @@ def fromarray(obj, mode=None):
|
|||
|
||||
def fromqimage(im):
|
||||
"""Creates an image instance from a QImage image"""
|
||||
from PIL import ImageQt
|
||||
from . import ImageQt
|
||||
if not ImageQt.qt_is_installed:
|
||||
raise ImportError("Qt bindings are not installed")
|
||||
return ImageQt.fromqimage(im)
|
||||
|
@ -2206,11 +2400,12 @@ def fromqimage(im):
|
|||
|
||||
def fromqpixmap(im):
|
||||
"""Creates an image instance from a QPixmap image"""
|
||||
from PIL import ImageQt
|
||||
from . import ImageQt
|
||||
if not ImageQt.qt_is_installed:
|
||||
raise ImportError("Qt bindings are not installed")
|
||||
return ImageQt.fromqpixmap(im)
|
||||
|
||||
|
||||
_fromarray_typemap = {
|
||||
# (shape, typestr) => mode, rawmode
|
||||
# first two members of shape are set to one
|
||||
|
@ -2276,6 +2471,7 @@ def open(fp, mode="r"):
|
|||
if mode != "r":
|
||||
raise ValueError("bad mode %r" % mode)
|
||||
|
||||
exclusive_fp = False
|
||||
filename = ""
|
||||
if isPath(fp):
|
||||
filename = fp
|
||||
|
@ -2289,11 +2485,13 @@ def open(fp, mode="r"):
|
|||
|
||||
if filename:
|
||||
fp = builtins.open(filename, "rb")
|
||||
exclusive_fp = True
|
||||
|
||||
try:
|
||||
fp.seek(0)
|
||||
except (AttributeError, io.UnsupportedOperation):
|
||||
fp = io.BytesIO(fp.read())
|
||||
exclusive_fp = True
|
||||
|
||||
prefix = fp.read(16)
|
||||
|
||||
|
@ -2322,8 +2520,11 @@ def open(fp, mode="r"):
|
|||
im = _open_core(fp, filename, prefix)
|
||||
|
||||
if im:
|
||||
im._exclusive_fp = exclusive_fp
|
||||
return im
|
||||
|
||||
if exclusive_fp:
|
||||
fp.close()
|
||||
raise IOError("cannot identify image file %r"
|
||||
% (filename if filename else fp))
|
||||
|
||||
|
@ -2491,6 +2692,44 @@ def register_extension(id, extension):
|
|||
EXTENSION[extension.lower()] = id.upper()
|
||||
|
||||
|
||||
def registered_extensions():
|
||||
"""
|
||||
Returns a dictionary containing all file extensions belonging
|
||||
to registered plugins
|
||||
"""
|
||||
if not bool(EXTENSION):
|
||||
init()
|
||||
return EXTENSION
|
||||
|
||||
|
||||
def register_decoder(name, decoder):
|
||||
"""
|
||||
Registers an image decoder. This function should not be
|
||||
used in application code.
|
||||
|
||||
:param name: The name of the decoder
|
||||
:param decoder: A callable(mode, args) that returns an
|
||||
ImageFile.PyDecoder object
|
||||
|
||||
.. versionadded:: 4.1.0
|
||||
"""
|
||||
DECODERS[name] = decoder
|
||||
|
||||
|
||||
def register_encoder(name, encoder):
|
||||
"""
|
||||
Registers an image encoder. This function should not be
|
||||
used in application code.
|
||||
|
||||
:param name: The name of the encoder
|
||||
:param encoder: A callable(mode, args) that returns an
|
||||
ImageFile.PyEncoder object
|
||||
|
||||
.. versionadded:: 4.1.0
|
||||
"""
|
||||
ENCODERS[name] = encoder
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Simple display support. User code may override this.
|
||||
|
||||
|
@ -2500,7 +2739,7 @@ def _show(image, **options):
|
|||
|
||||
|
||||
def _showxv(image, title=None, **options):
|
||||
from PIL import ImageShow
|
||||
from . import ImageShow
|
||||
ImageShow.show(image, title, **options)
|
||||
|
||||
|
||||
|
@ -2529,3 +2768,21 @@ def effect_noise(size, sigma):
|
|||
:param sigma: Standard deviation of noise.
|
||||
"""
|
||||
return Image()._new(core.effect_noise(size, sigma))
|
||||
|
||||
|
||||
def linear_gradient(mode):
|
||||
"""
|
||||
Generate 256x256 linear gradient from black to white, top to bottom.
|
||||
|
||||
:param mode: Input mode.
|
||||
"""
|
||||
return Image()._new(core.linear_gradient(mode))
|
||||
|
||||
|
||||
def radial_gradient(mode):
|
||||
"""
|
||||
Generate 256x256 radial gradient from black to white, centre to edge.
|
||||
|
||||
:param mode: Input mode.
|
||||
"""
|
||||
return Image()._new(core.radial_gradient(mode))
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
|
||||
def constant(image, value):
|
||||
|
|
|
@ -166,7 +166,6 @@ class ImageCmsProfile(object):
|
|||
self._set(profile)
|
||||
else:
|
||||
raise TypeError("Invalid type for Profile")
|
||||
|
||||
|
||||
def _set(self, profile, filename=None):
|
||||
self.profile = profile
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
import re
|
||||
|
||||
|
||||
|
|
|
@ -31,10 +31,9 @@
|
|||
#
|
||||
|
||||
import numbers
|
||||
import warnings
|
||||
|
||||
from PIL import Image, ImageColor
|
||||
from PIL._util import isStringType
|
||||
from . import Image, ImageColor
|
||||
from ._util import isStringType
|
||||
|
||||
"""
|
||||
A simple 2D drawing interface for PIL images.
|
||||
|
@ -87,25 +86,14 @@ class ImageDraw(object):
|
|||
self.fill = 0
|
||||
self.font = None
|
||||
|
||||
def setink(self, ink):
|
||||
raise NotImplementedError("setink() has been removed. " +
|
||||
"Please use keyword arguments instead.")
|
||||
|
||||
def setfill(self, onoff):
|
||||
raise NotImplementedError("setfill() has been removed. " +
|
||||
"Please use keyword arguments instead.")
|
||||
|
||||
def setfont(self, font):
|
||||
warnings.warn("setfont() is deprecated. " +
|
||||
"Please set the attribute directly instead.")
|
||||
# compatibility
|
||||
self.font = font
|
||||
|
||||
def getfont(self):
|
||||
"""Get the current default font."""
|
||||
"""
|
||||
Get the current default font.
|
||||
|
||||
:returns: An image font."""
|
||||
if not self.font:
|
||||
# FIXME: should add a font repository
|
||||
from PIL import ImageFont
|
||||
from . import ImageFont
|
||||
self.font = ImageFont.load_default()
|
||||
return self.font
|
||||
|
||||
|
@ -222,7 +210,6 @@ class ImageDraw(object):
|
|||
if self._multiline_check(text):
|
||||
return self.multiline_text(xy, text, fill, font, anchor,
|
||||
*args, **kwargs)
|
||||
|
||||
ink, fill = self._getink(fill)
|
||||
if font is None:
|
||||
font = self.getfont()
|
||||
|
@ -230,17 +217,17 @@ class ImageDraw(object):
|
|||
ink = fill
|
||||
if ink is not None:
|
||||
try:
|
||||
mask, offset = font.getmask2(text, self.fontmode)
|
||||
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs)
|
||||
xy = xy[0] + offset[0], xy[1] + offset[1]
|
||||
except AttributeError:
|
||||
try:
|
||||
mask = font.getmask(text, self.fontmode)
|
||||
mask = font.getmask(text, self.fontmode, *args, **kwargs)
|
||||
except TypeError:
|
||||
mask = font.getmask(text)
|
||||
self.draw.draw_bitmap(xy, mask, ink)
|
||||
|
||||
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
|
||||
spacing=4, align="left"):
|
||||
spacing=4, align="left", direction=None, features=None):
|
||||
widths = []
|
||||
max_width = 0
|
||||
lines = self._multiline_split(text)
|
||||
|
@ -259,25 +246,30 @@ class ImageDraw(object):
|
|||
left += (max_width - widths[idx])
|
||||
else:
|
||||
assert False, 'align must be "left", "center" or "right"'
|
||||
self.text((left, top), line, fill, font, anchor)
|
||||
self.text((left, top), line, fill, font, anchor,
|
||||
direction=direction, features=features)
|
||||
top += line_spacing
|
||||
left = xy[0]
|
||||
|
||||
def textsize(self, text, font=None, *args, **kwargs):
|
||||
def textsize(self, text, font=None, spacing=4, direction=None,
|
||||
features=None):
|
||||
"""Get the size of a given string, in pixels."""
|
||||
if self._multiline_check(text):
|
||||
return self.multiline_textsize(text, font, *args, **kwargs)
|
||||
return self.multiline_textsize(text, font, spacing,
|
||||
direction, features)
|
||||
|
||||
if font is None:
|
||||
font = self.getfont()
|
||||
return font.getsize(text)
|
||||
return font.getsize(text, direction, features)
|
||||
|
||||
def multiline_textsize(self, text, font=None, spacing=4):
|
||||
def multiline_textsize(self, text, font=None, spacing=4, direction=None,
|
||||
features=None):
|
||||
max_width = 0
|
||||
lines = self._multiline_split(text)
|
||||
line_spacing = self.textsize('A', font=font)[1] + spacing
|
||||
for line in lines:
|
||||
line_width, line_height = self.textsize(line, font)
|
||||
line_width, line_height = self.textsize(line, font, spacing,
|
||||
direction, features)
|
||||
max_width = max(max_width, line_width)
|
||||
return max_width, len(lines)*line_spacing
|
||||
|
||||
|
@ -298,6 +290,7 @@ def Draw(im, mode=None):
|
|||
except AttributeError:
|
||||
return ImageDraw(im, mode)
|
||||
|
||||
|
||||
# experimental access to the outline API
|
||||
try:
|
||||
Outline = Image.core.outline
|
||||
|
@ -319,17 +312,17 @@ def getdraw(im=None, hints=None):
|
|||
handler = None
|
||||
if not hints or "nicest" in hints:
|
||||
try:
|
||||
from PIL import _imagingagg as handler
|
||||
from . import _imagingagg as handler
|
||||
except ImportError:
|
||||
pass
|
||||
if handler is None:
|
||||
from PIL import ImageDraw2 as handler
|
||||
from . import ImageDraw2 as handler
|
||||
if im:
|
||||
im = handler.Draw(im)
|
||||
return im, handler
|
||||
|
||||
|
||||
def floodfill(image, xy, value, border=None):
|
||||
def floodfill(image, xy, value, border=None, thresh=0):
|
||||
"""
|
||||
(experimental) Fills a bounded region with a given color.
|
||||
|
||||
|
@ -340,16 +333,20 @@ def floodfill(image, xy, value, border=None):
|
|||
pixels with a color different from the border color. If not given,
|
||||
the region consists of pixels having the same color as the seed
|
||||
pixel.
|
||||
:param thresh: Optional threshold value which specifies a maximum
|
||||
tolerable difference of a pixel value from the 'background' in
|
||||
order for it to be replaced. Useful for filling regions of non-
|
||||
homogeneous, but similar, colors.
|
||||
"""
|
||||
# based on an implementation by Eric S. Raymond
|
||||
pixel = image.load()
|
||||
x, y = xy
|
||||
try:
|
||||
background = pixel[x, y]
|
||||
if background == value:
|
||||
if _color_diff(value, background) <= thresh:
|
||||
return # seed point already has fill color
|
||||
pixel[x, y] = value
|
||||
except IndexError:
|
||||
except (ValueError, IndexError):
|
||||
return # seed point outside image
|
||||
edge = [(x, y)]
|
||||
if border is None:
|
||||
|
@ -362,7 +359,7 @@ def floodfill(image, xy, value, border=None):
|
|||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if p == background:
|
||||
if _color_diff(p, background) <= thresh:
|
||||
pixel[s, t] = value
|
||||
newedge.append((s, t))
|
||||
edge = newedge
|
||||
|
@ -380,3 +377,10 @@ def floodfill(image, xy, value, border=None):
|
|||
pixel[s, t] = value
|
||||
newedge.append((s, t))
|
||||
edge = newedge
|
||||
|
||||
|
||||
def _color_diff(rgb1, rgb2):
|
||||
"""
|
||||
Uses 1-norm distance to calculate difference between two rgb values.
|
||||
"""
|
||||
return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2])
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||
|
||||
|
||||
class Pen(object):
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFilter, ImageStat
|
||||
from . import Image, ImageFilter, ImageStat
|
||||
|
||||
|
||||
class _Enhance(object):
|
||||
|
|
180
PIL/ImageFile.py
180
PIL/ImageFile.py
|
@ -27,8 +27,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isPath
|
||||
from . import Image
|
||||
from ._util import isPath
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
@ -88,10 +88,13 @@ class ImageFile(Image.Image):
|
|||
# filename
|
||||
self.fp = open(fp, "rb")
|
||||
self.filename = fp
|
||||
self._exclusive_fp = True
|
||||
else:
|
||||
# stream
|
||||
self.fp = fp
|
||||
self.filename = filename
|
||||
# can be overridden
|
||||
self._exclusive_fp = None
|
||||
|
||||
try:
|
||||
self._open()
|
||||
|
@ -100,6 +103,9 @@ class ImageFile(Image.Image):
|
|||
KeyError, # unsupported mode
|
||||
EOFError, # got header but not the first frame
|
||||
struct.error) as v:
|
||||
# close the file only if we have opened it this constructor
|
||||
if self._exclusive_fp:
|
||||
self.fp.close()
|
||||
raise SyntaxError(v)
|
||||
|
||||
if not self.mode or self.size[0] <= 0:
|
||||
|
@ -115,6 +121,8 @@ class ImageFile(Image.Image):
|
|||
|
||||
# raise exception if something's wrong. must be called
|
||||
# directly after open, and closes file when finished.
|
||||
if self._exclusive_fp:
|
||||
self.fp.close()
|
||||
self.fp = None
|
||||
|
||||
def load(self):
|
||||
|
@ -152,7 +160,7 @@ class ImageFile(Image.Image):
|
|||
# try memory mapping
|
||||
decoder_name, extents, offset, args = self.tile[0]
|
||||
if decoder_name == "raw" and len(args) >= 3 and args[0] == self.mode \
|
||||
and args[0] in Image._MAPMODES:
|
||||
and args[0] in Image._MAPMODES:
|
||||
try:
|
||||
if hasattr(Image.core, "map"):
|
||||
# use built-in mapper WIN32 only
|
||||
|
@ -178,7 +186,7 @@ class ImageFile(Image.Image):
|
|||
self.map = None
|
||||
|
||||
self.load_prepare()
|
||||
|
||||
err_code = -3 # initialize to unknown error
|
||||
if not self.map:
|
||||
# sort tiles in file order
|
||||
self.tile.sort(key=_tilesort)
|
||||
|
@ -191,12 +199,9 @@ class ImageFile(Image.Image):
|
|||
|
||||
for decoder_name, extents, offset, args in self.tile:
|
||||
decoder = Image._getdecoder(self.mode, decoder_name,
|
||||
args, self.decoderconfig)
|
||||
args, self.decoderconfig)
|
||||
seek(offset)
|
||||
try:
|
||||
decoder.setimage(self.im, extents)
|
||||
except ValueError:
|
||||
continue
|
||||
decoder.setimage(self.im, extents)
|
||||
if decoder.pulls_fd:
|
||||
decoder.setfd(self.fp)
|
||||
status, err_code = decoder.decode(b"")
|
||||
|
@ -211,7 +216,7 @@ class ImageFile(Image.Image):
|
|||
else:
|
||||
raise IOError("image file is truncated")
|
||||
|
||||
if not s and not decoder.handles_eof: # truncated jpeg
|
||||
if not s: # truncated jpeg
|
||||
self.tile = []
|
||||
|
||||
# JpegDecode needs to clean things up here either way
|
||||
|
@ -237,20 +242,16 @@ class ImageFile(Image.Image):
|
|||
self.tile = []
|
||||
self.readonly = readonly
|
||||
|
||||
self.fp = None # might be shared
|
||||
self.load_end()
|
||||
|
||||
if self._exclusive_fp and self._close_exclusive_fp_after_loading:
|
||||
self.fp.close()
|
||||
self.fp = None
|
||||
|
||||
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
|
||||
# still raised if decoder fails to return anything
|
||||
raise_ioerror(err_code)
|
||||
|
||||
# post processing
|
||||
if hasattr(self, "tile_post_rotate"):
|
||||
# FIXME: This is a hack to handle rotated PCD's
|
||||
self.im = self.im.rotate(self.tile_post_rotate)
|
||||
self.size = self.im.size
|
||||
|
||||
self.load_end()
|
||||
|
||||
return Image.Image.load(self)
|
||||
|
||||
def load_prepare(self):
|
||||
|
@ -379,11 +380,8 @@ class Parser(object):
|
|||
|
||||
# attempt to open this file
|
||||
try:
|
||||
try:
|
||||
fp = io.BytesIO(self.data)
|
||||
with io.BytesIO(self.data) as fp:
|
||||
im = Image.open(fp)
|
||||
finally:
|
||||
fp.close() # explicitly close the virtual file
|
||||
except IOError:
|
||||
# traceback.print_exc()
|
||||
pass # not enough data
|
||||
|
@ -431,12 +429,11 @@ class Parser(object):
|
|||
if self.data:
|
||||
# incremental parsing not possible; reopen the file
|
||||
# not that we have all data
|
||||
try:
|
||||
fp = io.BytesIO(self.data)
|
||||
self.image = Image.open(fp)
|
||||
finally:
|
||||
self.image.load()
|
||||
fp.close() # explicitly close the virtual file
|
||||
with io.BytesIO(self.data) as fp:
|
||||
try:
|
||||
self.image = Image.open(fp)
|
||||
finally:
|
||||
self.image.load()
|
||||
return self.image
|
||||
|
||||
|
||||
|
@ -526,3 +523,128 @@ def _safe_read(fp, size):
|
|||
data.append(block)
|
||||
size -= len(block)
|
||||
return b"".join(data)
|
||||
|
||||
|
||||
class PyCodecState(object):
|
||||
def __init__(self):
|
||||
self.xsize = 0
|
||||
self.ysize = 0
|
||||
self.xoff = 0
|
||||
self.yoff = 0
|
||||
|
||||
def extents(self):
|
||||
return (self.xoff, self.yoff,
|
||||
self.xoff+self.xsize, self.yoff+self.ysize)
|
||||
|
||||
|
||||
class PyDecoder(object):
|
||||
"""
|
||||
Python implementation of a format decoder. Override this class and
|
||||
add the decoding logic in the `decode` method.
|
||||
|
||||
See :ref:`Writing Your Own File Decoder in Python<file-decoders-py>`
|
||||
"""
|
||||
|
||||
_pulls_fd = False
|
||||
|
||||
def __init__(self, mode, *args):
|
||||
self.im = None
|
||||
self.state = PyCodecState()
|
||||
self.fd = None
|
||||
self.mode = mode
|
||||
self.init(args)
|
||||
|
||||
def init(self, args):
|
||||
"""
|
||||
Override to perform decoder specific initialization
|
||||
|
||||
:param args: Array of args items from the tile entry
|
||||
:returns: None
|
||||
"""
|
||||
self.args = args
|
||||
|
||||
@property
|
||||
def pulls_fd(self):
|
||||
return self._pulls_fd
|
||||
|
||||
def decode(self, buffer):
|
||||
"""
|
||||
Override to perform the decoding process.
|
||||
|
||||
:param buffer: A bytes object with the data to be decoded. If `handles_eof`
|
||||
is set, then `buffer` will be empty and `self.fd` will be set.
|
||||
:returns: A tuple of (bytes consumed, errcode). If finished with decoding
|
||||
return <0 for the bytes consumed. Err codes are from `ERRORS`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Override to perform decoder specific cleanup
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
pass
|
||||
|
||||
def setfd(self, fd):
|
||||
"""
|
||||
Called from ImageFile to set the python file-like object
|
||||
|
||||
:param fd: A python file-like object
|
||||
:returns: None
|
||||
"""
|
||||
self.fd = fd
|
||||
|
||||
def setimage(self, im, extents=None):
|
||||
"""
|
||||
Called from ImageFile to set the core output image for the decoder
|
||||
|
||||
:param im: A core image object
|
||||
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
|
||||
for this tile
|
||||
:returns: None
|
||||
"""
|
||||
|
||||
# following c code
|
||||
self.im = im
|
||||
|
||||
if extents:
|
||||
(x0, y0, x1, y1) = extents
|
||||
else:
|
||||
(x0, y0, x1, y1) = (0, 0, 0, 0)
|
||||
|
||||
if x0 == 0 and x1 == 0:
|
||||
self.state.xsize, self.state.ysize = self.im.size
|
||||
else:
|
||||
self.state.xoff = x0
|
||||
self.state.yoff = y0
|
||||
self.state.xsize = x1 - x0
|
||||
self.state.ysize = y1 - y0
|
||||
|
||||
if self.state.xsize <= 0 or self.state.ysize <= 0:
|
||||
raise ValueError("Size cannot be negative")
|
||||
|
||||
if (self.state.xsize + self.state.xoff > self.im.size[0] or
|
||||
self.state.ysize + self.state.yoff > self.im.size[1]):
|
||||
raise ValueError("Tile cannot extend outside image")
|
||||
|
||||
def set_as_raw(self, data, rawmode=None):
|
||||
"""
|
||||
Convenience method to set the internal image from a stream of raw data
|
||||
|
||||
:param data: Bytes to be set
|
||||
:param rawmode: The rawmode to be used for the decoder. If not specified,
|
||||
it will default to the mode of the image
|
||||
:returns: None
|
||||
"""
|
||||
|
||||
if not rawmode:
|
||||
rawmode = self.mode
|
||||
d = Image._getdecoder(self.mode, 'raw', (rawmode))
|
||||
d.setimage(self.im, self.state.extents())
|
||||
s = d.decode(data)
|
||||
|
||||
if s[0] >= 0:
|
||||
raise ValueError("not enough image data")
|
||||
if s[1] != 0:
|
||||
raise ValueError("cannot decode image data")
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isDirectory, isPath
|
||||
from . import Image
|
||||
from ._util import isDirectory, isPath
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
@ -37,10 +37,13 @@ class _imagingft_not_installed(object):
|
|||
raise ImportError("The _imagingft C module is not installed")
|
||||
|
||||
try:
|
||||
from PIL import _imagingft as core
|
||||
from . import _imagingft as core
|
||||
except ImportError:
|
||||
core = _imagingft_not_installed()
|
||||
|
||||
LAYOUT_BASIC = 0
|
||||
LAYOUT_RAQM = 1
|
||||
|
||||
# FIXME: add support for pilfont2 format (see FontFile.py)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -103,9 +106,12 @@ class ImageFont(object):
|
|||
|
||||
self.font = Image.core.font(image.im, data)
|
||||
|
||||
# delegate critical operations to internal type
|
||||
self.getsize = self.font.getsize
|
||||
self.getmask = self.font.getmask
|
||||
def getsize(self, text, *args, **kwargs):
|
||||
return self.font.getsize(text)
|
||||
|
||||
def getmask(self, text, mode="", *args, **kwargs):
|
||||
return self.font.getmask(text, mode)
|
||||
|
||||
|
||||
|
||||
##
|
||||
|
@ -115,7 +121,8 @@ class ImageFont(object):
|
|||
class FreeTypeFont(object):
|
||||
"FreeType font wrapper (requires _imagingft service)"
|
||||
|
||||
def __init__(self, font=None, size=10, index=0, encoding=""):
|
||||
def __init__(self, font=None, size=10, index=0, encoding="",
|
||||
layout_engine=None):
|
||||
# FIXME: use service provider instead
|
||||
|
||||
self.path = font
|
||||
|
@ -123,12 +130,21 @@ class FreeTypeFont(object):
|
|||
self.index = index
|
||||
self.encoding = encoding
|
||||
|
||||
if layout_engine not in (LAYOUT_BASIC, LAYOUT_RAQM):
|
||||
layout_engine = LAYOUT_BASIC
|
||||
if core.HAVE_RAQM:
|
||||
layout_engine = LAYOUT_RAQM
|
||||
if layout_engine == LAYOUT_RAQM and not core.HAVE_RAQM:
|
||||
layout_engine = LAYOUT_BASIC
|
||||
|
||||
self.layout_engine = layout_engine
|
||||
|
||||
if isPath(font):
|
||||
self.font = core.getfont(font, size, index, encoding)
|
||||
self.font = core.getfont(font, size, index, encoding, layout_engine=layout_engine)
|
||||
else:
|
||||
self.font_bytes = font.read()
|
||||
self.font = core.getfont(
|
||||
"", size, index, encoding, self.font_bytes)
|
||||
"", size, index, encoding, self.font_bytes, layout_engine)
|
||||
|
||||
def getname(self):
|
||||
return self.font.family, self.font.style
|
||||
|
@ -136,23 +152,24 @@ class FreeTypeFont(object):
|
|||
def getmetrics(self):
|
||||
return self.font.ascent, self.font.descent
|
||||
|
||||
def getsize(self, text):
|
||||
size, offset = self.font.getsize(text)
|
||||
def getsize(self, text, direction=None, features=None):
|
||||
size, offset = self.font.getsize(text, direction, features)
|
||||
return (size[0] + offset[0], size[1] + offset[1])
|
||||
|
||||
def getoffset(self, text):
|
||||
return self.font.getsize(text)[1]
|
||||
|
||||
def getmask(self, text, mode=""):
|
||||
return self.getmask2(text, mode)[0]
|
||||
def getmask(self, text, mode="", direction=None, features=None):
|
||||
return self.getmask2(text, mode, direction=direction, features=features)[0]
|
||||
|
||||
def getmask2(self, text, mode="", fill=Image.core.fill):
|
||||
size, offset = self.font.getsize(text)
|
||||
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, features=None):
|
||||
size, offset = self.font.getsize(text, direction, features)
|
||||
im = fill("L", size, 0)
|
||||
self.font.render(text, im.id, mode == "1")
|
||||
self.font.render(text, im.id, mode == "1", direction, features)
|
||||
return im, offset
|
||||
|
||||
def font_variant(self, font=None, size=None, index=None, encoding=None):
|
||||
def font_variant(self, font=None, size=None, index=None, encoding=None,
|
||||
layout_engine=None):
|
||||
"""
|
||||
Create a copy of this FreeTypeFont object,
|
||||
using any specified arguments to override the settings.
|
||||
|
@ -165,8 +182,9 @@ class 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)
|
||||
encoding=self.encoding if encoding is None else encoding,
|
||||
layout_engine=self.layout_engine if layout_engine is None else layout_engine
|
||||
)
|
||||
|
||||
|
||||
class TransposedFont(object):
|
||||
|
@ -185,14 +203,14 @@ class TransposedFont(object):
|
|||
self.font = font
|
||||
self.orientation = orientation # any 'transpose' argument, or None
|
||||
|
||||
def getsize(self, text):
|
||||
def getsize(self, text, *args, **kwargs):
|
||||
w, h = self.font.getsize(text)
|
||||
if self.orientation in (Image.ROTATE_90, Image.ROTATE_270):
|
||||
return h, w
|
||||
return w, h
|
||||
|
||||
def getmask(self, text, mode=""):
|
||||
im = self.font.getmask(text, mode)
|
||||
def getmask(self, text, mode="", *args, **kwargs):
|
||||
im = self.font.getmask(text, mode, *args, **kwargs)
|
||||
if self.orientation is not None:
|
||||
return im.transpose(self.orientation)
|
||||
return im
|
||||
|
@ -212,7 +230,8 @@ def load(filename):
|
|||
return f
|
||||
|
||||
|
||||
def truetype(font=None, size=10, index=0, encoding=""):
|
||||
def truetype(font=None, size=10, index=0, encoding="",
|
||||
layout_engine=None):
|
||||
"""
|
||||
Load a TrueType or OpenType font file, and create a font object.
|
||||
This function loads a font object from the given file, and creates
|
||||
|
@ -230,12 +249,14 @@ def truetype(font=None, size=10, index=0, encoding=""):
|
|||
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
||||
and "armn" (Apple Roman). See the FreeType documentation
|
||||
for more information.
|
||||
:param layout_engine: Which layout engine to use, if available:
|
||||
`ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`.
|
||||
:return: A font object.
|
||||
:exception IOError: If the file could not be read.
|
||||
"""
|
||||
|
||||
try:
|
||||
return FreeTypeFont(font, size, index, encoding)
|
||||
return FreeTypeFont(font, size, index, encoding, layout_engine)
|
||||
except IOError:
|
||||
ttf_filename = os.path.basename(font)
|
||||
|
||||
|
@ -266,16 +287,16 @@ def truetype(font=None, size=10, index=0, encoding=""):
|
|||
for walkfilename in walkfilenames:
|
||||
if ext and walkfilename == ttf_filename:
|
||||
fontpath = os.path.join(walkroot, walkfilename)
|
||||
return FreeTypeFont(fontpath, size, index, encoding)
|
||||
return FreeTypeFont(fontpath, size, index, encoding, layout_engine)
|
||||
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)
|
||||
return FreeTypeFont(fontpath, size, index, encoding, layout_engine)
|
||||
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)
|
||||
index, encoding, layout_engine)
|
||||
raise
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
import sys
|
||||
if sys.platform not in ["win32", "darwin"]:
|
||||
|
@ -75,7 +75,7 @@ def grabclipboard():
|
|||
debug = 0 # temporary interface
|
||||
data = Image.core.grabclipboard(debug)
|
||||
if isinstance(data, bytes):
|
||||
from PIL import BmpImagePlugin
|
||||
from . import BmpImagePlugin
|
||||
import io
|
||||
return BmpImagePlugin.DibImageFile(io.BytesIO(data))
|
||||
return data
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL import _imagingmath
|
||||
from . import Image, _imagingmath
|
||||
|
||||
try:
|
||||
import builtins
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#
|
||||
|
||||
# mode descriptor cache
|
||||
_modes = {}
|
||||
_modes = None
|
||||
|
||||
|
||||
class ModeDescriptor(object):
|
||||
|
@ -32,19 +32,24 @@ class ModeDescriptor(object):
|
|||
|
||||
def getmode(mode):
|
||||
"""Gets a mode descriptor for the given mode."""
|
||||
global _modes
|
||||
if not _modes:
|
||||
# initialize mode cache
|
||||
from PIL import Image
|
||||
|
||||
from . import Image
|
||||
modes = {}
|
||||
# core modes
|
||||
for m, (basemode, basetype, bands) in Image._MODEINFO.items():
|
||||
_modes[m] = ModeDescriptor(m, bands, basemode, basetype)
|
||||
modes[m] = ModeDescriptor(m, bands, basemode, basetype)
|
||||
# extra experimental modes
|
||||
_modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L")
|
||||
_modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
|
||||
_modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
|
||||
_modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
|
||||
modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L")
|
||||
modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
|
||||
modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
|
||||
modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
|
||||
# mapping modes
|
||||
_modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")
|
||||
_modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L")
|
||||
_modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L")
|
||||
modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")
|
||||
modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L")
|
||||
modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L")
|
||||
# set global mode cache atomically
|
||||
_modes = modes
|
||||
return _modes[mode]
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
#
|
||||
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
|
||||
|
||||
from PIL import Image
|
||||
from PIL import _imagingmorph
|
||||
from __future__ import print_function
|
||||
|
||||
from . import Image, _imagingmorph
|
||||
import re
|
||||
|
||||
LUT_SIZE = 1 << 9
|
||||
|
@ -122,7 +123,7 @@ class LutBuilder(object):
|
|||
.replace('0', 'Z')
|
||||
.replace('1', '0')
|
||||
.replace('Z', '1'))
|
||||
res = '%d' % (1-int(res))
|
||||
res = 1-int(res)
|
||||
patterns.append((pattern, res))
|
||||
|
||||
return patterns
|
||||
|
@ -151,9 +152,9 @@ class LutBuilder(object):
|
|||
patterns += self._pattern_permute(pattern, options, result)
|
||||
|
||||
# # Debugging
|
||||
# for p,r in patterns:
|
||||
# print p,r
|
||||
# print '--'
|
||||
# for p, r in patterns:
|
||||
# print(p, r)
|
||||
# print('--')
|
||||
|
||||
# compile the patterns into regular expressions for speed
|
||||
for i, pattern in enumerate(patterns):
|
||||
|
@ -233,7 +234,7 @@ class MorphOp(object):
|
|||
with open(filename, 'rb') as f:
|
||||
self.lut = bytearray(f.read())
|
||||
|
||||
if len(self.lut) != 8192:
|
||||
if len(self.lut) != LUT_SIZE:
|
||||
self.lut = None
|
||||
raise Exception('Wrong size operator file!')
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isStringType
|
||||
from . import Image
|
||||
from ._util import isStringType
|
||||
import operator
|
||||
import functools
|
||||
|
||||
|
@ -39,7 +39,7 @@ def _border(border):
|
|||
|
||||
def _color(color, mode):
|
||||
if isStringType(color):
|
||||
from PIL import ImageColor
|
||||
from . import ImageColor
|
||||
color = ImageColor.getcolor(color, mode)
|
||||
return color
|
||||
|
||||
|
@ -206,7 +206,8 @@ def deform(image, deformer, resample=Image.BILINEAR):
|
|||
:param image: The image to deform.
|
||||
:param deformer: A deformer object. Any object that implements a
|
||||
**getmesh** method can be used.
|
||||
:param resample: What resampling filter to use.
|
||||
:param resample: An optional resampling filter. Same values possible as
|
||||
in the PIL.Image.transform function.
|
||||
:return: An image.
|
||||
"""
|
||||
return image.transform(
|
||||
|
|
|
@ -17,10 +17,7 @@
|
|||
#
|
||||
|
||||
import array
|
||||
from PIL import ImageColor
|
||||
from PIL import GimpPaletteFile
|
||||
from PIL import GimpGradientFile
|
||||
from PIL import PaletteFile
|
||||
from . import ImageColor, GimpPaletteFile, GimpGradientFile, PaletteFile
|
||||
|
||||
|
||||
class ImagePalette(object):
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
|
||||
# the Python class below is overridden by the C implementation.
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isPath
|
||||
from . import Image
|
||||
from ._util import isPath
|
||||
from io import BytesIO
|
||||
|
||||
qt_is_installed = True
|
||||
|
|
|
@ -69,7 +69,7 @@ class Viewer(object):
|
|||
# FIXME: auto-contrast if max() > 255?
|
||||
else:
|
||||
base = Image.getmodebase(image.mode)
|
||||
if base != image.mode and image.mode != "1":
|
||||
if base != image.mode and image.mode != "1" and image.mode != "RGBA":
|
||||
image = image.convert(base)
|
||||
|
||||
return self.show_image(image, **options)
|
||||
|
@ -77,6 +77,7 @@ class Viewer(object):
|
|||
# hook methods
|
||||
|
||||
format = None
|
||||
options = {}
|
||||
|
||||
def get_format(self, image):
|
||||
"""Return format name, or None to save as PGM/PPM"""
|
||||
|
@ -87,7 +88,7 @@ class Viewer(object):
|
|||
|
||||
def save_image(self, image):
|
||||
"""Save to temporary file, and return filename"""
|
||||
return image._dump(format=self.get_format(image))
|
||||
return image._dump(format=self.get_format(image), **self.options)
|
||||
|
||||
def show_image(self, image, **options):
|
||||
"""Display given image"""
|
||||
|
@ -115,7 +116,8 @@ if sys.platform == "win32":
|
|||
elif sys.platform == "darwin":
|
||||
|
||||
class MacViewer(Viewer):
|
||||
format = "BMP"
|
||||
format = "PNG"
|
||||
options = {'compress_level': 1}
|
||||
|
||||
def get_command(self, file, **options):
|
||||
# on darwin open returns immediately resulting in the temp
|
||||
|
@ -142,6 +144,9 @@ else:
|
|||
return None
|
||||
|
||||
class UnixViewer(Viewer):
|
||||
format = "PNG"
|
||||
options = {'compress_level': 1}
|
||||
|
||||
def show_file(self, file, **options):
|
||||
command, executable = self.get_command_ex(file, **options)
|
||||
command = "(%s %s; rm -f %s)&" % (command, quote(file),
|
||||
|
|
|
@ -25,14 +25,21 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
try:
|
||||
import tkinter
|
||||
except ImportError:
|
||||
import Tkinter
|
||||
tkinter = Tkinter
|
||||
del Tkinter
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
if sys.version_info[0] > 2:
|
||||
import tkinter
|
||||
else:
|
||||
import Tkinter as tkinter
|
||||
|
||||
# required for pypy, which always has cffi installed
|
||||
try:
|
||||
from cffi import FFI
|
||||
ffi = FFI()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from . import Image
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
|
@ -182,9 +189,15 @@ class PhotoImage(object):
|
|||
except tkinter.TclError:
|
||||
# activate Tkinter hook
|
||||
try:
|
||||
from PIL import _imagingtk
|
||||
from . import _imagingtk
|
||||
try:
|
||||
_imagingtk.tkinit(tk.interpaddr(), 1)
|
||||
if hasattr(tk, 'interp'):
|
||||
# Pypy is using a ffi cdata element
|
||||
# (Pdb) self.tk.interp
|
||||
# <cdata 'Tcl_Interp *' 0x3061b50>
|
||||
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
|
||||
else:
|
||||
_imagingtk.tkinit(tk.interpaddr(), 1)
|
||||
except AttributeError:
|
||||
_imagingtk.tkinit(id(tk), 0)
|
||||
tk.call("PyImagingPhoto", self.__photo, block.id)
|
||||
|
@ -264,6 +277,8 @@ class BitmapImage(object):
|
|||
|
||||
|
||||
def getimage(photo):
|
||||
""" This function is unimplemented """
|
||||
|
||||
"""Copies the contents of a PhotoImage to a PIL image memory."""
|
||||
photo.tk.call("PyImagingPhotoGet", photo)
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
|
||||
class Transform(Image.ImageTransformHandler):
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
|
||||
class HDC(object):
|
||||
|
@ -182,14 +182,6 @@ class Dib(object):
|
|||
"""
|
||||
return self.image.tobytes()
|
||||
|
||||
def fromstring(self, *args, **kw):
|
||||
raise NotImplementedError("fromstring() has been removed. " +
|
||||
"Please use frombytes() instead.")
|
||||
|
||||
def tostring(self, *args, **kw):
|
||||
raise NotImplementedError("tostring() has been removed. " +
|
||||
"Please use tobytes() instead.")
|
||||
|
||||
|
||||
class Window(object):
|
||||
"""Create a Window with the given title size."""
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import re
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
|
|
@ -17,17 +17,13 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i8, i16be as i16, i32be as i32, o8
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16be
|
||||
i32 = _binary.i32be
|
||||
o8 = _binary.o8
|
||||
|
||||
COMPRESSION = {
|
||||
1: "raw",
|
||||
5: "jpeg"
|
||||
|
@ -99,7 +95,7 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
tagdata = self.fp.read(size)
|
||||
else:
|
||||
tagdata = None
|
||||
if tag in list(self.info.keys()):
|
||||
if tag in self.info:
|
||||
if isinstance(self.info[tag], list):
|
||||
self.info[tag].append(tagdata)
|
||||
else:
|
||||
|
@ -107,7 +103,7 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
else:
|
||||
self.info[tag] = tagdata
|
||||
|
||||
# print tag, self.info[tag]
|
||||
# print(tag, self.info[tag])
|
||||
|
||||
# mode
|
||||
layers = i8(self.info[(3, 60)][0])
|
||||
|
@ -191,7 +187,7 @@ def getiptcinfo(im):
|
|||
:returns: A dictionary containing IPTC information, or None if
|
||||
no IPTC information block was found.
|
||||
"""
|
||||
from PIL import TiffImagePlugin, JpegImagePlugin
|
||||
from . import TiffImagePlugin, JpegImagePlugin
|
||||
import io
|
||||
|
||||
data = None
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
import struct
|
||||
import os
|
||||
import io
|
||||
|
|
|
@ -32,19 +32,16 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import array
|
||||
import struct
|
||||
import io
|
||||
import warnings
|
||||
from struct import unpack_from
|
||||
from PIL import Image, ImageFile, TiffImagePlugin, _binary
|
||||
from PIL.JpegPresets import presets
|
||||
from PIL._util import isStringType
|
||||
|
||||
i8 = _binary.i8
|
||||
o8 = _binary.o8
|
||||
i16 = _binary.i16be
|
||||
i32 = _binary.i32be
|
||||
from . import Image, ImageFile, TiffImagePlugin
|
||||
from ._binary import i8, o8, i16be as i16
|
||||
from .JpegPresets import presets
|
||||
from ._util import isStringType
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
|
@ -120,6 +117,25 @@ def APP(self, marker):
|
|||
# plus constant header size
|
||||
self.info["mpoffset"] = self.fp.tell() - n + 4
|
||||
|
||||
# If DPI isn't in JPEG header, fetch from EXIF
|
||||
if "dpi" not in self.info and "exif" in self.info:
|
||||
try:
|
||||
exif = self._getexif()
|
||||
resolution_unit = exif[0x0128]
|
||||
x_resolution = exif[0x011A]
|
||||
try:
|
||||
dpi = x_resolution[0] / x_resolution[1]
|
||||
except TypeError:
|
||||
dpi = x_resolution
|
||||
if resolution_unit == 3: # cm
|
||||
# 1 dpcm = 2.54 dpi
|
||||
dpi *= 2.54
|
||||
self.info["dpi"] = dpi, dpi
|
||||
except (KeyError, SyntaxError):
|
||||
# SyntaxError for invalid/unreadable exif
|
||||
# KeyError for dpi not included
|
||||
self.info["dpi"] = 72, 72
|
||||
|
||||
|
||||
def COM(self, marker):
|
||||
#
|
||||
|
@ -316,7 +332,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
|
||||
if i in MARKER:
|
||||
name, description, handler = MARKER[i]
|
||||
# print hex(i), name, description
|
||||
# print(hex(i), name, description)
|
||||
if handler is not None:
|
||||
handler(self, i)
|
||||
if i == 0xFFDA: # start of scan
|
||||
|
@ -409,7 +425,8 @@ def _fixup_dict(src_dict):
|
|||
try:
|
||||
if len(value) == 1 and not isinstance(value, dict):
|
||||
return value[0]
|
||||
except: pass
|
||||
except:
|
||||
pass
|
||||
return value
|
||||
|
||||
return {k: _fixup(v) for k, v in src_dict.items()}
|
||||
|
@ -491,7 +508,7 @@ def _getmp(self):
|
|||
try:
|
||||
rawmpentries = mp[0xB002]
|
||||
for entrynum in range(0, quant):
|
||||
unpackedentry = unpack_from(
|
||||
unpackedentry = struct.unpack_from(
|
||||
'{}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
|
||||
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
||||
'EntryNo2')
|
||||
|
@ -540,7 +557,6 @@ RAWMODE = {
|
|||
"1": "L",
|
||||
"L": "L",
|
||||
"RGB": "RGB",
|
||||
"RGBA": "RGB",
|
||||
"RGBX": "RGB",
|
||||
"CMYK": "CMYK;I", # assume adobe conventions
|
||||
"YCbCr": "YCbCr",
|
||||
|
@ -589,14 +605,6 @@ def _save(im, fp, filename):
|
|||
except KeyError:
|
||||
raise IOError("cannot write mode %s as JPEG" % im.mode)
|
||||
|
||||
if im.mode == 'RGBA':
|
||||
warnings.warn(
|
||||
'You are saving RGBA image as JPEG. The alpha channel will be '
|
||||
'discarded. This conversion is deprecated and will be disabled '
|
||||
'in Pillow 3.7. Please, convert the image to RGB explicitly.',
|
||||
DeprecationWarning
|
||||
)
|
||||
|
||||
info = im.encoderinfo
|
||||
|
||||
dpi = [int(round(x)) for x in info.get("dpi", (0, 0))]
|
||||
|
@ -691,8 +699,8 @@ def _save(im, fp, filename):
|
|||
# "progressive" is the official name, but older documentation
|
||||
# says "progression"
|
||||
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
|
||||
progressive = info.get("progressive", False) or\
|
||||
info.get("progression", False)
|
||||
progressive = (info.get("progressive", False) or
|
||||
info.get("progression", False))
|
||||
|
||||
optimize = info.get("optimize", False)
|
||||
|
||||
|
@ -716,15 +724,19 @@ def _save(im, fp, filename):
|
|||
# https://github.com/matthewwithanm/django-imagekit/issues/50
|
||||
bufsize = 0
|
||||
if optimize or progressive:
|
||||
# CMYK can be bigger
|
||||
if im.mode == 'CMYK':
|
||||
bufsize = 4 * im.size[0] * im.size[1]
|
||||
# keep sets quality to 0, but the actual value may be high.
|
||||
if quality >= 95 or quality == 0:
|
||||
elif quality >= 95 or quality == 0:
|
||||
bufsize = 2 * im.size[0] * im.size[1]
|
||||
else:
|
||||
bufsize = im.size[0] * im.size[1]
|
||||
|
||||
# The exif info needs to be written as one block, + APP1, + one spare byte.
|
||||
# Ensure that our buffer is big enough
|
||||
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5)
|
||||
# Ensure that our buffer is big enough. Same with the icc_profile block.
|
||||
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5,
|
||||
len(extra) + 1)
|
||||
|
||||
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ The tables format between im.quantization and quantization in presets differ in
|
|||
You can convert the dict format to the preset format with the
|
||||
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
|
||||
|
||||
Libjpeg ref.: http://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
||||
Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#
|
||||
|
||||
import struct
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
@ -66,6 +66,7 @@ class McIdasImageFile(ImageFile.ImageFile):
|
|||
|
||||
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# registry
|
||||
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, TiffImagePlugin
|
||||
from PIL.OleFileIO import MAGIC, OleFileIO
|
||||
from . import Image, TiffImagePlugin
|
||||
|
||||
import olefile
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
@ -28,7 +29,7 @@ __version__ = "0.1"
|
|||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:8] == MAGIC
|
||||
return prefix[:8] == olefile.MAGIC
|
||||
|
||||
|
||||
##
|
||||
|
@ -38,6 +39,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
|
||||
format = "MIC"
|
||||
format_description = "Microsoft Image Composer"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
|
||||
|
@ -45,7 +47,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
# to be a Microsoft Image Composer file
|
||||
|
||||
try:
|
||||
self.ole = OleFileIO(self.fp)
|
||||
self.ole = olefile.OleFileIO(self.fp)
|
||||
except IOError:
|
||||
raise SyntaxError("not an MIC file; invalid OLE file")
|
||||
|
||||
|
@ -95,6 +97,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
|
||||
return self.frame
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL._binary import i8
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i8
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, JpegImagePlugin
|
||||
from . import Image, JpegImagePlugin
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
@ -39,7 +39,8 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
|
||||
format = "MPO"
|
||||
format_description = "MPO (CIPA DC-007)"
|
||||
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
self.fp.seek(0) # prep the fp in order to pass the JPEG test
|
||||
JpegImagePlugin.JpegImageFile._open(self)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# MSP file handling
|
||||
#
|
||||
|
@ -9,15 +8,25 @@
|
|||
# History:
|
||||
# 95-09-05 fl Created
|
||||
# 97-01-03 fl Read/write MSP images
|
||||
# 17-02-21 es Fixed RLE interpretation
|
||||
#
|
||||
# Copyright (c) Secret Labs AB 1997.
|
||||
# Copyright (c) Fredrik Lundh 1995-97.
|
||||
# Copyright (c) Eric Soroos 2017.
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
# More info on this format: https://archive.org/details/gg243631
|
||||
# Page 313:
|
||||
# Figure 205. Windows Paint Version 1: "DanM" Format
|
||||
# Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03
|
||||
#
|
||||
# See also: http://www.fileformat.info/format/mspaint/egff.htm
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i16le as i16, o16le as o16, i8
|
||||
import struct
|
||||
import io
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
@ -25,8 +34,6 @@ __version__ = "0.1"
|
|||
#
|
||||
# read MSP files
|
||||
|
||||
i16 = _binary.i16le
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] in [b"DanM", b"LinS"]
|
||||
|
@ -61,13 +68,93 @@ class MspImageFile(ImageFile.ImageFile):
|
|||
if s[:4] == b"DanM":
|
||||
self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
|
||||
else:
|
||||
self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)]
|
||||
self.tile = [("MSP", (0, 0)+self.size, 32, None)]
|
||||
|
||||
|
||||
class MspDecoder(ImageFile.PyDecoder):
|
||||
# The algo for the MSP decoder is from
|
||||
# http://www.fileformat.info/format/mspaint/egff.htm
|
||||
# cc-by-attribution -- That page references is taken from the
|
||||
# Encyclopedia of Graphics File Formats and is licensed by
|
||||
# O'Reilly under the Creative Common/Attribution license
|
||||
#
|
||||
# For RLE encoded files, the 32byte header is followed by a scan
|
||||
# line map, encoded as one 16bit word of encoded byte length per
|
||||
# line.
|
||||
#
|
||||
# NOTE: the encoded length of the line can be 0. This was not
|
||||
# handled in the previous version of this encoder, and there's no
|
||||
# mention of how to handle it in the documentation. From the few
|
||||
# examples I've seen, I've assumed that it is a fill of the
|
||||
# background color, in this case, white.
|
||||
#
|
||||
#
|
||||
# Pseudocode of the decoder:
|
||||
# Read a BYTE value as the RunType
|
||||
# If the RunType value is zero
|
||||
# Read next byte as the RunCount
|
||||
# Read the next byte as the RunValue
|
||||
# Write the RunValue byte RunCount times
|
||||
# If the RunType value is non-zero
|
||||
# Use this value as the RunCount
|
||||
# Read and write the next RunCount bytes literally
|
||||
#
|
||||
# e.g.:
|
||||
# 0x00 03 ff 05 00 01 02 03 04
|
||||
# would yield the bytes:
|
||||
# 0xff ff ff 00 01 02 03 04
|
||||
#
|
||||
# which are then interpreted as a bit packed mode '1' image
|
||||
|
||||
_pulls_fd = True
|
||||
|
||||
def decode(self, buffer):
|
||||
|
||||
img = io.BytesIO()
|
||||
blank_line = bytearray((0xff,)*((self.state.xsize+7)//8))
|
||||
try:
|
||||
self.fd.seek(32)
|
||||
rowmap = struct.unpack_from("<%dH" % (self.state.ysize),
|
||||
self.fd.read(self.state.ysize*2))
|
||||
except struct.error:
|
||||
raise IOError("Truncated MSP file in row map")
|
||||
|
||||
for x, rowlen in enumerate(rowmap):
|
||||
try:
|
||||
if rowlen == 0:
|
||||
img.write(blank_line)
|
||||
continue
|
||||
row = self.fd.read(rowlen)
|
||||
if len(row) != rowlen:
|
||||
raise IOError("Truncated MSP file, expected %d bytes on row %s",
|
||||
(rowlen, x))
|
||||
idx = 0
|
||||
while idx < rowlen:
|
||||
runtype = i8(row[idx])
|
||||
idx += 1
|
||||
if runtype == 0:
|
||||
(runcount, runval) = struct.unpack("Bc", row[idx:idx+2])
|
||||
img.write(runval * runcount)
|
||||
idx += 2
|
||||
else:
|
||||
runcount = runtype
|
||||
img.write(row[idx:idx+runcount])
|
||||
idx += runcount
|
||||
|
||||
except struct.error:
|
||||
raise IOError("Corrupted MSP file in row %d" % x)
|
||||
|
||||
self.set_as_raw(img.getvalue(), ("1", 0, 1))
|
||||
|
||||
return 0, 0
|
||||
|
||||
|
||||
Image.register_decoder('MSP', MspDecoder)
|
||||
|
||||
|
||||
#
|
||||
# write MSP files (uncompressed only)
|
||||
|
||||
o16 = _binary.o16le
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
|
||||
|
@ -95,6 +182,7 @@ def _save(im, fp, filename):
|
|||
# image body
|
||||
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
|
||||
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
olefile (formerly OleFileIO_PL)
|
||||
===============================
|
||||
|
||||
[olefile](http://www.decalage.info/olefile) is a Python package to parse, read and write
|
||||
[Microsoft OLE2 files](http://en.wikipedia.org/wiki/Compound_File_Binary_Format)
|
||||
(also called Structured Storage, Compound File Binary Format or Compound Document File Format),
|
||||
such as Microsoft Office 97-2003 documents, vbaProject.bin in MS Office 2007+ files, Image Composer
|
||||
and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats, McAfee antivirus quarantine files,
|
||||
etc.
|
||||
|
||||
|
||||
**Quick links:** [Home page](http://www.decalage.info/olefile) -
|
||||
[Download/Install](https://bitbucket.org/decalage/olefileio_pl/wiki/Install) -
|
||||
[Documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) -
|
||||
[Report Issues/Suggestions/Questions](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open) -
|
||||
[Contact the author](http://decalage.info/contact) -
|
||||
[Repository](https://bitbucket.org/decalage/olefileio_pl) -
|
||||
[Updates on Twitter](https://twitter.com/decalage2)
|
||||
|
||||
|
||||
News
|
||||
----
|
||||
|
||||
Follow all updates and news on Twitter: <https://twitter.com/decalage2>
|
||||
|
||||
- **2015-01-25 v0.42**: improved handling of special characters in stream/storage names on Python 2.x (using UTF-8
|
||||
instead of Latin-1), fixed bug in listdir with empty storages.
|
||||
- 2014-11-25 v0.41: OleFileIO.open and isOleFile now support OLE files stored in byte strings, fixed installer for
|
||||
python 3, added support for Jython (Niko Ehrenfeuchter)
|
||||
- 2014-10-01 v0.40: renamed OleFileIO_PL to olefile, added initial write support for streams >4K, updated doc and
|
||||
license, improved the setup script.
|
||||
- 2014-07-27 v0.31: fixed support for large files with 4K sectors, thanks to Niko Ehrenfeuchter, Martijn Berger and
|
||||
Dave Jones. Added test scripts from Pillow (by hugovk). Fixed setup for Python 3 (Martin Panter)
|
||||
- 2014-02-04 v0.30: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work.
|
||||
- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed
|
||||
parsing of direntry timestamps
|
||||
- 2013-05-27 v0.25: improved metadata extraction, properties parsing and exception handling, fixed
|
||||
[issue #12](https://bitbucket.org/decalage/olefileio_pl/issue/12/error-when-converting-timestamps-in-ole)
|
||||
- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved
|
||||
getproperties to convert timestamps to Python datetime
|
||||
- 2012-10-09: published [python-oletools](http://www.decalage.info/python/oletools), a package of analysis tools based
|
||||
on OleFileIO_PL
|
||||
- 2012-09-11 v0.23: added support for file-like objects, fixed [issue #8](https://bitbucket.org/decalage/olefileio_pl/issue/8/bug-with-file-object)
|
||||
- 2012-02-17 v0.22: fixed issues #7 (bug in getproperties) and #2 (added close method)
|
||||
- 2011-10-20: code hosted on bitbucket to ease contributions and bug tracking
|
||||
- 2010-01-24 v0.21: fixed support for big-endian CPUs, such as PowerPC Macs.
|
||||
- 2009-12-11 v0.20: small bugfix in OleFileIO.open when filename is not plain str.
|
||||
- 2009-12-10 v0.19: fixed support for 64 bits platforms (thanks to Ben G. and Martijn for reporting the bug)
|
||||
- see changelog in source code for more info.
|
||||
|
||||
Download/Install
|
||||
----------------
|
||||
|
||||
If you have pip or setuptools installed (pip is included in Python 2.7.9+), you may simply run **pip install olefile**
|
||||
or **easy_install olefile** for the first installation.
|
||||
|
||||
To update olefile, run **pip install -U olefile**.
|
||||
|
||||
Otherwise, see https://bitbucket.org/decalage/olefileio_pl/wiki/Install
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Parse, read and write any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls,
|
||||
PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes,
|
||||
Zeiss AxioVision ZVI files, Olympus FluoView OIB files, etc
|
||||
- List all the streams and storages contained in an OLE file
|
||||
- Open streams as files
|
||||
- Parse and read property streams, containing metadata of the file
|
||||
- Portable, pure Python module, no dependency
|
||||
|
||||
olefile can be used as an independent package or with PIL/Pillow.
|
||||
|
||||
olefile is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data (especially
|
||||
for security purposes such as malware analysis and forensics), then please also check my
|
||||
[python-oletools](http://www.decalage.info/python/oletools), which are built upon olefile and provide a higher-level interface.
|
||||
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
olefile is based on the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent
|
||||
Python Imaging Library, created and maintained by Fredrik Lundh. The olefile API is still compatible with PIL, but
|
||||
since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust
|
||||
design. From 2005 to 2014 the project was called OleFileIO_PL, and in 2014 I changed its name to olefile to celebrate
|
||||
its 9 years and its new write features.
|
||||
|
||||
As far as I know, olefile is the most complete and robust Python implementation to read MS OLE2 files, portable on
|
||||
several operating systems. (please tell me if you know other similar Python modules)
|
||||
|
||||
Since 2014 olefile/OleFileIO_PL has been integrated into [Pillow](http://python-pillow.org), the friendly fork
|
||||
of PIL. olefile will continue to be improved as a separate project, and new versions will be merged into Pillow
|
||||
regularly.
|
||||
|
||||
|
||||
Main improvements over the original version of OleFileIO in PIL:
|
||||
----------------------------------------------------------------
|
||||
|
||||
- Compatible with Python 3.x and 2.6+
|
||||
- Many bug fixes
|
||||
- Support for files larger than 6.8MB
|
||||
- Support for 64 bits platforms and big-endian CPUs
|
||||
- Robust: many checks to detect malformed files
|
||||
- Runtime option to choose if malformed files should be parsed or raise exceptions
|
||||
- Improved API
|
||||
- Metadata extraction, stream/storage timestamps (e.g. for document forensics)
|
||||
- Can open file-like objects
|
||||
- Added setup.py and install.bat to ease installation
|
||||
- More convenient slash-based syntax for stream paths
|
||||
- Write features
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Please see the [online documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) for more information,
|
||||
especially the [OLE overview](https://bitbucket.org/decalage/olefileio_pl/wiki/OLE_Overview) and the
|
||||
[API page](https://bitbucket.org/decalage/olefileio_pl/wiki/API) which describe how to use olefile in Python applications.
|
||||
A copy of the same documentation is also provided in the doc subfolder of the olefile package.
|
||||
|
||||
|
||||
## Real-life examples ##
|
||||
|
||||
A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/).
|
||||
|
||||
See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features olefile.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
olefile (formerly OleFileIO_PL) is copyright (c) 2005-2015 Philippe Lagadec
|
||||
([http://www.decalage.info](http://www.decalage.info))
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
olefile is based on source code from the OleFileIO module of the Python Imaging Library (PIL) published by Fredrik
|
||||
Lundh under the following license:
|
||||
|
||||
The Python Imaging Library (PIL) is
|
||||
|
||||
Copyright © 1997-2011 by Secret Labs AB
|
||||
Copyright © 1995-2011 by Fredrik Lundh
|
||||
|
||||
By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read,
|
||||
understood, and will comply with the following terms and conditions:
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and
|
||||
without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that
|
||||
copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or
|
||||
the author not be used in advertising or publicity pertaining to distribution of the software without specific, written
|
||||
prior permission.
|
||||
|
||||
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||
SOFTWARE.
|
2309
PIL/OleFileIO.py
Executable file → Normal file
2309
PIL/OleFileIO.py
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
|
@ -15,7 +15,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import EpsImagePlugin
|
||||
from . import EpsImagePlugin
|
||||
import sys
|
||||
|
||||
##
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL._binary import o8
|
||||
from ._binary import o8
|
||||
|
||||
|
||||
##
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
# Image plugin for Palm pixmap images (output only).
|
||||
##
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import o8, o16be as o16b
|
||||
|
||||
__version__ = "1.0"
|
||||
|
||||
|
@ -108,9 +109,6 @@ _COMPRESSION_TYPES = {
|
|||
"scanline": 0x00,
|
||||
}
|
||||
|
||||
o8 = _binary.o8
|
||||
o16b = _binary.o16be
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -15,12 +15,11 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i8
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
i8 = _binary.i8
|
||||
|
||||
|
||||
##
|
||||
# Image plugin for PhotoCD images. This plugin only reads the 768x512
|
||||
|
@ -42,8 +41,9 @@ class PcdImageFile(ImageFile.ImageFile):
|
|||
raise SyntaxError("not a PCD file")
|
||||
|
||||
orientation = i8(s[1538]) & 3
|
||||
self.tile_post_rotate = None
|
||||
if orientation == 1:
|
||||
self.tile_post_rotate = 90 # hack
|
||||
self.tile_post_rotate = 90
|
||||
elif orientation == 3:
|
||||
self.tile_post_rotate = -90
|
||||
|
||||
|
@ -51,6 +51,13 @@ class PcdImageFile(ImageFile.ImageFile):
|
|||
self.size = 768, 512 # FIXME: not correct for rotated images!
|
||||
self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)]
|
||||
|
||||
def load_end(self):
|
||||
if self.tile_post_rotate:
|
||||
# Handle rotated PCDs
|
||||
self.im = self.im.rotate(self.tile_post_rotate)
|
||||
self.size = self.im.size
|
||||
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
|
|
|
@ -16,9 +16,8 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL import FontFile
|
||||
from PIL import _binary
|
||||
from . import Image, FontFile
|
||||
from ._binary import i8, i16le as l16, i32le as l32, i16be as b16, i32be as b32
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# declarations
|
||||
|
@ -42,12 +41,6 @@ BYTES_PER_ROW = [
|
|||
lambda bits: ((bits+63) >> 3) & ~7,
|
||||
]
|
||||
|
||||
i8 = _binary.i8
|
||||
l16 = _binary.i16le
|
||||
l32 = _binary.i32le
|
||||
b16 = _binary.i16be
|
||||
b32 = _binary.i32be
|
||||
|
||||
|
||||
def sz(s, o):
|
||||
return s[o:s.index(b"\0", o)]
|
||||
|
|
|
@ -25,17 +25,12 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, i16le as i16, o8, o16le as o16
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
o8 = _binary.o8
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
|
||||
|
@ -123,8 +118,6 @@ SAVE = {
|
|||
"RGB": (5, 8, 3, "RGB;L"),
|
||||
}
|
||||
|
||||
o16 = _binary.o16le
|
||||
|
||||
|
||||
def _save(im, fp, filename, check=0):
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
# Image plugin for PDF images (output only).
|
||||
##
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL._binary import i8
|
||||
from . import Image, ImageFile, ImageSequence
|
||||
from ._binary import i8
|
||||
import io
|
||||
|
||||
__version__ = "0.4"
|
||||
|
@ -37,11 +37,11 @@ __version__ = "0.4"
|
|||
# 4. page
|
||||
# 5. page contents
|
||||
|
||||
def _obj(fp, obj, **dict):
|
||||
def _obj(fp, obj, **dictionary):
|
||||
fp.write("%d 0 obj\n" % obj)
|
||||
if dict:
|
||||
if dictionary:
|
||||
fp.write("<<\n")
|
||||
for k, v in dict.items():
|
||||
for k, v in dictionary.items():
|
||||
if v is not None:
|
||||
fp.write("/%s %s\n" % (k, v))
|
||||
fp.write(">>\n")
|
||||
|
@ -133,13 +133,24 @@ def _save(im, fp, filename, save_all=False):
|
|||
|
||||
#
|
||||
# pages
|
||||
numberOfPages = 1
|
||||
ims = [im]
|
||||
if save_all:
|
||||
try:
|
||||
numberOfPages = im.n_frames
|
||||
except AttributeError:
|
||||
# Image format does not have n_frames. It is a single frame image
|
||||
pass
|
||||
append_images = im.encoderinfo.get("append_images", [])
|
||||
for append_im in append_images:
|
||||
if append_im.mode != im.mode:
|
||||
append_im = append_im.convert(im.mode)
|
||||
append_im.encoderinfo = im.encoderinfo.copy()
|
||||
ims.append(append_im)
|
||||
numberOfPages = 0
|
||||
for im in ims:
|
||||
im_numberOfPages = 1
|
||||
if save_all:
|
||||
try:
|
||||
im_numberOfPages = im.n_frames
|
||||
except AttributeError:
|
||||
# Image format does not have n_frames. It is a single frame image
|
||||
pass
|
||||
numberOfPages += im_numberOfPages
|
||||
pages = [str(pageNumber*3+4)+" 0 R"
|
||||
for pageNumber in range(0, numberOfPages)]
|
||||
|
||||
|
@ -151,90 +162,92 @@ def _save(im, fp, filename, save_all=False):
|
|||
Kids="["+"\n".join(pages)+"]")
|
||||
_endobj(fp)
|
||||
|
||||
for pageNumber in range(0, numberOfPages):
|
||||
im.seek(pageNumber)
|
||||
pageNumber = 0
|
||||
for imSequence in ims:
|
||||
for im in ImageSequence.Iterator(imSequence):
|
||||
#
|
||||
# image
|
||||
|
||||
#
|
||||
# image
|
||||
op = io.BytesIO()
|
||||
|
||||
op = io.BytesIO()
|
||||
if filter == "/ASCIIHexDecode":
|
||||
if bits == 1:
|
||||
# FIXME: the hex encoder doesn't support packed 1-bit
|
||||
# images; do things the hard way...
|
||||
data = im.tobytes("raw", "1")
|
||||
im = Image.new("L", (len(data), 1), None)
|
||||
im.putdata(data)
|
||||
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
|
||||
elif filter == "/DCTDecode":
|
||||
Image.SAVE["JPEG"](im, op, filename)
|
||||
elif filter == "/FlateDecode":
|
||||
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
|
||||
elif filter == "/RunLengthDecode":
|
||||
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
|
||||
else:
|
||||
raise ValueError("unsupported PDF filter (%s)" % filter)
|
||||
|
||||
if filter == "/ASCIIHexDecode":
|
||||
if bits == 1:
|
||||
# FIXME: the hex encoder doesn't support packed 1-bit
|
||||
# images; do things the hard way...
|
||||
data = im.tobytes("raw", "1")
|
||||
im = Image.new("L", (len(data), 1), None)
|
||||
im.putdata(data)
|
||||
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
|
||||
elif filter == "/DCTDecode":
|
||||
Image.SAVE["JPEG"](im, op, filename)
|
||||
elif filter == "/FlateDecode":
|
||||
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
|
||||
elif filter == "/RunLengthDecode":
|
||||
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
|
||||
else:
|
||||
raise ValueError("unsupported PDF filter (%s)" % filter)
|
||||
#
|
||||
# Get image characteristics
|
||||
|
||||
#
|
||||
# Get image characteristics
|
||||
width, height = im.size
|
||||
|
||||
width, height = im.size
|
||||
xref.append(fp.tell())
|
||||
_obj(
|
||||
fp, pageNumber*3+3,
|
||||
Type="/XObject",
|
||||
Subtype="/Image",
|
||||
Width=width, # * 72.0 / resolution,
|
||||
Height=height, # * 72.0 / resolution,
|
||||
Length=len(op.getvalue()),
|
||||
Filter=filter,
|
||||
BitsPerComponent=bits,
|
||||
DecodeParams=params,
|
||||
ColorSpace=colorspace)
|
||||
|
||||
xref.append(fp.tell())
|
||||
_obj(
|
||||
fp, pageNumber*3+3,
|
||||
Type="/XObject",
|
||||
Subtype="/Image",
|
||||
Width=width, # * 72.0 / resolution,
|
||||
Height=height, # * 72.0 / resolution,
|
||||
Length=len(op.getvalue()),
|
||||
Filter=filter,
|
||||
BitsPerComponent=bits,
|
||||
DecodeParams=params,
|
||||
ColorSpace=colorspace)
|
||||
fp.write("stream\n")
|
||||
fp.fp.write(op.getvalue())
|
||||
fp.write("\nendstream\n")
|
||||
|
||||
fp.write("stream\n")
|
||||
fp.fp.write(op.getvalue())
|
||||
fp.write("\nendstream\n")
|
||||
_endobj(fp)
|
||||
|
||||
_endobj(fp)
|
||||
#
|
||||
# page
|
||||
|
||||
#
|
||||
# page
|
||||
xref.append(fp.tell())
|
||||
_obj(fp, pageNumber*3+4)
|
||||
fp.write(
|
||||
"<<\n/Type /Page\n/Parent 2 0 R\n"
|
||||
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
|
||||
"/XObject << /image %d 0 R >>\n>>\n"
|
||||
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
|
||||
procset,
|
||||
pageNumber*3+3,
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution),
|
||||
pageNumber*3+5))
|
||||
_endobj(fp)
|
||||
|
||||
xref.append(fp.tell())
|
||||
_obj(fp, pageNumber*3+4)
|
||||
fp.write(
|
||||
"<<\n/Type /Page\n/Parent 2 0 R\n"
|
||||
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
|
||||
"/XObject << /image %d 0 R >>\n>>\n"
|
||||
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
|
||||
procset,
|
||||
pageNumber*3+3,
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution),
|
||||
pageNumber*3+5))
|
||||
_endobj(fp)
|
||||
#
|
||||
# page contents
|
||||
|
||||
#
|
||||
# page contents
|
||||
op = TextWriter(io.BytesIO())
|
||||
|
||||
op = TextWriter(io.BytesIO())
|
||||
op.write(
|
||||
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution)))
|
||||
|
||||
op.write(
|
||||
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution)))
|
||||
xref.append(fp.tell())
|
||||
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
|
||||
|
||||
xref.append(fp.tell())
|
||||
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
|
||||
fp.write("stream\n")
|
||||
fp.fp.write(op.fp.getvalue())
|
||||
fp.write("\nendstream\n")
|
||||
|
||||
fp.write("stream\n")
|
||||
fp.fp.write(op.fp.getvalue())
|
||||
fp.write("\nendstream\n")
|
||||
_endobj(fp)
|
||||
|
||||
_endobj(fp)
|
||||
pageNumber += 1
|
||||
|
||||
#
|
||||
# trailer
|
||||
|
|
|
@ -19,16 +19,15 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i16le as i16
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
|
||||
#
|
||||
# helpers
|
||||
|
||||
i16 = _binary.i16le
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == b"\200\350\000\000"
|
||||
|
||||
|
@ -63,6 +62,7 @@ class PixarImageFile(ImageFile.ImageFile):
|
|||
# create tile descriptor (assuming "dumped")
|
||||
self.tile = [("raw", (0, 0)+self.size, 1024, (self.mode, 0, 1))]
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -31,23 +31,18 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
import re
|
||||
import zlib
|
||||
import struct
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, i16be as i16, i32be as i32, o16be as o16, o32be as o32
|
||||
|
||||
__version__ = "0.9"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16be
|
||||
i32 = _binary.i32be
|
||||
|
||||
is_cid = re.compile(br"\w\w\w\w").match
|
||||
|
||||
|
||||
|
@ -118,7 +113,8 @@ class ChunkStream(object):
|
|||
length = i32(s)
|
||||
|
||||
if not is_cid(cid):
|
||||
raise SyntaxError("broken PNG file (chunk %s)" % repr(cid))
|
||||
if not ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||
raise SyntaxError("broken PNG file (chunk %s)" % repr(cid))
|
||||
|
||||
return cid, pos, length
|
||||
|
||||
|
@ -621,10 +617,6 @@ class PngImageFile(ImageFile.ImageFile):
|
|||
# --------------------------------------------------------------------
|
||||
# PNG writer
|
||||
|
||||
o8 = _binary.o8
|
||||
o16 = _binary.o16be
|
||||
o32 = _binary.o32be
|
||||
|
||||
_OUTMODES = {
|
||||
# supported PIL modes, and corresponding rawmodes/bits/color combinations
|
||||
"1": ("1", b'\x01\x00'),
|
||||
|
@ -722,6 +714,32 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
|||
b'\0', # 11: filter category
|
||||
b'\0') # 12: interlace flag
|
||||
|
||||
chunks = [b"cHRM", b"gAMA", b"sBIT", b"sRGB", b"tIME"]
|
||||
|
||||
icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
|
||||
if icc:
|
||||
# ICC profile
|
||||
# according to PNG spec, the iCCP chunk contains:
|
||||
# Profile name 1-79 bytes (character string)
|
||||
# Null separator 1 byte (null character)
|
||||
# Compression method 1 byte (0)
|
||||
# Compressed profile n bytes (zlib with deflate compression)
|
||||
name = b"ICC Profile"
|
||||
data = name + b"\0\0" + zlib.compress(icc)
|
||||
chunk(fp, b"iCCP", data)
|
||||
else:
|
||||
chunks.remove(b"sRGB")
|
||||
|
||||
info = im.encoderinfo.get("pnginfo")
|
||||
if info:
|
||||
chunks_multiple_allowed = [b"sPLT", b"iTXt", b"tEXt", b"zTXt"]
|
||||
for cid, data in info.chunks:
|
||||
if cid in chunks:
|
||||
chunks.remove(cid)
|
||||
chunk(fp, cid, data)
|
||||
elif cid in chunks_multiple_allowed:
|
||||
chunk(fp, cid, data)
|
||||
|
||||
if im.mode == "P":
|
||||
palette_byte_number = (2 ** bits) * 3
|
||||
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
|
||||
|
@ -768,20 +786,11 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
|||
|
||||
info = im.encoderinfo.get("pnginfo")
|
||||
if info:
|
||||
chunks = [b"bKGD", b"hIST"]
|
||||
for cid, data in info.chunks:
|
||||
chunk(fp, cid, data)
|
||||
|
||||
icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
|
||||
if icc:
|
||||
# ICC profile
|
||||
# according to PNG spec, the iCCP chunk contains:
|
||||
# Profile name 1-79 bytes (character string)
|
||||
# Null separator 1 byte (null character)
|
||||
# Compression method 1 byte (0)
|
||||
# Compressed profile n bytes (zlib with deflate compression)
|
||||
name = b"ICC Profile"
|
||||
data = name + b"\0\0" + zlib.compress(icc)
|
||||
chunk(fp, b"iCCP", data)
|
||||
if cid in chunks:
|
||||
chunks.remove(cid)
|
||||
chunk(fp, cid, data)
|
||||
|
||||
ImageFile._save(im, _idat(fp, chunk),
|
||||
[("zip", (0, 0)+im.size, 0, rawmode)])
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import string
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
|
||||
__version__ = "0.4"
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, i16be as i16, i32be as i32
|
||||
|
||||
MODES = {
|
||||
# (photoshop mode, bits) -> (pil mode, required channels)
|
||||
|
@ -33,13 +34,6 @@ MODES = {
|
|||
(9, 8): ("LAB", 3)
|
||||
}
|
||||
|
||||
#
|
||||
# helpers
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16be
|
||||
i32 = _binary.i32be
|
||||
|
||||
|
||||
# --------------------------------------------------------------------.
|
||||
# read PSD images
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
# Access.c implementation.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
|
@ -167,7 +165,7 @@ class _PyAccess8(PyAccess):
|
|||
try:
|
||||
# integer
|
||||
self.pixels[y][x] = min(color, 255)
|
||||
except:
|
||||
except TypeError:
|
||||
# tuple
|
||||
self.pixels[y][x] = min(color[0], 255)
|
||||
|
||||
|
@ -184,7 +182,7 @@ class _PyAccessI16_N(PyAccess):
|
|||
try:
|
||||
# integer
|
||||
self.pixels[y][x] = min(color, 65535)
|
||||
except:
|
||||
except TypeError:
|
||||
# tuple
|
||||
self.pixels[y][x] = min(color[0], 65535)
|
||||
|
||||
|
@ -272,7 +270,7 @@ class _PyAccessF(PyAccess):
|
|||
try:
|
||||
# not a tuple
|
||||
self.pixels[y][x] = color
|
||||
except:
|
||||
except TypeError:
|
||||
# tuple
|
||||
self.pixels[y][x] = color[0]
|
||||
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
# See "The SGI Image File Format (Draft version 0.97)", Paul Haeberli.
|
||||
# <ftp://ftp.sgi.com/graphics/SGIIMAGESPEC>
|
||||
#
|
||||
#
|
||||
# History:
|
||||
# 2016-16-10 mb Add save method without compression
|
||||
# 1995-09-10 fl Created
|
||||
#
|
||||
# Copyright (c) 2016 by Mickael Bonfill.
|
||||
# Copyright (c) 2008 by Karsten Hiddemann.
|
||||
# Copyright (c) 1997 by Secret Labs AB.
|
||||
# Copyright (c) 1995 by Fredrik Lundh.
|
||||
|
@ -18,12 +21,12 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i8, o8, i16be as i16
|
||||
import struct
|
||||
import os
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16be
|
||||
__version__ = "0.3"
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
|
@ -76,12 +79,79 @@ class SgiImageFile(ImageFile.ImageFile):
|
|||
elif compression == 1:
|
||||
raise ValueError("SGI RLE encoding not supported")
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L":
|
||||
raise ValueError("Unsupported SGI image mode")
|
||||
|
||||
# Flip the image, since the origin of SGI file is the bottom-left corner
|
||||
im = im.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
# Define the file as SGI File Format
|
||||
magicNumber = 474
|
||||
# Run-Length Encoding Compression - Unsupported at this time
|
||||
rle = 0
|
||||
# Byte-per-pixel precision, 1 = 8bits per pixel
|
||||
bpc = 1
|
||||
# Number of dimensions (x,y,z)
|
||||
dim = 3
|
||||
# X Dimension = width / Y Dimension = height
|
||||
x, y = im.size
|
||||
if im.mode == "L" and y == 1:
|
||||
dim = 1
|
||||
elif im.mode == "L":
|
||||
dim = 2
|
||||
# Z Dimension: Number of channels
|
||||
z = len(im.mode)
|
||||
if dim == 1 or dim == 2:
|
||||
z = 1
|
||||
# Minimum Byte value
|
||||
pinmin = 0
|
||||
# Maximum Byte value (255 = 8bits per pixel)
|
||||
pinmax = 255
|
||||
# Image name (79 characters max, truncated below in write)
|
||||
imgName = os.path.splitext(os.path.basename(filename))[0]
|
||||
if str is not bytes:
|
||||
imgName = imgName.encode('ascii', 'ignore')
|
||||
# Standard representation of pixel in the file
|
||||
colormap = 0
|
||||
fp.write(struct.pack('>h', magicNumber))
|
||||
fp.write(o8(rle))
|
||||
fp.write(o8(bpc))
|
||||
fp.write(struct.pack('>H', dim))
|
||||
fp.write(struct.pack('>H', x))
|
||||
fp.write(struct.pack('>H', y))
|
||||
fp.write(struct.pack('>H', z))
|
||||
fp.write(struct.pack('>l', pinmin))
|
||||
fp.write(struct.pack('>l', pinmax))
|
||||
|
||||
fp.write(struct.pack('4s', b'')) # dummy
|
||||
fp.write(struct.pack('79s', imgName)) # truncates to 79 chars
|
||||
fp.write(struct.pack('s', b'')) # force null byte after imgname
|
||||
fp.write(struct.pack('>l', colormap))
|
||||
|
||||
fp.write(struct.pack('404s', b'')) # dummy
|
||||
|
||||
# assert we've got the right number of bands.
|
||||
if len(im.getbands()) != z:
|
||||
raise ValueError("incorrect number of bands in SGI write: %s vs %s" %
|
||||
(z, len(im.getbands())))
|
||||
|
||||
for channel in im.split():
|
||||
fp.write(channel.tobytes())
|
||||
|
||||
fp.close()
|
||||
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
Image.register_open(SgiImageFile.format, SgiImageFile, _accept)
|
||||
|
||||
Image.register_save(SgiImageFile.format, _save)
|
||||
Image.register_mime(SgiImageFile.format, "image/sgi")
|
||||
Image.register_mime(SgiImageFile.format, "image/rgb")
|
||||
Image.register_extension(SgiImageFile.format, ".bw")
|
||||
Image.register_extension(SgiImageFile.format, ".rgb")
|
||||
Image.register_extension(SgiImageFile.format, ".rgba")
|
||||
Image.register_extension(SgiImageFile.format, ".sgi")
|
||||
|
||||
# End of file
|
||||
|
|
|
@ -27,10 +27,10 @@
|
|||
# image data from electron microscopy and tomography.
|
||||
#
|
||||
# Spider home page:
|
||||
# http://spider.wadsworth.org/spider_doc/spider/docs/spider.html
|
||||
# https://spider.wadsworth.org/spider_doc/spider/docs/spider.html
|
||||
#
|
||||
# Details about the Spider image format:
|
||||
# http://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html
|
||||
# https://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
@ -75,7 +75,7 @@ def isSpiderHeader(t):
|
|||
labrec = int(h[13]) # no. records in file header
|
||||
labbyt = int(h[22]) # total no. of bytes in header
|
||||
lenbyt = int(h[23]) # record length in bytes
|
||||
# print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt)
|
||||
# print("labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt))
|
||||
if labbyt != (labrec * lenbyt):
|
||||
return 0
|
||||
# looks like a valid header
|
||||
|
@ -83,9 +83,8 @@ def isSpiderHeader(t):
|
|||
|
||||
|
||||
def isSpiderImage(filename):
|
||||
fp = open(filename, 'rb')
|
||||
f = fp.read(92) # read 23 * 4 bytes
|
||||
fp.close()
|
||||
with open(filename, 'rb') as fp:
|
||||
f = fp.read(92) # read 23 * 4 bytes
|
||||
t = struct.unpack('>23f', f) # try big-endian first
|
||||
hdrlen = isSpiderHeader(t)
|
||||
if hdrlen == 0:
|
||||
|
@ -98,6 +97,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
|
||||
format = "SPIDER"
|
||||
format_description = "Spider 2D image"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
# check header
|
||||
|
|
|
@ -17,12 +17,11 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i32be as i32
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
i32 = _binary.i32be
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return len(prefix) >= 4 and i32(prefix) == 0x59a66a95
|
||||
|
@ -38,7 +37,8 @@ class SunImageFile(ImageFile.ImageFile):
|
|||
|
||||
def _open(self):
|
||||
|
||||
# The Sun Raster file header is 32 bytes in length and has the following format:
|
||||
# The Sun Raster file header is 32 bytes in length
|
||||
# and has the following format:
|
||||
|
||||
# typedef struct _SunRaster
|
||||
# {
|
||||
|
@ -52,7 +52,6 @@ class SunImageFile(ImageFile.ImageFile):
|
|||
# DWORD ColorMapLength; /* Size of the color map in bytes */
|
||||
# } SUNRASTER;
|
||||
|
||||
|
||||
# HEAD
|
||||
s = self.fp.read(32)
|
||||
if i32(s) != 0x59a66a95:
|
||||
|
@ -63,11 +62,11 @@ class SunImageFile(ImageFile.ImageFile):
|
|||
self.size = i32(s[4:8]), i32(s[8:12])
|
||||
|
||||
depth = i32(s[12:16])
|
||||
data_length = i32(s[16:20]) # unreliable, ignore.
|
||||
data_length = i32(s[16:20]) # unreliable, ignore.
|
||||
file_type = i32(s[20:24])
|
||||
palette_type = i32(s[24:28]) # 0: None, 1: RGB, 2: Raw/arbitrary
|
||||
palette_type = i32(s[24:28]) # 0: None, 1: RGB, 2: Raw/arbitrary
|
||||
palette_length = i32(s[28:32])
|
||||
|
||||
|
||||
if depth == 1:
|
||||
self.mode, rawmode = "1", "1;I"
|
||||
elif depth == 4:
|
||||
|
@ -85,23 +84,23 @@ class SunImageFile(ImageFile.ImageFile):
|
|||
else:
|
||||
self.mode, rawmode = 'RGB', 'BGRX'
|
||||
else:
|
||||
raise SyntaxError("Unsupported Mode/Bit Depth")
|
||||
|
||||
raise SyntaxError("Unsupported Mode/Bit Depth")
|
||||
|
||||
if palette_length:
|
||||
if palette_length > 1024:
|
||||
raise SyntaxError("Unsupported Color Palette Length")
|
||||
|
||||
if palette_type != 1:
|
||||
raise SyntaxError("Unsupported Palette Type")
|
||||
|
||||
|
||||
offset = offset + palette_length
|
||||
self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length))
|
||||
if self.mode == "L":
|
||||
self.mode = "P"
|
||||
rawmode = rawmode.replace('L', 'P')
|
||||
|
||||
|
||||
# 16 bit boundaries on stride
|
||||
stride = ((self.size[0] * depth + 15) // 16) * 2
|
||||
stride = ((self.size[0] * depth + 15) // 16) * 2
|
||||
|
||||
# file type: Type is the version (or flavor) of the bitmap
|
||||
# file. The following values are typically found in the Type
|
||||
|
@ -119,7 +118,7 @@ class SunImageFile(ImageFile.ImageFile):
|
|||
# RGB looks similar to standard, but RGB byte order
|
||||
# TIFF and IFF mean that they were converted from T/IFF
|
||||
# Experimental means that it's something else.
|
||||
# (http://www.fileformat.info/format/sunraster/egff.htm)
|
||||
# (https://www.fileformat.info/format/sunraster/egff.htm)
|
||||
|
||||
if file_type in (0, 1, 3, 4, 5):
|
||||
self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))]
|
||||
|
@ -127,7 +126,7 @@ class SunImageFile(ImageFile.ImageFile):
|
|||
self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)]
|
||||
else:
|
||||
raise SyntaxError('Unsupported Sun Raster file type')
|
||||
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import ContainerIO
|
||||
from . import ContainerIO
|
||||
|
||||
|
||||
##
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
#
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, i16le as i16, o8, o16le as o16
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
|
@ -26,9 +27,6 @@ __version__ = "0.3"
|
|||
# --------------------------------------------------------------------
|
||||
# Read RGA file
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
|
||||
|
||||
MODES = {
|
||||
# map imagetype/depth to rawmode
|
||||
|
@ -132,10 +130,6 @@ class TgaImageFile(ImageFile.ImageFile):
|
|||
# --------------------------------------------------------------------
|
||||
# Write TGA file
|
||||
|
||||
o8 = _binary.o8
|
||||
o16 = _binary.o16le
|
||||
o32 = _binary.o32le
|
||||
|
||||
SAVE = {
|
||||
"1": ("1", 1, 0, 3),
|
||||
"L": ("L", 8, 0, 3),
|
||||
|
|
|
@ -41,10 +41,8 @@
|
|||
|
||||
from __future__ import division, print_function
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL import ImagePalette
|
||||
from PIL import _binary
|
||||
from PIL import TiffTags
|
||||
from . import Image, ImageFile, ImagePalette, TiffTags
|
||||
from ._binary import i8, o8
|
||||
|
||||
import collections
|
||||
from fractions import Fraction
|
||||
|
@ -71,9 +69,6 @@ IFD_LEGACY_API = True
|
|||
II = b"II" # little-endian (Intel style)
|
||||
MM = b"MM" # big-endian (Motorola style)
|
||||
|
||||
i8 = _binary.i8
|
||||
o8 = _binary.o8
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Read TIFF files
|
||||
|
@ -247,6 +242,7 @@ def _limit_rational(val, max_val):
|
|||
n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
|
||||
return n_d[::-1] if inv else n_d
|
||||
|
||||
|
||||
##
|
||||
# Wrapper for TIFF IFDs.
|
||||
|
||||
|
@ -338,7 +334,7 @@ class IFDRational(Rational):
|
|||
'rfloordiv','mod','rmod', 'pow','rpow', 'pos', 'neg',
|
||||
'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'nonzero',
|
||||
'ceil', 'floor', 'round']
|
||||
print "\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a)
|
||||
print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a))
|
||||
"""
|
||||
|
||||
__add__ = _delegate('__add__')
|
||||
|
@ -467,15 +463,6 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
def __str__(self):
|
||||
return str(dict(self))
|
||||
|
||||
def as_dict(self):
|
||||
"""Return a dictionary of the image's tags.
|
||||
|
||||
.. deprecated:: 3.0.0
|
||||
"""
|
||||
warnings.warn("as_dict() is deprecated. " +
|
||||
"Please use dict(ifd) instead.", DeprecationWarning)
|
||||
return dict(self)
|
||||
|
||||
def named(self):
|
||||
"""
|
||||
:returns: dict of name|key: value
|
||||
|
@ -540,7 +527,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
self.tagtype[tag] = 2
|
||||
|
||||
if self.tagtype[tag] == 7 and bytes is not str:
|
||||
values = [value.encode("ascii", 'replace') if isinstance(value, str) else value]
|
||||
values = [value.encode("ascii", 'replace') if isinstance(
|
||||
value, str) else value]
|
||||
|
||||
values = tuple(info.cvt_enum(value) for value in values)
|
||||
|
||||
|
@ -569,7 +557,7 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
|
||||
def _register_loader(idx, size):
|
||||
def decorator(func):
|
||||
from PIL.TiffTags import TYPES
|
||||
from .TiffTags import TYPES
|
||||
if func.__name__.startswith("load_"):
|
||||
TYPES[idx] = func.__name__[5:].replace("_", " ")
|
||||
_load_dispatch[idx] = size, func
|
||||
|
@ -583,7 +571,7 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
return decorator
|
||||
|
||||
def _register_basic(idx_fmt_name):
|
||||
from PIL.TiffTags import TYPES
|
||||
from .TiffTags import TYPES
|
||||
idx, fmt, name = idx_fmt_name
|
||||
TYPES[idx] = name
|
||||
size = struct.calcsize("=" + fmt)
|
||||
|
@ -593,9 +581,13 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
b"".join(self._pack(fmt, value) for value in values))
|
||||
|
||||
list(map(_register_basic,
|
||||
[(3, "H", "short"), (4, "L", "long"),
|
||||
(6, "b", "signed byte"), (8, "h", "signed short"),
|
||||
(9, "l", "signed long"), (11, "f", "float"), (12, "d", "double")]))
|
||||
[(3, "H", "short"),
|
||||
(4, "L", "long"),
|
||||
(6, "b", "signed byte"),
|
||||
(8, "h", "signed short"),
|
||||
(9, "l", "signed long"),
|
||||
(11, "f", "float"),
|
||||
(12, "d", "double")]))
|
||||
|
||||
@_register_loader(1, 1) # Basic type, except for the legacy API.
|
||||
def load_byte(self, data, legacy_api=True):
|
||||
|
@ -621,7 +613,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
@_register_loader(5, 8)
|
||||
def load_rational(self, data, legacy_api=True):
|
||||
vals = self._unpack("{}L".format(len(data) // 4), data)
|
||||
combine = lambda a, b: (a, b) if legacy_api else IFDRational(a, b)
|
||||
|
||||
def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b)
|
||||
return tuple(combine(num, denom)
|
||||
for num, denom in zip(vals[::2], vals[1::2]))
|
||||
|
||||
|
@ -641,7 +634,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
@_register_loader(10, 8)
|
||||
def load_signed_rational(self, data, legacy_api=True):
|
||||
vals = self._unpack("{}l".format(len(data) // 4), data)
|
||||
combine = lambda a, b: (a, b) if legacy_api else IFDRational(a, b)
|
||||
|
||||
def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b)
|
||||
return tuple(combine(num, denom)
|
||||
for num, denom in zip(vals[::2], vals[1::2]))
|
||||
|
||||
|
@ -665,7 +659,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
|
||||
try:
|
||||
for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]):
|
||||
tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12))
|
||||
tag, typ, count, data = self._unpack("HHL4s",
|
||||
self._ensure_read(fp, 12))
|
||||
if DEBUG:
|
||||
tagname = TiffTags.lookup(tag).name
|
||||
typname = TYPES.get(typ, "unknown")
|
||||
|
@ -693,8 +688,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
|
||||
if len(data) != size:
|
||||
warnings.warn("Possibly corrupt EXIF data. "
|
||||
"Expecting to read %d bytes but only got %d. "
|
||||
"Skipping tag %s" % (size, len(data), tag))
|
||||
"Expecting to read %d bytes but only got %d."
|
||||
" Skipping tag %s" % (size, len(data), tag))
|
||||
continue
|
||||
|
||||
if not data:
|
||||
|
@ -753,7 +748,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
if len(data) <= 4:
|
||||
entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
|
||||
else:
|
||||
entries.append((tag, typ, count, self._pack("L", offset), data))
|
||||
entries.append((tag, typ, count, self._pack("L", offset),
|
||||
data))
|
||||
offset += (len(data) + 1) // 2 * 2 # pad to word
|
||||
|
||||
# update strip offset data to point beyond auxiliary data
|
||||
|
@ -782,6 +778,7 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
|||
|
||||
return offset
|
||||
|
||||
|
||||
ImageFileDirectory_v2._load_dispatch = _load_dispatch
|
||||
ImageFileDirectory_v2._write_dispatch = _write_dispatch
|
||||
for idx, name in TYPES.items():
|
||||
|
@ -800,7 +797,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
|||
ifd = ImageFileDirectory_v1()
|
||||
ifd[key] = 'Some Data'
|
||||
ifd.tagtype[key] = 2
|
||||
print ifd[key]
|
||||
print(ifd[key])
|
||||
('Some Data',)
|
||||
|
||||
Also contains a dictionary of tag types as read from the tiff image file,
|
||||
|
@ -889,6 +886,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
|
||||
format = "TIFF"
|
||||
format_description = "Adobe TIFF"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
"Open the first image in a TIFF file"
|
||||
|
@ -974,6 +972,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
self.__frame += 1
|
||||
self.fp.seek(self._frame_pos[frame])
|
||||
self.tag_v2.load(self.fp)
|
||||
self.__next = self.tag_v2.next
|
||||
# fill the legacy tag/ifd entries
|
||||
self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
|
||||
self.__frame = frame
|
||||
|
@ -1006,9 +1005,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
# Section 14: Differencing Predictor
|
||||
self.decoderconfig = (self.tag_v2[PREDICTOR],)
|
||||
|
||||
if ICCPROFILE in self.tag_v2:
|
||||
self.info['icc_profile'] = self.tag_v2[ICCPROFILE]
|
||||
|
||||
return args
|
||||
|
||||
def load(self):
|
||||
|
@ -1016,6 +1012,12 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
return self._load_libtiff()
|
||||
return super(TiffImageFile, self).load()
|
||||
|
||||
def load_end(self):
|
||||
# allow closing if we're on the first frame, there's no next
|
||||
# This is the ImageFile.load path only, libtiff specific below.
|
||||
if self.__frame == 0 and not self.__next:
|
||||
self._close_exclusive_fp_after_loading = True
|
||||
|
||||
def _load_libtiff(self):
|
||||
""" Overload method triggered when we detect a compressed tiff
|
||||
Calls out to libtiff """
|
||||
|
@ -1093,16 +1095,14 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
self.tile = []
|
||||
self.readonly = 0
|
||||
# libtiff closed the fp in a, we need to close self.fp, if possible
|
||||
if hasattr(self.fp, 'close'):
|
||||
if not self.__next:
|
||||
if self._exclusive_fp:
|
||||
if self.__frame == 0 and not self.__next:
|
||||
self.fp.close()
|
||||
self.fp = None # might be shared
|
||||
self.fp = None # might be shared
|
||||
|
||||
if err < 0:
|
||||
raise IOError(err)
|
||||
|
||||
self.load_end()
|
||||
|
||||
return Image.Image.load(self)
|
||||
|
||||
def _setup(self):
|
||||
|
@ -1138,7 +1138,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
|
||||
sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))
|
||||
if (len(sampleFormat) > 1
|
||||
and max(sampleFormat) == min(sampleFormat) == 1):
|
||||
and max(sampleFormat) == min(sampleFormat) == 1):
|
||||
# SAMPLEFORMAT is properly per band, so an RGB image will
|
||||
# be (1,1,1). But, we don't support per band pixel types,
|
||||
# and anything more than one band is a uint8. So, just
|
||||
|
@ -1171,11 +1171,16 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
yres = self.tag_v2.get(Y_RESOLUTION, 1)
|
||||
|
||||
if xres and yres:
|
||||
resunit = self.tag_v2.get(RESOLUTION_UNIT, 1)
|
||||
resunit = self.tag_v2.get(RESOLUTION_UNIT)
|
||||
if resunit == 2: # dots per inch
|
||||
self.info["dpi"] = xres, yres
|
||||
elif resunit == 3: # dots per centimeter. convert to dpi
|
||||
self.info["dpi"] = xres * 2.54, yres * 2.54
|
||||
elif resunit is None: # used to default to 1, but now 2)
|
||||
self.info["dpi"] = xres, yres
|
||||
# For backward compatibility,
|
||||
# we also preserve the old behavior
|
||||
self.info["resolution"] = xres, yres
|
||||
else: # No absolute unit of measurement
|
||||
self.info["resolution"] = xres, yres
|
||||
|
||||
|
@ -1197,7 +1202,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
"tiff_sgilog24",
|
||||
"tiff_raw_16"]:
|
||||
# if DEBUG:
|
||||
# print "Activating g4 compression for whole file"
|
||||
# print("Activating g4 compression for whole file")
|
||||
|
||||
# Decoder expects entire file as one tile.
|
||||
# There's a buffer size limit in load (64k)
|
||||
|
@ -1281,11 +1286,17 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
print("- unsupported data organization")
|
||||
raise SyntaxError("unknown data organization")
|
||||
|
||||
# Fix up info.
|
||||
if ICCPROFILE in self.tag_v2:
|
||||
self.info['icc_profile'] = self.tag_v2[ICCPROFILE]
|
||||
|
||||
# fixup palette descriptor
|
||||
|
||||
if self.mode == "P":
|
||||
palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
|
||||
self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Write TIFF files
|
||||
|
@ -1362,10 +1373,10 @@ def _save(im, fp, filename):
|
|||
ifd[key] = im.tag_v2[key]
|
||||
ifd.tagtype[key] = im.tag_v2.tagtype[key]
|
||||
|
||||
# preserve ICC profile (should also work when saving other formats
|
||||
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech
|
||||
if "icc_profile" in im.info:
|
||||
ifd[ICCPROFILE] = im.info["icc_profile"]
|
||||
# preserve ICC profile (should also work when saving other formats
|
||||
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech
|
||||
if "icc_profile" in im.info:
|
||||
ifd[ICCPROFILE] = im.info["icc_profile"]
|
||||
|
||||
for key, name in [(IMAGEDESCRIPTION, "description"),
|
||||
(X_RESOLUTION, "resolution"),
|
||||
|
@ -1377,11 +1388,6 @@ def _save(im, fp, filename):
|
|||
(DATE_TIME, "date_time"),
|
||||
(ARTIST, "artist"),
|
||||
(COPYRIGHT, "copyright")]:
|
||||
name_with_spaces = name.replace("_", " ")
|
||||
if "_" in name and name_with_spaces in im.encoderinfo:
|
||||
warnings.warn("%r is deprecated; use %r instead" %
|
||||
(name_with_spaces, name), DeprecationWarning)
|
||||
ifd[key] = im.encoderinfo[name.replace("_", " ")]
|
||||
if name in im.encoderinfo:
|
||||
ifd[key] = im.encoderinfo[name]
|
||||
|
||||
|
@ -1491,6 +1497,7 @@ def _save(im, fp, filename):
|
|||
# just to access o32 and o16 (using correct byte order)
|
||||
im._debug_multipage = ifd
|
||||
|
||||
|
||||
class AppendingTiffWriter:
|
||||
fieldSizes = [
|
||||
0, # None
|
||||
|
@ -1678,13 +1685,10 @@ class AppendingTiffWriter:
|
|||
|
||||
def fixIFD(self):
|
||||
numTags = self.readShort()
|
||||
#trace("fixing IFD at %X; number of tags: %u (0x%X)", self.f.tell()-2,
|
||||
# numTags, numTags)
|
||||
|
||||
for i in range(numTags):
|
||||
tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8))
|
||||
#trace(" at %X: tag %u (0x%X), type %u, count %u", self.f.tell()-8,
|
||||
# tag, tag, fieldType, count)
|
||||
tag, fieldType, count = struct.unpack(self.tagFormat,
|
||||
self.f.read(8))
|
||||
|
||||
fieldSize = self.fieldSizes[fieldType]
|
||||
totalSize = fieldSize * count
|
||||
|
@ -1736,21 +1740,34 @@ class AppendingTiffWriter:
|
|||
else:
|
||||
self.rewriteLastLong(offset)
|
||||
|
||||
|
||||
def _save_all(im, fp, filename):
|
||||
if not hasattr(im, "n_frames"):
|
||||
encoderinfo = im.encoderinfo.copy()
|
||||
encoderconfig = im.encoderconfig
|
||||
append_images = encoderinfo.get("append_images", [])
|
||||
if not hasattr(im, "n_frames") and not len(append_images):
|
||||
return _save(im, fp, filename)
|
||||
|
||||
cur_idx = im.tell()
|
||||
try:
|
||||
with AppendingTiffWriter(fp) as tf:
|
||||
for idx in range(im.n_frames):
|
||||
im.seek(idx)
|
||||
im.load()
|
||||
_save(im, tf, filename)
|
||||
tf.newFrame()
|
||||
for ims in [im]+append_images:
|
||||
ims.encoderinfo = encoderinfo
|
||||
ims.encoderconfig = encoderconfig
|
||||
if not hasattr(ims, "n_frames"):
|
||||
nfr = 1
|
||||
else:
|
||||
nfr = ims.n_frames
|
||||
|
||||
for idx in range(nfr):
|
||||
ims.seek(idx)
|
||||
ims.load()
|
||||
_save(ims, tf, filename)
|
||||
tf.newFrame()
|
||||
finally:
|
||||
im.seek(cur_idx)
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Register
|
||||
|
|
|
@ -18,12 +18,11 @@
|
|||
# the WalImageFile.open() function instead.
|
||||
|
||||
# This reader is based on the specification available from:
|
||||
# http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
|
||||
# https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
|
||||
# and has been tested with a few sample files found using google.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image, _binary
|
||||
from . import Image
|
||||
from ._binary import i32le as i32
|
||||
|
||||
try:
|
||||
import builtins
|
||||
|
@ -31,8 +30,6 @@ except ImportError:
|
|||
import __builtin__
|
||||
builtins = __builtin__
|
||||
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
def open(filename):
|
||||
"""
|
||||
|
@ -47,33 +44,35 @@ def open(filename):
|
|||
# FIXME: modify to return a WalImageFile instance instead of
|
||||
# plain Image object ?
|
||||
|
||||
def imopen(fp):
|
||||
# read header fields
|
||||
header = fp.read(32+24+32+12)
|
||||
size = i32(header, 32), i32(header, 36)
|
||||
offset = i32(header, 40)
|
||||
|
||||
# load pixel data
|
||||
fp.seek(offset)
|
||||
|
||||
Image._decompression_bomb_check(size)
|
||||
im = Image.frombytes("P", size, fp.read(size[0] * size[1]))
|
||||
im.putpalette(quake2palette)
|
||||
|
||||
im.format = "WAL"
|
||||
im.format_description = "Quake2 Texture"
|
||||
|
||||
# strings are null-terminated
|
||||
im.info["name"] = header[:32].split(b"\0", 1)[0]
|
||||
next_name = header[56:56+32].split(b"\0", 1)[0]
|
||||
if next_name:
|
||||
im.info["next_name"] = next_name
|
||||
|
||||
return im
|
||||
|
||||
if hasattr(filename, "read"):
|
||||
fp = filename
|
||||
return imopen(filename)
|
||||
else:
|
||||
fp = builtins.open(filename, "rb")
|
||||
|
||||
# read header fields
|
||||
header = fp.read(32+24+32+12)
|
||||
size = i32(header, 32), i32(header, 36)
|
||||
offset = i32(header, 40)
|
||||
|
||||
# load pixel data
|
||||
fp.seek(offset)
|
||||
|
||||
im = Image.frombytes("P", size, fp.read(size[0] * size[1]))
|
||||
im.putpalette(quake2palette)
|
||||
|
||||
im.format = "WAL"
|
||||
im.format_description = "Quake2 Texture"
|
||||
|
||||
# strings are null-terminated
|
||||
im.info["name"] = header[:32].split(b"\0", 1)[0]
|
||||
next_name = header[56:56+32].split(b"\0", 1)[0]
|
||||
if next_name:
|
||||
im.info["next_name"] = next_name
|
||||
|
||||
return im
|
||||
|
||||
with builtins.open(filename, "rb") as fp:
|
||||
return imopen(fp)
|
||||
|
||||
quake2palette = (
|
||||
# default palette taken from piffo 0.93 by Hans Häggström
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
from PIL import Image
|
||||
from PIL import ImageFile
|
||||
from . import Image, ImageFile, _webp
|
||||
from io import BytesIO
|
||||
from PIL import _webp
|
||||
|
||||
|
||||
_VALID_WEBP_MODES = {
|
||||
|
@ -43,7 +41,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
|||
self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
|
||||
|
||||
def _getexif(self):
|
||||
from PIL.JpegImagePlugin import _getexif
|
||||
from .JpegImagePlugin import _getexif
|
||||
return _getexif(self)
|
||||
|
||||
|
||||
|
|
|
@ -14,8 +14,16 @@
|
|||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
# WMF/EMF reference documentation:
|
||||
# https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-WMF/[MS-WMF].pdf
|
||||
# http://wvware.sourceforge.net/caolan/index.html
|
||||
# http://wvware.sourceforge.net/caolan/ora-wmf.html
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i16le as word, si16le as short, i32le as dword, si32le as _long
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
@ -53,24 +61,11 @@ if hasattr(Image.core, "drawwmf"):
|
|||
|
||||
register_handler(WmfHandler())
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
word = _binary.i16le
|
||||
|
||||
|
||||
def short(c, o=0):
|
||||
v = word(c, o)
|
||||
if v >= 32768:
|
||||
v -= 65536
|
||||
return v
|
||||
|
||||
dword = _binary.i32le
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Read WMF file
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return (
|
||||
prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or
|
||||
|
@ -111,7 +106,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
|||
|
||||
self.info["dpi"] = 72
|
||||
|
||||
# print self.mode, self.size, self.info
|
||||
# print(self.mode, self.size, self.info)
|
||||
|
||||
# sanity check (standard metafile header)
|
||||
if s[22:26] != b"\x01\x00\t\x00":
|
||||
|
@ -121,13 +116,13 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
|||
# enhanced metafile
|
||||
|
||||
# get bounding box
|
||||
x0 = dword(s, 8)
|
||||
y0 = dword(s, 12)
|
||||
x1 = dword(s, 16)
|
||||
y1 = dword(s, 20)
|
||||
x0 = _long(s, 8)
|
||||
y0 = _long(s, 12)
|
||||
x1 = _long(s, 16)
|
||||
y1 = _long(s, 20)
|
||||
|
||||
# get frame (in 0.01 millimeter units)
|
||||
frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36)
|
||||
frame = _long(s, 24), _long(s, 28), _long(s, 32), _long(s, 36)
|
||||
|
||||
# normalize size to 72 dots per inch
|
||||
size = x1 - x0, y1 - y0
|
||||
|
|
|
@ -17,12 +17,11 @@
|
|||
# FIXME: make save work (this requires quantization support)
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, o8
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
o8 = _binary.o8
|
||||
|
||||
_MAGIC = b"P7 332"
|
||||
|
||||
# standard color palette for thumbnails (RGB332)
|
||||
|
@ -48,7 +47,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
|
|||
def _open(self):
|
||||
|
||||
# check magic
|
||||
if self.fp.read(6) != _MAGIC:
|
||||
if not _accept(self.fp.read(6)):
|
||||
raise SyntaxError("not an XV thumbnail file")
|
||||
|
||||
# Skip to beginning of next line
|
||||
|
@ -59,14 +58,14 @@ class XVThumbImageFile(ImageFile.ImageFile):
|
|||
s = self.fp.readline()
|
||||
if not s:
|
||||
raise SyntaxError("Unexpected EOF reading XV thumbnail file")
|
||||
if s[0] != b'#':
|
||||
if i8(s[0]) != 35: # ie. when not a comment: '#'
|
||||
break
|
||||
|
||||
# parse header line (already read)
|
||||
s = s.strip().split()
|
||||
|
||||
self.mode = "P"
|
||||
self.size = int(s[0:1]), int(s[1:2])
|
||||
self.size = int(s[0]), int(s[1])
|
||||
|
||||
self.palette = ImagePalette.raw("RGB", PALETTE)
|
||||
|
||||
|
@ -75,6 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
|
|||
self.fp.tell(), (self.mode, 0, 1)
|
||||
)]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open(XVThumbImageFile.format, XVThumbImageFile, _accept)
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#
|
||||
|
||||
import re
|
||||
from PIL import Image, ImageFile
|
||||
from . import Image, ImageFile
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
|
||||
import re
|
||||
from PIL import Image, ImageFile, ImagePalette
|
||||
from PIL._binary import i8, o8
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, o8
|
||||
|
||||
__version__ = "0.2"
|
||||
|
||||
|
@ -116,8 +116,6 @@ class XpmImageFile(ImageFile.ImageFile):
|
|||
for i in range(ysize):
|
||||
s[i] = self.fp.readline()[1:xsize+1].ljust(xsize)
|
||||
|
||||
self.fp = None
|
||||
|
||||
return b"".join(s)
|
||||
|
||||
#
|
||||
|
|
|
@ -11,8 +11,10 @@
|
|||
|
||||
# ;-)
|
||||
|
||||
VERSION = '1.1.7' # PIL version
|
||||
PILLOW_VERSION = '3.5.0.dev0' # Pillow
|
||||
from . import version
|
||||
|
||||
VERSION = '1.1.7' # PIL Version
|
||||
PILLOW_VERSION = version.__version__
|
||||
|
||||
__version__ = PILLOW_VERSION
|
||||
|
||||
|
|
|
@ -28,10 +28,9 @@ else:
|
|||
|
||||
|
||||
# Input, le = little endian, be = big endian
|
||||
# TODO: replace with more readable struct.unpack equivalent
|
||||
def i16le(c, o=0):
|
||||
"""
|
||||
Converts a 2-bytes (16 bits) string to an integer.
|
||||
Converts a 2-bytes (16 bits) string to an unsigned integer.
|
||||
|
||||
c: string containing bytes to convert
|
||||
o: offset of bytes to convert in string
|
||||
|
@ -39,9 +38,19 @@ def i16le(c, o=0):
|
|||
return unpack("<H", c[o:o+2])[0]
|
||||
|
||||
|
||||
def si16le(c, o=0):
|
||||
"""
|
||||
Converts a 2-bytes (16 bits) string to a signed integer.
|
||||
|
||||
c: string containing bytes to convert
|
||||
o: offset of bytes to convert in string
|
||||
"""
|
||||
return unpack("<h", c[o:o+2])[0]
|
||||
|
||||
|
||||
def i32le(c, o=0):
|
||||
"""
|
||||
Converts a 4-bytes (32 bits) string to an integer.
|
||||
Converts a 4-bytes (32 bits) string to an unsigned integer.
|
||||
|
||||
c: string containing bytes to convert
|
||||
o: offset of bytes to convert in string
|
||||
|
@ -49,6 +58,16 @@ def i32le(c, o=0):
|
|||
return unpack("<I", c[o:o+4])[0]
|
||||
|
||||
|
||||
def si32le(c, o=0):
|
||||
"""
|
||||
Converts a 4-bytes (32 bits) string to a signed integer.
|
||||
|
||||
c: string containing bytes to convert
|
||||
o: offset of bytes to convert in string
|
||||
"""
|
||||
return unpack("<i", c[o:o+4])[0]
|
||||
|
||||
|
||||
def i16be(c, o=0):
|
||||
return unpack(">H", c[o:o+2])[0]
|
||||
|
||||
|
|
|
@ -1,46 +1,27 @@
|
|||
from PIL import Image
|
||||
from . import Image
|
||||
|
||||
modules = {
|
||||
"pil": "PIL._imaging",
|
||||
"tkinter": "PIL._imagingtk",
|
||||
"tkinter": "PIL._tkinter_finder",
|
||||
"freetype2": "PIL._imagingft",
|
||||
"littlecms2": "PIL._imagingcms",
|
||||
"webp": "PIL._webp",
|
||||
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
|
||||
}
|
||||
|
||||
|
||||
def check_module(feature):
|
||||
if feature not in modules:
|
||||
if not (feature in modules):
|
||||
raise ValueError("Unknown module %s" % feature)
|
||||
|
||||
module = modules[feature]
|
||||
|
||||
method_to_call = None
|
||||
if isinstance(module, 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
|
||||
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def get_supported_modules():
|
||||
supported_modules = []
|
||||
for feature in modules:
|
||||
if check_module(feature):
|
||||
supported_modules.append(feature)
|
||||
return supported_modules
|
||||
return [f for f in modules if check_module(f)]
|
||||
|
||||
codecs = {
|
||||
"jpg": "jpeg",
|
||||
|
@ -49,7 +30,6 @@ codecs = {
|
|||
"libtiff": "libtiff"
|
||||
}
|
||||
|
||||
|
||||
def check_codec(feature):
|
||||
if feature not in codecs:
|
||||
raise ValueError("Unknown codec %s" % feature)
|
||||
|
@ -60,8 +40,39 @@ def check_codec(feature):
|
|||
|
||||
|
||||
def get_supported_codecs():
|
||||
supported_codecs = []
|
||||
for feature in codecs:
|
||||
if check_codec(feature):
|
||||
supported_codecs.append(feature)
|
||||
return supported_codecs
|
||||
return [f for f in codecs if check_codec(f)]
|
||||
|
||||
features = {
|
||||
"webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'),
|
||||
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"),
|
||||
"raqm": ("PIL._imagingft", "HAVE_RAQM")
|
||||
}
|
||||
|
||||
def check_feature(feature):
|
||||
if feature not in features:
|
||||
raise ValueError("Unknown feature %s" % feature)
|
||||
|
||||
module, flag = features[feature]
|
||||
|
||||
try:
|
||||
imported_module = __import__(module, fromlist=['PIL'])
|
||||
return getattr(imported_module, flag)
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
|
||||
def get_supported_features():
|
||||
return [f for f in features if check_feature(f)]
|
||||
|
||||
|
||||
def check(feature):
|
||||
return (feature in modules and check_module(feature) or \
|
||||
feature in codecs and check_codec(feature) or \
|
||||
feature in features and check_feature(feature))
|
||||
|
||||
def get_supported():
|
||||
ret = get_supported_modules()
|
||||
ret.extend(get_supported_features())
|
||||
ret.extend(get_supported_codecs())
|
||||
return ret
|
||||
|
||||
|
|
2
PIL/version.py
Normal file
2
PIL/version.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Master version for Pillow
|
||||
__version__ = '4.3.0.dev0'
|
12
README.rst
12
README.rst
|
@ -14,9 +14,9 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
|
|||
* - docs
|
||||
- |docs|
|
||||
* - tests
|
||||
- | |linux| |macos| |windows| |coverage| |health|
|
||||
- | |linux| |macos| |windows| |coverage|
|
||||
* - package
|
||||
- |zenodo| |version| |downloads|
|
||||
- |zenodo| |version|
|
||||
|
||||
.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest
|
||||
:target: https://pillow.readthedocs.io/?badge=latest
|
||||
|
@ -38,10 +38,6 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
|
|||
:target: https://coveralls.io/github/python-pillow/Pillow?branch=master
|
||||
:alt: Code coverage
|
||||
|
||||
.. |health| image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.svg
|
||||
:target: https://landscape.io/github/python-pillow/Pillow/master
|
||||
:alt: Code health
|
||||
|
||||
.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
|
||||
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
|
||||
|
||||
|
@ -49,10 +45,6 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
|
|||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
.. |downloads| image:: https://img.shields.io/pypi/dm/pillow.svg
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Number of PyPI downloads
|
||||
|
||||
.. end-badges
|
||||
|
||||
|
||||
|
|
45
RELEASING.md
45
RELEASING.md
|
@ -7,10 +7,8 @@ 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) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
|
||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
|
||||
```
|
||||
PIL/__init__.py setup.py _imaging.c appveyor.yml
|
||||
```
|
||||
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in TravisCI.
|
||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `PIL/version.py`
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||
* [ ] Create branch and tag for release e.g.:
|
||||
|
@ -20,12 +18,12 @@ Released quarterly on the first day of January, April, July, October.
|
|||
$ git push --all
|
||||
$ git push --tags
|
||||
```
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
* [ ] Create source distributions e.g.:
|
||||
```
|
||||
$ make sdist
|
||||
$ make upload
|
||||
```
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
* [ ] Create binary distributions [binary distributions](#binary-distributions)
|
||||
* [ ] Upload all binaries and source distributions with ``twine upload dist/Pillow-4.1.0-*``
|
||||
* [ ] Manually hide old versions on PyPI such that only the latest major release is visible when viewing https://pypi.python.org/pypi/Pillow (https://pypi.python.org/pypi?:action=pkg_edit&name=Pillow)
|
||||
|
||||
## Point Release
|
||||
|
@ -40,24 +38,18 @@ Released as needed for security, installation or critical bug fixes.
|
|||
```
|
||||
git checkout -t remotes/origin/2.9.x
|
||||
```
|
||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
|
||||
```
|
||||
PIL/__init__.py
|
||||
setup.py
|
||||
_imaging.c
|
||||
appveyor.yml
|
||||
```
|
||||
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `PIL/version.py`
|
||||
* [ ] Run pre-release check via `make release-test`.
|
||||
* [ ] Create tag for release e.g.:
|
||||
```
|
||||
$ git tag 2.9.1
|
||||
$ git push --tags
|
||||
```
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
* [ ] Create source distributions e.g.:
|
||||
```
|
||||
$ make sdistup
|
||||
$ make sdist
|
||||
```
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
* [ ] Create [binary distributions](#binary-distributions)
|
||||
|
||||
## Embargoed Release
|
||||
|
||||
|
@ -76,11 +68,11 @@ Released as needed privately to individual vendors for critical security-related
|
|||
git push origin 2.5.x
|
||||
git push origin --tags
|
||||
```
|
||||
* [ ] Create and upload source distributions e.g.:
|
||||
* [ ] Create source distributions e.g.:
|
||||
```
|
||||
$ make sdistup
|
||||
$ make sdist
|
||||
```
|
||||
* [ ] Create and upload [binary distributions](#binary-distributions)
|
||||
* [ ] Create [binary distributions](#binary-distributions)
|
||||
|
||||
## Binary Distributions
|
||||
|
||||
|
@ -88,8 +80,8 @@ Released as needed privately to individual vendors for critical security-related
|
|||
* [ ] 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 *``.
|
||||
|
||||
### macOS
|
||||
* [ ] Use the [Pillow macOS Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
||||
### Mac and Linux
|
||||
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
||||
```
|
||||
$ git checkout https://github.com/python-pillow/pillow-wheels
|
||||
$ cd pillow-wheels
|
||||
|
@ -97,12 +89,13 @@ Released as needed privately to individual vendors for critical security-related
|
|||
$ git submodule update
|
||||
$ cd Pillow
|
||||
$ git fetch --all
|
||||
$ git commit -a -m "Pillow -> 2.9.0"
|
||||
$ git push
|
||||
$ git checkout [[release tag]]
|
||||
$ cd ..
|
||||
$ git commit -m "Pillow -> 2.9.0" Pillow
|
||||
$ git push
|
||||
```
|
||||
* [ ] Download distributions from the [Pillow macOS Wheel Builder container](http://cdf58691c5cf45771290-6a3b6a0f5f6ab91aadc447b2a897dd9a.r50.cf2.rackcdn.com/) and ``twine upload *``.
|
||||
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
|
||||
|
||||
### Linux
|
||||
|
||||
## Publicize Release
|
||||
|
||||
|
|
0
Scripts/createfontdatachunk.py
Normal file → Executable file
0
Scripts/createfontdatachunk.py
Normal file → Executable file
27
Scripts/enhancer.py
Normal file → Executable file
27
Scripts/enhancer.py
Normal file → Executable file
|
@ -7,21 +7,22 @@
|
|||
# drag the slider to modify the image.
|
||||
#
|
||||
|
||||
try:
|
||||
from tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
|
||||
except ImportError:
|
||||
from Tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
import tkinter
|
||||
else:
|
||||
import Tkinter as tkinter
|
||||
|
||||
from PIL import Image, ImageTk, ImageEnhance
|
||||
import sys
|
||||
|
||||
#
|
||||
# enhancer widget
|
||||
|
||||
|
||||
class Enhance(Frame):
|
||||
class Enhance(tkinter.Frame):
|
||||
def __init__(self, master, image, name, enhancer, lo, hi):
|
||||
Frame.__init__(self, master)
|
||||
tkinter.Frame.__init__(self, master)
|
||||
|
||||
# set up the image
|
||||
self.tkim = ImageTk.PhotoImage(image.mode, image.size)
|
||||
|
@ -29,10 +30,10 @@ class Enhance(Frame):
|
|||
self.update("1.0") # normalize
|
||||
|
||||
# image window
|
||||
Label(self, image=self.tkim).pack()
|
||||
tkinter.Label(self, image=self.tkim).pack()
|
||||
|
||||
# scale
|
||||
s = Scale(self, label=name, orient=HORIZONTAL,
|
||||
s = tkinter.Scale(self, label=name, orient=tkinter.HORIZONTAL,
|
||||
from_=lo, to=hi, resolution=0.01,
|
||||
command=self.update)
|
||||
s.set(self.value)
|
||||
|
@ -49,15 +50,15 @@ if len(sys.argv) != 2:
|
|||
print("Usage: enhancer file")
|
||||
sys.exit(1)
|
||||
|
||||
root = Tk()
|
||||
root = tkinter.Tk()
|
||||
|
||||
im = Image.open(sys.argv[1])
|
||||
|
||||
im.thumbnail((200, 200))
|
||||
|
||||
Enhance(root, im, "Color", ImageEnhance.Color, 0.0, 4.0).pack()
|
||||
Enhance(Toplevel(), im, "Sharpness", ImageEnhance.Sharpness, -2.0, 2.0).pack()
|
||||
Enhance(Toplevel(), im, "Brightness", ImageEnhance.Brightness, -1.0, 3.0).pack()
|
||||
Enhance(Toplevel(), im, "Contrast", ImageEnhance.Contrast, -1.0, 3.0).pack()
|
||||
Enhance(tkinter.Toplevel(), im, "Sharpness", ImageEnhance.Sharpness, -2.0, 2.0).pack()
|
||||
Enhance(tkinter.Toplevel(), im, "Brightness", ImageEnhance.Brightness, -1.0, 3.0).pack()
|
||||
Enhance(tkinter.Toplevel(), im, "Contrast", ImageEnhance.Contrast, -1.0, 3.0).pack()
|
||||
|
||||
root.mainloop()
|
||||
|
|
0
Scripts/explode.py
Normal file → Executable file
0
Scripts/explode.py
Normal file → Executable file
0
Scripts/gifmaker.py
Normal file → Executable file
0
Scripts/gifmaker.py
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user