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
*.container binary

View File

@ -6,7 +6,9 @@
### What versions of Pillow and Python are you using?
Please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, django, or buildout, try to replicate the issue just using Pillow.
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, Django, or buildout, try to replicate the issue just using Pillow.
```python
code goes here

3
.gitignore vendored
View File

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

View File

@ -5,46 +5,40 @@ notifications:
# Run slow PyPy* first, to give them a headstart and reduce waiting time.
# Run latest 3.x and 2.x next, to get quick compatibility results.
# Then run the remainder.
python:
- "pypy"
- "pypy3"
- 3.5
- 2.7
- "2.7_with_system_site_packages" # For PyQt4
- 3.3
- 3.4
- nightly
# Then run the remainder, with fastest Docker jobs last.
matrix:
fast_finish: true
include:
- python: "pypy-5.7.1"
- python: "pypy3.3-5.2-alpha1"
- python: '3.6'
- python: '2.7'
- python: "2.7_with_system_site_packages" # For PyQt4
- python: '3.5'
- python: '3.4'
- python: '3.3'
- env: DOCKER="alpine"
- env: DOCKER="arch" # contains PyQt5
- env: DOCKER="ubuntu-trusty-x86"
- env: DOCKER="ubuntu-xenial-amd64"
- env: DOCKER="ubuntu-precise-amd64"
- env: DOCKER="debian-stretch-x86"
- env: DOCKER="centos-6-amd64"
- env: DOCKER="amazon-amd64"
dist: trusty
sudo: required
services:
- docker
install:
- "travis_retry sudo apt-get update"
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
- "travis_retry pip install cffi"
- "travis_retry pip install nose"
- "travis_retry pip install check-manifest"
# Pyroma tests sometimes hang on PyPy; skip
- if [ $TRAVIS_PYTHON_VERSION != "pypy" ]; then travis_retry pip install pyroma; fi
- if [ "$DOCKER" == "" ]; then .travis/install.sh; fi
- "travis_retry pip install coverage"
# docs only on python 2.7
- if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then travis_retry pip install -r requirements.txt ; fi
# clean checkout for manifest
- mkdir /tmp/check-manifest && cp -a . /tmp/check-manifest
# webp
- pushd depends && ./install_webp.sh && popd
# openjpeg
- pushd depends && ./install_openjpeg.sh && popd
# libimagequant
- pushd depends && ./install_imagequant.sh && popd
# extra test images
- pushd depends && ./install_extra_test_images.sh && popd
before_install:
- if [ "$DOCKER" ]; then docker pull pythonpillow/$DOCKER; fi
before_script:
# Qt needs a display for some of the tests, and it's only run on the system site packages install
@ -52,59 +46,17 @@ before_script:
- "sh -e /etc/init.d/xvfb start"
script:
- coverage erase
- python setup.py clean
- CFLAGS="-coverage" python setup.py build_ext --inplace
- coverage run --append --include=PIL/* selftest.py
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
- pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd
# Docs
- if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make install && make doccheck; fi
- |
if [ "$DOCKER" == "" ]; then
.travis/script.sh
else
# the Pillow user in the docker container is UID 1000
sudo chown -R 1000 $TRAVIS_BUILD_DIR
docker run -v $TRAVIS_BUILD_DIR:/Pillow pythonpillow/$DOCKER
fi
after_success:
# gather the coverage data
- travis_retry sudo apt-get -qq install lcov
- lcov --capture --directory . -b . --output-file coverage.info
# filter to remove system headers
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
# convert to json
- travis_retry gem install coveralls-lcov
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
- coverage report
- travis_retry pip install coveralls-merge
- coveralls-merge coverage.c.json
- travis_retry pip install pep8 pyflakes
- pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py
- pyflakes *.py | tee >(wc -l)
- pyflakes PIL/*.py | tee >(wc -l)
- pyflakes Tests/*.py | tee >(wc -l)
# Coverage and quality reports on just the latest diff.
# (Installation is very slow on Py3, so just do it for Py2.)
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-install.sh; fi
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then depends/diffcover-run.sh; fi
# after_all
- |
if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
python travis_after_all.py
export $(cat .to_export_back)
if [ "$BUILD_LEADER" = "YES" ]; then
if [ "$BUILD_AGGREGATE_STATUS" = "others_succeeded" ]; then
echo "All jobs succeded! Triggering macOS build..."
# Trigger a macOS build at the pillow-wheels repo
./build_children.sh
else
echo "Some jobs failed"
fi
fi
fi
- .travis/after_success.sh
after_failure:
- |
@ -127,11 +79,6 @@ after_script:
echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS
fi
matrix:
fast_finish: true
allow_failures:
- python: nightly
env:
global:
# travis encrypt AUTH_TOKEN=

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

View File

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

View File

@ -58,6 +58,13 @@ install:
python setup.py install
python selftest.py --installed
debug:
# make a debug version if we don't have a -dbg python. Leaves in symbols
# for our stuff, kills optimization, and redirects to dev null so we
# see any build failures.
make clean > /dev/null
CFLAGS='-g -O0' python setup.py build_ext install > /dev/null
install-req:
pip install -r requirements.txt
@ -77,7 +84,7 @@ release-test:
viewdoc
sdist:
python setup.py sdist --format=gztar,zip
python setup.py sdist --format=gztar
test:
python test-installed.py
@ -88,10 +95,10 @@ upload-test:
# username:
# password:
# repository = http://test.pythonpackages.com
python setup.py sdist --format=gztar,zip upload -r test
python setup.py sdist --format=gztar upload -r test
upload:
python setup.py sdist --format=gztar,zip upload
python setup.py sdist --format=gztar upload
readme:
viewdoc

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,21 +24,14 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image, ImageFile, ImagePalette, \
ImageChops, ImageSequence, _binary
from . import Image, ImageFile, ImagePalette, ImageChops, ImageSequence
from ._binary import i8, i16le as i16, o8, o16le as o16
import itertools
__version__ = "0.9"
# --------------------------------------------------------------------
# Helpers
i8 = _binary.i8
i16 = _binary.i16le
o8 = _binary.o8
o16 = _binary.o16le
# --------------------------------------------------------------------
# Identify/read GIF files
@ -54,6 +47,8 @@ class GifImageFile(ImageFile.ImageFile):
format = "GIF"
format_description = "Compuserve GIF"
_close_exclusive_fp_after_loading = False
global_palette = None
def data(self):
@ -262,7 +257,7 @@ class GifImageFile(ImageFile.ImageFile):
# only dispose the extent in this frame
if self.dispose:
self.dispose = self.dispose.crop(self.dispose_extent)
self.dispose = self._crop(self.dispose, self.dispose_extent)
except (AttributeError, KeyError):
pass
@ -285,7 +280,7 @@ class GifImageFile(ImageFile.ImageFile):
if self._prev_im and self.disposal_method == 1:
# we do this by pasting the updated area onto the previous
# frame which we then use as the current image content
updated = self.im.crop(self.dispose_extent)
updated = self._crop(self.im, self.dispose_extent)
self._prev_im.paste(updated, self.dispose_extent,
updated.convert('RGBA'))
self.im = self._prev_im
@ -294,52 +289,168 @@ class GifImageFile(ImageFile.ImageFile):
# --------------------------------------------------------------------
# Write GIF files
try:
import _imaging_gif
except ImportError:
_imaging_gif = None
RAWMODE = {
"1": "L",
"L": "L",
"P": "P",
"P": "P"
}
def _convert_mode(im, initial_call=False):
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
# should automatically convert images on save...)
def _normalize_mode(im, initial_call=False):
"""
Takes an image (or frame), returns an image in a mode that is appropriate
for saving in a Gif.
It may return the original image, or it may return an image converted to
palette or 'L' mode.
UNDONE: What is the point of mucking with the initial call palette, for
an image that shouldn't have a palette, or it would be a mode 'P' and
get returned in the RAWMODE clause.
:param im: Image object
:param initial_call: Default false, set to true for a single frame.
:returns: Image object
"""
if im.mode in RAWMODE:
im.load()
return im
if Image.getmodebase(im.mode) == "RGB":
if initial_call:
palette_size = 256
if im.palette:
palette_size = len(im.palette.getdata()[1]) // 3
return im.convert("P", palette=1, colors=palette_size)
return im.convert("P", palette=Image.ADAPTIVE, colors=palette_size)
else:
return im.convert("P")
return im.convert("L")
def _normalize_palette(im, palette, info):
"""
Normalizes the palette for image.
- Sets the palette to the incoming palette, if provided.
- Ensures that there's a palette for L mode images
- Optimizes the palette if necessary/desired.
:param im: Image object
:param palette: bytes object containing the source palette, or ....
:param info: encoderinfo
:returns: Image object
"""
source_palette = None
if palette:
# a bytes palette
if isinstance(palette, (bytes, bytearray, list)):
source_palette = bytearray(palette[:768])
if isinstance(palette, ImagePalette.ImagePalette):
source_palette = bytearray(itertools.chain.from_iterable(
zip(palette.palette[:256],
palette.palette[256:512],
palette.palette[512:768])))
if im.mode == "P":
if not source_palette:
source_palette = im.im.getpalette("RGB")[:768]
else: # L-mode
if not source_palette:
source_palette = bytearray(i//3 for i in range(768))
im.palette = ImagePalette.ImagePalette("RGB",
palette=source_palette)
used_palette_colors = _get_optimize(im, info)
if used_palette_colors is not None:
return im.remap_palette(used_palette_colors, source_palette)
im.palette.palette = source_palette
return im
def _write_single_frame(im, fp, palette):
im_out = _normalize_mode(im, True)
im_out = _normalize_palette(im_out, palette, im.encoderinfo)
for s in _get_global_header(im_out, im.encoderinfo):
fp.write(s)
# local image header
flags = 0
if get_interlace(im):
flags = flags | 64
_write_local_header(fp, im, (0, 0), flags)
im_out.encoderconfig = (8, get_interlace(im))
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
RAWMODE[im_out.mode])])
fp.write(b"\0") # end of image data
def _write_multiple_frames(im, fp, palette):
duration = im.encoderinfo.get("duration", None)
im_frames = []
frame_count = 0
for imSequence in [im]+im.encoderinfo.get("append_images", []):
for im_frame in ImageSequence.Iterator(imSequence):
# a copy is required here since seek can still mutate the image
im_frame = _normalize_mode(im_frame.copy())
im_frame = _normalize_palette(im_frame, palette, im.encoderinfo)
encoderinfo = im.encoderinfo.copy()
if isinstance(duration, (list, tuple)):
encoderinfo['duration'] = duration[frame_count]
frame_count += 1
if im_frames:
# delta frame
previous = im_frames[-1]
if _get_palette_bytes(im_frame) == _get_palette_bytes(previous['im']):
delta = ImageChops.subtract_modulo(im_frame,
previous['im'])
else:
delta = ImageChops.subtract_modulo(im_frame.convert('RGB'),
previous['im'].convert('RGB'))
bbox = delta.getbbox()
if not bbox:
# This frame is identical to the previous frame
if duration:
previous['encoderinfo']['duration'] += encoderinfo['duration']
continue
else:
bbox = None
im_frames.append({
'im': im_frame,
'bbox': bbox,
'encoderinfo': encoderinfo
})
if len(im_frames) > 1:
for frame_data in im_frames:
im_frame = frame_data['im']
if not frame_data['bbox']:
# global header
for s in _get_global_header(im_frame,
frame_data['encoderinfo']):
fp.write(s)
offset = (0, 0)
else:
# compress difference
frame_data['encoderinfo']['include_color_table'] = True
im_frame = im_frame.crop(frame_data['bbox'])
offset = frame_data['bbox'][:2]
_write_frame_data(fp, im_frame, offset, frame_data['encoderinfo'])
return True
def _save_all(im, fp, filename):
_save(im, fp, filename, save_all=True)
def _save(im, fp, filename, save_all=False):
im.encoderinfo.update(im.info)
if _imaging_gif:
# call external driver
try:
_imaging_gif.save(im, fp, filename)
return
except IOError:
pass # write uncompressed file
if im.mode in RAWMODE:
im_out = im.copy()
else:
im_out = _convert_mode(im, True)
# header
try:
palette = im.encoderinfo["palette"]
@ -347,62 +458,8 @@ def _save(im, fp, filename, save_all=False):
palette = None
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
if save_all:
previous = None
first_frame = None
append_images = im.encoderinfo.get("append_images", [])
for imSequence in [im]+append_images:
for im_frame in ImageSequence.Iterator(imSequence):
encoderinfo = im.encoderinfo.copy()
im_frame = _convert_mode(im_frame)
# To specify duration, add the time in milliseconds to getdata(),
# e.g. getdata(im_frame, duration=1000)
if not previous:
# global header
first_frame = getheader(im_frame, palette, encoderinfo)[0]
first_frame += getdata(im_frame, (0, 0), **encoderinfo)
else:
if first_frame:
for s in first_frame:
fp.write(s)
first_frame = None
# delta frame
delta = ImageChops.subtract_modulo(im_frame, previous.copy())
bbox = delta.getbbox()
if bbox:
# compress difference
encoderinfo['include_color_table'] = True
for s in getdata(im_frame.crop(bbox),
bbox[:2], **encoderinfo):
fp.write(s)
else:
# FIXME: what should we do in this case?
pass
previous = im_frame
if first_frame:
save_all = False
if not save_all:
header = getheader(im_out, palette, im.encoderinfo)[0]
for s in header:
fp.write(s)
flags = 0
if get_interlace(im):
flags = flags | 64
# local image header
_get_local_header(fp, im, (0, 0), flags)
im_out.encoderconfig = (8, get_interlace(im))
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
RAWMODE[im_out.mode])])
fp.write(b"\0") # end of image data
if not save_all or not _write_multiple_frames(im, fp, palette):
_write_single_frame(im, fp, palette)
fp.write(b";") # end of file
@ -411,10 +468,7 @@ def _save(im, fp, filename, save_all=False):
def get_interlace(im):
try:
interlace = im.encoderinfo["interlace"]
except KeyError:
interlace = 1
interlace = im.encoderinfo.get("interlace", 1)
# workaround for @PIL153
if min(im.size) < 16:
@ -423,7 +477,7 @@ def get_interlace(im):
return interlace
def _get_local_header(fp, im, offset, flags):
def _write_local_header(fp, im, offset, flags):
transparent_color_exists = False
try:
transparency = im.encoderinfo["transparency"]
@ -434,18 +488,13 @@ def _get_local_header(fp, im, offset, flags):
# optimize the block away if transparent color is not used
transparent_color_exists = True
if _get_optimize(im, im.encoderinfo):
used_palette_colors = _get_used_palette_colors(im)
used_palette_colors = _get_optimize(im, im.encoderinfo)
if used_palette_colors is not None:
# adjust the transparency index after optimize
if len(used_palette_colors) < 256:
for i in range(len(used_palette_colors)):
if used_palette_colors[i] == transparency:
transparency = i
transparent_color_exists = True
break
else:
transparent_color_exists = False
try:
transparency = used_palette_colors.index(transparency)
except ValueError:
transparent_color_exists = False
if "duration" in im.encoderinfo:
duration = int(im.encoderinfo["duration"] / 10)
@ -482,11 +531,8 @@ def _get_local_header(fp, im, offset, flags):
o8(0))
include_color_table = im.encoderinfo.get('include_color_table')
if include_color_table:
try:
palette = im.encoderinfo["palette"]
except KeyError:
palette = None
palette_bytes = _get_palette_bytes(im, palette, im.encoderinfo)[0]
palette = im.encoderinfo.get("palette", None)
palette_bytes = _get_palette_bytes(im)
color_table_size = _get_color_table_size(palette_bytes)
if color_table_size:
flags = flags | 128 # local color table flag
@ -505,6 +551,8 @@ def _get_local_header(fp, im, offset, flags):
def _save_netpbm(im, fp, filename):
# Unused by default.
# To use, uncomment the register_save call at the end of the file.
#
# If you need real GIF compression and/or RGB quantization, you
# can use the external NETPBM/PBMPLUS utilities. See comments
@ -512,25 +560,21 @@ def _save_netpbm(im, fp, filename):
import os
from subprocess import Popen, check_call, PIPE, CalledProcessError
import tempfile
file = im._dump()
if im.mode != "RGB":
with open(filename, 'wb') as f:
stderr = tempfile.TemporaryFile()
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
else:
with open(filename, 'wb') as f:
with open(filename, 'wb') as f:
if im.mode != "RGB":
with open(os.devnull, 'wb') as devnull:
check_call(["ppmtogif", file], stdout=f, stderr=devnull)
else:
# Pipe ppmquant output into ppmtogif
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
quant_cmd = ["ppmquant", "256", file]
togif_cmd = ["ppmtogif"]
stderr = tempfile.TemporaryFile()
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
stderr = tempfile.TemporaryFile()
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
stderr=stderr)
with open(os.devnull, 'wb') as devnull:
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout,
stdout=f, stderr=devnull)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
quant_proc.stdout.close()
@ -549,24 +593,45 @@ def _save_netpbm(im, fp, filename):
pass
# --------------------------------------------------------------------
# GIF utilities
# Force optimization so that we can test performance against
# cases where it took lots of memory and time previously.
_FORCE_OPTIMIZE = False
def _get_optimize(im, info):
return im.mode in ("P", "L") and info and info.get("optimize", 0)
"""
Palette optimization is a potentially expensive operation.
This function determines if the palette should be optimized using
some heuristics, then returns the list of palette entries in use.
def _get_used_palette_colors(im):
used_palette_colors = []
:param im: Image object
:param info: encoderinfo
:returns: list of indexes of palette entries in use, or None
"""
if im.mode in ("P", "L") and info and info.get("optimize", 0):
# Potentially expensive operation.
# check which colors are used
i = 0
for count in im.histogram():
if count:
used_palette_colors.append(i)
i += 1
# The palette saves 3 bytes per color not used, but palette
# lengths are restricted to 3*(2**N) bytes. Max saving would
# be 768 -> 6 bytes if we went all the way down to 2 colors.
# * If we're over 128 colors, we can't save any space.
# * If there aren't any holes, it's not worth collapsing.
# * If we have a 'large' image, the palette is in the noise.
# create the new palette if not every color is used
optimise = _FORCE_OPTIMIZE or im.mode == 'L'
if optimise or im.width * im.height < 512 * 512:
# check which colors are used
used_palette_colors = []
for i, count in enumerate(im.histogram()):
if count:
used_palette_colors.append(i)
if optimise or (len(used_palette_colors) <= 128 and
max(used_palette_colors) > len(used_palette_colors)):
return used_palette_colors
return used_palette_colors
def _get_color_table_size(palette_bytes):
# calculate the palette size for the header
@ -576,7 +641,15 @@ def _get_color_table_size(palette_bytes):
color_table_size = 0
return color_table_size
def _get_header_palette(palette_bytes):
"""
Returns the palette, null padded to the next power of 2 (*3) bytes
suitable for direct inclusion in the GIF header
:param palette_bytes: Unpadded palette bytes, in RGBRGB form
:returns: Null padded palette
"""
color_table_size = _get_color_table_size(palette_bytes)
# add the missing amount of bytes
@ -586,102 +659,18 @@ def _get_header_palette(palette_bytes):
palette_bytes += o8(0) * 3 * actual_target_size_diff
return palette_bytes
# Force optimization so that we can test performance against
# cases where it took lots of memory and time previously.
_FORCE_OPTIMIZE = False
def _get_palette_bytes(im, palette, info):
if im.mode == "P":
if palette and isinstance(palette, bytes):
source_palette = palette[:768]
else:
source_palette = im.im.getpalette("RGB")[:768]
else: # L-mode
if palette and isinstance(palette, bytes):
source_palette = palette[:768]
else:
source_palette = bytearray(i//3 for i in range(768))
def _get_palette_bytes(im):
"""
Gets the palette for inclusion in the gif header
used_palette_colors = palette_bytes = None
:param im: Image object
:returns: Bytes, len<=768 suitable for inclusion in gif header
"""
return im.palette.palette
if _get_optimize(im, info):
used_palette_colors = _get_used_palette_colors(im)
# Potentially expensive operation.
# The palette saves 3 bytes per color not used, but palette
# lengths are restricted to 3*(2**N) bytes. Max saving would
# be 768 -> 6 bytes if we went all the way down to 2 colors.
# * If we're over 128 colors, we can't save any space.
# * If there aren't any holes, it's not worth collapsing.
# * If we have a 'large' image, the palette is in the noise.
# create the new palette if not every color is used
if _FORCE_OPTIMIZE or im.mode == 'L' or \
(len(used_palette_colors) <= 128 and
max(used_palette_colors) > len(used_palette_colors) and
im.width * im.height < 512 * 512):
palette_bytes = b""
new_positions = [0]*256
# pick only the used colors from the palette
for i, oldPosition in enumerate(used_palette_colors):
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
new_positions[oldPosition] = i
# replace the palette color id of all pixel with the new id
# Palette images are [0..255], mapped through a 1 or 3
# byte/color map. We need to remap the whole image
# from palette 1 to palette 2. New_positions is
# an array of indexes into palette 1. Palette 2 is
# palette 1 with any holes removed.
# We're going to leverage the convert mechanism to use the
# C code to remap the image from palette 1 to palette 2,
# by forcing the source image into 'L' mode and adding a
# mapping 'L' mode palette, then converting back to 'L'
# sans palette thus converting the image bytes, then
# assigning the optimized RGB palette.
# perf reference, 9500x4000 gif, w/~135 colors
# 14 sec prepatch, 1 sec postpatch with optimization forced.
mapping_palette = bytearray(new_positions)
m_im = im.copy()
m_im.mode = 'P'
m_im.palette = ImagePalette.ImagePalette("RGB",
palette=mapping_palette*3,
size=768)
#possibly set palette dirty, then
#m_im.putpalette(mapping_palette, 'L') # converts to 'P'
# or just force it.
# UNDONE -- this is part of the general issue with palettes
m_im.im.putpalette(*m_im.palette.getdata())
m_im = m_im.convert('L')
# Internally, we require 768 bytes for a palette.
new_palette_bytes = (palette_bytes +
(768 - len(palette_bytes)) * b'\x00')
m_im.putpalette(new_palette_bytes)
m_im.palette = ImagePalette.ImagePalette("RGB",
palette=palette_bytes,
size=len(palette_bytes))
# oh gawd, this is modifying the image in place so I can pass by ref.
# REFACTOR SOONEST
im.frombytes(m_im.tobytes())
if not palette_bytes:
palette_bytes = source_palette
# returning palette, _not_ padded to 768 bytes like our internal ones.
return palette_bytes, used_palette_colors
def getheader(im, palette=None, info=None):
def _get_global_header(im, info):
"""Return a list of strings representing a GIF header"""
# Header Block
@ -691,7 +680,7 @@ def getheader(im, palette=None, info=None):
for extensionKey in ["transparency", "duration", "loop", "comment"]:
if info and extensionKey in info:
if ((extensionKey == "duration" and info[extensionKey] == 0) or
(extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))):
(extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))):
continue
version = b"89a"
break
@ -699,42 +688,89 @@ def getheader(im, palette=None, info=None):
if im.info.get("version") == b"89a":
version = b"89a"
header = [
b"GIF"+version + # signature + version
o16(im.size[0]) + # canvas width
o16(im.size[1]) # canvas height
palette_bytes = _get_palette_bytes(im)
color_table_size = _get_color_table_size(palette_bytes)
background = info["background"] if "background" in info else 0
return [
b"GIF"+version + # signature + version
o16(im.size[0]) + # canvas width
o16(im.size[1]), # canvas height
# Logical Screen Descriptor
# size of global color table + global color table flag
o8(color_table_size + 128), # packed fields
# background + reserved/aspect
o8(background) + o8(0),
# Global Color Table
_get_header_palette(palette_bytes)
]
palette_bytes, used_palette_colors = _get_palette_bytes(im, palette, info)
# Logical Screen Descriptor
color_table_size = _get_color_table_size(palette_bytes)
# size of global color table + global color table flag
header.append(o8(color_table_size + 128)) # packed fields
# background + reserved/aspect
if info and "background" in info:
background = info["background"]
elif "background" in im.info:
# This elif is redundant within GifImagePlugin
# since im.info parameters are bundled into the info dictionary
# However, external scripts may call getheader directly
# So this maintains earlier behaviour
background = im.info["background"]
else:
background = 0
header.append(o8(background) + o8(0))
# end of Logical Screen Descriptor
def _write_frame_data(fp, im_frame, offset, params):
try:
im_frame.encoderinfo = params
# local image header
_write_local_header(fp, im_frame, offset, 0)
ImageFile._save(im_frame, fp, [("gif", (0, 0)+im_frame.size, 0,
RAWMODE[im_frame.mode])])
fp.write(b"\0") # end of image data
finally:
del im_frame.encoderinfo
# --------------------------------------------------------------------
# Legacy GIF utilities
def getheader(im, palette=None, info=None):
"""
Legacy Method to get Gif data from image.
Warning:: May modify image data.
:param im: Image object
:param palette: bytes object containing the source palette, or ....
:param info: encoderinfo
:returns: tuple of(list of header items, optimized palette)
"""
used_palette_colors = _get_optimize(im, info)
if info is None:
info = {}
if "background" not in info and "background" in im.info:
info["background"] = im.info["background"]
im_mod = _normalize_palette(im, palette, info)
im.palette = im_mod.palette
im.im = im_mod.im
header = _get_global_header(im, info)
# Header + Logical Screen Descriptor + Global Color Table
header.append(_get_header_palette(palette_bytes))
return header, used_palette_colors
# To specify duration, add the time in milliseconds to getdata(),
# e.g. getdata(im_frame, duration=1000)
def getdata(im, offset=(0, 0), **params):
"""Return a list of strings representing this image.
The first string is a local image header, the rest contains
encoded image data."""
"""
Legacy Method
Return a list of strings representing this image.
The first string is a local image header, the rest contains
encoded image data.
:param im: Image object
:param offset: Tuple of (x, y) pixels. Defaults to (0,0)
:param **params: E.g. duration or other encoder info parameters
:returns: List of Bytes containing gif encoded frame data
"""
class Collector(object):
data = []
@ -745,18 +781,7 @@ def getdata(im, offset=(0, 0), **params):
fp = Collector()
try:
im.encoderinfo = params
# local image header
_get_local_header(fp, im, offset, 0)
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
fp.write(b"\0") # end of image data
finally:
del im.encoderinfo
_write_frame_data(fp, im, offset, params)
return fp.data

View File

@ -14,7 +14,7 @@
#
from math import pi, log, sin, sqrt
from PIL._binary import o8
from ._binary import o8
# --------------------------------------------------------------------
# Stuff to translate curve segments to palette values (derived from

View File

@ -15,7 +15,7 @@
#
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.
#
from PIL import Image, ImageFile
from . import Image, ImageFile
from ._binary import i8
_handler = None
@ -28,7 +29,7 @@ def register_handler(handler):
# Image adapter
def _accept(prefix):
return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01'
return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1
class GribStubImageFile(ImageFile.StubImageFile):

View File

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

View File

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

View File

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

View File

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

View File

@ -24,9 +24,7 @@
# See the README file for information on usage and redistribution.
#
from __future__ import print_function
from PIL import VERSION, PILLOW_VERSION, _plugins
from . import VERSION, PILLOW_VERSION, _plugins
import logging
import warnings
@ -48,15 +46,6 @@ class _imaging_not_installed(object):
# Limit to around a quarter gigabyte for a 24 bit (3 bpp) image
MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3)
try:
# give Tk a chance to set up the environment, in case we're
# using an _imaging module linked against libtcl/libtk (use
# __import__ to hide this from naive packagers; we don't really
# depend on Tk unless ImageTk is used, and that module already
# imports Tkinter)
__import__("FixTk")
except ImportError:
pass
try:
# If the _imaging C module is not present, Pillow will not load.
@ -64,10 +53,13 @@ try:
# import Image and use the Image.core variable instead.
# Also note that Image.core is not a publicly documented interface,
# and should be considered private and subject to change.
from PIL import _imaging as core
from . import _imaging as core
if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
raise ImportError("The _imaging extension was built for another "
"version of Pillow or PIL")
"version of Pillow or PIL: Core Version: %s"
"Pillow Version: %s" %
(getattr(core, 'PILLOW_VERSION', None),
PILLOW_VERSION))
except ImportError as v:
core = _imaging_not_installed()
@ -109,11 +101,9 @@ except ImportError:
import __builtin__
builtins = __builtin__
from PIL import ImageMode
from PIL._binary import i8
from PIL._util import isPath
from PIL._util import isStringType
from PIL._util import deferred_error
from . import ImageMode
from ._binary import i8
from ._util import isPath, isStringType, deferred_error
import os
import sys
@ -146,6 +136,7 @@ def isImageType(t):
"""
return hasattr(t, "im")
#
# Constants (also defined in _imagingmodule.c!)
@ -211,6 +202,8 @@ MIME = {}
SAVE = {}
SAVE_ALL = {}
EXTENSION = {}
DECODERS = {}
ENCODERS = {}
# --------------------------------------------------------------------
# Modes supported by this version
@ -284,7 +277,7 @@ def _conv_type_shape(im):
return shape+(extra,), typ
MODES = sorted(_MODEINFO.keys())
MODES = sorted(_MODEINFO)
# raw modes that may be memory mapped. NOTE: if you change this, you
# may have to modify the stride calculation in map.c too!
@ -341,6 +334,7 @@ def getmodebands(mode):
"""
return len(ImageMode.getmode(mode).bands)
# --------------------------------------------------------------------
# Helpers
@ -355,23 +349,23 @@ def preinit():
return
try:
from PIL import BmpImagePlugin
from . import BmpImagePlugin
except ImportError:
pass
try:
from PIL import GifImagePlugin
from . import GifImagePlugin
except ImportError:
pass
try:
from PIL import JpegImagePlugin
from . import JpegImagePlugin
except ImportError:
pass
try:
from PIL import PpmImagePlugin
from . import PpmImagePlugin
except ImportError:
pass
try:
from PIL import PngImagePlugin
from . import PngImagePlugin
except ImportError:
pass
# try:
@ -415,6 +409,11 @@ def _getdecoder(mode, decoder_name, args, extra=()):
elif not isinstance(args, tuple):
args = (args,)
try:
decoder = DECODERS[decoder_name]
return decoder(mode, *args + extra)
except KeyError:
pass
try:
# get decoder
decoder = getattr(core, decoder_name + "_decoder")
@ -432,6 +431,11 @@ def _getencoder(mode, encoder_name, args, extra=()):
elif not isinstance(args, tuple):
args = (args,)
try:
encoder = ENCODERS[encoder_name]
return encoder(mode, *args + extra)
except KeyError:
pass
try:
# get encoder
encoder = getattr(core, encoder_name + "_encoder")
@ -496,6 +500,7 @@ class Image(object):
"""
format = None
format_description = None
_close_exclusive_fp_after_loading = True
def __init__(self):
# FIXME: take "new" parameters / other image?
@ -525,13 +530,11 @@ class Image(object):
if self.palette:
new.palette = self.palette.copy()
if im.mode == "P" and not new.palette:
from PIL import ImagePalette
from . import ImagePalette
new.palette = ImagePalette.ImagePalette()
new.info = self.info.copy()
return new
_makeself = _new # compatibility
# Context Manager Support
def __enter__(self):
return self
@ -552,21 +555,32 @@ class Image(object):
"""
try:
self.fp.close()
self.fp = None
except Exception as msg:
logger.debug("Error closing: %s", msg)
if getattr(self, 'map', None):
self.map = None
# Instead of simply setting to None, we're setting up a
# deferred error that will better explain that the core image
# object is gone.
self.im = deferred_error(ValueError("Operation on closed image"))
if sys.version_info >= (3, 4, 0):
def __del__(self):
if (hasattr(self, 'fp') and hasattr(self, '_exclusive_fp')
and self.fp and self._exclusive_fp):
self.fp.close()
self.fp = None
def _copy(self):
self.load()
self.im = self.im.copy()
self.pyaccess = None
self.readonly = 0
def _dump(self, file=None, format=None):
def _dump(self, file=None, format=None, **options):
import tempfile
suffix = ''
if format:
@ -581,7 +595,7 @@ class Image(object):
else:
if not file.endswith(format):
file = file + "." + format
self.save(file, format)
self.save(file, format, **options)
return file
def __eq__(self, other):
@ -695,8 +709,8 @@ class Image(object):
return b"".join(data)
def tostring(self, *args, **kw):
raise NotImplementedError("tostring() has been removed. " +
"Please call tobytes() instead.")
raise NotImplementedError("tostring() has been removed. "
"Please call tobytes() instead.")
def tobitmap(self, name="image"):
"""
@ -746,8 +760,8 @@ class Image(object):
raise ValueError("cannot decode image data")
def fromstring(self, *args, **kw):
raise NotImplementedError("fromstring() has been removed. " +
"Please call frombytes() instead.")
raise NotImplementedError("fromstring() has been removed. "
"Please call frombytes() instead.")
def load(self):
"""
@ -777,7 +791,7 @@ class Image(object):
if HAS_CFFI and USE_CFFI_ACCESS:
if self.pyaccess:
return self.pyaccess
from PIL import PyAccess
from . import PyAccess
self.pyaccess = PyAccess.new(self, self.readonly)
if self.pyaccess:
return self.pyaccess
@ -883,7 +897,7 @@ class Image(object):
try:
t = trns_im.palette.getcolor(t)
except:
raise ValueError("Couldn't allocate a palette " +
raise ValueError("Couldn't allocate a palette "
"color for transparency")
trns_im.putpixel((0, 0), t)
@ -910,7 +924,7 @@ class Image(object):
if mode == "P" and palette == ADAPTIVE:
im = self.im.quantize(colors)
new = self._new(im)
from PIL import ImagePalette
from . import ImagePalette
new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB"))
if delete_trns:
# This could possibly happen if we requantize to fewer colors.
@ -997,7 +1011,7 @@ class Image(object):
"only RGB or L mode images can be quantized to a palette"
)
im = self.im.convert("P", 1, palette.im)
return self._makeself(im)
return self._new(im)
return self._new(self.im.quantize(colors, method, kmeans))
@ -1031,6 +1045,20 @@ class Image(object):
if box is None:
return self.copy()
return self._new(self._crop(self.im, box))
def _crop(self, im, box):
"""
Returns a rectangular region from the core image object im.
This is equivalent to calling im.crop((x0, y0, x1, y1)), but
includes additional sanity checks.
:param im: a core image object
:param box: The crop rectangle, as a (left, upper, right, lower)-tuple.
:returns: A core image object.
"""
x0, y0, x1, y1 = map(int, map(round, box))
if x1 < x0:
@ -1038,8 +1066,9 @@ class Image(object):
if y1 < y0:
y1 = y0
return self._new(self.im.crop(( x0, y0, x1, y1)))
_decompression_bomb_check((x1, y1))
return im.crop((x0, y0, x1, y1))
def draft(self, mode, size):
"""
@ -1053,6 +1082,9 @@ class Image(object):
in place. If the image has already been loaded, this method has no
effect.
Note: This method is not implemented for most images. It is
currently implemented only for JPEG and PCD images.
:param mode: The requested mode.
:param size: The requested size.
"""
@ -1258,8 +1290,8 @@ class Image(object):
return self.im.histogram()
def offset(self, xoffset, yoffset=None):
raise NotImplementedError("offset() has been removed. " +
"Please call ImageChops.offset() instead.")
raise NotImplementedError("offset() has been removed. "
"Please call ImageChops.offset() instead.")
def paste(self, im, box=None, mask=None):
"""
@ -1323,7 +1355,7 @@ class Image(object):
box += (box[0]+size[0], box[1]+size[1])
if isStringType(im):
from PIL import ImageColor
from . import ImageColor
im = ImageColor.getcolor(im, self.mode)
elif isImageType(im):
@ -1344,6 +1376,54 @@ class Image(object):
else:
self.im.paste(im, box)
def alpha_composite(self, im, dest=(0,0), source=(0,0)):
""" 'In-place' analog of Image.alpha_composite. Composites an image
onto this image.
:param im: image to composite over this one
:param dest: Optional 2 tuple (left, top) specifying the upper
left corner in this (destination) image.
:param source: Optional 2 (left, top) tuple for the upper left
corner in the overlay source image, or 4 tuple (left, top, right,
bottom) for the bounds of the source rectangle
Performance Note: Not currently implemented in-place in the core layer.
"""
if not isinstance(source, tuple):
raise ValueError("Source must be a tuple")
if not isinstance(dest, tuple):
raise ValueError("Destination must be a tuple")
if not len(source) in (2, 4):
raise ValueError("Source must be a 2 or 4-tuple")
if not len(dest) == 2:
raise ValueError("Destination must be a 2-tuple")
if min(source) < 0:
raise ValueError("Source must be non-negative")
if min(dest) < 0:
raise ValueError("Destination must be non-negative")
if len(source) == 2:
source = source + im.size
# over image, crop if it's not the whole thing.
if source == (0,0) + im.size:
overlay = im
else:
overlay = im.crop(source)
# target for the paste
box = dest + (dest[0] + overlay.width, dest[1] + overlay.height)
# destination image. don't copy if we're using the whole image.
if dest == (0,0) + self.size:
background = self
else:
background = self.crop(box)
result = alpha_composite(background, overlay)
self.paste(result, box)
def point(self, lut, mode=None):
"""
Maps this image through a lookup table or function.
@ -1470,7 +1550,7 @@ class Image(object):
:param data: A palette sequence (either a list or a string).
"""
from PIL import ImagePalette
from . import ImagePalette
if self.mode not in ("L", "P"):
raise ValueError("illegal image mode")
@ -1519,6 +1599,80 @@ class Image(object):
return self.pyaccess.putpixel(xy, value)
return self.im.putpixel(xy, value)
def remap_palette(self, dest_map, source_palette=None):
"""
Rewrites the image to reorder the palette.
:param dest_map: A list of indexes into the original palette.
e.g. [1,0] would swap a two item palette, and list(range(255))
is the identity transform.
:param source_palette: Bytes or None.
:returns: An :py:class:`~PIL.Image.Image` object.
"""
from . import ImagePalette
if self.mode not in ("L", "P"):
raise ValueError("illegal image mode")
if source_palette is None:
if self.mode == "P":
source_palette = self.im.getpalette("RGB")[:768]
else: # L-mode
source_palette = bytearray(i//3 for i in range(768))
palette_bytes = b""
new_positions = [0]*256
# pick only the used colors from the palette
for i, oldPosition in enumerate(dest_map):
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
new_positions[oldPosition] = i
# replace the palette color id of all pixel with the new id
# Palette images are [0..255], mapped through a 1 or 3
# byte/color map. We need to remap the whole image
# from palette 1 to palette 2. New_positions is
# an array of indexes into palette 1. Palette 2 is
# palette 1 with any holes removed.
# We're going to leverage the convert mechanism to use the
# C code to remap the image from palette 1 to palette 2,
# by forcing the source image into 'L' mode and adding a
# mapping 'L' mode palette, then converting back to 'L'
# sans palette thus converting the image bytes, then
# assigning the optimized RGB palette.
# perf reference, 9500x4000 gif, w/~135 colors
# 14 sec prepatch, 1 sec postpatch with optimization forced.
mapping_palette = bytearray(new_positions)
m_im = self.copy()
m_im.mode = 'P'
m_im.palette = ImagePalette.ImagePalette("RGB",
palette=mapping_palette*3,
size=768)
# possibly set palette dirty, then
# m_im.putpalette(mapping_palette, 'L') # converts to 'P'
# or just force it.
# UNDONE -- this is part of the general issue with palettes
m_im.im.putpalette(*m_im.palette.getdata())
m_im = m_im.convert('L')
# Internally, we require 768 bytes for a palette.
new_palette_bytes = (palette_bytes +
(768 - len(palette_bytes)) * b'\x00')
m_im.putpalette(new_palette_bytes)
m_im.palette = ImagePalette.ImagePalette("RGB",
palette=palette_bytes,
size=len(palette_bytes))
return m_im
def resize(self, size, resample=NEAREST, box=None):
"""
Returns a resized copy of this image.
@ -1567,7 +1721,8 @@ class Image(object):
return self._new(self.im.resize(size, resample, box))
def rotate(self, angle, resample=NEAREST, expand=0):
def rotate(self, angle, resample=NEAREST, expand=0, center=None,
translate=None):
"""
Returns a rotated copy of this image. This method returns a
copy of this image, rotated the given number of degrees counter
@ -1584,48 +1739,86 @@ class Image(object):
:param expand: Optional expansion flag. If true, expands the output
image to make it large enough to hold the entire rotated image.
If false or omitted, make the output image the same size as the
input image.
input image. Note that the expand flag assumes rotation around
the center and no translation.
:param center: Optional center of rotation (a 2-tuple). Origin is
the upper left corner. Default is the center of the image.
:param translate: An optional post-rotate translation (a 2-tuple).
:returns: An :py:class:`~PIL.Image.Image` object.
"""
angle = angle % 360.0
# Fast paths regardless of filter
if angle == 0:
return self.copy()
if angle == 180:
return self.transpose(ROTATE_180)
if angle == 90 and expand:
return self.transpose(ROTATE_90)
if angle == 270 and expand:
return self.transpose(ROTATE_270)
# Fast paths regardless of filter, as long as we're not
# translating or changing the center.
if not (center or translate):
if angle == 0:
return self.copy()
if angle == 180:
return self.transpose(ROTATE_180)
if angle == 90 and expand:
return self.transpose(ROTATE_90)
if angle == 270 and expand:
return self.transpose(ROTATE_270)
# Calculate the affine matrix. Note that this is the reverse
# transformation (from destination image to source) because we
# want to interpolate the (discrete) destination pixel from
# the local area around the (floating) source pixel.
# The matrix we actually want (note that it operates from the right):
# (1, 0, tx) (1, 0, cx) ( cos a, sin a, 0) (1, 0, -cx)
# (0, 1, ty) * (0, 1, cy) * (-sin a, cos a, 0) * (0, 1, -cy)
# (0, 0, 1) (0, 0, 1) ( 0, 0, 1) (0, 0, 1)
# The reverse matrix is thus:
# (1, 0, cx) ( cos -a, sin -a, 0) (1, 0, -cx) (1, 0, -tx)
# (0, 1, cy) * (-sin -a, cos -a, 0) * (0, 1, -cy) * (0, 1, -ty)
# (0, 0, 1) ( 0, 0, 1) (0, 0, 1) (0, 0, 1)
# In any case, the final translation may be updated at the end to
# compensate for the expand flag.
w, h = self.size
if translate is None:
translate = [0, 0]
if center is None:
center = [w / 2.0, h / 2.0]
angle = - math.radians(angle)
matrix = [
round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0,
round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0
]
]
def transform(x, y, matrix=matrix):
def transform(x, y, matrix):
(a, b, c, d, e, f) = matrix
return a*x + b*y + c, d*x + e*y + f
w, h = self.size
matrix[2], matrix[5] = transform(-center[0] - translate[0],
-center[1] - translate[1], matrix)
matrix[2] += center[0]
matrix[5] += center[1]
if expand:
# calculate output size
xx = []
yy = []
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
x, y = transform(x, y)
x, y = transform(x, y, matrix)
xx.append(x)
yy.append(y)
w = int(math.ceil(max(xx)) - math.floor(min(xx)))
h = int(math.ceil(max(yy)) - math.floor(min(yy)))
nw = int(math.ceil(max(xx)) - math.floor(min(xx)))
nh = int(math.ceil(max(yy)) - math.floor(min(yy)))
# adjust center
x, y = transform(w / 2.0, h / 2.0)
matrix[2] = self.size[0] / 2.0 - x
matrix[5] = self.size[1] / 2.0 - y
# We multiply a translation matrix from the right. Because of its
# special form, this is the same as taking the image of the
# translation vector as new translation vector.
matrix[2], matrix[5] = transform(-(nw - w) / 2.0,
-(nh - h) / 2.0,
matrix)
w, h = nw, nh
return self.transform((w, h), AFFINE, matrix, resample)
@ -1689,7 +1882,10 @@ class Image(object):
if not format:
if ext not in EXTENSION:
init()
format = EXTENSION[ext]
try:
format = EXTENSION[ext]
except KeyError:
raise ValueError('unknown file extension: {}'.format(ext))
if format.upper() not in SAVE:
init()
@ -1740,8 +1936,8 @@ class Image(object):
PPM file, and calls either the **xv** utility or the **display**
utility, depending on which one can be found.
On macOS, this method saves the image to a temporary BMP file, and opens
it with the native Preview application.
On macOS, this method saves the image to a temporary BMP file, and
opens it with the native Preview application.
On Windows, it saves the image to a temporary BMP file, and uses
the standard BMP display utility to show it (usually Paint).
@ -1956,20 +2152,19 @@ class Image(object):
def toqimage(self):
"""Returns a QImage copy of this image"""
from PIL import ImageQt
from . import ImageQt
if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed")
return ImageQt.toqimage(self)
def toqpixmap(self):
"""Returns a QPixmap copy of this image"""
from PIL import ImageQt
from . import ImageQt
if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed")
return ImageQt.toqpixmap(self)
# --------------------------------------------------------------------
# Abstract handlers.
@ -1994,6 +2189,7 @@ def _wedge():
return Image()._new(core.wedge("L"))
def _check_size(size):
"""
Common check to enforce type and sanity check on size tuples
@ -2006,11 +2202,12 @@ def _check_size(size):
raise ValueError("Size must be a tuple")
if len(size) != 2:
raise ValueError("Size must be a tuple of length 2")
if size[0] <= 0 or size[1] <= 0:
raise ValueError("Width and Height must be > 0")
if size[0] < 0 or size[1] < 0:
raise ValueError("Width and height must be >= 0")
return True
def new(mode, size, color=0):
"""
Creates a new image with the given mode and size.
@ -2036,7 +2233,7 @@ def new(mode, size, color=0):
if isStringType(color):
# css3-style specifier
from PIL import ImageColor
from . import ImageColor
color = ImageColor.getcolor(color, mode)
return Image()._new(core.fill(mode, size, color))
@ -2082,7 +2279,7 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
def fromstring(*args, **kw):
raise NotImplementedError("fromstring() has been removed. " +
"Please call frombytes() instead.")
"Please call frombytes() instead.")
def frombuffer(mode, size, data, decoder_name="raw", *args):
@ -2164,16 +2361,13 @@ def fromarray(obj, mode=None):
arr = obj.__array_interface__
shape = arr['shape']
ndim = len(shape)
try:
strides = arr['strides']
except KeyError:
strides = None
strides = arr.get('strides', None)
if mode is None:
try:
typekey = (1, 1) + shape[2:], arr['typestr']
mode, rawmode = _fromarray_typemap[typekey]
except KeyError:
# print typekey
# print(typekey)
raise TypeError("Cannot handle this data type")
else:
rawmode = mode
@ -2198,7 +2392,7 @@ def fromarray(obj, mode=None):
def fromqimage(im):
"""Creates an image instance from a QImage image"""
from PIL import ImageQt
from . import ImageQt
if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed")
return ImageQt.fromqimage(im)
@ -2206,11 +2400,12 @@ def fromqimage(im):
def fromqpixmap(im):
"""Creates an image instance from a QPixmap image"""
from PIL import ImageQt
from . import ImageQt
if not ImageQt.qt_is_installed:
raise ImportError("Qt bindings are not installed")
return ImageQt.fromqpixmap(im)
_fromarray_typemap = {
# (shape, typestr) => mode, rawmode
# first two members of shape are set to one
@ -2276,6 +2471,7 @@ def open(fp, mode="r"):
if mode != "r":
raise ValueError("bad mode %r" % mode)
exclusive_fp = False
filename = ""
if isPath(fp):
filename = fp
@ -2289,11 +2485,13 @@ def open(fp, mode="r"):
if filename:
fp = builtins.open(filename, "rb")
exclusive_fp = True
try:
fp.seek(0)
except (AttributeError, io.UnsupportedOperation):
fp = io.BytesIO(fp.read())
exclusive_fp = True
prefix = fp.read(16)
@ -2322,8 +2520,11 @@ def open(fp, mode="r"):
im = _open_core(fp, filename, prefix)
if im:
im._exclusive_fp = exclusive_fp
return im
if exclusive_fp:
fp.close()
raise IOError("cannot identify image file %r"
% (filename if filename else fp))
@ -2491,6 +2692,44 @@ def register_extension(id, extension):
EXTENSION[extension.lower()] = id.upper()
def registered_extensions():
"""
Returns a dictionary containing all file extensions belonging
to registered plugins
"""
if not bool(EXTENSION):
init()
return EXTENSION
def register_decoder(name, decoder):
"""
Registers an image decoder. This function should not be
used in application code.
:param name: The name of the decoder
:param decoder: A callable(mode, args) that returns an
ImageFile.PyDecoder object
.. versionadded:: 4.1.0
"""
DECODERS[name] = decoder
def register_encoder(name, encoder):
"""
Registers an image encoder. This function should not be
used in application code.
:param name: The name of the encoder
:param encoder: A callable(mode, args) that returns an
ImageFile.PyEncoder object
.. versionadded:: 4.1.0
"""
ENCODERS[name] = encoder
# --------------------------------------------------------------------
# Simple display support. User code may override this.
@ -2500,7 +2739,7 @@ def _show(image, **options):
def _showxv(image, title=None, **options):
from PIL import ImageShow
from . import ImageShow
ImageShow.show(image, title, **options)
@ -2529,3 +2768,21 @@ def effect_noise(size, sigma):
:param sigma: Standard deviation of noise.
"""
return Image()._new(core.effect_noise(size, sigma))
def linear_gradient(mode):
"""
Generate 256x256 linear gradient from black to white, top to bottom.
:param mode: Input mode.
"""
return Image()._new(core.linear_gradient(mode))
def radial_gradient(mode):
"""
Generate 256x256 radial gradient from black to white, centre to edge.
:param mode: Input mode.
"""
return Image()._new(core.radial_gradient(mode))

View File

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

View File

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

View File

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

View File

@ -31,10 +31,9 @@
#
import numbers
import warnings
from PIL import Image, ImageColor
from PIL._util import isStringType
from . import Image, ImageColor
from ._util import isStringType
"""
A simple 2D drawing interface for PIL images.
@ -87,25 +86,14 @@ class ImageDraw(object):
self.fill = 0
self.font = None
def setink(self, ink):
raise NotImplementedError("setink() has been removed. " +
"Please use keyword arguments instead.")
def setfill(self, onoff):
raise NotImplementedError("setfill() has been removed. " +
"Please use keyword arguments instead.")
def setfont(self, font):
warnings.warn("setfont() is deprecated. " +
"Please set the attribute directly instead.")
# compatibility
self.font = font
def getfont(self):
"""Get the current default font."""
"""
Get the current default font.
:returns: An image font."""
if not self.font:
# FIXME: should add a font repository
from PIL import ImageFont
from . import ImageFont
self.font = ImageFont.load_default()
return self.font
@ -222,7 +210,6 @@ class ImageDraw(object):
if self._multiline_check(text):
return self.multiline_text(xy, text, fill, font, anchor,
*args, **kwargs)
ink, fill = self._getink(fill)
if font is None:
font = self.getfont()
@ -230,17 +217,17 @@ class ImageDraw(object):
ink = fill
if ink is not None:
try:
mask, offset = font.getmask2(text, self.fontmode)
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs)
xy = xy[0] + offset[0], xy[1] + offset[1]
except AttributeError:
try:
mask = font.getmask(text, self.fontmode)
mask = font.getmask(text, self.fontmode, *args, **kwargs)
except TypeError:
mask = font.getmask(text)
self.draw.draw_bitmap(xy, mask, ink)
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
spacing=4, align="left"):
spacing=4, align="left", direction=None, features=None):
widths = []
max_width = 0
lines = self._multiline_split(text)
@ -259,25 +246,30 @@ class ImageDraw(object):
left += (max_width - widths[idx])
else:
assert False, 'align must be "left", "center" or "right"'
self.text((left, top), line, fill, font, anchor)
self.text((left, top), line, fill, font, anchor,
direction=direction, features=features)
top += line_spacing
left = xy[0]
def textsize(self, text, font=None, *args, **kwargs):
def textsize(self, text, font=None, spacing=4, direction=None,
features=None):
"""Get the size of a given string, in pixels."""
if self._multiline_check(text):
return self.multiline_textsize(text, font, *args, **kwargs)
return self.multiline_textsize(text, font, spacing,
direction, features)
if font is None:
font = self.getfont()
return font.getsize(text)
return font.getsize(text, direction, features)
def multiline_textsize(self, text, font=None, spacing=4):
def multiline_textsize(self, text, font=None, spacing=4, direction=None,
features=None):
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines:
line_width, line_height = self.textsize(line, font)
line_width, line_height = self.textsize(line, font, spacing,
direction, features)
max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing
@ -298,6 +290,7 @@ def Draw(im, mode=None):
except AttributeError:
return ImageDraw(im, mode)
# experimental access to the outline API
try:
Outline = Image.core.outline
@ -319,17 +312,17 @@ def getdraw(im=None, hints=None):
handler = None
if not hints or "nicest" in hints:
try:
from PIL import _imagingagg as handler
from . import _imagingagg as handler
except ImportError:
pass
if handler is None:
from PIL import ImageDraw2 as handler
from . import ImageDraw2 as handler
if im:
im = handler.Draw(im)
return im, handler
def floodfill(image, xy, value, border=None):
def floodfill(image, xy, value, border=None, thresh=0):
"""
(experimental) Fills a bounded region with a given color.
@ -340,16 +333,20 @@ def floodfill(image, xy, value, border=None):
pixels with a color different from the border color. If not given,
the region consists of pixels having the same color as the seed
pixel.
:param thresh: Optional threshold value which specifies a maximum
tolerable difference of a pixel value from the 'background' in
order for it to be replaced. Useful for filling regions of non-
homogeneous, but similar, colors.
"""
# based on an implementation by Eric S. Raymond
pixel = image.load()
x, y = xy
try:
background = pixel[x, y]
if background == value:
if _color_diff(value, background) <= thresh:
return # seed point already has fill color
pixel[x, y] = value
except IndexError:
except (ValueError, IndexError):
return # seed point outside image
edge = [(x, y)]
if border is None:
@ -362,7 +359,7 @@ def floodfill(image, xy, value, border=None):
except IndexError:
pass
else:
if p == background:
if _color_diff(p, background) <= thresh:
pixel[s, t] = value
newedge.append((s, t))
edge = newedge
@ -380,3 +377,10 @@ def floodfill(image, xy, value, border=None):
pixel[s, t] = value
newedge.append((s, t))
edge = newedge
def _color_diff(rgb1, rgb2):
"""
Uses 1-norm distance to calculate difference between two rgb values.
"""
return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2])

View File

@ -16,7 +16,7 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
class Pen(object):

View File

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

View File

@ -27,8 +27,8 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image
from PIL._util import isPath
from . import Image
from ._util import isPath
import io
import os
import sys
@ -88,10 +88,13 @@ class ImageFile(Image.Image):
# filename
self.fp = open(fp, "rb")
self.filename = fp
self._exclusive_fp = True
else:
# stream
self.fp = fp
self.filename = filename
# can be overridden
self._exclusive_fp = None
try:
self._open()
@ -100,6 +103,9 @@ class ImageFile(Image.Image):
KeyError, # unsupported mode
EOFError, # got header but not the first frame
struct.error) as v:
# close the file only if we have opened it this constructor
if self._exclusive_fp:
self.fp.close()
raise SyntaxError(v)
if not self.mode or self.size[0] <= 0:
@ -115,6 +121,8 @@ class ImageFile(Image.Image):
# raise exception if something's wrong. must be called
# directly after open, and closes file when finished.
if self._exclusive_fp:
self.fp.close()
self.fp = None
def load(self):
@ -152,7 +160,7 @@ class ImageFile(Image.Image):
# try memory mapping
decoder_name, extents, offset, args = self.tile[0]
if decoder_name == "raw" and len(args) >= 3 and args[0] == self.mode \
and args[0] in Image._MAPMODES:
and args[0] in Image._MAPMODES:
try:
if hasattr(Image.core, "map"):
# use built-in mapper WIN32 only
@ -178,7 +186,7 @@ class ImageFile(Image.Image):
self.map = None
self.load_prepare()
err_code = -3 # initialize to unknown error
if not self.map:
# sort tiles in file order
self.tile.sort(key=_tilesort)
@ -191,12 +199,9 @@ class ImageFile(Image.Image):
for decoder_name, extents, offset, args in self.tile:
decoder = Image._getdecoder(self.mode, decoder_name,
args, self.decoderconfig)
args, self.decoderconfig)
seek(offset)
try:
decoder.setimage(self.im, extents)
except ValueError:
continue
decoder.setimage(self.im, extents)
if decoder.pulls_fd:
decoder.setfd(self.fp)
status, err_code = decoder.decode(b"")
@ -211,7 +216,7 @@ class ImageFile(Image.Image):
else:
raise IOError("image file is truncated")
if not s and not decoder.handles_eof: # truncated jpeg
if not s: # truncated jpeg
self.tile = []
# JpegDecode needs to clean things up here either way
@ -237,20 +242,16 @@ class ImageFile(Image.Image):
self.tile = []
self.readonly = readonly
self.fp = None # might be shared
self.load_end()
if self._exclusive_fp and self._close_exclusive_fp_after_loading:
self.fp.close()
self.fp = None
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
# still raised if decoder fails to return anything
raise_ioerror(err_code)
# post processing
if hasattr(self, "tile_post_rotate"):
# FIXME: This is a hack to handle rotated PCD's
self.im = self.im.rotate(self.tile_post_rotate)
self.size = self.im.size
self.load_end()
return Image.Image.load(self)
def load_prepare(self):
@ -379,11 +380,8 @@ class Parser(object):
# attempt to open this file
try:
try:
fp = io.BytesIO(self.data)
with io.BytesIO(self.data) as fp:
im = Image.open(fp)
finally:
fp.close() # explicitly close the virtual file
except IOError:
# traceback.print_exc()
pass # not enough data
@ -431,12 +429,11 @@ class Parser(object):
if self.data:
# incremental parsing not possible; reopen the file
# not that we have all data
try:
fp = io.BytesIO(self.data)
self.image = Image.open(fp)
finally:
self.image.load()
fp.close() # explicitly close the virtual file
with io.BytesIO(self.data) as fp:
try:
self.image = Image.open(fp)
finally:
self.image.load()
return self.image
@ -526,3 +523,128 @@ def _safe_read(fp, size):
data.append(block)
size -= len(block)
return b"".join(data)
class PyCodecState(object):
def __init__(self):
self.xsize = 0
self.ysize = 0
self.xoff = 0
self.yoff = 0
def extents(self):
return (self.xoff, self.yoff,
self.xoff+self.xsize, self.yoff+self.ysize)
class PyDecoder(object):
"""
Python implementation of a format decoder. Override this class and
add the decoding logic in the `decode` method.
See :ref:`Writing Your Own File Decoder in Python<file-decoders-py>`
"""
_pulls_fd = False
def __init__(self, mode, *args):
self.im = None
self.state = PyCodecState()
self.fd = None
self.mode = mode
self.init(args)
def init(self, args):
"""
Override to perform decoder specific initialization
:param args: Array of args items from the tile entry
:returns: None
"""
self.args = args
@property
def pulls_fd(self):
return self._pulls_fd
def decode(self, buffer):
"""
Override to perform the decoding process.
:param buffer: A bytes object with the data to be decoded. If `handles_eof`
is set, then `buffer` will be empty and `self.fd` will be set.
:returns: A tuple of (bytes consumed, errcode). If finished with decoding
return <0 for the bytes consumed. Err codes are from `ERRORS`
"""
raise NotImplementedError()
def cleanup(self):
"""
Override to perform decoder specific cleanup
:returns: None
"""
pass
def setfd(self, fd):
"""
Called from ImageFile to set the python file-like object
:param fd: A python file-like object
:returns: None
"""
self.fd = fd
def setimage(self, im, extents=None):
"""
Called from ImageFile to set the core output image for the decoder
:param im: A core image object
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
for this tile
:returns: None
"""
# following c code
self.im = im
if extents:
(x0, y0, x1, y1) = extents
else:
(x0, y0, x1, y1) = (0, 0, 0, 0)
if x0 == 0 and x1 == 0:
self.state.xsize, self.state.ysize = self.im.size
else:
self.state.xoff = x0
self.state.yoff = y0
self.state.xsize = x1 - x0
self.state.ysize = y1 - y0
if self.state.xsize <= 0 or self.state.ysize <= 0:
raise ValueError("Size cannot be negative")
if (self.state.xsize + self.state.xoff > self.im.size[0] or
self.state.ysize + self.state.yoff > self.im.size[1]):
raise ValueError("Tile cannot extend outside image")
def set_as_raw(self, data, rawmode=None):
"""
Convenience method to set the internal image from a stream of raw data
:param data: Bytes to be set
:param rawmode: The rawmode to be used for the decoder. If not specified,
it will default to the mode of the image
:returns: None
"""
if not rawmode:
rawmode = self.mode
d = Image._getdecoder(self.mode, 'raw', (rawmode))
d.setimage(self.im, self.state.extents())
s = d.decode(data)
if s[0] >= 0:
raise ValueError("not enough image data")
if s[1] != 0:
raise ValueError("cannot decode image data")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,8 +17,8 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image
from PIL._util import isStringType
from . import Image
from ._util import isStringType
import operator
import functools
@ -39,7 +39,7 @@ def _border(border):
def _color(color, mode):
if isStringType(color):
from PIL import ImageColor
from . import ImageColor
color = ImageColor.getcolor(color, mode)
return color
@ -206,7 +206,8 @@ def deform(image, deformer, resample=Image.BILINEAR):
:param image: The image to deform.
:param deformer: A deformer object. Any object that implements a
**getmesh** method can be used.
:param resample: What resampling filter to use.
:param resample: An optional resampling filter. Same values possible as
in the PIL.Image.transform function.
:return: An image.
"""
return image.transform(

View File

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

View File

@ -14,7 +14,7 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image
from . import Image
# the Python class below is overridden by the C implementation.

View File

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

View File

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

View File

@ -25,14 +25,21 @@
# See the README file for information on usage and redistribution.
#
try:
import tkinter
except ImportError:
import Tkinter
tkinter = Tkinter
del Tkinter
import sys
from PIL import Image
if sys.version_info[0] > 2:
import tkinter
else:
import Tkinter as tkinter
# required for pypy, which always has cffi installed
try:
from cffi import FFI
ffi = FFI()
except ImportError:
pass
from . import Image
from io import BytesIO
@ -182,9 +189,15 @@ class PhotoImage(object):
except tkinter.TclError:
# activate Tkinter hook
try:
from PIL import _imagingtk
from . import _imagingtk
try:
_imagingtk.tkinit(tk.interpaddr(), 1)
if hasattr(tk, 'interp'):
# Pypy is using a ffi cdata element
# (Pdb) self.tk.interp
# <cdata 'Tcl_Interp *' 0x3061b50>
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
else:
_imagingtk.tkinit(tk.interpaddr(), 1)
except AttributeError:
_imagingtk.tkinit(id(tk), 0)
tk.call("PyImagingPhoto", self.__photo, block.id)
@ -264,6 +277,8 @@ class BitmapImage(object):
def getimage(photo):
""" This function is unimplemented """
"""Copies the contents of a PhotoImage to a PIL image memory."""
photo.tk.call("PyImagingPhotoGet", photo)

View File

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

View File

@ -17,7 +17,7 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image
from . import Image
class HDC(object):
@ -182,14 +182,6 @@ class Dib(object):
"""
return self.image.tobytes()
def fromstring(self, *args, **kw):
raise NotImplementedError("fromstring() has been removed. " +
"Please use frombytes() instead.")
def tostring(self, *args, **kw):
raise NotImplementedError("tostring() has been removed. " +
"Please use tobytes() instead.")
class Window(object):
"""Create a Window with the given title size."""

View File

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

View File

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

View File

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

View File

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

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
`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
from PIL import Image, ImageFile
from . import Image, ImageFile
__version__ = "0.2"
@ -66,6 +66,7 @@ class McIdasImageFile(ImageFile.ImageFile):
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
# --------------------------------------------------------------------
# registry

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
#
# The Python Imaging Library.
# $Id$
#
# MSP file handling
#
@ -9,15 +8,25 @@
# History:
# 95-09-05 fl Created
# 97-01-03 fl Read/write MSP images
# 17-02-21 es Fixed RLE interpretation
#
# Copyright (c) Secret Labs AB 1997.
# Copyright (c) Fredrik Lundh 1995-97.
# Copyright (c) Eric Soroos 2017.
#
# See the README file for information on usage and redistribution.
#
# More info on this format: https://archive.org/details/gg243631
# Page 313:
# Figure 205. Windows Paint Version 1: "DanM" Format
# Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03
#
# See also: http://www.fileformat.info/format/mspaint/egff.htm
from PIL import Image, ImageFile, _binary
from . import Image, ImageFile
from ._binary import i16le as i16, o16le as o16, i8
import struct
import io
__version__ = "0.1"
@ -25,8 +34,6 @@ __version__ = "0.1"
#
# read MSP files
i16 = _binary.i16le
def _accept(prefix):
return prefix[:4] in [b"DanM", b"LinS"]
@ -61,13 +68,93 @@ class MspImageFile(ImageFile.ImageFile):
if s[:4] == b"DanM":
self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
else:
self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)]
self.tile = [("MSP", (0, 0)+self.size, 32, None)]
class MspDecoder(ImageFile.PyDecoder):
# The algo for the MSP decoder is from
# http://www.fileformat.info/format/mspaint/egff.htm
# cc-by-attribution -- That page references is taken from the
# Encyclopedia of Graphics File Formats and is licensed by
# O'Reilly under the Creative Common/Attribution license
#
# For RLE encoded files, the 32byte header is followed by a scan
# line map, encoded as one 16bit word of encoded byte length per
# line.
#
# NOTE: the encoded length of the line can be 0. This was not
# handled in the previous version of this encoder, and there's no
# mention of how to handle it in the documentation. From the few
# examples I've seen, I've assumed that it is a fill of the
# background color, in this case, white.
#
#
# Pseudocode of the decoder:
# Read a BYTE value as the RunType
# If the RunType value is zero
# Read next byte as the RunCount
# Read the next byte as the RunValue
# Write the RunValue byte RunCount times
# If the RunType value is non-zero
# Use this value as the RunCount
# Read and write the next RunCount bytes literally
#
# e.g.:
# 0x00 03 ff 05 00 01 02 03 04
# would yield the bytes:
# 0xff ff ff 00 01 02 03 04
#
# which are then interpreted as a bit packed mode '1' image
_pulls_fd = True
def decode(self, buffer):
img = io.BytesIO()
blank_line = bytearray((0xff,)*((self.state.xsize+7)//8))
try:
self.fd.seek(32)
rowmap = struct.unpack_from("<%dH" % (self.state.ysize),
self.fd.read(self.state.ysize*2))
except struct.error:
raise IOError("Truncated MSP file in row map")
for x, rowlen in enumerate(rowmap):
try:
if rowlen == 0:
img.write(blank_line)
continue
row = self.fd.read(rowlen)
if len(row) != rowlen:
raise IOError("Truncated MSP file, expected %d bytes on row %s",
(rowlen, x))
idx = 0
while idx < rowlen:
runtype = i8(row[idx])
idx += 1
if runtype == 0:
(runcount, runval) = struct.unpack("Bc", row[idx:idx+2])
img.write(runval * runcount)
idx += 2
else:
runcount = runtype
img.write(row[idx:idx+runcount])
idx += runcount
except struct.error:
raise IOError("Corrupted MSP file in row %d" % x)
self.set_as_raw(img.getvalue(), ("1", 0, 1))
return 0, 0
Image.register_decoder('MSP', MspDecoder)
#
# write MSP files (uncompressed only)
o16 = _binary.o16le
def _save(im, fp, filename):
@ -95,6 +182,7 @@ def _save(im, fp, filename):
# image body
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
#
# registry

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.
#
from PIL import EpsImagePlugin
from . import EpsImagePlugin
import sys
##

View File

@ -13,7 +13,7 @@
# 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).
##
from PIL import Image, ImageFile, _binary
from . import Image, ImageFile
from ._binary import o8, o16be as o16b
__version__ = "1.0"
@ -108,9 +109,6 @@ _COMPRESSION_TYPES = {
"scanline": 0x00,
}
o8 = _binary.o8
o16b = _binary.o16be
#
# --------------------------------------------------------------------

View File

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

View File

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

View File

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

View File

@ -20,8 +20,8 @@
# Image plugin for PDF images (output only).
##
from PIL import Image, ImageFile
from PIL._binary import i8
from . import Image, ImageFile, ImageSequence
from ._binary import i8
import io
__version__ = "0.4"
@ -37,11 +37,11 @@ __version__ = "0.4"
# 4. page
# 5. page contents
def _obj(fp, obj, **dict):
def _obj(fp, obj, **dictionary):
fp.write("%d 0 obj\n" % obj)
if dict:
if dictionary:
fp.write("<<\n")
for k, v in dict.items():
for k, v in dictionary.items():
if v is not None:
fp.write("/%s %s\n" % (k, v))
fp.write(">>\n")
@ -133,13 +133,24 @@ def _save(im, fp, filename, save_all=False):
#
# pages
numberOfPages = 1
ims = [im]
if save_all:
try:
numberOfPages = im.n_frames
except AttributeError:
# Image format does not have n_frames. It is a single frame image
pass
append_images = im.encoderinfo.get("append_images", [])
for append_im in append_images:
if append_im.mode != im.mode:
append_im = append_im.convert(im.mode)
append_im.encoderinfo = im.encoderinfo.copy()
ims.append(append_im)
numberOfPages = 0
for im in ims:
im_numberOfPages = 1
if save_all:
try:
im_numberOfPages = im.n_frames
except AttributeError:
# Image format does not have n_frames. It is a single frame image
pass
numberOfPages += im_numberOfPages
pages = [str(pageNumber*3+4)+" 0 R"
for pageNumber in range(0, numberOfPages)]
@ -151,90 +162,92 @@ def _save(im, fp, filename, save_all=False):
Kids="["+"\n".join(pages)+"]")
_endobj(fp)
for pageNumber in range(0, numberOfPages):
im.seek(pageNumber)
pageNumber = 0
for imSequence in ims:
for im in ImageSequence.Iterator(imSequence):
#
# image
#
# image
op = io.BytesIO()
op = io.BytesIO()
if filter == "/ASCIIHexDecode":
if bits == 1:
# FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way...
data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None)
im.putdata(data)
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else:
raise ValueError("unsupported PDF filter (%s)" % filter)
if filter == "/ASCIIHexDecode":
if bits == 1:
# FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way...
data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None)
im.putdata(data)
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else:
raise ValueError("unsupported PDF filter (%s)" % filter)
#
# Get image characteristics
#
# Get image characteristics
width, height = im.size
width, height = im.size
xref.append(fp.tell())
_obj(
fp, pageNumber*3+3,
Type="/XObject",
Subtype="/Image",
Width=width, # * 72.0 / resolution,
Height=height, # * 72.0 / resolution,
Length=len(op.getvalue()),
Filter=filter,
BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
xref.append(fp.tell())
_obj(
fp, pageNumber*3+3,
Type="/XObject",
Subtype="/Image",
Width=width, # * 72.0 / resolution,
Height=height, # * 72.0 / resolution,
Length=len(op.getvalue()),
Filter=filter,
BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
fp.write("stream\n")
fp.fp.write(op.getvalue())
fp.write("\nendstream\n")
fp.write("stream\n")
fp.fp.write(op.getvalue())
fp.write("\nendstream\n")
_endobj(fp)
_endobj(fp)
#
# page
#
# page
xref.append(fp.tell())
_obj(fp, pageNumber*3+4)
fp.write(
"<<\n/Type /Page\n/Parent 2 0 R\n"
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/XObject << /image %d 0 R >>\n>>\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
procset,
pageNumber*3+3,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution),
pageNumber*3+5))
_endobj(fp)
xref.append(fp.tell())
_obj(fp, pageNumber*3+4)
fp.write(
"<<\n/Type /Page\n/Parent 2 0 R\n"
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/XObject << /image %d 0 R >>\n>>\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
procset,
pageNumber*3+3,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution),
pageNumber*3+5))
_endobj(fp)
#
# page contents
#
# page contents
op = TextWriter(io.BytesIO())
op = TextWriter(io.BytesIO())
op.write(
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
op.write(
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
xref.append(fp.tell())
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
xref.append(fp.tell())
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
fp.write("stream\n")
fp.fp.write(op.fp.getvalue())
fp.write("\nendstream\n")
fp.write("stream\n")
fp.fp.write(op.fp.getvalue())
fp.write("\nendstream\n")
_endobj(fp)
_endobj(fp)
pageNumber += 1
#
# trailer

View File

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

View File

@ -31,23 +31,18 @@
# See the README file for information on usage and redistribution.
#
from __future__ import print_function
import logging
import re
import zlib
import struct
from PIL import Image, ImageFile, ImagePalette, _binary
from . import Image, ImageFile, ImagePalette
from ._binary import i8, i16be as i16, i32be as i32, o16be as o16, o32be as o32
__version__ = "0.9"
logger = logging.getLogger(__name__)
i8 = _binary.i8
i16 = _binary.i16be
i32 = _binary.i32be
is_cid = re.compile(br"\w\w\w\w").match
@ -118,7 +113,8 @@ class ChunkStream(object):
length = i32(s)
if not is_cid(cid):
raise SyntaxError("broken PNG file (chunk %s)" % repr(cid))
if not ImageFile.LOAD_TRUNCATED_IMAGES:
raise SyntaxError("broken PNG file (chunk %s)" % repr(cid))
return cid, pos, length
@ -621,10 +617,6 @@ class PngImageFile(ImageFile.ImageFile):
# --------------------------------------------------------------------
# PNG writer
o8 = _binary.o8
o16 = _binary.o16be
o32 = _binary.o32be
_OUTMODES = {
# supported PIL modes, and corresponding rawmodes/bits/color combinations
"1": ("1", b'\x01\x00'),
@ -722,6 +714,32 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
b'\0', # 11: filter category
b'\0') # 12: interlace flag
chunks = [b"cHRM", b"gAMA", b"sBIT", b"sRGB", b"tIME"]
icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
if icc:
# ICC profile
# according to PNG spec, the iCCP chunk contains:
# Profile name 1-79 bytes (character string)
# Null separator 1 byte (null character)
# Compression method 1 byte (0)
# Compressed profile n bytes (zlib with deflate compression)
name = b"ICC Profile"
data = name + b"\0\0" + zlib.compress(icc)
chunk(fp, b"iCCP", data)
else:
chunks.remove(b"sRGB")
info = im.encoderinfo.get("pnginfo")
if info:
chunks_multiple_allowed = [b"sPLT", b"iTXt", b"tEXt", b"zTXt"]
for cid, data in info.chunks:
if cid in chunks:
chunks.remove(cid)
chunk(fp, cid, data)
elif cid in chunks_multiple_allowed:
chunk(fp, cid, data)
if im.mode == "P":
palette_byte_number = (2 ** bits) * 3
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
@ -768,20 +786,11 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
info = im.encoderinfo.get("pnginfo")
if info:
chunks = [b"bKGD", b"hIST"]
for cid, data in info.chunks:
chunk(fp, cid, data)
icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
if icc:
# ICC profile
# according to PNG spec, the iCCP chunk contains:
# Profile name 1-79 bytes (character string)
# Null separator 1 byte (null character)
# Compression method 1 byte (0)
# Compressed profile n bytes (zlib with deflate compression)
name = b"ICC Profile"
data = name + b"\0\0" + zlib.compress(icc)
chunk(fp, b"iCCP", data)
if cid in chunks:
chunks.remove(cid)
chunk(fp, cid, data)
ImageFile._save(im, _idat(fp, chunk),
[("zip", (0, 0)+im.size, 0, rawmode)])

View File

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

View File

@ -18,7 +18,8 @@
__version__ = "0.4"
from PIL import Image, ImageFile, ImagePalette, _binary
from . import Image, ImageFile, ImagePalette
from ._binary import i8, i16be as i16, i32be as i32
MODES = {
# (photoshop mode, bits) -> (pil mode, required channels)
@ -33,13 +34,6 @@ MODES = {
(9, 8): ("LAB", 3)
}
#
# helpers
i8 = _binary.i8
i16 = _binary.i16be
i32 = _binary.i32be
# --------------------------------------------------------------------.
# read PSD images

View File

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

View File

@ -7,9 +7,12 @@
# See "The SGI Image File Format (Draft version 0.97)", Paul Haeberli.
# <ftp://ftp.sgi.com/graphics/SGIIMAGESPEC>
#
#
# History:
# 2016-16-10 mb Add save method without compression
# 1995-09-10 fl Created
#
# Copyright (c) 2016 by Mickael Bonfill.
# Copyright (c) 2008 by Karsten Hiddemann.
# Copyright (c) 1997 by Secret Labs AB.
# Copyright (c) 1995 by Fredrik Lundh.
@ -18,12 +21,12 @@
#
from PIL import Image, ImageFile, _binary
from . import Image, ImageFile
from ._binary import i8, o8, i16be as i16
import struct
import os
__version__ = "0.2"
i8 = _binary.i8
i16 = _binary.i16be
__version__ = "0.3"
def _accept(prefix):
@ -76,12 +79,79 @@ class SgiImageFile(ImageFile.ImageFile):
elif compression == 1:
raise ValueError("SGI RLE encoding not supported")
def _save(im, fp, filename):
if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L":
raise ValueError("Unsupported SGI image mode")
# Flip the image, since the origin of SGI file is the bottom-left corner
im = im.transpose(Image.FLIP_TOP_BOTTOM)
# Define the file as SGI File Format
magicNumber = 474
# Run-Length Encoding Compression - Unsupported at this time
rle = 0
# Byte-per-pixel precision, 1 = 8bits per pixel
bpc = 1
# Number of dimensions (x,y,z)
dim = 3
# X Dimension = width / Y Dimension = height
x, y = im.size
if im.mode == "L" and y == 1:
dim = 1
elif im.mode == "L":
dim = 2
# Z Dimension: Number of channels
z = len(im.mode)
if dim == 1 or dim == 2:
z = 1
# Minimum Byte value
pinmin = 0
# Maximum Byte value (255 = 8bits per pixel)
pinmax = 255
# Image name (79 characters max, truncated below in write)
imgName = os.path.splitext(os.path.basename(filename))[0]
if str is not bytes:
imgName = imgName.encode('ascii', 'ignore')
# Standard representation of pixel in the file
colormap = 0
fp.write(struct.pack('>h', magicNumber))
fp.write(o8(rle))
fp.write(o8(bpc))
fp.write(struct.pack('>H', dim))
fp.write(struct.pack('>H', x))
fp.write(struct.pack('>H', y))
fp.write(struct.pack('>H', z))
fp.write(struct.pack('>l', pinmin))
fp.write(struct.pack('>l', pinmax))
fp.write(struct.pack('4s', b'')) # dummy
fp.write(struct.pack('79s', imgName)) # truncates to 79 chars
fp.write(struct.pack('s', b'')) # force null byte after imgname
fp.write(struct.pack('>l', colormap))
fp.write(struct.pack('404s', b'')) # dummy
# assert we've got the right number of bands.
if len(im.getbands()) != z:
raise ValueError("incorrect number of bands in SGI write: %s vs %s" %
(z, len(im.getbands())))
for channel in im.split():
fp.write(channel.tobytes())
fp.close()
#
# registry
Image.register_open(SgiImageFile.format, SgiImageFile, _accept)
Image.register_save(SgiImageFile.format, _save)
Image.register_mime(SgiImageFile.format, "image/sgi")
Image.register_mime(SgiImageFile.format, "image/rgb")
Image.register_extension(SgiImageFile.format, ".bw")
Image.register_extension(SgiImageFile.format, ".rgb")
Image.register_extension(SgiImageFile.format, ".rgba")
Image.register_extension(SgiImageFile.format, ".sgi")
# End of file

View File

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

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

View File

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

View File

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

View File

@ -18,12 +18,11 @@
# the WalImageFile.open() function instead.
# This reader is based on the specification available from:
# http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
# https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
# and has been tested with a few sample files found using google.
from __future__ import print_function
from PIL import Image, _binary
from . import Image
from ._binary import i32le as i32
try:
import builtins
@ -31,8 +30,6 @@ except ImportError:
import __builtin__
builtins = __builtin__
i32 = _binary.i32le
def open(filename):
"""
@ -47,33 +44,35 @@ def open(filename):
# FIXME: modify to return a WalImageFile instance instead of
# plain Image object ?
def imopen(fp):
# read header fields
header = fp.read(32+24+32+12)
size = i32(header, 32), i32(header, 36)
offset = i32(header, 40)
# load pixel data
fp.seek(offset)
Image._decompression_bomb_check(size)
im = Image.frombytes("P", size, fp.read(size[0] * size[1]))
im.putpalette(quake2palette)
im.format = "WAL"
im.format_description = "Quake2 Texture"
# strings are null-terminated
im.info["name"] = header[:32].split(b"\0", 1)[0]
next_name = header[56:56+32].split(b"\0", 1)[0]
if next_name:
im.info["next_name"] = next_name
return im
if hasattr(filename, "read"):
fp = filename
return imopen(filename)
else:
fp = builtins.open(filename, "rb")
# read header fields
header = fp.read(32+24+32+12)
size = i32(header, 32), i32(header, 36)
offset = i32(header, 40)
# load pixel data
fp.seek(offset)
im = Image.frombytes("P", size, fp.read(size[0] * size[1]))
im.putpalette(quake2palette)
im.format = "WAL"
im.format_description = "Quake2 Texture"
# strings are null-terminated
im.info["name"] = header[:32].split(b"\0", 1)[0]
next_name = header[56:56+32].split(b"\0", 1)[0]
if next_name:
im.info["next_name"] = next_name
return im
with builtins.open(filename, "rb") as fp:
return imopen(fp)
quake2palette = (
# default palette taken from piffo 0.93 by Hans Häggström

View File

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

View File

@ -14,8 +14,16 @@
#
# See the README file for information on usage and redistribution.
#
# WMF/EMF reference documentation:
# https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-WMF/[MS-WMF].pdf
# http://wvware.sourceforge.net/caolan/index.html
# http://wvware.sourceforge.net/caolan/ora-wmf.html
from __future__ import print_function
from . import Image, ImageFile
from ._binary import i16le as word, si16le as short, i32le as dword, si32le as _long
from PIL import Image, ImageFile, _binary
__version__ = "0.2"
@ -53,24 +61,11 @@ if hasattr(Image.core, "drawwmf"):
register_handler(WmfHandler())
# --------------------------------------------------------------------
word = _binary.i16le
def short(c, o=0):
v = word(c, o)
if v >= 32768:
v -= 65536
return v
dword = _binary.i32le
#
# --------------------------------------------------------------------
# Read WMF file
def _accept(prefix):
return (
prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or
@ -111,7 +106,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
self.info["dpi"] = 72
# print self.mode, self.size, self.info
# print(self.mode, self.size, self.info)
# sanity check (standard metafile header)
if s[22:26] != b"\x01\x00\t\x00":
@ -121,13 +116,13 @@ class WmfStubImageFile(ImageFile.StubImageFile):
# enhanced metafile
# get bounding box
x0 = dword(s, 8)
y0 = dword(s, 12)
x1 = dword(s, 16)
y1 = dword(s, 20)
x0 = _long(s, 8)
y0 = _long(s, 12)
x1 = _long(s, 16)
y1 = _long(s, 20)
# get frame (in 0.01 millimeter units)
frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36)
frame = _long(s, 24), _long(s, 28), _long(s, 32), _long(s, 36)
# normalize size to 72 dots per inch
size = x1 - x0, y1 - y0

View File

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

View File

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

View File

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

View File

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

View File

@ -28,10 +28,9 @@ else:
# Input, le = little endian, be = big endian
# TODO: replace with more readable struct.unpack equivalent
def i16le(c, o=0):
"""
Converts a 2-bytes (16 bits) string to an integer.
Converts a 2-bytes (16 bits) string to an unsigned integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
@ -39,9 +38,19 @@ def i16le(c, o=0):
return unpack("<H", c[o:o+2])[0]
def si16le(c, o=0):
"""
Converts a 2-bytes (16 bits) string to a signed integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
"""
return unpack("<h", c[o:o+2])[0]
def i32le(c, o=0):
"""
Converts a 4-bytes (32 bits) string to an integer.
Converts a 4-bytes (32 bits) string to an unsigned integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
@ -49,6 +58,16 @@ def i32le(c, o=0):
return unpack("<I", c[o:o+4])[0]
def si32le(c, o=0):
"""
Converts a 4-bytes (32 bits) string to a signed integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
"""
return unpack("<i", c[o:o+4])[0]
def i16be(c, o=0):
return unpack(">H", c[o:o+2])[0]

View File

@ -1,46 +1,27 @@
from PIL import Image
from . import Image
modules = {
"pil": "PIL._imaging",
"tkinter": "PIL._imagingtk",
"tkinter": "PIL._tkinter_finder",
"freetype2": "PIL._imagingft",
"littlecms2": "PIL._imagingcms",
"webp": "PIL._webp",
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
}
def check_module(feature):
if feature not in modules:
if not (feature in modules):
raise ValueError("Unknown module %s" % feature)
module = modules[feature]
method_to_call = None
if isinstance(module, tuple):
module, method_to_call = module
try:
imported_module = __import__(module)
except ImportError:
# If a method is being checked, None means that
# rather than the method failing, the module required for the method
# failed to be imported first
return None if method_to_call else False
if method_to_call:
method = getattr(imported_module, method_to_call)
return method() is True
else:
return True
except ImportError:
return False
def get_supported_modules():
supported_modules = []
for feature in modules:
if check_module(feature):
supported_modules.append(feature)
return supported_modules
return [f for f in modules if check_module(f)]
codecs = {
"jpg": "jpeg",
@ -49,7 +30,6 @@ codecs = {
"libtiff": "libtiff"
}
def check_codec(feature):
if feature not in codecs:
raise ValueError("Unknown codec %s" % feature)
@ -60,8 +40,39 @@ def check_codec(feature):
def get_supported_codecs():
supported_codecs = []
for feature in codecs:
if check_codec(feature):
supported_codecs.append(feature)
return supported_codecs
return [f for f in codecs if check_codec(f)]
features = {
"webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'),
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"),
"raqm": ("PIL._imagingft", "HAVE_RAQM")
}
def check_feature(feature):
if feature not in features:
raise ValueError("Unknown feature %s" % feature)
module, flag = features[feature]
try:
imported_module = __import__(module, fromlist=['PIL'])
return getattr(imported_module, flag)
except ImportError:
return None
def get_supported_features():
return [f for f in features if check_feature(f)]
def check(feature):
return (feature in modules and check_module(feature) or \
feature in codecs and check_codec(feature) or \
feature in features and check_feature(feature))
def get_supported():
ret = get_supported_modules()
ret.extend(get_supported_features())
ret.extend(get_supported_codecs())
return ret

2
PIL/version.py Normal file
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|
* - tests
- | |linux| |macos| |windows| |coverage| |health|
- | |linux| |macos| |windows| |coverage|
* - package
- |zenodo| |version| |downloads|
- |zenodo| |version|
.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest
:target: https://pillow.readthedocs.io/?badge=latest
@ -38,10 +38,6 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
:target: https://coveralls.io/github/python-pillow/Pillow?branch=master
:alt: Code coverage
.. |health| image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.svg
:target: https://landscape.io/github/python-pillow/Pillow/master
:alt: Code health
.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
@ -49,10 +45,6 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
:target: https://pypi.python.org/pypi/Pillow/
:alt: Latest PyPI version
.. |downloads| image:: https://img.shields.io/pypi/dm/pillow.svg
:target: https://pypi.python.org/pypi/Pillow/
:alt: Number of PyPI downloads
.. end-badges

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
* [ ] Develop and prepare release in ``master`` branch.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch.
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
```
PIL/__init__.py setup.py _imaging.c appveyor.yml
```
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in TravisCI.
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `PIL/version.py`
* [ ] Update `CHANGES.rst`.
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
* [ ] Create branch and tag for release e.g.:
@ -20,12 +18,12 @@ Released quarterly on the first day of January, April, July, October.
$ git push --all
$ git push --tags
```
* [ ] Create and upload source distributions e.g.:
* [ ] Create source distributions e.g.:
```
$ make sdist
$ make upload
```
* [ ] Create and upload [binary distributions](#binary-distributions)
* [ ] Create binary distributions [binary distributions](#binary-distributions)
* [ ] Upload all binaries and source distributions with ``twine upload dist/Pillow-4.1.0-*``
* [ ] Manually hide old versions on PyPI such that only the latest major release is visible when viewing https://pypi.python.org/pypi/Pillow (https://pypi.python.org/pypi?:action=pkg_edit&name=Pillow)
## Point Release
@ -40,24 +38,18 @@ Released as needed for security, installation or critical bug fixes.
```
git checkout -t remotes/origin/2.9.x
```
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in:
```
PIL/__init__.py
setup.py
_imaging.c
appveyor.yml
```
* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `PIL/version.py`
* [ ] Run pre-release check via `make release-test`.
* [ ] Create tag for release e.g.:
```
$ git tag 2.9.1
$ git push --tags
```
* [ ] Create and upload source distributions e.g.:
* [ ] Create source distributions e.g.:
```
$ make sdistup
$ make sdist
```
* [ ] Create and upload [binary distributions](#binary-distributions)
* [ ] Create [binary distributions](#binary-distributions)
## Embargoed Release
@ -76,11 +68,11 @@ Released as needed privately to individual vendors for critical security-related
git push origin 2.5.x
git push origin --tags
```
* [ ] Create and upload source distributions e.g.:
* [ ] Create source distributions e.g.:
```
$ make sdistup
$ make sdist
```
* [ ] Create and upload [binary distributions](#binary-distributions)
* [ ] Create [binary distributions](#binary-distributions)
## Binary Distributions
@ -88,8 +80,8 @@ Released as needed privately to individual vendors for critical security-related
* [ ] Contact @cgohlke for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174.
* [ ] Download and extract tarball from @cgohlke and ``twine upload *``.
### macOS
* [ ] Use the [Pillow macOS Wheel Builder](https://github.com/python-pillow/pillow-wheels):
### Mac and Linux
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
```
$ git checkout https://github.com/python-pillow/pillow-wheels
$ cd pillow-wheels
@ -97,12 +89,13 @@ Released as needed privately to individual vendors for critical security-related
$ git submodule update
$ cd Pillow
$ git fetch --all
$ git commit -a -m "Pillow -> 2.9.0"
$ git push
$ git checkout [[release tag]]
$ cd ..
$ git commit -m "Pillow -> 2.9.0" Pillow
$ git push
```
* [ ] Download distributions from the [Pillow macOS Wheel Builder container](http://cdf58691c5cf45771290-6a3b6a0f5f6ab91aadc447b2a897dd9a.r50.cf2.rackcdn.com/) and ``twine upload *``.
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
### Linux
## Publicize Release

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

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

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

0
Scripts/explode.py Normal file → Executable file
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