Merge branch 'master' into resample-roi

This commit is contained in:
Alexander Karpinsky 2017-08-11 19:10:11 +03:00 committed by GitHub
commit 09a2e1641b
324 changed files with 7609 additions and 6449 deletions

1
.gitattributes vendored
View File

@ -1 +1,2 @@
*.ppm binary *.ppm binary
*.container binary

View File

@ -6,7 +6,9 @@
### What versions of Pillow and Python are you using? ### 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 ```python
code goes here code goes here

3
.gitignore vendored
View File

@ -34,6 +34,9 @@ htmlcov/
nosetests.xml nosetests.xml
coverage.xml coverage.xml
# Test files
test_images
# Translations # Translations
*.mo *.mo

View File

@ -5,46 +5,40 @@ notifications:
# Run slow PyPy* first, to give them a headstart and reduce waiting time. # 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. # Run latest 3.x and 2.x next, to get quick compatibility results.
# Then run the remainder. # Then run the remainder, with fastest Docker jobs last.
python:
- "pypy" matrix:
- "pypy3" fast_finish: true
- 3.5 include:
- 2.7 - python: "pypy-5.7.1"
- "2.7_with_system_site_packages" # For PyQt4 - python: "pypy3.3-5.2-alpha1"
- 3.3 - python: '3.6'
- 3.4 - python: '2.7'
- nightly - 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: install:
- "travis_retry sudo apt-get update" - if [ "$DOCKER" == "" ]; then .travis/install.sh; fi
- "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
- "travis_retry pip install coverage" before_install:
- if [ "$DOCKER" ]; then docker pull pythonpillow/$DOCKER; fi
# 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_script: before_script:
# Qt needs a display for some of the tests, and it's only run on the system site packages install # 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" - "sh -e /etc/init.d/xvfb start"
script: script:
- coverage erase - |
- python setup.py clean if [ "$DOCKER" == "" ]; then
- CFLAGS="-coverage" python setup.py build_ext --inplace .travis/script.sh
else
- coverage run --append --include=PIL/* selftest.py # the Pillow user in the docker container is UID 1000
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py sudo chown -R 1000 $TRAVIS_BUILD_DIR
- pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd docker run -v $TRAVIS_BUILD_DIR:/Pillow pythonpillow/$DOCKER
fi
# Docs
- if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make install && make doccheck; fi
after_success: after_success:
# gather the coverage data - .travis/after_success.sh
- 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
after_failure: after_failure:
- | - |
@ -127,11 +79,6 @@ after_script:
echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS
fi fi
matrix:
fast_finish: true
allow_failures:
- python: nightly
env: env:
global: global:
# travis encrypt AUTH_TOKEN= # travis encrypt AUTH_TOKEN=

49
.travis/after_success.sh Executable file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,9 @@ The Python Imaging Library (PIL) is
Pillow is the friendly PIL fork. It 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: 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:

View File

@ -23,6 +23,8 @@ prune docs/_static
exclude .coveragerc exclude .coveragerc
exclude .editorconfig exclude .editorconfig
exclude .landscape.yaml exclude .landscape.yaml
exclude .travis
exclude .travis/*
exclude appveyor.yml exclude appveyor.yml
exclude build_children.sh exclude build_children.sh
exclude tox.ini exclude tox.ini

View File

@ -58,6 +58,13 @@ install:
python setup.py install python setup.py install
python selftest.py --installed 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: install-req:
pip install -r requirements.txt pip install -r requirements.txt
@ -77,7 +84,7 @@ release-test:
viewdoc viewdoc
sdist: sdist:
python setup.py sdist --format=gztar,zip python setup.py sdist --format=gztar
test: test:
python test-installed.py python test-installed.py
@ -88,10 +95,10 @@ upload-test:
# username: # username:
# password: # password:
# repository = http://test.pythonpackages.com # 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: upload:
python setup.py sdist --format=gztar,zip upload python setup.py sdist --format=gztar upload
readme: readme:
viewdoc viewdoc

View File

@ -17,8 +17,9 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from __future__ import print_function
from PIL import FontFile
from . import Image, FontFile
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -119,9 +120,9 @@ class BdfFontFile(FontFile.FontFile):
# fontname = ";".join(font[1:]) # fontname = ";".join(font[1:])
# print "#", fontname # print("#", fontname)
# for i in comments: # for i in comments:
# print "#", i # print("#", i)
while True: while True:
c = bdf_char(fp) c = bdf_char(fp)

View File

@ -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 import math
__version__ = "0.7" __version__ = "0.7"
i8 = _binary.i8
i16 = _binary.i16le
i32 = _binary.i32le
o8 = _binary.o8
o16 = _binary.o16le
o32 = _binary.o32le
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Read BMP file # Read BMP file
@ -136,7 +131,7 @@ class BmpImageFile(ImageFile.ImageFile):
# ----------------- Process BMP with Bitfields compression (not palette) # ----------------- Process BMP with Bitfields compression (not palette)
if file_info['compression'] == self.BITFIELDS: if file_info['compression'] == self.BITFIELDS:
SUPPORTED = { 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)], 24: [(0xff0000, 0xff00, 0xff)],
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)] 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
} }

View File

@ -9,7 +9,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFile from . import Image, ImageFile
_handler = None _handler = None
@ -40,7 +40,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
offset = self.fp.tell() offset = self.fp.tell()
if not _accept(self.fp.read(8)): if not _accept(self.fp.read(4)):
raise SyntaxError("Not a BUFR file") raise SyntaxError("Not a BUFR file")
self.fp.seek(offset) self.fp.seek(offset)

View File

@ -16,18 +16,16 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from __future__ import print_function
from PIL import Image, BmpImagePlugin, _binary from . import Image, BmpImagePlugin
from ._binary import i8, i16le as i16, i32le as i32
__version__ = "0.1" __version__ = "0.1"
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
i8 = _binary.i8
i16 = _binary.i16le
i32 = _binary.i32le
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"\0\0\2\0" return prefix[:4] == b"\0\0\2\0"
@ -58,14 +56,14 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
m = s m = s
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]): elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
m = s m = s
# print "width", i8(s[0]) # print("width", i8(s[0]))
# print "height", i8(s[1]) # print("height", i8(s[1]))
# print "colors", i8(s[2]) # print("colors", i8(s[2]))
# print "reserved", i8(s[3]) # print("reserved", i8(s[3]))
# print "hotspot x", i16(s[4:]) # print("hotspot x", i16(s[4:]))
# print "hotspot y", i16(s[6:]) # print("hotspot y", i16(s[6:]))
# print "bytes", i32(s[8:]) # print("bytes", i32(s[8:]))
# print "offset", i32(s[12:]) # print("offset", i32(s[12:]))
if not m: if not m:
raise TypeError("No cursors were found") raise TypeError("No cursors were found")

View File

@ -21,15 +21,14 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, _binary from . import Image
from PIL.PcxImagePlugin import PcxImageFile from ._binary import i32le as i32
from .PcxImagePlugin import PcxImageFile
__version__ = "0.2" __version__ = "0.2"
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then? MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
i32 = _binary.i32le
def _accept(prefix): def _accept(prefix):
return len(prefix) >= 4 and i32(prefix) == MAGIC return len(prefix) >= 4 and i32(prefix) == MAGIC
@ -42,7 +41,8 @@ class DcxImageFile(PcxImageFile):
format = "DCX" format = "DCX"
format_description = "Intel DCX" format_description = "Intel DCX"
_close_exclusive_fp_after_loading = False
def _open(self): def _open(self):
# Header # Header

View File

@ -12,7 +12,7 @@ Full text of the CC0 license:
import struct import struct
from io import BytesIO from io import BytesIO
from PIL import Image, ImageFile from . import Image, ImageFile
# Magic ("DDS ") # Magic ("DDS ")

View File

@ -22,17 +22,16 @@
import re import re
import io import io
import os
import sys import sys
from PIL import Image, ImageFile, _binary from . import Image, ImageFile
from ._binary import i32le as i32
__version__ = "0.5" __version__ = "0.5"
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
i32 = _binary.i32le
o32 = _binary.o32le
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$") split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
@ -59,8 +58,8 @@ def has_ghostscript():
if not sys.platform.startswith('win'): if not sys.platform.startswith('win'):
import subprocess import subprocess
try: try:
gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE) with open(os.devnull, 'wb') as devnull:
gs.stdout.read() subprocess.check_call(['gs', '--version'], stdout=devnull)
return True return True
except OSError: except OSError:
# no ghostscript # no ghostscript
@ -85,7 +84,6 @@ def Ghostscript(tile, size, fp, scale=1):
float((72.0 * size[1]) / (bbox[3]-bbox[1]))) float((72.0 * size[1]) / (bbox[3]-bbox[1])))
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res) # print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
import os
import subprocess import subprocess
import tempfile import tempfile
@ -123,6 +121,7 @@ def Ghostscript(tile, size, fp, scale=1):
"-q", # quiet mode "-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels) "-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch) "-r%fx%f" % res, # set input DPI (dots per inch)
"-dBATCH", # exit after processing
"-dNOPAUSE", # don't pause between pages, "-dNOPAUSE", # don't pause between pages,
"-dSAFER", # safe mode "-dSAFER", # safe mode
"-sDEVICE=ppmraw", # ppm driver "-sDEVICE=ppmraw", # ppm driver
@ -139,12 +138,8 @@ def Ghostscript(tile, size, fp, scale=1):
# push data through ghostscript # push data through ghostscript
try: try:
gs = subprocess.Popen(command, stdin=subprocess.PIPE, with open(os.devnull, 'w+b') as devnull:
stdout=subprocess.PIPE) subprocess.check_call(command, stdin=devnull, stdout=devnull)
gs.stdin.close()
status = gs.wait()
if status:
raise IOError("gs failed (status %d)" % status)
im = Image.open(outfile) im = Image.open(outfile)
im.load() im.load()
finally: finally:
@ -323,7 +318,7 @@ class EpsImageFile(ImageFile.ImageFile):
# EPS can contain binary data # EPS can contain binary data
# or start directly with latin coding # or start directly with latin coding
# more info see: # 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]) offset = i32(s[4:8])
length = i32(s[8:12]) length = i32(s[8:12])
else: else:

View File

@ -9,7 +9,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFile from . import Image, ImageFile
_handler = None _handler = None

View File

@ -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" __version__ = "0.2"
i8 = _binary.i8
i16 = _binary.i16le
i32 = _binary.i32le
o8 = _binary.o8
# #
# decoder # decoder
@ -41,7 +37,8 @@ class FliImageFile(ImageFile.ImageFile):
format = "FLI" format = "FLI"
format_description = "Autodesk FLI/FLC Animation" format_description = "Autodesk FLI/FLC Animation"
_close_exclusive_fp_after_loading = False
def _open(self): def _open(self):
# HEAD # HEAD

View File

@ -14,8 +14,10 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from __future__ import print_function
import os import os
from PIL import Image, _binary from . import Image, _binary
WIDTH = 800 WIDTH = 800
@ -88,7 +90,7 @@ class FontFile(object):
x = xx x = xx
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0 s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
self.bitmap.paste(im.crop(src), s) self.bitmap.paste(im.crop(src), s)
# print chr(i), dst, s # print(chr(i), dst, s)
self.metrics[i] = d, dst, s self.metrics[i] = d, dst, s
def save(self, filename): def save(self, filename):

View File

@ -15,13 +15,15 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from __future__ import print_function
from PIL import Image, ImageFile from . import Image, ImageFile
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO from ._binary import i32le as i32, i8
import olefile
__version__ = "0.1" __version__ = "0.1"
# we map from colour field tuples to (mode, rawmode) descriptors # we map from colour field tuples to (mode, rawmode) descriptors
MODES = { MODES = {
# opacity # opacity
@ -42,7 +44,7 @@ MODES = {
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _accept(prefix): 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 # to be a FlashPix file
try: try:
self.ole = OleFileIO(self.fp) self.ole = olefile.OleFileIO(self.fp)
except IOError: except IOError:
raise SyntaxError("not an FPX file; invalid OLE file") raise SyntaxError("not an FPX file; invalid OLE file")
@ -112,7 +114,7 @@ class FpxImageFile(ImageFile.ImageFile):
if id in prop: if id in prop:
self.jpeg[i] = prop[id] self.jpeg[i] = prop[id]
# print len(self.jpeg), "tables loaded" # print(len(self.jpeg), "tables loaded")
self._open_subimage(1, self.maxid) self._open_subimage(1, self.maxid)
@ -141,7 +143,7 @@ class FpxImageFile(ImageFile.ImageFile):
offset = i32(s, 28) offset = i32(s, 28)
length = i32(s, 32) length = i32(s, 32)
# print size, self.mode, self.rawmode # print(size, self.mode, self.rawmode)
if size != self.size: if size != self.size:
raise IOError("subimage mismatch") raise IOError("subimage mismatch")

View File

@ -42,7 +42,7 @@ Note: All data is stored in little-Endian (Intel) byte order.
import struct import struct
from io import BytesIO from io import BytesIO
from PIL import Image, ImageFile from . import Image, ImageFile
MAGIC = b"FTEX" MAGIC = b"FTEX"

View File

@ -24,9 +24,8 @@
# Version 3 files have a format specifier of 18 for 16bit floats in # Version 3 files have a format specifier of 18 for 16bit floats in
# the color depth field. This is currently unsupported by Pillow. # the color depth field. This is currently unsupported by Pillow.
from PIL import Image, ImageFile, _binary from . import Image, ImageFile
from ._binary import i32be as i32
i32 = _binary.i32be
def _accept(prefix): def _accept(prefix):

View File

@ -23,8 +23,9 @@
# purposes only. # purposes only.
from PIL import ImageFile, ImagePalette, _binary from . import ImageFile, ImagePalette
from PIL._util import isPath from ._binary import i16be as i16
from ._util import isPath
__version__ = "0.1" __version__ = "0.1"
@ -34,8 +35,6 @@ except ImportError:
import __builtin__ import __builtin__
builtins = __builtin__ builtins = __builtin__
i16 = _binary.i16be
## ##
# Image plugin for the GD uncompressed format. Note that this format # Image plugin for the GD uncompressed format. Note that this format

View File

@ -24,21 +24,14 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFile, ImagePalette, \ from . import Image, ImageFile, ImagePalette, ImageChops, ImageSequence
ImageChops, ImageSequence, _binary from ._binary import i8, i16le as i16, o8, o16le as o16
import itertools
__version__ = "0.9" __version__ = "0.9"
# --------------------------------------------------------------------
# Helpers
i8 = _binary.i8
i16 = _binary.i16le
o8 = _binary.o8
o16 = _binary.o16le
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Identify/read GIF files # Identify/read GIF files
@ -54,6 +47,8 @@ class GifImageFile(ImageFile.ImageFile):
format = "GIF" format = "GIF"
format_description = "Compuserve GIF" format_description = "Compuserve GIF"
_close_exclusive_fp_after_loading = False
global_palette = None global_palette = None
def data(self): def data(self):
@ -262,7 +257,7 @@ class GifImageFile(ImageFile.ImageFile):
# only dispose the extent in this frame # only dispose the extent in this frame
if self.dispose: if self.dispose:
self.dispose = self.dispose.crop(self.dispose_extent) self.dispose = self._crop(self.dispose, self.dispose_extent)
except (AttributeError, KeyError): except (AttributeError, KeyError):
pass pass
@ -285,7 +280,7 @@ class GifImageFile(ImageFile.ImageFile):
if self._prev_im and self.disposal_method == 1: if self._prev_im and self.disposal_method == 1:
# we do this by pasting the updated area onto the previous # we do this by pasting the updated area onto the previous
# frame which we then use as the current image content # frame which we then use as the current image content
updated = self.im.crop(self.dispose_extent) updated = self._crop(self.im, self.dispose_extent)
self._prev_im.paste(updated, self.dispose_extent, self._prev_im.paste(updated, self.dispose_extent,
updated.convert('RGBA')) updated.convert('RGBA'))
self.im = self._prev_im self.im = self._prev_im
@ -294,52 +289,168 @@ class GifImageFile(ImageFile.ImageFile):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write GIF files # Write GIF files
try:
import _imaging_gif
except ImportError:
_imaging_gif = None
RAWMODE = { RAWMODE = {
"1": "L", "1": "L",
"L": "L", "L": "L",
"P": "P", "P": "P"
} }
def _convert_mode(im, initial_call=False): def _normalize_mode(im, initial_call=False):
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL """
# should automatically convert images on save...) 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 Image.getmodebase(im.mode) == "RGB":
if initial_call: if initial_call:
palette_size = 256 palette_size = 256
if im.palette: if im.palette:
palette_size = len(im.palette.getdata()[1]) // 3 palette_size = len(im.palette.getdata()[1]) // 3
return im.convert("P", palette=1, colors=palette_size) return im.convert("P", palette=Image.ADAPTIVE, colors=palette_size)
else: else:
return im.convert("P") return im.convert("P")
return im.convert("L") 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): def _save_all(im, fp, filename):
_save(im, fp, filename, save_all=True) _save(im, fp, filename, save_all=True)
def _save(im, fp, filename, save_all=False): def _save(im, fp, filename, save_all=False):
im.encoderinfo.update(im.info) 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 # header
try: try:
palette = im.encoderinfo["palette"] palette = im.encoderinfo["palette"]
@ -347,62 +458,8 @@ def _save(im, fp, filename, save_all=False):
palette = None palette = None
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
if save_all: if not save_all or not _write_multiple_frames(im, fp, palette):
previous = None _write_single_frame(im, fp, palette)
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
fp.write(b";") # end of file fp.write(b";") # end of file
@ -411,10 +468,7 @@ def _save(im, fp, filename, save_all=False):
def get_interlace(im): def get_interlace(im):
try: interlace = im.encoderinfo.get("interlace", 1)
interlace = im.encoderinfo["interlace"]
except KeyError:
interlace = 1
# workaround for @PIL153 # workaround for @PIL153
if min(im.size) < 16: if min(im.size) < 16:
@ -423,7 +477,7 @@ def get_interlace(im):
return interlace return interlace
def _get_local_header(fp, im, offset, flags): def _write_local_header(fp, im, offset, flags):
transparent_color_exists = False transparent_color_exists = False
try: try:
transparency = im.encoderinfo["transparency"] 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 # optimize the block away if transparent color is not used
transparent_color_exists = True transparent_color_exists = True
if _get_optimize(im, im.encoderinfo): used_palette_colors = _get_optimize(im, im.encoderinfo)
used_palette_colors = _get_used_palette_colors(im) if used_palette_colors is not None:
# adjust the transparency index after optimize # adjust the transparency index after optimize
if len(used_palette_colors) < 256: try:
for i in range(len(used_palette_colors)): transparency = used_palette_colors.index(transparency)
if used_palette_colors[i] == transparency: except ValueError:
transparency = i transparent_color_exists = False
transparent_color_exists = True
break
else:
transparent_color_exists = False
if "duration" in im.encoderinfo: if "duration" in im.encoderinfo:
duration = int(im.encoderinfo["duration"] / 10) duration = int(im.encoderinfo["duration"] / 10)
@ -482,11 +531,8 @@ def _get_local_header(fp, im, offset, flags):
o8(0)) o8(0))
include_color_table = im.encoderinfo.get('include_color_table') include_color_table = im.encoderinfo.get('include_color_table')
if include_color_table: if include_color_table:
try: palette = im.encoderinfo.get("palette", None)
palette = im.encoderinfo["palette"] palette_bytes = _get_palette_bytes(im)
except KeyError:
palette = None
palette_bytes = _get_palette_bytes(im, palette, im.encoderinfo)[0]
color_table_size = _get_color_table_size(palette_bytes) color_table_size = _get_color_table_size(palette_bytes)
if color_table_size: if color_table_size:
flags = flags | 128 # local color table flag 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): 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 # If you need real GIF compression and/or RGB quantization, you
# can use the external NETPBM/PBMPLUS utilities. See comments # can use the external NETPBM/PBMPLUS utilities. See comments
@ -512,25 +560,21 @@ def _save_netpbm(im, fp, filename):
import os import os
from subprocess import Popen, check_call, PIPE, CalledProcessError from subprocess import Popen, check_call, PIPE, CalledProcessError
import tempfile
file = im._dump() file = im._dump()
if im.mode != "RGB": with open(filename, 'wb') as f:
with open(filename, 'wb') as f: if im.mode != "RGB":
stderr = tempfile.TemporaryFile() with open(os.devnull, 'wb') as devnull:
check_call(["ppmtogif", file], stdout=f, stderr=stderr) check_call(["ppmtogif", file], stdout=f, stderr=devnull)
else: else:
with open(filename, 'wb') as f:
# Pipe ppmquant output into ppmtogif # Pipe ppmquant output into ppmtogif
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename) # "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
quant_cmd = ["ppmquant", "256", file] quant_cmd = ["ppmquant", "256", file]
togif_cmd = ["ppmtogif"] togif_cmd = ["ppmtogif"]
stderr = tempfile.TemporaryFile() with open(os.devnull, 'wb') as devnull:
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr) quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
stderr = tempfile.TemporaryFile() togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout,
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f, stdout=f, stderr=devnull)
stderr=stderr)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits # Allow ppmquant to receive SIGPIPE if ppmtogif exits
quant_proc.stdout.close() quant_proc.stdout.close()
@ -549,24 +593,45 @@ def _save_netpbm(im, fp, filename):
pass pass
# -------------------------------------------------------------------- # Force optimization so that we can test performance against
# GIF utilities # cases where it took lots of memory and time previously.
_FORCE_OPTIMIZE = False
def _get_optimize(im, info): 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): :param im: Image object
used_palette_colors = [] :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 # The palette saves 3 bytes per color not used, but palette
i = 0 # lengths are restricted to 3*(2**N) bytes. Max saving would
for count in im.histogram(): # be 768 -> 6 bytes if we went all the way down to 2 colors.
if count: # * If we're over 128 colors, we can't save any space.
used_palette_colors.append(i) # * If there aren't any holes, it's not worth collapsing.
i += 1 # * 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): def _get_color_table_size(palette_bytes):
# calculate the palette size for the header # calculate the palette size for the header
@ -576,7 +641,15 @@ def _get_color_table_size(palette_bytes):
color_table_size = 0 color_table_size = 0
return color_table_size return color_table_size
def _get_header_palette(palette_bytes): 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) color_table_size = _get_color_table_size(palette_bytes)
# add the missing amount of 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 palette_bytes += o8(0) * 3 * actual_target_size_diff
return palette_bytes 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): def _get_palette_bytes(im):
if im.mode == "P": """
if palette and isinstance(palette, bytes): Gets the palette for inclusion in the gif header
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))
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. def _get_global_header(im, info):
# 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):
"""Return a list of strings representing a GIF header""" """Return a list of strings representing a GIF header"""
# Header Block # Header Block
@ -691,7 +680,7 @@ def getheader(im, palette=None, info=None):
for extensionKey in ["transparency", "duration", "loop", "comment"]: for extensionKey in ["transparency", "duration", "loop", "comment"]:
if info and extensionKey in info: if info and extensionKey in info:
if ((extensionKey == "duration" and info[extensionKey] == 0) or 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 continue
version = b"89a" version = b"89a"
break break
@ -699,42 +688,89 @@ def getheader(im, palette=None, info=None):
if im.info.get("version") == b"89a": if im.info.get("version") == b"89a":
version = b"89a" version = b"89a"
header = [ palette_bytes = _get_palette_bytes(im)
b"GIF"+version + # signature + version color_table_size = _get_color_table_size(palette_bytes)
o16(im.size[0]) + # canvas width
o16(im.size[1]) # canvas height 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 def _write_frame_data(fp, im_frame, offset, params):
color_table_size = _get_color_table_size(palette_bytes) try:
# size of global color table + global color table flag im_frame.encoderinfo = params
header.append(o8(color_table_size + 128)) # packed fields
# background + reserved/aspect # local image header
if info and "background" in info: _write_local_header(fp, im_frame, offset, 0)
background = info["background"]
elif "background" in im.info: ImageFile._save(im_frame, fp, [("gif", (0, 0)+im_frame.size, 0,
# This elif is redundant within GifImagePlugin RAWMODE[im_frame.mode])])
# since im.info parameters are bundled into the info dictionary
# However, external scripts may call getheader directly fp.write(b"\0") # end of image data
# So this maintains earlier behaviour finally:
background = im.info["background"] del im_frame.encoderinfo
else:
background = 0 # --------------------------------------------------------------------
header.append(o8(background) + o8(0)) # Legacy GIF utilities
# end of Logical Screen Descriptor
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 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): 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 Legacy Method
encoded image data."""
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): class Collector(object):
data = [] data = []
@ -745,18 +781,7 @@ def getdata(im, offset=(0, 0), **params):
fp = Collector() fp = Collector()
try: _write_frame_data(fp, im, offset, params)
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
return fp.data return fp.data

View File

@ -14,7 +14,7 @@
# #
from math import pi, log, sin, sqrt 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 # Stuff to translate curve segments to palette values (derived from

View File

@ -15,7 +15,7 @@
# #
import re import re
from PIL._binary import o8 from ._binary import o8
## ##

View File

@ -9,7 +9,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFile from . import Image, ImageFile
from ._binary import i8
_handler = None _handler = None
@ -28,7 +29,7 @@ def register_handler(handler):
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01' return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1
class GribStubImageFile(ImageFile.StubImageFile): class GribStubImageFile(ImageFile.StubImageFile):

View File

@ -9,7 +9,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFile from . import Image, ImageFile
_handler = None _handler = None

View File

@ -15,7 +15,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFile, PngImagePlugin, _binary from PIL import Image, ImageFile, PngImagePlugin
from PIL._binary import i8
import io import io
import os import os
import shutil import shutil
@ -27,8 +28,6 @@ enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
if enable_jpeg2k: if enable_jpeg2k:
from PIL import Jpeg2KImagePlugin from PIL import Jpeg2KImagePlugin
i8 = _binary.i8
HEADERSIZE = 8 HEADERSIZE = 8
@ -330,8 +329,8 @@ def _save(im, fp, filename):
from subprocess import Popen, PIPE, CalledProcessError from subprocess import Popen, PIPE, CalledProcessError
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
stderr = tempfile.TemporaryFile() with open(os.devnull, 'wb') as devnull:
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr) convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=devnull)
convert_proc.stdout.close() convert_proc.stdout.close()

View File

@ -25,7 +25,8 @@
import struct import struct
from io import BytesIO 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 from math import log, ceil
__version__ = "0.1" __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" _MAGIC = b"\0\0\1\0"
@ -44,16 +41,19 @@ def _save(im, fp, filename):
fp.write(_MAGIC) # (2+2) fp.write(_MAGIC) # (2+2)
sizes = im.encoderinfo.get("sizes", sizes = im.encoderinfo.get("sizes",
[(16, 16), (24, 24), (32, 32), (48, 48), [(16, 16), (24, 24), (32, 32), (48, 48),
(64, 64), (128, 128), (255, 255)]) (64, 64), (128, 128), (256, 256)])
width, height = im.size width, height = im.size
filter(lambda x: False if (x[0] > width or x[1] > height or sizes = filter(lambda x: False if (x[0] > width or x[1] > height or
x[0] > 255 or x[1] > 255) else True, sizes) x[0] > 256 or x[1] > 256) else True,
sizes)
sizes = list(sizes)
fp.write(struct.pack("<H", len(sizes))) # idCount(2) fp.write(struct.pack("<H", len(sizes))) # idCount(2)
offset = fp.tell() + len(sizes)*16 offset = fp.tell() + len(sizes)*16
for size in sizes: for size in sizes:
width, height = size width, height = size
fp.write(struct.pack("B", width)) # bWidth(1) # 0 means 256
fp.write(struct.pack("B", height)) # bHeight(1) 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") # bColorCount(1)
fp.write(b"\0") # bReserved(1) fp.write(b"\0") # bReserved(1)
fp.write(b"\0\0") # wPlanes(2) fp.write(b"\0\0") # wPlanes(2)
@ -176,8 +176,8 @@ class IcoFile(object):
# figure out where AND mask image starts # figure out where AND mask image starts
mode = a[0] mode = a[0]
bpp = 8 bpp = 8
for k in BmpImagePlugin.BIT2MODE.keys(): for k, v in BmpImagePlugin.BIT2MODE.items():
if mode == BmpImagePlugin.BIT2MODE[k][1]: if mode == v[1]:
bpp = k bpp = k
break break
@ -215,13 +215,13 @@ class IcoFile(object):
total_bytes = int((w * im.size[1]) / 8) total_bytes = int((w * im.size[1]) / 8)
self.buf.seek(and_mask_offset) self.buf.seek(and_mask_offset)
maskData = self.buf.read(total_bytes) mask_data = self.buf.read(total_bytes)
# convert raw data to image # convert raw data to image
mask = Image.frombuffer( mask = Image.frombuffer(
'1', # 1 bpp '1', # 1 bpp
im.size, # (w, h) im.size, # (w, h)
maskData, # source chars mask_data, # source chars
'raw', # raw decoder 'raw', # raw decoder
('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed ('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_open(IcoImageFile.format, IcoImageFile, _accept)
Image.register_save(IcoImageFile.format, _save) Image.register_save(IcoImageFile.format, _save)
Image.register_extension(IcoImageFile.format, ".ico") Image.register_extension(IcoImageFile.format, ".ico")

View File

@ -27,8 +27,8 @@
import re import re
from PIL import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from PIL._binary import i8 from ._binary import i8
__version__ = "0.7" __version__ = "0.7"
@ -109,6 +109,7 @@ class ImImageFile(ImageFile.ImageFile):
format = "IM" format = "IM"
format_description = "IFUNC Image Memory" format_description = "IFUNC Image Memory"
_close_exclusive_fp_after_loading = False
def _open(self): def _open(self):
@ -325,10 +326,7 @@ def _save(im, fp, filename, check=0):
except KeyError: except KeyError:
raise ValueError("Cannot save %s images as IM" % im.mode) raise ValueError("Cannot save %s images as IM" % im.mode)
try: frames = im.encoderinfo.get("frames", 1)
frames = im.encoderinfo["frames"]
except KeyError:
frames = 1
if check: if check:
return check return check

View File

@ -24,9 +24,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from __future__ import print_function from . import VERSION, PILLOW_VERSION, _plugins
from PIL import VERSION, PILLOW_VERSION, _plugins
import logging import logging
import warnings import warnings
@ -48,15 +46,6 @@ class _imaging_not_installed(object):
# Limit to around a quarter gigabyte for a 24 bit (3 bpp) image # Limit to around a quarter gigabyte for a 24 bit (3 bpp) image
MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3) 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: try:
# If the _imaging C module is not present, Pillow will not load. # 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. # import Image and use the Image.core variable instead.
# Also note that Image.core is not a publicly documented interface, # Also note that Image.core is not a publicly documented interface,
# and should be considered private and subject to change. # 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): if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
raise ImportError("The _imaging extension was built for another " raise ImportError("The _imaging extension was built for another "
"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: except ImportError as v:
core = _imaging_not_installed() core = _imaging_not_installed()
@ -109,11 +101,9 @@ except ImportError:
import __builtin__ import __builtin__
builtins = __builtin__ builtins = __builtin__
from PIL import ImageMode from . import ImageMode
from PIL._binary import i8 from ._binary import i8
from PIL._util import isPath from ._util import isPath, isStringType, deferred_error
from PIL._util import isStringType
from PIL._util import deferred_error
import os import os
import sys import sys
@ -146,6 +136,7 @@ def isImageType(t):
""" """
return hasattr(t, "im") return hasattr(t, "im")
# #
# Constants (also defined in _imagingmodule.c!) # Constants (also defined in _imagingmodule.c!)
@ -211,6 +202,8 @@ MIME = {}
SAVE = {} SAVE = {}
SAVE_ALL = {} SAVE_ALL = {}
EXTENSION = {} EXTENSION = {}
DECODERS = {}
ENCODERS = {}
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Modes supported by this version # Modes supported by this version
@ -284,7 +277,7 @@ def _conv_type_shape(im):
return shape+(extra,), typ return shape+(extra,), typ
MODES = sorted(_MODEINFO.keys()) MODES = sorted(_MODEINFO)
# raw modes that may be memory mapped. NOTE: if you change this, you # raw modes that may be memory mapped. NOTE: if you change this, you
# may have to modify the stride calculation in map.c too! # may have to modify the stride calculation in map.c too!
@ -341,6 +334,7 @@ def getmodebands(mode):
""" """
return len(ImageMode.getmode(mode).bands) return len(ImageMode.getmode(mode).bands)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Helpers # Helpers
@ -355,23 +349,23 @@ def preinit():
return return
try: try:
from PIL import BmpImagePlugin from . import BmpImagePlugin
except ImportError: except ImportError:
pass pass
try: try:
from PIL import GifImagePlugin from . import GifImagePlugin
except ImportError: except ImportError:
pass pass
try: try:
from PIL import JpegImagePlugin from . import JpegImagePlugin
except ImportError: except ImportError:
pass pass
try: try:
from PIL import PpmImagePlugin from . import PpmImagePlugin
except ImportError: except ImportError:
pass pass
try: try:
from PIL import PngImagePlugin from . import PngImagePlugin
except ImportError: except ImportError:
pass pass
# try: # try:
@ -415,6 +409,11 @@ def _getdecoder(mode, decoder_name, args, extra=()):
elif not isinstance(args, tuple): elif not isinstance(args, tuple):
args = (args,) args = (args,)
try:
decoder = DECODERS[decoder_name]
return decoder(mode, *args + extra)
except KeyError:
pass
try: try:
# get decoder # get decoder
decoder = getattr(core, decoder_name + "_decoder") decoder = getattr(core, decoder_name + "_decoder")
@ -432,6 +431,11 @@ def _getencoder(mode, encoder_name, args, extra=()):
elif not isinstance(args, tuple): elif not isinstance(args, tuple):
args = (args,) args = (args,)
try:
encoder = ENCODERS[encoder_name]
return encoder(mode, *args + extra)
except KeyError:
pass
try: try:
# get encoder # get encoder
encoder = getattr(core, encoder_name + "_encoder") encoder = getattr(core, encoder_name + "_encoder")
@ -496,6 +500,7 @@ class Image(object):
""" """
format = None format = None
format_description = None format_description = None
_close_exclusive_fp_after_loading = True
def __init__(self): def __init__(self):
# FIXME: take "new" parameters / other image? # FIXME: take "new" parameters / other image?
@ -525,13 +530,11 @@ class Image(object):
if self.palette: if self.palette:
new.palette = self.palette.copy() new.palette = self.palette.copy()
if im.mode == "P" and not new.palette: if im.mode == "P" and not new.palette:
from PIL import ImagePalette from . import ImagePalette
new.palette = ImagePalette.ImagePalette() new.palette = ImagePalette.ImagePalette()
new.info = self.info.copy() new.info = self.info.copy()
return new return new
_makeself = _new # compatibility
# Context Manager Support # Context Manager Support
def __enter__(self): def __enter__(self):
return self return self
@ -552,21 +555,32 @@ class Image(object):
""" """
try: try:
self.fp.close() self.fp.close()
self.fp = None
except Exception as msg: except Exception as msg:
logger.debug("Error closing: %s", 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 # Instead of simply setting to None, we're setting up a
# deferred error that will better explain that the core image # deferred error that will better explain that the core image
# object is gone. # object is gone.
self.im = deferred_error(ValueError("Operation on closed image")) 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): def _copy(self):
self.load() self.load()
self.im = self.im.copy() self.im = self.im.copy()
self.pyaccess = None self.pyaccess = None
self.readonly = 0 self.readonly = 0
def _dump(self, file=None, format=None): def _dump(self, file=None, format=None, **options):
import tempfile import tempfile
suffix = '' suffix = ''
if format: if format:
@ -581,7 +595,7 @@ class Image(object):
else: else:
if not file.endswith(format): if not file.endswith(format):
file = file + "." + format file = file + "." + format
self.save(file, format) self.save(file, format, **options)
return file return file
def __eq__(self, other): def __eq__(self, other):
@ -695,8 +709,8 @@ class Image(object):
return b"".join(data) return b"".join(data)
def tostring(self, *args, **kw): def tostring(self, *args, **kw):
raise NotImplementedError("tostring() has been removed. " + raise NotImplementedError("tostring() has been removed. "
"Please call tobytes() instead.") "Please call tobytes() instead.")
def tobitmap(self, name="image"): def tobitmap(self, name="image"):
""" """
@ -746,8 +760,8 @@ class Image(object):
raise ValueError("cannot decode image data") raise ValueError("cannot decode image data")
def fromstring(self, *args, **kw): def fromstring(self, *args, **kw):
raise NotImplementedError("fromstring() has been removed. " + raise NotImplementedError("fromstring() has been removed. "
"Please call frombytes() instead.") "Please call frombytes() instead.")
def load(self): def load(self):
""" """
@ -777,7 +791,7 @@ class Image(object):
if HAS_CFFI and USE_CFFI_ACCESS: if HAS_CFFI and USE_CFFI_ACCESS:
if self.pyaccess: if self.pyaccess:
return self.pyaccess return self.pyaccess
from PIL import PyAccess from . import PyAccess
self.pyaccess = PyAccess.new(self, self.readonly) self.pyaccess = PyAccess.new(self, self.readonly)
if self.pyaccess: if self.pyaccess:
return self.pyaccess return self.pyaccess
@ -883,7 +897,7 @@ class Image(object):
try: try:
t = trns_im.palette.getcolor(t) t = trns_im.palette.getcolor(t)
except: except:
raise ValueError("Couldn't allocate a palette " + raise ValueError("Couldn't allocate a palette "
"color for transparency") "color for transparency")
trns_im.putpixel((0, 0), t) trns_im.putpixel((0, 0), t)
@ -910,7 +924,7 @@ class Image(object):
if mode == "P" and palette == ADAPTIVE: if mode == "P" and palette == ADAPTIVE:
im = self.im.quantize(colors) im = self.im.quantize(colors)
new = self._new(im) new = self._new(im)
from PIL import ImagePalette from . import ImagePalette
new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB")) new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB"))
if delete_trns: if delete_trns:
# This could possibly happen if we requantize to fewer colors. # 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" "only RGB or L mode images can be quantized to a palette"
) )
im = self.im.convert("P", 1, palette.im) 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)) return self._new(self.im.quantize(colors, method, kmeans))
@ -1031,6 +1045,20 @@ class Image(object):
if box is None: if box is None:
return self.copy() 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)) x0, y0, x1, y1 = map(int, map(round, box))
if x1 < x0: if x1 < x0:
@ -1038,8 +1066,9 @@ class Image(object):
if y1 < y0: if y1 < y0:
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): 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 in place. If the image has already been loaded, this method has no
effect. 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 mode: The requested mode.
:param size: The requested size. :param size: The requested size.
""" """
@ -1258,8 +1290,8 @@ class Image(object):
return self.im.histogram() return self.im.histogram()
def offset(self, xoffset, yoffset=None): def offset(self, xoffset, yoffset=None):
raise NotImplementedError("offset() has been removed. " + raise NotImplementedError("offset() has been removed. "
"Please call ImageChops.offset() instead.") "Please call ImageChops.offset() instead.")
def paste(self, im, box=None, mask=None): def paste(self, im, box=None, mask=None):
""" """
@ -1323,7 +1355,7 @@ class Image(object):
box += (box[0]+size[0], box[1]+size[1]) box += (box[0]+size[0], box[1]+size[1])
if isStringType(im): if isStringType(im):
from PIL import ImageColor from . import ImageColor
im = ImageColor.getcolor(im, self.mode) im = ImageColor.getcolor(im, self.mode)
elif isImageType(im): elif isImageType(im):
@ -1344,6 +1376,54 @@ class Image(object):
else: else:
self.im.paste(im, box) 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): def point(self, lut, mode=None):
""" """
Maps this image through a lookup table or function. 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). :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"): if self.mode not in ("L", "P"):
raise ValueError("illegal image mode") raise ValueError("illegal image mode")
@ -1519,6 +1599,80 @@ class Image(object):
return self.pyaccess.putpixel(xy, value) return self.pyaccess.putpixel(xy, value)
return self.im.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): def resize(self, size, resample=NEAREST, box=None):
""" """
Returns a resized copy of this image. Returns a resized copy of this image.
@ -1567,7 +1721,8 @@ class Image(object):
return self._new(self.im.resize(size, resample, box)) 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 Returns a rotated copy of this image. This method returns a
copy of this image, rotated the given number of degrees counter 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 :param expand: Optional expansion flag. If true, expands the output
image to make it large enough to hold the entire rotated image. 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 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. :returns: An :py:class:`~PIL.Image.Image` object.
""" """
angle = angle % 360.0 angle = angle % 360.0
# Fast paths regardless of filter # Fast paths regardless of filter, as long as we're not
if angle == 0: # translating or changing the center.
return self.copy() if not (center or translate):
if angle == 180: if angle == 0:
return self.transpose(ROTATE_180) return self.copy()
if angle == 90 and expand: if angle == 180:
return self.transpose(ROTATE_90) return self.transpose(ROTATE_180)
if angle == 270 and expand: if angle == 90 and expand:
return self.transpose(ROTATE_270) 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) angle = - math.radians(angle)
matrix = [ matrix = [
round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0, round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0,
round(-math.sin(angle), 15), round(math.cos(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 (a, b, c, d, e, f) = matrix
return a*x + b*y + c, d*x + e*y + f 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: if expand:
# calculate output size # calculate output size
xx = [] xx = []
yy = [] yy = []
for x, y in ((0, 0), (w, 0), (w, h), (0, h)): 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) xx.append(x)
yy.append(y) yy.append(y)
w = int(math.ceil(max(xx)) - math.floor(min(xx))) nw = int(math.ceil(max(xx)) - math.floor(min(xx)))
h = int(math.ceil(max(yy)) - math.floor(min(yy))) nh = int(math.ceil(max(yy)) - math.floor(min(yy)))
# adjust center # We multiply a translation matrix from the right. Because of its
x, y = transform(w / 2.0, h / 2.0) # special form, this is the same as taking the image of the
matrix[2] = self.size[0] / 2.0 - x # translation vector as new translation vector.
matrix[5] = self.size[1] / 2.0 - y 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) return self.transform((w, h), AFFINE, matrix, resample)
@ -1689,7 +1882,10 @@ class Image(object):
if not format: if not format:
if ext not in EXTENSION: if ext not in EXTENSION:
init() init()
format = EXTENSION[ext] try:
format = EXTENSION[ext]
except KeyError:
raise ValueError('unknown file extension: {}'.format(ext))
if format.upper() not in SAVE: if format.upper() not in SAVE:
init() init()
@ -1740,8 +1936,8 @@ class Image(object):
PPM file, and calls either the **xv** utility or the **display** PPM file, and calls either the **xv** utility or the **display**
utility, depending on which one can be found. utility, depending on which one can be found.
On macOS, this method saves the image to a temporary BMP file, and opens On macOS, this method saves the image to a temporary BMP file, and
it with the native Preview application. opens it with the native Preview application.
On Windows, it saves the image to a temporary BMP file, and uses On Windows, it saves the image to a temporary BMP file, and uses
the standard BMP display utility to show it (usually Paint). the standard BMP display utility to show it (usually Paint).
@ -1956,20 +2152,19 @@ class Image(object):
def toqimage(self): def toqimage(self):
"""Returns a QImage copy of this image""" """Returns a QImage copy of this image"""
from PIL import ImageQt from . import ImageQt
if not ImageQt.qt_is_installed: if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed") raise ImportError("Qt bindings are not installed")
return ImageQt.toqimage(self) return ImageQt.toqimage(self)
def toqpixmap(self): def toqpixmap(self):
"""Returns a QPixmap copy of this image""" """Returns a QPixmap copy of this image"""
from PIL import ImageQt from . import ImageQt
if not ImageQt.qt_is_installed: if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed") raise ImportError("Qt bindings are not installed")
return ImageQt.toqpixmap(self) return ImageQt.toqpixmap(self)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Abstract handlers. # Abstract handlers.
@ -1994,6 +2189,7 @@ def _wedge():
return Image()._new(core.wedge("L")) return Image()._new(core.wedge("L"))
def _check_size(size): def _check_size(size):
""" """
Common check to enforce type and sanity check on size tuples 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") raise ValueError("Size must be a tuple")
if len(size) != 2: if len(size) != 2:
raise ValueError("Size must be a tuple of length 2") raise ValueError("Size must be a tuple of length 2")
if size[0] <= 0 or size[1] <= 0: if size[0] < 0 or size[1] < 0:
raise ValueError("Width and Height must be > 0") raise ValueError("Width and height must be >= 0")
return True return True
def new(mode, size, color=0): def new(mode, size, color=0):
""" """
Creates a new image with the given mode and size. Creates a new image with the given mode and size.
@ -2036,7 +2233,7 @@ def new(mode, size, color=0):
if isStringType(color): if isStringType(color):
# css3-style specifier # css3-style specifier
from PIL import ImageColor from . import ImageColor
color = ImageColor.getcolor(color, mode) color = ImageColor.getcolor(color, mode)
return Image()._new(core.fill(mode, size, color)) 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): def fromstring(*args, **kw):
raise NotImplementedError("fromstring() has been removed. " + raise NotImplementedError("fromstring() has been removed. " +
"Please call frombytes() instead.") "Please call frombytes() instead.")
def frombuffer(mode, size, data, decoder_name="raw", *args): def frombuffer(mode, size, data, decoder_name="raw", *args):
@ -2164,16 +2361,13 @@ def fromarray(obj, mode=None):
arr = obj.__array_interface__ arr = obj.__array_interface__
shape = arr['shape'] shape = arr['shape']
ndim = len(shape) ndim = len(shape)
try: strides = arr.get('strides', None)
strides = arr['strides']
except KeyError:
strides = None
if mode is None: if mode is None:
try: try:
typekey = (1, 1) + shape[2:], arr['typestr'] typekey = (1, 1) + shape[2:], arr['typestr']
mode, rawmode = _fromarray_typemap[typekey] mode, rawmode = _fromarray_typemap[typekey]
except KeyError: except KeyError:
# print typekey # print(typekey)
raise TypeError("Cannot handle this data type") raise TypeError("Cannot handle this data type")
else: else:
rawmode = mode rawmode = mode
@ -2198,7 +2392,7 @@ def fromarray(obj, mode=None):
def fromqimage(im): def fromqimage(im):
"""Creates an image instance from a QImage image""" """Creates an image instance from a QImage image"""
from PIL import ImageQt from . import ImageQt
if not ImageQt.qt_is_installed: if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed") raise ImportError("Qt bindings are not installed")
return ImageQt.fromqimage(im) return ImageQt.fromqimage(im)
@ -2206,11 +2400,12 @@ def fromqimage(im):
def fromqpixmap(im): def fromqpixmap(im):
"""Creates an image instance from a QPixmap image""" """Creates an image instance from a QPixmap image"""
from PIL import ImageQt from . import ImageQt
if not ImageQt.qt_is_installed: if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed") raise ImportError("Qt bindings are not installed")
return ImageQt.fromqpixmap(im) return ImageQt.fromqpixmap(im)
_fromarray_typemap = { _fromarray_typemap = {
# (shape, typestr) => mode, rawmode # (shape, typestr) => mode, rawmode
# first two members of shape are set to one # first two members of shape are set to one
@ -2276,6 +2471,7 @@ def open(fp, mode="r"):
if mode != "r": if mode != "r":
raise ValueError("bad mode %r" % mode) raise ValueError("bad mode %r" % mode)
exclusive_fp = False
filename = "" filename = ""
if isPath(fp): if isPath(fp):
filename = fp filename = fp
@ -2289,11 +2485,13 @@ def open(fp, mode="r"):
if filename: if filename:
fp = builtins.open(filename, "rb") fp = builtins.open(filename, "rb")
exclusive_fp = True
try: try:
fp.seek(0) fp.seek(0)
except (AttributeError, io.UnsupportedOperation): except (AttributeError, io.UnsupportedOperation):
fp = io.BytesIO(fp.read()) fp = io.BytesIO(fp.read())
exclusive_fp = True
prefix = fp.read(16) prefix = fp.read(16)
@ -2322,8 +2520,11 @@ def open(fp, mode="r"):
im = _open_core(fp, filename, prefix) im = _open_core(fp, filename, prefix)
if im: if im:
im._exclusive_fp = exclusive_fp
return im return im
if exclusive_fp:
fp.close()
raise IOError("cannot identify image file %r" raise IOError("cannot identify image file %r"
% (filename if filename else fp)) % (filename if filename else fp))
@ -2491,6 +2692,44 @@ def register_extension(id, extension):
EXTENSION[extension.lower()] = id.upper() 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. # Simple display support. User code may override this.
@ -2500,7 +2739,7 @@ def _show(image, **options):
def _showxv(image, title=None, **options): def _showxv(image, title=None, **options):
from PIL import ImageShow from . import ImageShow
ImageShow.show(image, title, **options) ImageShow.show(image, title, **options)
@ -2529,3 +2768,21 @@ def effect_noise(size, sigma):
:param sigma: Standard deviation of noise. :param sigma: Standard deviation of noise.
""" """
return Image()._new(core.effect_noise(size, sigma)) 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))

View File

@ -15,7 +15,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image
def constant(image, value): def constant(image, value):

View File

@ -166,7 +166,6 @@ class ImageCmsProfile(object):
self._set(profile) self._set(profile)
else: else:
raise TypeError("Invalid type for Profile") raise TypeError("Invalid type for Profile")
def _set(self, profile, filename=None): def _set(self, profile, filename=None):
self.profile = profile self.profile = profile

View File

@ -17,7 +17,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image
import re import re

View File

@ -31,10 +31,9 @@
# #
import numbers import numbers
import warnings
from PIL import Image, ImageColor from . import Image, ImageColor
from PIL._util import isStringType from ._util import isStringType
""" """
A simple 2D drawing interface for PIL images. A simple 2D drawing interface for PIL images.
@ -87,25 +86,14 @@ class ImageDraw(object):
self.fill = 0 self.fill = 0
self.font = None 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): def getfont(self):
"""Get the current default font.""" """
Get the current default font.
:returns: An image font."""
if not self.font: if not self.font:
# FIXME: should add a font repository # FIXME: should add a font repository
from PIL import ImageFont from . import ImageFont
self.font = ImageFont.load_default() self.font = ImageFont.load_default()
return self.font return self.font
@ -222,7 +210,6 @@ class ImageDraw(object):
if self._multiline_check(text): if self._multiline_check(text):
return self.multiline_text(xy, text, fill, font, anchor, return self.multiline_text(xy, text, fill, font, anchor,
*args, **kwargs) *args, **kwargs)
ink, fill = self._getink(fill) ink, fill = self._getink(fill)
if font is None: if font is None:
font = self.getfont() font = self.getfont()
@ -230,17 +217,17 @@ class ImageDraw(object):
ink = fill ink = fill
if ink is not None: if ink is not None:
try: 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] xy = xy[0] + offset[0], xy[1] + offset[1]
except AttributeError: except AttributeError:
try: try:
mask = font.getmask(text, self.fontmode) mask = font.getmask(text, self.fontmode, *args, **kwargs)
except TypeError: except TypeError:
mask = font.getmask(text) mask = font.getmask(text)
self.draw.draw_bitmap(xy, mask, ink) self.draw.draw_bitmap(xy, mask, ink)
def multiline_text(self, xy, text, fill=None, font=None, anchor=None, 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 = [] widths = []
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
@ -259,25 +246,30 @@ class ImageDraw(object):
left += (max_width - widths[idx]) left += (max_width - widths[idx])
else: else:
assert False, 'align must be "left", "center" or "right"' 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 top += line_spacing
left = xy[0] 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.""" """Get the size of a given string, in pixels."""
if self._multiline_check(text): 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: if font is None:
font = self.getfont() 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 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines: 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) max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing return max_width, len(lines)*line_spacing
@ -298,6 +290,7 @@ def Draw(im, mode=None):
except AttributeError: except AttributeError:
return ImageDraw(im, mode) return ImageDraw(im, mode)
# experimental access to the outline API # experimental access to the outline API
try: try:
Outline = Image.core.outline Outline = Image.core.outline
@ -319,17 +312,17 @@ def getdraw(im=None, hints=None):
handler = None handler = None
if not hints or "nicest" in hints: if not hints or "nicest" in hints:
try: try:
from PIL import _imagingagg as handler from . import _imagingagg as handler
except ImportError: except ImportError:
pass pass
if handler is None: if handler is None:
from PIL import ImageDraw2 as handler from . import ImageDraw2 as handler
if im: if im:
im = handler.Draw(im) im = handler.Draw(im)
return im, handler 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. (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, pixels with a color different from the border color. If not given,
the region consists of pixels having the same color as the seed the region consists of pixels having the same color as the seed
pixel. 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 # based on an implementation by Eric S. Raymond
pixel = image.load() pixel = image.load()
x, y = xy x, y = xy
try: try:
background = pixel[x, y] background = pixel[x, y]
if background == value: if _color_diff(value, background) <= thresh:
return # seed point already has fill color return # seed point already has fill color
pixel[x, y] = value pixel[x, y] = value
except IndexError: except (ValueError, IndexError):
return # seed point outside image return # seed point outside image
edge = [(x, y)] edge = [(x, y)]
if border is None: if border is None:
@ -362,7 +359,7 @@ def floodfill(image, xy, value, border=None):
except IndexError: except IndexError:
pass pass
else: else:
if p == background: if _color_diff(p, background) <= thresh:
pixel[s, t] = value pixel[s, t] = value
newedge.append((s, t)) newedge.append((s, t))
edge = newedge edge = newedge
@ -380,3 +377,10 @@ def floodfill(image, xy, value, border=None):
pixel[s, t] = value pixel[s, t] = value
newedge.append((s, t)) newedge.append((s, t))
edge = newedge 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])

View File

@ -16,7 +16,7 @@
# See the README file for information on usage and redistribution. # 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): class Pen(object):

View File

@ -18,7 +18,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFilter, ImageStat from . import Image, ImageFilter, ImageStat
class _Enhance(object): class _Enhance(object):

View File

@ -27,8 +27,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image
from PIL._util import isPath from ._util import isPath
import io import io
import os import os
import sys import sys
@ -88,10 +88,13 @@ class ImageFile(Image.Image):
# filename # filename
self.fp = open(fp, "rb") self.fp = open(fp, "rb")
self.filename = fp self.filename = fp
self._exclusive_fp = True
else: else:
# stream # stream
self.fp = fp self.fp = fp
self.filename = filename self.filename = filename
# can be overridden
self._exclusive_fp = None
try: try:
self._open() self._open()
@ -100,6 +103,9 @@ class ImageFile(Image.Image):
KeyError, # unsupported mode KeyError, # unsupported mode
EOFError, # got header but not the first frame EOFError, # got header but not the first frame
struct.error) as v: 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) raise SyntaxError(v)
if not self.mode or self.size[0] <= 0: 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 # raise exception if something's wrong. must be called
# directly after open, and closes file when finished. # directly after open, and closes file when finished.
if self._exclusive_fp:
self.fp.close()
self.fp = None self.fp = None
def load(self): def load(self):
@ -152,7 +160,7 @@ class ImageFile(Image.Image):
# try memory mapping # try memory mapping
decoder_name, extents, offset, args = self.tile[0] decoder_name, extents, offset, args = self.tile[0]
if decoder_name == "raw" and len(args) >= 3 and args[0] == self.mode \ 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: try:
if hasattr(Image.core, "map"): if hasattr(Image.core, "map"):
# use built-in mapper WIN32 only # use built-in mapper WIN32 only
@ -178,7 +186,7 @@ class ImageFile(Image.Image):
self.map = None self.map = None
self.load_prepare() self.load_prepare()
err_code = -3 # initialize to unknown error
if not self.map: if not self.map:
# sort tiles in file order # sort tiles in file order
self.tile.sort(key=_tilesort) self.tile.sort(key=_tilesort)
@ -191,12 +199,9 @@ class ImageFile(Image.Image):
for decoder_name, extents, offset, args in self.tile: for decoder_name, extents, offset, args in self.tile:
decoder = Image._getdecoder(self.mode, decoder_name, decoder = Image._getdecoder(self.mode, decoder_name,
args, self.decoderconfig) args, self.decoderconfig)
seek(offset) seek(offset)
try: decoder.setimage(self.im, extents)
decoder.setimage(self.im, extents)
except ValueError:
continue
if decoder.pulls_fd: if decoder.pulls_fd:
decoder.setfd(self.fp) decoder.setfd(self.fp)
status, err_code = decoder.decode(b"") status, err_code = decoder.decode(b"")
@ -211,7 +216,7 @@ class ImageFile(Image.Image):
else: else:
raise IOError("image file is truncated") raise IOError("image file is truncated")
if not s and not decoder.handles_eof: # truncated jpeg if not s: # truncated jpeg
self.tile = [] self.tile = []
# JpegDecode needs to clean things up here either way # JpegDecode needs to clean things up here either way
@ -237,20 +242,16 @@ class ImageFile(Image.Image):
self.tile = [] self.tile = []
self.readonly = readonly 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: if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
# still raised if decoder fails to return anything # still raised if decoder fails to return anything
raise_ioerror(err_code) 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) return Image.Image.load(self)
def load_prepare(self): def load_prepare(self):
@ -379,11 +380,8 @@ class Parser(object):
# attempt to open this file # attempt to open this file
try: try:
try: with io.BytesIO(self.data) as fp:
fp = io.BytesIO(self.data)
im = Image.open(fp) im = Image.open(fp)
finally:
fp.close() # explicitly close the virtual file
except IOError: except IOError:
# traceback.print_exc() # traceback.print_exc()
pass # not enough data pass # not enough data
@ -431,12 +429,11 @@ class Parser(object):
if self.data: if self.data:
# incremental parsing not possible; reopen the file # incremental parsing not possible; reopen the file
# not that we have all data # not that we have all data
try: with io.BytesIO(self.data) as fp:
fp = io.BytesIO(self.data) try:
self.image = Image.open(fp) self.image = Image.open(fp)
finally: finally:
self.image.load() self.image.load()
fp.close() # explicitly close the virtual file
return self.image return self.image
@ -526,3 +523,128 @@ def _safe_read(fp, size):
data.append(block) data.append(block)
size -= len(block) size -= len(block)
return b"".join(data) 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")

View File

@ -25,8 +25,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image
from PIL._util import isDirectory, isPath from ._util import isDirectory, isPath
import os import os
import sys import sys
@ -37,10 +37,13 @@ class _imagingft_not_installed(object):
raise ImportError("The _imagingft C module is not installed") raise ImportError("The _imagingft C module is not installed")
try: try:
from PIL import _imagingft as core from . import _imagingft as core
except ImportError: except ImportError:
core = _imagingft_not_installed() core = _imagingft_not_installed()
LAYOUT_BASIC = 0
LAYOUT_RAQM = 1
# FIXME: add support for pilfont2 format (see FontFile.py) # FIXME: add support for pilfont2 format (see FontFile.py)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -103,9 +106,12 @@ class ImageFont(object):
self.font = Image.core.font(image.im, data) self.font = Image.core.font(image.im, data)
# delegate critical operations to internal type def getsize(self, text, *args, **kwargs):
self.getsize = self.font.getsize return self.font.getsize(text)
self.getmask = self.font.getmask
def getmask(self, text, mode="", *args, **kwargs):
return self.font.getmask(text, mode)
## ##
@ -115,7 +121,8 @@ class ImageFont(object):
class FreeTypeFont(object): class FreeTypeFont(object):
"FreeType font wrapper (requires _imagingft service)" "FreeType font wrapper (requires _imagingft service)"
def __init__(self, font=None, size=10, index=0, encoding=""): def __init__(self, font=None, size=10, index=0, encoding="",
layout_engine=None):
# FIXME: use service provider instead # FIXME: use service provider instead
self.path = font self.path = font
@ -123,12 +130,21 @@ class FreeTypeFont(object):
self.index = index self.index = index
self.encoding = encoding 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): if isPath(font):
self.font = core.getfont(font, size, index, encoding) self.font = core.getfont(font, size, index, encoding, layout_engine=layout_engine)
else: else:
self.font_bytes = font.read() self.font_bytes = font.read()
self.font = core.getfont( self.font = core.getfont(
"", size, index, encoding, self.font_bytes) "", size, index, encoding, self.font_bytes, layout_engine)
def getname(self): def getname(self):
return self.font.family, self.font.style return self.font.family, self.font.style
@ -136,23 +152,24 @@ class FreeTypeFont(object):
def getmetrics(self): def getmetrics(self):
return self.font.ascent, self.font.descent return self.font.ascent, self.font.descent
def getsize(self, text): def getsize(self, text, direction=None, features=None):
size, offset = self.font.getsize(text) size, offset = self.font.getsize(text, direction, features)
return (size[0] + offset[0], size[1] + offset[1]) return (size[0] + offset[0], size[1] + offset[1])
def getoffset(self, text): def getoffset(self, text):
return self.font.getsize(text)[1] return self.font.getsize(text)[1]
def getmask(self, text, mode=""): def getmask(self, text, mode="", direction=None, features=None):
return self.getmask2(text, mode)[0] return self.getmask2(text, mode, direction=direction, features=features)[0]
def getmask2(self, text, mode="", fill=Image.core.fill): def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, features=None):
size, offset = self.font.getsize(text) size, offset = self.font.getsize(text, direction, features)
im = fill("L", size, 0) 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 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, Create a copy of this FreeTypeFont object,
using any specified arguments to override the settings. 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, return FreeTypeFont(font=self.path if font is None else font,
size=self.size if size is None else size, size=self.size if size is None else size,
index=self.index if index is None else index, index=self.index if index is None else index,
encoding=self.encoding if encoding is None else encoding=self.encoding if encoding is None else encoding,
encoding) layout_engine=self.layout_engine if layout_engine is None else layout_engine
)
class TransposedFont(object): class TransposedFont(object):
@ -185,14 +203,14 @@ class TransposedFont(object):
self.font = font self.font = font
self.orientation = orientation # any 'transpose' argument, or None self.orientation = orientation # any 'transpose' argument, or None
def getsize(self, text): def getsize(self, text, *args, **kwargs):
w, h = self.font.getsize(text) w, h = self.font.getsize(text)
if self.orientation in (Image.ROTATE_90, Image.ROTATE_270): if self.orientation in (Image.ROTATE_90, Image.ROTATE_270):
return h, w return h, w
return w, h return w, h
def getmask(self, text, mode=""): def getmask(self, text, mode="", *args, **kwargs):
im = self.font.getmask(text, mode) im = self.font.getmask(text, mode, *args, **kwargs)
if self.orientation is not None: if self.orientation is not None:
return im.transpose(self.orientation) return im.transpose(self.orientation)
return im return im
@ -212,7 +230,8 @@ def load(filename):
return f 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. Load a TrueType or OpenType font file, and create a font object.
This function loads a font object from the given file, and creates 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), Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
and "armn" (Apple Roman). See the FreeType documentation and "armn" (Apple Roman). See the FreeType documentation
for more information. for more information.
:param layout_engine: Which layout engine to use, if available:
`ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`.
:return: A font object. :return: A font object.
:exception IOError: If the file could not be read. :exception IOError: If the file could not be read.
""" """
try: try:
return FreeTypeFont(font, size, index, encoding) return FreeTypeFont(font, size, index, encoding, layout_engine)
except IOError: except IOError:
ttf_filename = os.path.basename(font) ttf_filename = os.path.basename(font)
@ -266,16 +287,16 @@ def truetype(font=None, size=10, index=0, encoding=""):
for walkfilename in walkfilenames: for walkfilename in walkfilenames:
if ext and walkfilename == ttf_filename: if ext and walkfilename == ttf_filename:
fontpath = os.path.join(walkroot, walkfilename) 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: elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
fontpath = os.path.join(walkroot, walkfilename) fontpath = os.path.join(walkroot, walkfilename)
if os.path.splitext(fontpath)[1] == '.ttf': 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: if not ext and first_font_with_a_different_extension is None:
first_font_with_a_different_extension = fontpath first_font_with_a_different_extension = fontpath
if first_font_with_a_different_extension: if first_font_with_a_different_extension:
return FreeTypeFont(first_font_with_a_different_extension, size, return FreeTypeFont(first_font_with_a_different_extension, size,
index, encoding) index, encoding, layout_engine)
raise raise

View File

@ -15,7 +15,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image
import sys import sys
if sys.platform not in ["win32", "darwin"]: if sys.platform not in ["win32", "darwin"]:
@ -75,7 +75,7 @@ def grabclipboard():
debug = 0 # temporary interface debug = 0 # temporary interface
data = Image.core.grabclipboard(debug) data = Image.core.grabclipboard(debug)
if isinstance(data, bytes): if isinstance(data, bytes):
from PIL import BmpImagePlugin from . import BmpImagePlugin
import io import io
return BmpImagePlugin.DibImageFile(io.BytesIO(data)) return BmpImagePlugin.DibImageFile(io.BytesIO(data))
return data return data

View File

@ -15,8 +15,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image, _imagingmath
from PIL import _imagingmath
try: try:
import builtins import builtins

View File

@ -14,7 +14,7 @@
# #
# mode descriptor cache # mode descriptor cache
_modes = {} _modes = None
class ModeDescriptor(object): class ModeDescriptor(object):
@ -32,19 +32,24 @@ class ModeDescriptor(object):
def getmode(mode): def getmode(mode):
"""Gets a mode descriptor for the given mode.""" """Gets a mode descriptor for the given mode."""
global _modes
if not _modes: if not _modes:
# initialize mode cache # initialize mode cache
from PIL import Image
from . import Image
modes = {}
# core modes # core modes
for m, (basemode, basetype, bands) in Image._MODEINFO.items(): 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 # extra experimental modes
_modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "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["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
_modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
# mapping modes # mapping modes
_modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L") modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")
_modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L") modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L")
_modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L") modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L")
# set global mode cache atomically
_modes = modes
return _modes[mode] return _modes[mode]

View File

@ -5,8 +5,9 @@
# #
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com> # Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
from PIL import Image from __future__ import print_function
from PIL import _imagingmorph
from . import Image, _imagingmorph
import re import re
LUT_SIZE = 1 << 9 LUT_SIZE = 1 << 9
@ -122,7 +123,7 @@ class LutBuilder(object):
.replace('0', 'Z') .replace('0', 'Z')
.replace('1', '0') .replace('1', '0')
.replace('Z', '1')) .replace('Z', '1'))
res = '%d' % (1-int(res)) res = 1-int(res)
patterns.append((pattern, res)) patterns.append((pattern, res))
return patterns return patterns
@ -151,9 +152,9 @@ class LutBuilder(object):
patterns += self._pattern_permute(pattern, options, result) patterns += self._pattern_permute(pattern, options, result)
# # Debugging # # Debugging
# for p,r in patterns: # for p, r in patterns:
# print p,r # print(p, r)
# print '--' # print('--')
# compile the patterns into regular expressions for speed # compile the patterns into regular expressions for speed
for i, pattern in enumerate(patterns): for i, pattern in enumerate(patterns):
@ -233,7 +234,7 @@ class MorphOp(object):
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
self.lut = bytearray(f.read()) self.lut = bytearray(f.read())
if len(self.lut) != 8192: if len(self.lut) != LUT_SIZE:
self.lut = None self.lut = None
raise Exception('Wrong size operator file!') raise Exception('Wrong size operator file!')

View File

@ -17,8 +17,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image
from PIL._util import isStringType from ._util import isStringType
import operator import operator
import functools import functools
@ -39,7 +39,7 @@ def _border(border):
def _color(color, mode): def _color(color, mode):
if isStringType(color): if isStringType(color):
from PIL import ImageColor from . import ImageColor
color = ImageColor.getcolor(color, mode) color = ImageColor.getcolor(color, mode)
return color return color
@ -206,7 +206,8 @@ def deform(image, deformer, resample=Image.BILINEAR):
:param image: The image to deform. :param image: The image to deform.
:param deformer: A deformer object. Any object that implements a :param deformer: A deformer object. Any object that implements a
**getmesh** method can be used. **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: An image.
""" """
return image.transform( return image.transform(

View File

@ -17,10 +17,7 @@
# #
import array import array
from PIL import ImageColor from . import ImageColor, GimpPaletteFile, GimpGradientFile, PaletteFile
from PIL import GimpPaletteFile
from PIL import GimpGradientFile
from PIL import PaletteFile
class ImagePalette(object): class ImagePalette(object):

View File

@ -14,7 +14,7 @@
# See the README file for information on usage and redistribution. # 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. # the Python class below is overridden by the C implementation.

View File

@ -16,8 +16,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image
from PIL._util import isPath from ._util import isPath
from io import BytesIO from io import BytesIO
qt_is_installed = True qt_is_installed = True

View File

@ -69,7 +69,7 @@ class Viewer(object):
# FIXME: auto-contrast if max() > 255? # FIXME: auto-contrast if max() > 255?
else: else:
base = Image.getmodebase(image.mode) 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) image = image.convert(base)
return self.show_image(image, **options) return self.show_image(image, **options)
@ -77,6 +77,7 @@ class Viewer(object):
# hook methods # hook methods
format = None format = None
options = {}
def get_format(self, image): def get_format(self, image):
"""Return format name, or None to save as PGM/PPM""" """Return format name, or None to save as PGM/PPM"""
@ -87,7 +88,7 @@ class Viewer(object):
def save_image(self, image): def save_image(self, image):
"""Save to temporary file, and return filename""" """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): def show_image(self, image, **options):
"""Display given image""" """Display given image"""
@ -115,7 +116,8 @@ if sys.platform == "win32":
elif sys.platform == "darwin": elif sys.platform == "darwin":
class MacViewer(Viewer): class MacViewer(Viewer):
format = "BMP" format = "PNG"
options = {'compress_level': 1}
def get_command(self, file, **options): def get_command(self, file, **options):
# on darwin open returns immediately resulting in the temp # on darwin open returns immediately resulting in the temp
@ -142,6 +144,9 @@ else:
return None return None
class UnixViewer(Viewer): class UnixViewer(Viewer):
format = "PNG"
options = {'compress_level': 1}
def show_file(self, file, **options): def show_file(self, file, **options):
command, executable = self.get_command_ex(file, **options) command, executable = self.get_command_ex(file, **options)
command = "(%s %s; rm -f %s)&" % (command, quote(file), command = "(%s %s; rm -f %s)&" % (command, quote(file),

View File

@ -25,14 +25,21 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
try: import sys
import tkinter
except ImportError:
import Tkinter
tkinter = Tkinter
del Tkinter
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 from io import BytesIO
@ -182,9 +189,15 @@ class PhotoImage(object):
except tkinter.TclError: except tkinter.TclError:
# activate Tkinter hook # activate Tkinter hook
try: try:
from PIL import _imagingtk from . import _imagingtk
try: 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: except AttributeError:
_imagingtk.tkinit(id(tk), 0) _imagingtk.tkinit(id(tk), 0)
tk.call("PyImagingPhoto", self.__photo, block.id) tk.call("PyImagingPhoto", self.__photo, block.id)
@ -264,6 +277,8 @@ class BitmapImage(object):
def getimage(photo): def getimage(photo):
""" This function is unimplemented """
"""Copies the contents of a PhotoImage to a PIL image memory.""" """Copies the contents of a PhotoImage to a PIL image memory."""
photo.tk.call("PyImagingPhotoGet", photo) photo.tk.call("PyImagingPhotoGet", photo)

View File

@ -13,7 +13,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image
class Transform(Image.ImageTransformHandler): class Transform(Image.ImageTransformHandler):

View File

@ -17,7 +17,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image
class HDC(object): class HDC(object):
@ -182,14 +182,6 @@ class Dib(object):
""" """
return self.image.tobytes() 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): class Window(object):
"""Create a Window with the given title size.""" """Create a Window with the given title size."""

View File

@ -17,7 +17,7 @@
import re import re
from PIL import Image, ImageFile from . import Image, ImageFile
__version__ = "0.2" __version__ = "0.2"

View File

@ -17,17 +17,13 @@
from __future__ import print_function 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 os
import tempfile import tempfile
__version__ = "0.3" __version__ = "0.3"
i8 = _binary.i8
i16 = _binary.i16be
i32 = _binary.i32be
o8 = _binary.o8
COMPRESSION = { COMPRESSION = {
1: "raw", 1: "raw",
5: "jpeg" 5: "jpeg"
@ -99,7 +95,7 @@ class IptcImageFile(ImageFile.ImageFile):
tagdata = self.fp.read(size) tagdata = self.fp.read(size)
else: else:
tagdata = None tagdata = None
if tag in list(self.info.keys()): if tag in self.info:
if isinstance(self.info[tag], list): if isinstance(self.info[tag], list):
self.info[tag].append(tagdata) self.info[tag].append(tagdata)
else: else:
@ -107,7 +103,7 @@ class IptcImageFile(ImageFile.ImageFile):
else: else:
self.info[tag] = tagdata self.info[tag] = tagdata
# print tag, self.info[tag] # print(tag, self.info[tag])
# mode # mode
layers = i8(self.info[(3, 60)][0]) layers = i8(self.info[(3, 60)][0])
@ -191,7 +187,7 @@ def getiptcinfo(im):
:returns: A dictionary containing IPTC information, or None if :returns: A dictionary containing IPTC information, or None if
no IPTC information block was found. no IPTC information block was found.
""" """
from PIL import TiffImagePlugin, JpegImagePlugin from . import TiffImagePlugin, JpegImagePlugin
import io import io
data = None data = None

View File

@ -12,7 +12,7 @@
# #
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFile from . import Image, ImageFile
import struct import struct
import os import os
import io import io

View File

@ -32,19 +32,16 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from __future__ import print_function
import array import array
import struct import struct
import io import io
import warnings import warnings
from struct import unpack_from from . import Image, ImageFile, TiffImagePlugin
from PIL import Image, ImageFile, TiffImagePlugin, _binary from ._binary import i8, o8, i16be as i16
from PIL.JpegPresets import presets from .JpegPresets import presets
from PIL._util import isStringType from ._util import isStringType
i8 = _binary.i8
o8 = _binary.o8
i16 = _binary.i16be
i32 = _binary.i32be
__version__ = "0.6" __version__ = "0.6"
@ -120,6 +117,25 @@ def APP(self, marker):
# plus constant header size # plus constant header size
self.info["mpoffset"] = self.fp.tell() - n + 4 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): def COM(self, marker):
# #
@ -316,7 +332,7 @@ class JpegImageFile(ImageFile.ImageFile):
if i in MARKER: if i in MARKER:
name, description, handler = MARKER[i] name, description, handler = MARKER[i]
# print hex(i), name, description # print(hex(i), name, description)
if handler is not None: if handler is not None:
handler(self, i) handler(self, i)
if i == 0xFFDA: # start of scan if i == 0xFFDA: # start of scan
@ -409,7 +425,8 @@ def _fixup_dict(src_dict):
try: try:
if len(value) == 1 and not isinstance(value, dict): if len(value) == 1 and not isinstance(value, dict):
return value[0] return value[0]
except: pass except:
pass
return value return value
return {k: _fixup(v) for k, v in src_dict.items()} return {k: _fixup(v) for k, v in src_dict.items()}
@ -491,7 +508,7 @@ def _getmp(self):
try: try:
rawmpentries = mp[0xB002] rawmpentries = mp[0xB002]
for entrynum in range(0, quant): for entrynum in range(0, quant):
unpackedentry = unpack_from( unpackedentry = struct.unpack_from(
'{}LLLHH'.format(endianness), rawmpentries, entrynum * 16) '{}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
'EntryNo2') 'EntryNo2')
@ -540,7 +557,6 @@ RAWMODE = {
"1": "L", "1": "L",
"L": "L", "L": "L",
"RGB": "RGB", "RGB": "RGB",
"RGBA": "RGB",
"RGBX": "RGB", "RGBX": "RGB",
"CMYK": "CMYK;I", # assume adobe conventions "CMYK": "CMYK;I", # assume adobe conventions
"YCbCr": "YCbCr", "YCbCr": "YCbCr",
@ -589,14 +605,6 @@ def _save(im, fp, filename):
except KeyError: except KeyError:
raise IOError("cannot write mode %s as JPEG" % im.mode) 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 info = im.encoderinfo
dpi = [int(round(x)) for x in info.get("dpi", (0, 0))] 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 # "progressive" is the official name, but older documentation
# says "progression" # says "progression"
# FIXME: issue a warning if the wrong form is used (post-1.1.7) # FIXME: issue a warning if the wrong form is used (post-1.1.7)
progressive = info.get("progressive", False) or\ progressive = (info.get("progressive", False) or
info.get("progression", False) info.get("progression", False))
optimize = info.get("optimize", False) optimize = info.get("optimize", False)
@ -716,15 +724,19 @@ def _save(im, fp, filename):
# https://github.com/matthewwithanm/django-imagekit/issues/50 # https://github.com/matthewwithanm/django-imagekit/issues/50
bufsize = 0 bufsize = 0
if optimize or progressive: 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. # 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] bufsize = 2 * im.size[0] * im.size[1]
else: else:
bufsize = im.size[0] * im.size[1] bufsize = im.size[0] * im.size[1]
# The exif info needs to be written as one block, + APP1, + one spare byte. # The exif info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough # Ensure that our buffer is big enough. Same with the icc_profile block.
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5) 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) ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)

View File

@ -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 You can convert the dict format to the preset format with the
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function. `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
""" """

View File

@ -17,7 +17,7 @@
# #
import struct import struct
from PIL import Image, ImageFile from . import Image, ImageFile
__version__ = "0.2" __version__ = "0.2"
@ -66,6 +66,7 @@ class McIdasImageFile(ImageFile.ImageFile):
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))] self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# registry # registry

View File

@ -17,8 +17,9 @@
# #
from PIL import Image, TiffImagePlugin from . import Image, TiffImagePlugin
from PIL.OleFileIO import MAGIC, OleFileIO
import olefile
__version__ = "0.1" __version__ = "0.1"
@ -28,7 +29,7 @@ __version__ = "0.1"
def _accept(prefix): def _accept(prefix):
return prefix[:8] == MAGIC return prefix[:8] == olefile.MAGIC
## ##
@ -38,6 +39,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
format = "MIC" format = "MIC"
format_description = "Microsoft Image Composer" format_description = "Microsoft Image Composer"
_close_exclusive_fp_after_loading = False
def _open(self): def _open(self):
@ -45,7 +47,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
# to be a Microsoft Image Composer file # to be a Microsoft Image Composer file
try: try:
self.ole = OleFileIO(self.fp) self.ole = olefile.OleFileIO(self.fp)
except IOError: except IOError:
raise SyntaxError("not an MIC file; invalid OLE file") raise SyntaxError("not an MIC file; invalid OLE file")
@ -95,6 +97,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
return self.frame return self.frame
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -14,8 +14,8 @@
# #
from PIL import Image, ImageFile from . import Image, ImageFile
from PIL._binary import i8 from ._binary import i8
__version__ = "0.1" __version__ = "0.1"

View File

@ -18,7 +18,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, JpegImagePlugin from . import Image, JpegImagePlugin
__version__ = "0.1" __version__ = "0.1"
@ -39,7 +39,8 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
format = "MPO" format = "MPO"
format_description = "MPO (CIPA DC-007)" format_description = "MPO (CIPA DC-007)"
_close_exclusive_fp_after_loading = False
def _open(self): def _open(self):
self.fp.seek(0) # prep the fp in order to pass the JPEG test self.fp.seek(0) # prep the fp in order to pass the JPEG test
JpegImagePlugin.JpegImageFile._open(self) JpegImagePlugin.JpegImageFile._open(self)

View File

@ -1,6 +1,5 @@
# #
# The Python Imaging Library. # The Python Imaging Library.
# $Id$
# #
# MSP file handling # MSP file handling
# #
@ -9,15 +8,25 @@
# History: # History:
# 95-09-05 fl Created # 95-09-05 fl Created
# 97-01-03 fl Read/write MSP images # 97-01-03 fl Read/write MSP images
# 17-02-21 es Fixed RLE interpretation
# #
# Copyright (c) Secret Labs AB 1997. # Copyright (c) Secret Labs AB 1997.
# Copyright (c) Fredrik Lundh 1995-97. # Copyright (c) Fredrik Lundh 1995-97.
# Copyright (c) Eric Soroos 2017.
# #
# See the README file for information on usage and redistribution. # 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 . import Image, ImageFile
from PIL import Image, ImageFile, _binary from ._binary import i16le as i16, o16le as o16, i8
import struct
import io
__version__ = "0.1" __version__ = "0.1"
@ -25,8 +34,6 @@ __version__ = "0.1"
# #
# read MSP files # read MSP files
i16 = _binary.i16le
def _accept(prefix): def _accept(prefix):
return prefix[:4] in [b"DanM", b"LinS"] return prefix[:4] in [b"DanM", b"LinS"]
@ -61,13 +68,93 @@ class MspImageFile(ImageFile.ImageFile):
if s[:4] == b"DanM": if s[:4] == b"DanM":
self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))] self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
else: else:
self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)] self.tile = [("MSP", (0, 0)+self.size, 32, 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) # write MSP files (uncompressed only)
o16 = _binary.o16le
def _save(im, fp, filename): def _save(im, fp, filename):
@ -95,6 +182,7 @@ def _save(im, fp, filename):
# image body # image body
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
# #
# registry # registry

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import EpsImagePlugin from . import EpsImagePlugin
import sys import sys
## ##

View File

@ -13,7 +13,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL._binary import o8 from ._binary import o8
## ##

View File

@ -7,7 +7,8 @@
# Image plugin for Palm pixmap images (output only). # 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" __version__ = "1.0"
@ -108,9 +109,6 @@ _COMPRESSION_TYPES = {
"scanline": 0x00, "scanline": 0x00,
} }
o8 = _binary.o8
o16b = _binary.o16be
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -15,12 +15,11 @@
# #
from PIL import Image, ImageFile, _binary from . import Image, ImageFile
from ._binary import i8
__version__ = "0.1" __version__ = "0.1"
i8 = _binary.i8
## ##
# Image plugin for PhotoCD images. This plugin only reads the 768x512 # Image plugin for PhotoCD images. This plugin only reads the 768x512
@ -42,8 +41,9 @@ class PcdImageFile(ImageFile.ImageFile):
raise SyntaxError("not a PCD file") raise SyntaxError("not a PCD file")
orientation = i8(s[1538]) & 3 orientation = i8(s[1538]) & 3
self.tile_post_rotate = None
if orientation == 1: if orientation == 1:
self.tile_post_rotate = 90 # hack self.tile_post_rotate = 90
elif orientation == 3: elif orientation == 3:
self.tile_post_rotate = -90 self.tile_post_rotate = -90
@ -51,6 +51,13 @@ class PcdImageFile(ImageFile.ImageFile):
self.size = 768, 512 # FIXME: not correct for rotated images! self.size = 768, 512 # FIXME: not correct for rotated images!
self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)] self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)]
def 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 # registry

View File

@ -16,9 +16,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image from . import Image, FontFile
from PIL import FontFile from ._binary import i8, i16le as l16, i32le as l32, i16be as b16, i32be as b32
from PIL import _binary
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# declarations # declarations
@ -42,12 +41,6 @@ BYTES_PER_ROW = [
lambda bits: ((bits+63) >> 3) & ~7, 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): def sz(s, o):
return s[o:s.index(b"\0", o)] return s[o:s.index(b"\0", o)]

View File

@ -25,17 +25,12 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from __future__ import print_function
import logging 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__) logger = logging.getLogger(__name__)
i8 = _binary.i8
i16 = _binary.i16le
o8 = _binary.o8
__version__ = "0.6" __version__ = "0.6"
@ -123,8 +118,6 @@ SAVE = {
"RGB": (5, 8, 3, "RGB;L"), "RGB": (5, 8, 3, "RGB;L"),
} }
o16 = _binary.o16le
def _save(im, fp, filename, check=0): def _save(im, fp, filename, check=0):

View File

@ -20,8 +20,8 @@
# Image plugin for PDF images (output only). # Image plugin for PDF images (output only).
## ##
from PIL import Image, ImageFile from . import Image, ImageFile, ImageSequence
from PIL._binary import i8 from ._binary import i8
import io import io
__version__ = "0.4" __version__ = "0.4"
@ -37,11 +37,11 @@ __version__ = "0.4"
# 4. page # 4. page
# 5. page contents # 5. page contents
def _obj(fp, obj, **dict): def _obj(fp, obj, **dictionary):
fp.write("%d 0 obj\n" % obj) fp.write("%d 0 obj\n" % obj)
if dict: if dictionary:
fp.write("<<\n") fp.write("<<\n")
for k, v in dict.items(): for k, v in dictionary.items():
if v is not None: if v is not None:
fp.write("/%s %s\n" % (k, v)) fp.write("/%s %s\n" % (k, v))
fp.write(">>\n") fp.write(">>\n")
@ -133,13 +133,24 @@ def _save(im, fp, filename, save_all=False):
# #
# pages # pages
numberOfPages = 1 ims = [im]
if save_all: if save_all:
try: append_images = im.encoderinfo.get("append_images", [])
numberOfPages = im.n_frames for append_im in append_images:
except AttributeError: if append_im.mode != im.mode:
# Image format does not have n_frames. It is a single frame image append_im = append_im.convert(im.mode)
pass 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" pages = [str(pageNumber*3+4)+" 0 R"
for pageNumber in range(0, numberOfPages)] for pageNumber in range(0, numberOfPages)]
@ -151,90 +162,92 @@ def _save(im, fp, filename, save_all=False):
Kids="["+"\n".join(pages)+"]") Kids="["+"\n".join(pages)+"]")
_endobj(fp) _endobj(fp)
for pageNumber in range(0, numberOfPages): pageNumber = 0
im.seek(pageNumber) for imSequence in ims:
for im in ImageSequence.Iterator(imSequence):
#
# image
# op = io.BytesIO()
# image
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: # Get image characteristics
# 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)
# width, height = im.size
# Get image characteristics
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()) fp.write("stream\n")
_obj( fp.fp.write(op.getvalue())
fp, pageNumber*3+3, fp.write("\nendstream\n")
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") _endobj(fp)
fp.fp.write(op.getvalue())
fp.write("\nendstream\n")
_endobj(fp) #
# page
# xref.append(fp.tell())
# page _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) # page contents
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)
# op = TextWriter(io.BytesIO())
# page contents
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( xref.append(fp.tell())
"q %d 0 0 %d 0 0 cm /image Do Q\n" % ( _obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
xref.append(fp.tell()) fp.write("stream\n")
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue())) fp.fp.write(op.fp.getvalue())
fp.write("\nendstream\n")
fp.write("stream\n") _endobj(fp)
fp.fp.write(op.fp.getvalue())
fp.write("\nendstream\n")
_endobj(fp) pageNumber += 1
# #
# trailer # trailer

View File

@ -19,16 +19,15 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image, ImageFile, _binary from . import Image, ImageFile
from ._binary import i16le as i16
__version__ = "0.1" __version__ = "0.1"
# #
# helpers # helpers
i16 = _binary.i16le
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"\200\350\000\000" return prefix[:4] == b"\200\350\000\000"
@ -63,6 +62,7 @@ class PixarImageFile(ImageFile.ImageFile):
# create tile descriptor (assuming "dumped") # create tile descriptor (assuming "dumped")
self.tile = [("raw", (0, 0)+self.size, 1024, (self.mode, 0, 1))] self.tile = [("raw", (0, 0)+self.size, 1024, (self.mode, 0, 1))]
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -31,23 +31,18 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from __future__ import print_function
import logging import logging
import re import re
import zlib import zlib
import struct 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" __version__ = "0.9"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
i8 = _binary.i8
i16 = _binary.i16be
i32 = _binary.i32be
is_cid = re.compile(br"\w\w\w\w").match is_cid = re.compile(br"\w\w\w\w").match
@ -118,7 +113,8 @@ class ChunkStream(object):
length = i32(s) length = i32(s)
if not is_cid(cid): 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 return cid, pos, length
@ -621,10 +617,6 @@ class PngImageFile(ImageFile.ImageFile):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG writer # PNG writer
o8 = _binary.o8
o16 = _binary.o16be
o32 = _binary.o32be
_OUTMODES = { _OUTMODES = {
# supported PIL modes, and corresponding rawmodes/bits/color combinations # supported PIL modes, and corresponding rawmodes/bits/color combinations
"1": ("1", b'\x01\x00'), "1": ("1", b'\x01\x00'),
@ -722,6 +714,32 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
b'\0', # 11: filter category b'\0', # 11: filter category
b'\0') # 12: interlace flag 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": if im.mode == "P":
palette_byte_number = (2 ** bits) * 3 palette_byte_number = (2 ** bits) * 3
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] 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") info = im.encoderinfo.get("pnginfo")
if info: if info:
chunks = [b"bKGD", b"hIST"]
for cid, data in info.chunks: for cid, data in info.chunks:
chunk(fp, cid, data) if cid in chunks:
chunks.remove(cid)
icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile")) chunk(fp, cid, data)
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)
ImageFile._save(im, _idat(fp, chunk), ImageFile._save(im, _idat(fp, chunk),
[("zip", (0, 0)+im.size, 0, rawmode)]) [("zip", (0, 0)+im.size, 0, rawmode)])

View File

@ -17,7 +17,7 @@
import string import string
from PIL import Image, ImageFile from . import Image, ImageFile
__version__ = "0.2" __version__ = "0.2"

View File

@ -18,7 +18,8 @@
__version__ = "0.4" __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 = { MODES = {
# (photoshop mode, bits) -> (pil mode, required channels) # (photoshop mode, bits) -> (pil mode, required channels)
@ -33,13 +34,6 @@ MODES = {
(9, 8): ("LAB", 3) (9, 8): ("LAB", 3)
} }
#
# helpers
i8 = _binary.i8
i16 = _binary.i16be
i32 = _binary.i32be
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
# read PSD images # read PSD images

View File

@ -20,8 +20,6 @@
# Access.c implementation. # Access.c implementation.
# #
from __future__ import print_function
import logging import logging
import sys import sys
@ -167,7 +165,7 @@ class _PyAccess8(PyAccess):
try: try:
# integer # integer
self.pixels[y][x] = min(color, 255) self.pixels[y][x] = min(color, 255)
except: except TypeError:
# tuple # tuple
self.pixels[y][x] = min(color[0], 255) self.pixels[y][x] = min(color[0], 255)
@ -184,7 +182,7 @@ class _PyAccessI16_N(PyAccess):
try: try:
# integer # integer
self.pixels[y][x] = min(color, 65535) self.pixels[y][x] = min(color, 65535)
except: except TypeError:
# tuple # tuple
self.pixels[y][x] = min(color[0], 65535) self.pixels[y][x] = min(color[0], 65535)
@ -272,7 +270,7 @@ class _PyAccessF(PyAccess):
try: try:
# not a tuple # not a tuple
self.pixels[y][x] = color self.pixels[y][x] = color
except: except TypeError:
# tuple # tuple
self.pixels[y][x] = color[0] self.pixels[y][x] = color[0]

View File

@ -7,9 +7,12 @@
# See "The SGI Image File Format (Draft version 0.97)", Paul Haeberli. # See "The SGI Image File Format (Draft version 0.97)", Paul Haeberli.
# <ftp://ftp.sgi.com/graphics/SGIIMAGESPEC> # <ftp://ftp.sgi.com/graphics/SGIIMAGESPEC>
# #
#
# History: # History:
# 2016-16-10 mb Add save method without compression
# 1995-09-10 fl Created # 1995-09-10 fl Created
# #
# Copyright (c) 2016 by Mickael Bonfill.
# Copyright (c) 2008 by Karsten Hiddemann. # Copyright (c) 2008 by Karsten Hiddemann.
# Copyright (c) 1997 by Secret Labs AB. # Copyright (c) 1997 by Secret Labs AB.
# Copyright (c) 1995 by Fredrik Lundh. # 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" __version__ = "0.3"
i8 = _binary.i8
i16 = _binary.i16be
def _accept(prefix): def _accept(prefix):
@ -76,12 +79,79 @@ class SgiImageFile(ImageFile.ImageFile):
elif compression == 1: elif compression == 1:
raise ValueError("SGI RLE encoding not supported") 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 # registry
Image.register_open(SgiImageFile.format, SgiImageFile, _accept) 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, ".bw")
Image.register_extension(SgiImageFile.format, ".rgb") Image.register_extension(SgiImageFile.format, ".rgb")
Image.register_extension(SgiImageFile.format, ".rgba") Image.register_extension(SgiImageFile.format, ".rgba")
Image.register_extension(SgiImageFile.format, ".sgi") Image.register_extension(SgiImageFile.format, ".sgi")
# End of file

View File

@ -27,10 +27,10 @@
# image data from electron microscopy and tomography. # image data from electron microscopy and tomography.
# #
# Spider home page: # 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: # 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 from __future__ import print_function
@ -75,7 +75,7 @@ def isSpiderHeader(t):
labrec = int(h[13]) # no. records in file header labrec = int(h[13]) # no. records in file header
labbyt = int(h[22]) # total no. of bytes in header labbyt = int(h[22]) # total no. of bytes in header
lenbyt = int(h[23]) # record length in bytes 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): if labbyt != (labrec * lenbyt):
return 0 return 0
# looks like a valid header # looks like a valid header
@ -83,9 +83,8 @@ def isSpiderHeader(t):
def isSpiderImage(filename): def isSpiderImage(filename):
fp = open(filename, 'rb') with open(filename, 'rb') as fp:
f = fp.read(92) # read 23 * 4 bytes f = fp.read(92) # read 23 * 4 bytes
fp.close()
t = struct.unpack('>23f', f) # try big-endian first t = struct.unpack('>23f', f) # try big-endian first
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
@ -98,6 +97,7 @@ class SpiderImageFile(ImageFile.ImageFile):
format = "SPIDER" format = "SPIDER"
format_description = "Spider 2D image" format_description = "Spider 2D image"
_close_exclusive_fp_after_loading = False
def _open(self): def _open(self):
# check header # check header

View File

@ -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" __version__ = "0.3"
i32 = _binary.i32be
def _accept(prefix): def _accept(prefix):
return len(prefix) >= 4 and i32(prefix) == 0x59a66a95 return len(prefix) >= 4 and i32(prefix) == 0x59a66a95
@ -38,7 +37,8 @@ class SunImageFile(ImageFile.ImageFile):
def _open(self): 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 # typedef struct _SunRaster
# { # {
@ -52,7 +52,6 @@ class SunImageFile(ImageFile.ImageFile):
# DWORD ColorMapLength; /* Size of the color map in bytes */ # DWORD ColorMapLength; /* Size of the color map in bytes */
# } SUNRASTER; # } SUNRASTER;
# HEAD # HEAD
s = self.fp.read(32) s = self.fp.read(32)
if i32(s) != 0x59a66a95: if i32(s) != 0x59a66a95:
@ -63,11 +62,11 @@ class SunImageFile(ImageFile.ImageFile):
self.size = i32(s[4:8]), i32(s[8:12]) self.size = i32(s[4:8]), i32(s[8:12])
depth = i32(s[12:16]) 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]) 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]) palette_length = i32(s[28:32])
if depth == 1: if depth == 1:
self.mode, rawmode = "1", "1;I" self.mode, rawmode = "1", "1;I"
elif depth == 4: elif depth == 4:
@ -85,23 +84,23 @@ class SunImageFile(ImageFile.ImageFile):
else: else:
self.mode, rawmode = 'RGB', 'BGRX' self.mode, rawmode = 'RGB', 'BGRX'
else: else:
raise SyntaxError("Unsupported Mode/Bit Depth") raise SyntaxError("Unsupported Mode/Bit Depth")
if palette_length: if palette_length:
if palette_length > 1024: if palette_length > 1024:
raise SyntaxError("Unsupported Color Palette Length") raise SyntaxError("Unsupported Color Palette Length")
if palette_type != 1: if palette_type != 1:
raise SyntaxError("Unsupported Palette Type") raise SyntaxError("Unsupported Palette Type")
offset = offset + palette_length offset = offset + palette_length
self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length)) self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length))
if self.mode == "L": if self.mode == "L":
self.mode = "P" self.mode = "P"
rawmode = rawmode.replace('L', 'P') rawmode = rawmode.replace('L', 'P')
# 16 bit boundaries on stride # 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 type: Type is the version (or flavor) of the bitmap
# file. The following values are typically found in the Type # 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 # RGB looks similar to standard, but RGB byte order
# TIFF and IFF mean that they were converted from T/IFF # TIFF and IFF mean that they were converted from T/IFF
# Experimental means that it's something else. # 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): if file_type in (0, 1, 3, 4, 5):
self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))] 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)] self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)]
else: else:
raise SyntaxError('Unsupported Sun Raster file type') raise SyntaxError('Unsupported Sun Raster file type')
# #
# registry # registry

View File

@ -14,7 +14,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import ContainerIO from . import ContainerIO
## ##

View File

@ -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" __version__ = "0.3"
@ -26,9 +27,6 @@ __version__ = "0.3"
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Read RGA file # Read RGA file
i8 = _binary.i8
i16 = _binary.i16le
MODES = { MODES = {
# map imagetype/depth to rawmode # map imagetype/depth to rawmode
@ -132,10 +130,6 @@ class TgaImageFile(ImageFile.ImageFile):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write TGA file # Write TGA file
o8 = _binary.o8
o16 = _binary.o16le
o32 = _binary.o32le
SAVE = { SAVE = {
"1": ("1", 1, 0, 3), "1": ("1", 1, 0, 3),
"L": ("L", 8, 0, 3), "L": ("L", 8, 0, 3),

View File

@ -41,10 +41,8 @@
from __future__ import division, print_function from __future__ import division, print_function
from PIL import Image, ImageFile from . import Image, ImageFile, ImagePalette, TiffTags
from PIL import ImagePalette from ._binary import i8, o8
from PIL import _binary
from PIL import TiffTags
import collections import collections
from fractions import Fraction from fractions import Fraction
@ -71,9 +69,6 @@ IFD_LEGACY_API = True
II = b"II" # little-endian (Intel style) II = b"II" # little-endian (Intel style)
MM = b"MM" # big-endian (Motorola style) MM = b"MM" # big-endian (Motorola style)
i8 = _binary.i8
o8 = _binary.o8
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Read TIFF files # 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) n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
return n_d[::-1] if inv else n_d return n_d[::-1] if inv else n_d
## ##
# Wrapper for TIFF IFDs. # Wrapper for TIFF IFDs.
@ -338,7 +334,7 @@ class IFDRational(Rational):
'rfloordiv','mod','rmod', 'pow','rpow', 'pos', 'neg', 'rfloordiv','mod','rmod', 'pow','rpow', 'pos', 'neg',
'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'nonzero', 'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'nonzero',
'ceil', 'floor', 'round'] '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__') __add__ = _delegate('__add__')
@ -467,15 +463,6 @@ class ImageFileDirectory_v2(collections.MutableMapping):
def __str__(self): def __str__(self):
return str(dict(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): def named(self):
""" """
:returns: dict of name|key: value :returns: dict of name|key: value
@ -540,7 +527,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
self.tagtype[tag] = 2 self.tagtype[tag] = 2
if self.tagtype[tag] == 7 and bytes is not str: 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) 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 _register_loader(idx, size):
def decorator(func): def decorator(func):
from PIL.TiffTags import TYPES from .TiffTags import TYPES
if func.__name__.startswith("load_"): if func.__name__.startswith("load_"):
TYPES[idx] = func.__name__[5:].replace("_", " ") TYPES[idx] = func.__name__[5:].replace("_", " ")
_load_dispatch[idx] = size, func _load_dispatch[idx] = size, func
@ -583,7 +571,7 @@ class ImageFileDirectory_v2(collections.MutableMapping):
return decorator return decorator
def _register_basic(idx_fmt_name): def _register_basic(idx_fmt_name):
from PIL.TiffTags import TYPES from .TiffTags import TYPES
idx, fmt, name = idx_fmt_name idx, fmt, name = idx_fmt_name
TYPES[idx] = name TYPES[idx] = name
size = struct.calcsize("=" + fmt) size = struct.calcsize("=" + fmt)
@ -593,9 +581,13 @@ class ImageFileDirectory_v2(collections.MutableMapping):
b"".join(self._pack(fmt, value) for value in values)) b"".join(self._pack(fmt, value) for value in values))
list(map(_register_basic, list(map(_register_basic,
[(3, "H", "short"), (4, "L", "long"), [(3, "H", "short"),
(6, "b", "signed byte"), (8, "h", "signed short"), (4, "L", "long"),
(9, "l", "signed long"), (11, "f", "float"), (12, "d", "double")])) (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. @_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data, legacy_api=True): def load_byte(self, data, legacy_api=True):
@ -621,7 +613,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
@_register_loader(5, 8) @_register_loader(5, 8)
def load_rational(self, data, legacy_api=True): def load_rational(self, data, legacy_api=True):
vals = self._unpack("{}L".format(len(data) // 4), data) 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) return tuple(combine(num, denom)
for num, denom in zip(vals[::2], vals[1::2])) for num, denom in zip(vals[::2], vals[1::2]))
@ -641,7 +634,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
@_register_loader(10, 8) @_register_loader(10, 8)
def load_signed_rational(self, data, legacy_api=True): def load_signed_rational(self, data, legacy_api=True):
vals = self._unpack("{}l".format(len(data) // 4), data) 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) return tuple(combine(num, denom)
for num, denom in zip(vals[::2], vals[1::2])) for num, denom in zip(vals[::2], vals[1::2]))
@ -665,7 +659,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
try: try:
for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]): 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: if DEBUG:
tagname = TiffTags.lookup(tag).name tagname = TiffTags.lookup(tag).name
typname = TYPES.get(typ, "unknown") typname = TYPES.get(typ, "unknown")
@ -693,8 +688,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
if len(data) != size: if len(data) != size:
warnings.warn("Possibly corrupt EXIF data. " warnings.warn("Possibly corrupt EXIF data. "
"Expecting to read %d bytes but only got %d. " "Expecting to read %d bytes but only got %d."
"Skipping tag %s" % (size, len(data), tag)) " Skipping tag %s" % (size, len(data), tag))
continue continue
if not data: if not data:
@ -753,7 +748,8 @@ class ImageFileDirectory_v2(collections.MutableMapping):
if len(data) <= 4: if len(data) <= 4:
entries.append((tag, typ, count, data.ljust(4, b"\0"), b"")) entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
else: 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 offset += (len(data) + 1) // 2 * 2 # pad to word
# update strip offset data to point beyond auxiliary data # update strip offset data to point beyond auxiliary data
@ -782,6 +778,7 @@ class ImageFileDirectory_v2(collections.MutableMapping):
return offset return offset
ImageFileDirectory_v2._load_dispatch = _load_dispatch ImageFileDirectory_v2._load_dispatch = _load_dispatch
ImageFileDirectory_v2._write_dispatch = _write_dispatch ImageFileDirectory_v2._write_dispatch = _write_dispatch
for idx, name in TYPES.items(): for idx, name in TYPES.items():
@ -800,7 +797,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
ifd = ImageFileDirectory_v1() ifd = ImageFileDirectory_v1()
ifd[key] = 'Some Data' ifd[key] = 'Some Data'
ifd.tagtype[key] = 2 ifd.tagtype[key] = 2
print ifd[key] print(ifd[key])
('Some Data',) ('Some Data',)
Also contains a dictionary of tag types as read from the tiff image file, 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 = "TIFF"
format_description = "Adobe TIFF" format_description = "Adobe TIFF"
_close_exclusive_fp_after_loading = False
def _open(self): def _open(self):
"Open the first image in a TIFF file" "Open the first image in a TIFF file"
@ -974,6 +972,7 @@ class TiffImageFile(ImageFile.ImageFile):
self.__frame += 1 self.__frame += 1
self.fp.seek(self._frame_pos[frame]) self.fp.seek(self._frame_pos[frame])
self.tag_v2.load(self.fp) self.tag_v2.load(self.fp)
self.__next = self.tag_v2.next
# fill the legacy tag/ifd entries # fill the legacy tag/ifd entries
self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2) self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
self.__frame = frame self.__frame = frame
@ -1006,9 +1005,6 @@ class TiffImageFile(ImageFile.ImageFile):
# Section 14: Differencing Predictor # Section 14: Differencing Predictor
self.decoderconfig = (self.tag_v2[PREDICTOR],) self.decoderconfig = (self.tag_v2[PREDICTOR],)
if ICCPROFILE in self.tag_v2:
self.info['icc_profile'] = self.tag_v2[ICCPROFILE]
return args return args
def load(self): def load(self):
@ -1016,6 +1012,12 @@ class TiffImageFile(ImageFile.ImageFile):
return self._load_libtiff() return self._load_libtiff()
return super(TiffImageFile, self).load() 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): def _load_libtiff(self):
""" Overload method triggered when we detect a compressed tiff """ Overload method triggered when we detect a compressed tiff
Calls out to libtiff """ Calls out to libtiff """
@ -1093,16 +1095,14 @@ class TiffImageFile(ImageFile.ImageFile):
self.tile = [] self.tile = []
self.readonly = 0 self.readonly = 0
# libtiff closed the fp in a, we need to close self.fp, if possible # libtiff closed the fp in a, we need to close self.fp, if possible
if hasattr(self.fp, 'close'): if self._exclusive_fp:
if not self.__next: if self.__frame == 0 and not self.__next:
self.fp.close() self.fp.close()
self.fp = None # might be shared self.fp = None # might be shared
if err < 0: if err < 0:
raise IOError(err) raise IOError(err)
self.load_end()
return Image.Image.load(self) return Image.Image.load(self)
def _setup(self): def _setup(self):
@ -1138,7 +1138,7 @@ class TiffImageFile(ImageFile.ImageFile):
sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,)) sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))
if (len(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 # SAMPLEFORMAT is properly per band, so an RGB image will
# be (1,1,1). But, we don't support per band pixel types, # be (1,1,1). But, we don't support per band pixel types,
# and anything more than one band is a uint8. So, just # 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) yres = self.tag_v2.get(Y_RESOLUTION, 1)
if xres and yres: 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 if resunit == 2: # dots per inch
self.info["dpi"] = xres, yres self.info["dpi"] = xres, yres
elif resunit == 3: # dots per centimeter. convert to dpi elif resunit == 3: # dots per centimeter. convert to dpi
self.info["dpi"] = xres * 2.54, yres * 2.54 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 else: # No absolute unit of measurement
self.info["resolution"] = xres, yres self.info["resolution"] = xres, yres
@ -1197,7 +1202,7 @@ class TiffImageFile(ImageFile.ImageFile):
"tiff_sgilog24", "tiff_sgilog24",
"tiff_raw_16"]: "tiff_raw_16"]:
# if DEBUG: # if DEBUG:
# print "Activating g4 compression for whole file" # print("Activating g4 compression for whole file")
# Decoder expects entire file as one tile. # Decoder expects entire file as one tile.
# There's a buffer size limit in load (64k) # There's a buffer size limit in load (64k)
@ -1281,11 +1286,17 @@ class TiffImageFile(ImageFile.ImageFile):
print("- unsupported data organization") print("- unsupported data organization")
raise SyntaxError("unknown 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 # fixup palette descriptor
if self.mode == "P": if self.mode == "P":
palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]] palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
self.palette = ImagePalette.raw("RGB;L", b"".join(palette)) self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write TIFF files # Write TIFF files
@ -1362,10 +1373,10 @@ def _save(im, fp, filename):
ifd[key] = im.tag_v2[key] ifd[key] = im.tag_v2[key]
ifd.tagtype[key] = im.tag_v2.tagtype[key] ifd.tagtype[key] = im.tag_v2.tagtype[key]
# preserve ICC profile (should also work when saving other formats # preserve ICC profile (should also work when saving other formats
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
if "icc_profile" in im.info: if "icc_profile" in im.info:
ifd[ICCPROFILE] = im.info["icc_profile"] ifd[ICCPROFILE] = im.info["icc_profile"]
for key, name in [(IMAGEDESCRIPTION, "description"), for key, name in [(IMAGEDESCRIPTION, "description"),
(X_RESOLUTION, "resolution"), (X_RESOLUTION, "resolution"),
@ -1377,11 +1388,6 @@ def _save(im, fp, filename):
(DATE_TIME, "date_time"), (DATE_TIME, "date_time"),
(ARTIST, "artist"), (ARTIST, "artist"),
(COPYRIGHT, "copyright")]: (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: if name in im.encoderinfo:
ifd[key] = im.encoderinfo[name] ifd[key] = im.encoderinfo[name]
@ -1491,6 +1497,7 @@ def _save(im, fp, filename):
# just to access o32 and o16 (using correct byte order) # just to access o32 and o16 (using correct byte order)
im._debug_multipage = ifd im._debug_multipage = ifd
class AppendingTiffWriter: class AppendingTiffWriter:
fieldSizes = [ fieldSizes = [
0, # None 0, # None
@ -1678,13 +1685,10 @@ class AppendingTiffWriter:
def fixIFD(self): def fixIFD(self):
numTags = self.readShort() 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): for i in range(numTags):
tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8)) tag, fieldType, count = struct.unpack(self.tagFormat,
#trace(" at %X: tag %u (0x%X), type %u, count %u", self.f.tell()-8, self.f.read(8))
# tag, tag, fieldType, count)
fieldSize = self.fieldSizes[fieldType] fieldSize = self.fieldSizes[fieldType]
totalSize = fieldSize * count totalSize = fieldSize * count
@ -1736,21 +1740,34 @@ class AppendingTiffWriter:
else: else:
self.rewriteLastLong(offset) self.rewriteLastLong(offset)
def _save_all(im, fp, filename): 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) return _save(im, fp, filename)
cur_idx = im.tell() cur_idx = im.tell()
try: try:
with AppendingTiffWriter(fp) as tf: with AppendingTiffWriter(fp) as tf:
for idx in range(im.n_frames): for ims in [im]+append_images:
im.seek(idx) ims.encoderinfo = encoderinfo
im.load() ims.encoderconfig = encoderconfig
_save(im, tf, filename) if not hasattr(ims, "n_frames"):
tf.newFrame() nfr = 1
else:
nfr = ims.n_frames
for idx in range(nfr):
ims.seek(idx)
ims.load()
_save(ims, tf, filename)
tf.newFrame()
finally: finally:
im.seek(cur_idx) im.seek(cur_idx)
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Register # Register

View File

@ -18,12 +18,11 @@
# the WalImageFile.open() function instead. # the WalImageFile.open() function instead.
# This reader is based on the specification available from: # 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. # and has been tested with a few sample files found using google.
from __future__ import print_function from . import Image
from ._binary import i32le as i32
from PIL import Image, _binary
try: try:
import builtins import builtins
@ -31,8 +30,6 @@ except ImportError:
import __builtin__ import __builtin__
builtins = __builtin__ builtins = __builtin__
i32 = _binary.i32le
def open(filename): def open(filename):
""" """
@ -47,33 +44,35 @@ def open(filename):
# FIXME: modify to return a WalImageFile instance instead of # FIXME: modify to return a WalImageFile instance instead of
# plain Image object ? # 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"): if hasattr(filename, "read"):
fp = filename return imopen(filename)
else: else:
fp = builtins.open(filename, "rb") with builtins.open(filename, "rb") as fp:
return 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)
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
quake2palette = ( quake2palette = (
# default palette taken from piffo 0.93 by Hans Häggström # default palette taken from piffo 0.93 by Hans Häggström

View File

@ -1,7 +1,5 @@
from PIL import Image from . import Image, ImageFile, _webp
from PIL import ImageFile
from io import BytesIO from io import BytesIO
from PIL import _webp
_VALID_WEBP_MODES = { _VALID_WEBP_MODES = {
@ -43,7 +41,7 @@ class WebPImageFile(ImageFile.ImageFile):
self.tile = [("raw", (0, 0) + self.size, 0, self.mode)] self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
def _getexif(self): def _getexif(self):
from PIL.JpegImagePlugin import _getexif from .JpegImagePlugin import _getexif
return _getexif(self) return _getexif(self)

View File

@ -14,8 +14,16 @@
# #
# See the README file for information on usage and redistribution. # 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" __version__ = "0.2"
@ -53,24 +61,11 @@ if hasattr(Image.core, "drawwmf"):
register_handler(WmfHandler()) 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 # Read WMF file
def _accept(prefix): def _accept(prefix):
return ( return (
prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or
@ -111,7 +106,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
self.info["dpi"] = 72 self.info["dpi"] = 72
# print self.mode, self.size, self.info # print(self.mode, self.size, self.info)
# sanity check (standard metafile header) # sanity check (standard metafile header)
if s[22:26] != b"\x01\x00\t\x00": if s[22:26] != b"\x01\x00\t\x00":
@ -121,13 +116,13 @@ class WmfStubImageFile(ImageFile.StubImageFile):
# enhanced metafile # enhanced metafile
# get bounding box # get bounding box
x0 = dword(s, 8) x0 = _long(s, 8)
y0 = dword(s, 12) y0 = _long(s, 12)
x1 = dword(s, 16) x1 = _long(s, 16)
y1 = dword(s, 20) y1 = _long(s, 20)
# get frame (in 0.01 millimeter units) # get frame (in 0.01 millimeter units)
frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36) frame = _long(s, 24), _long(s, 28), _long(s, 32), _long(s, 36)
# normalize size to 72 dots per inch # normalize size to 72 dots per inch
size = x1 - x0, y1 - y0 size = x1 - x0, y1 - y0

View File

@ -17,12 +17,11 @@
# FIXME: make save work (this requires quantization support) # 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" __version__ = "0.1"
o8 = _binary.o8
_MAGIC = b"P7 332" _MAGIC = b"P7 332"
# standard color palette for thumbnails (RGB332) # standard color palette for thumbnails (RGB332)
@ -48,7 +47,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
def _open(self): def _open(self):
# check magic # check magic
if self.fp.read(6) != _MAGIC: if not _accept(self.fp.read(6)):
raise SyntaxError("not an XV thumbnail file") raise SyntaxError("not an XV thumbnail file")
# Skip to beginning of next line # Skip to beginning of next line
@ -59,14 +58,14 @@ class XVThumbImageFile(ImageFile.ImageFile):
s = self.fp.readline() s = self.fp.readline()
if not s: if not s:
raise SyntaxError("Unexpected EOF reading XV thumbnail file") raise SyntaxError("Unexpected EOF reading XV thumbnail file")
if s[0] != b'#': if i8(s[0]) != 35: # ie. when not a comment: '#'
break break
# parse header line (already read) # parse header line (already read)
s = s.strip().split() s = s.strip().split()
self.mode = "P" 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) self.palette = ImagePalette.raw("RGB", PALETTE)
@ -75,6 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
self.fp.tell(), (self.mode, 0, 1) self.fp.tell(), (self.mode, 0, 1)
)] )]
# -------------------------------------------------------------------- # --------------------------------------------------------------------
Image.register_open(XVThumbImageFile.format, XVThumbImageFile, _accept) Image.register_open(XVThumbImageFile.format, XVThumbImageFile, _accept)

View File

@ -20,7 +20,7 @@
# #
import re import re
from PIL import Image, ImageFile from . import Image, ImageFile
__version__ = "0.6" __version__ = "0.6"

View File

@ -16,8 +16,8 @@
import re import re
from PIL import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from PIL._binary import i8, o8 from ._binary import i8, o8
__version__ = "0.2" __version__ = "0.2"
@ -116,8 +116,6 @@ class XpmImageFile(ImageFile.ImageFile):
for i in range(ysize): for i in range(ysize):
s[i] = self.fp.readline()[1:xsize+1].ljust(xsize) s[i] = self.fp.readline()[1:xsize+1].ljust(xsize)
self.fp = None
return b"".join(s) return b"".join(s)
# #

View File

@ -11,8 +11,10 @@
# ;-) # ;-)
VERSION = '1.1.7' # PIL version from . import version
PILLOW_VERSION = '3.5.0.dev0' # Pillow
VERSION = '1.1.7' # PIL Version
PILLOW_VERSION = version.__version__
__version__ = PILLOW_VERSION __version__ = PILLOW_VERSION

View File

@ -28,10 +28,9 @@ else:
# Input, le = little endian, be = big endian # Input, le = little endian, be = big endian
# TODO: replace with more readable struct.unpack equivalent
def i16le(c, o=0): def i16le(c, o=0):
""" """
Converts a 2-bytes (16 bits) string to an integer. Converts a 2-bytes (16 bits) string to an unsigned integer.
c: string containing bytes to convert c: string containing bytes to convert
o: offset of bytes to convert in string o: offset of bytes to convert in string
@ -39,9 +38,19 @@ def i16le(c, o=0):
return unpack("<H", c[o:o+2])[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): 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 c: string containing bytes to convert
o: offset of bytes to convert in string 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] 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): def i16be(c, o=0):
return unpack(">H", c[o:o+2])[0] return unpack(">H", c[o:o+2])[0]

View File

@ -1,46 +1,27 @@
from PIL import Image from . import Image
modules = { modules = {
"pil": "PIL._imaging", "pil": "PIL._imaging",
"tkinter": "PIL._imagingtk", "tkinter": "PIL._tkinter_finder",
"freetype2": "PIL._imagingft", "freetype2": "PIL._imagingft",
"littlecms2": "PIL._imagingcms", "littlecms2": "PIL._imagingcms",
"webp": "PIL._webp", "webp": "PIL._webp",
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
} }
def check_module(feature): def check_module(feature):
if feature not in modules: if not (feature in modules):
raise ValueError("Unknown module %s" % feature) raise ValueError("Unknown module %s" % feature)
module = modules[feature] module = modules[feature]
method_to_call = None
if isinstance(module, tuple):
module, method_to_call = module
try: try:
imported_module = __import__(module) 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 return True
except ImportError:
return False
def get_supported_modules(): def get_supported_modules():
supported_modules = [] return [f for f in modules if check_module(f)]
for feature in modules:
if check_module(feature):
supported_modules.append(feature)
return supported_modules
codecs = { codecs = {
"jpg": "jpeg", "jpg": "jpeg",
@ -49,7 +30,6 @@ codecs = {
"libtiff": "libtiff" "libtiff": "libtiff"
} }
def check_codec(feature): def check_codec(feature):
if feature not in codecs: if feature not in codecs:
raise ValueError("Unknown codec %s" % feature) raise ValueError("Unknown codec %s" % feature)
@ -60,8 +40,39 @@ def check_codec(feature):
def get_supported_codecs(): def get_supported_codecs():
supported_codecs = [] return [f for f in codecs if check_codec(f)]
for feature in codecs:
if check_codec(feature): features = {
supported_codecs.append(feature) "webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'),
return supported_codecs "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
View File

@ -0,0 +1,2 @@
# Master version for Pillow
__version__ = '4.3.0.dev0'

View File

@ -14,9 +14,9 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
* - docs * - docs
- |docs| - |docs|
* - tests * - tests
- | |linux| |macos| |windows| |coverage| |health| - | |linux| |macos| |windows| |coverage|
* - package * - package
- |zenodo| |version| |downloads| - |zenodo| |version|
.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest .. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest
:target: https://pillow.readthedocs.io/?badge=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 :target: https://coveralls.io/github/python-pillow/Pillow?branch=master
:alt: Code coverage :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 .. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow :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/ :target: https://pypi.python.org/pypi/Pillow/
:alt: Latest PyPI version :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 .. end-badges

View File

@ -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 * [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174
* [ ] Develop and prepare release in ``master`` branch. * [ ] 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. * [ ] 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: * [ ] 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`
PIL/__init__.py setup.py _imaging.c appveyor.yml
```
* [ ] Update `CHANGES.rst`. * [ ] Update `CHANGES.rst`.
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
* [ ] Create branch and tag for release e.g.: * [ ] 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 --all
$ git push --tags $ git push --tags
``` ```
* [ ] Create and upload source distributions e.g.: * [ ] Create source distributions e.g.:
``` ```
$ make sdist $ 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) * [ ] 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 ## Point Release
@ -40,24 +38,18 @@ Released as needed for security, installation or critical bug fixes.
``` ```
git checkout -t remotes/origin/2.9.x git checkout -t remotes/origin/2.9.x
``` ```
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in: * [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `PIL/version.py`
```
PIL/__init__.py
setup.py
_imaging.c
appveyor.yml
```
* [ ] Run pre-release check via `make release-test`. * [ ] Run pre-release check via `make release-test`.
* [ ] Create tag for release e.g.: * [ ] Create tag for release e.g.:
``` ```
$ git tag 2.9.1 $ git tag 2.9.1
$ git push --tags $ 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 ## 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 2.5.x
git push origin --tags 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 ## 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. * [ ] 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 *``. * [ ] Download and extract tarball from @cgohlke and ``twine upload *``.
### macOS ### Mac and Linux
* [ ] Use the [Pillow macOS Wheel Builder](https://github.com/python-pillow/pillow-wheels): * [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
``` ```
$ git checkout https://github.com/python-pillow/pillow-wheels $ git checkout https://github.com/python-pillow/pillow-wheels
$ cd pillow-wheels $ cd pillow-wheels
@ -97,12 +89,13 @@ Released as needed privately to individual vendors for critical security-related
$ git submodule update $ git submodule update
$ cd Pillow $ cd Pillow
$ git fetch --all $ git fetch --all
$ git commit -a -m "Pillow -> 2.9.0" $ git checkout [[release tag]]
$ git push $ 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 ## Publicize Release

0
Scripts/createfontdatachunk.py Normal file → Executable file
View File

27
Scripts/enhancer.py Normal file → Executable file
View File

@ -7,21 +7,22 @@
# drag the slider to modify the image. # drag the slider to modify the image.
# #
try: import sys
from tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL
except ImportError: if sys.version_info[0] > 2:
from Tkinter import Tk, Toplevel, Frame, Label, Scale, HORIZONTAL import tkinter
else:
import Tkinter as tkinter
from PIL import Image, ImageTk, ImageEnhance from PIL import Image, ImageTk, ImageEnhance
import sys
# #
# enhancer widget # enhancer widget
class Enhance(Frame): class Enhance(tkinter.Frame):
def __init__(self, master, image, name, enhancer, lo, hi): def __init__(self, master, image, name, enhancer, lo, hi):
Frame.__init__(self, master) tkinter.Frame.__init__(self, master)
# set up the image # set up the image
self.tkim = ImageTk.PhotoImage(image.mode, image.size) self.tkim = ImageTk.PhotoImage(image.mode, image.size)
@ -29,10 +30,10 @@ class Enhance(Frame):
self.update("1.0") # normalize self.update("1.0") # normalize
# image window # image window
Label(self, image=self.tkim).pack() tkinter.Label(self, image=self.tkim).pack()
# scale # scale
s = Scale(self, label=name, orient=HORIZONTAL, s = tkinter.Scale(self, label=name, orient=tkinter.HORIZONTAL,
from_=lo, to=hi, resolution=0.01, from_=lo, to=hi, resolution=0.01,
command=self.update) command=self.update)
s.set(self.value) s.set(self.value)
@ -49,15 +50,15 @@ if len(sys.argv) != 2:
print("Usage: enhancer file") print("Usage: enhancer file")
sys.exit(1) sys.exit(1)
root = Tk() root = tkinter.Tk()
im = Image.open(sys.argv[1]) im = Image.open(sys.argv[1])
im.thumbnail((200, 200)) im.thumbnail((200, 200))
Enhance(root, im, "Color", ImageEnhance.Color, 0.0, 4.0).pack() Enhance(root, im, "Color", ImageEnhance.Color, 0.0, 4.0).pack()
Enhance(Toplevel(), im, "Sharpness", ImageEnhance.Sharpness, -2.0, 2.0).pack() Enhance(tkinter.Toplevel(), im, "Sharpness", ImageEnhance.Sharpness, -2.0, 2.0).pack()
Enhance(Toplevel(), im, "Brightness", ImageEnhance.Brightness, -1.0, 3.0).pack() Enhance(tkinter.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, "Contrast", ImageEnhance.Contrast, -1.0, 3.0).pack()
root.mainloop() root.mainloop()

0
Scripts/explode.py Normal file → Executable file
View File

0
Scripts/gifmaker.py Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More