Merge from master

This commit is contained in:
wiredfool 2014-06-24 16:48:38 -07:00
commit a990af1b97
243 changed files with 9141 additions and 5420 deletions

11
.coveragerc Normal file
View File

@ -0,0 +1,11 @@
# .coveragerc to control coverage.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma:
pragma: no cover
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:

59
.gitignore vendored
View File

@ -1,10 +1,56 @@
*.pyc # Byte-compiled / optimized / DLL files
*.egg-info __pycache__/
build *.py[cod]
dist
.tox # C extensions
*.so *.so
docs/_build
# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
# Sphinx documentation
docs/_build/
# Vim cruft # Vim cruft
.*.swp .*.swp
@ -13,3 +59,4 @@ docs/_build
*~ *~
\#*# \#*#
.#* .#*

View File

@ -1,35 +1,46 @@
language: python language: python
# for python-qt4
virtualenv:
system_site_packages: true
notifications: notifications:
irc: "chat.freenode.net#pil" irc: "chat.freenode.net#pil"
python: python:
- "pypy"
- 2.6 - 2.6
- 2.7 - 2.7
- 3.2 - 3.2
- 3.3 - 3.3
- "pypy" - 3.4
install: install:
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake"
- "pip install cffi" - "pip install cffi"
- "pip install coveralls nose"
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi
# webp # webp
- pushd depends && ./install_webp.sh && popd - pushd depends && ./install_webp.sh && popd
# openjpeg # openjpeg
- pushd depends && ./install_openjpeg.sh && popd - pushd depends && ./install_openjpeg.sh && popd
script: script:
- coverage erase
- python setup.py clean - python setup.py clean
- python setup.py build_ext --inplace - python setup.py build_ext --inplace
- python selftest.py
- python Tests/run.py
matrix: # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640)
allow_failures: - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi
- python: "pypy" - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests Tests/test_*.py; fi
# Cover the others
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose Tests/test_*.py; fi
after_success:
- coverage report
- coveralls
- pip install pep8 pyflakes
- pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py
- pyflakes PIL/*.py | tee >(wc -l)
- pyflakes Tests/*.py | tee >(wc -l)

View File

@ -1,7 +1,94 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
2.4.0 (unreleased) 2.5.0 (unreleased)
------------------
- Fixed ImagePalette.save
[brightpisces]
- Support JPEG qtables
[csinchok]
- Add binary morphology addon
[dov, wiredfool]
- Decompression bomb protection
[hugovk]
- Put images in a single directory
[hugovk]
- Support OpenJpeg 2.1
[al45tair]
- Remove unistd.h #include for all platforms
[wiredfool]
- Use unittest for tests
[hugovk]
- ImageCms fixes
[hugovk]
- Added more ImageDraw tests
[hugovk]
- Added tests for Spider files
[hugovk]
- Use libtiff to write any compressed tiff files
[wiredfool]
- Support for pickling Image objects
[hugovk]
- Fixed resolution handling for EPS thumbnails
[eliempje]
- Fixed rendering of some binary EPS files (Issue #302)
[eliempje]
- Rename variables not to use built-in function names
[hugovk]
- Ignore junk JPEG markers
[hugovk]
- Change default interpolation for Image.thumbnail to Image.ANTIALIAS
[hugovk]
- Add tests and fixes for saving PDFs
[hugovk]
- Remove transparency resource after P->RGBA conversion
[hugovk]
- Clean up preprocessor cruft for Windows
[CounterPillow]
- Adjust Homebrew freetype detection logic
[jacknagel]
- Added Image.close, context manager support.
[wiredfool]
- Added support for 16 bit PGM files.
[wiredfool]
- Updated OleFileIO to version 0.30 from upstream
[hugovk]
- Added support for additional TIFF floating point format
[Hijackal]
- Have the tempfile use a suffix with a dot
[wiredfool]
- Fix variable name used for transparency manipulations
[nijel]
2.4.0 (2014-04-01)
------------------ ------------------
- Indexed Transparency handled for conversions between L, RGB, and P modes. Fixes #510 - Indexed Transparency handled for conversions between L, RGB, and P modes. Fixes #510
@ -22,7 +109,7 @@ Changelog (Pillow)
- Added support for JPEG 2000 - Added support for JPEG 2000
[al45tair] [al45tair]
- Add more detailed error messages to Image.py - Add more detailed error messages to Image.py
[larsmans] [larsmans]
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538 - Avoid conflicting _expand functions in PIL & MINGW, fixes #538
@ -50,7 +137,7 @@ Changelog (Pillow)
[wiredfool] [wiredfool]
- Fixed palette handling when converting from mode P->RGB->P - Fixed palette handling when converting from mode P->RGB->P
[d_schmidt] [d_schmidt]
- Fixed saving mode P image as a PNG with transparency = palette color 0 - Fixed saving mode P image as a PNG with transparency = palette color 0
[d-schmidt] [d-schmidt]
@ -60,7 +147,7 @@ Changelog (Pillow)
- Fixed DOS with invalid palette size or invalid image size in BMP file - Fixed DOS with invalid palette size or invalid image size in BMP file
[wiredfool] [wiredfool]
- Added support for BMP version 4 and 5 - Added support for BMP version 4 and 5
[eddwardo, wiredfool] [eddwardo, wiredfool]
@ -93,7 +180,7 @@ Changelog (Pillow)
- Prefer homebrew freetype over X11 freetype (but still allow both) - Prefer homebrew freetype over X11 freetype (but still allow both)
[dmckeone] [dmckeone]
2.3.1 (2014-03-14) 2.3.1 (2014-03-14)
------------------ ------------------
@ -218,7 +305,7 @@ Changelog (Pillow)
[nikmolnar] [nikmolnar]
- Fix for encoding of b_whitespace, similar to closed issue #272 - Fix for encoding of b_whitespace, similar to closed issue #272
[mhogg] [mhogg]
- Fix #273: Add numpy array interface support for 16 and 32 bit integer modes - Fix #273: Add numpy array interface support for 16 and 32 bit integer modes
[cgohlke] [cgohlke]
@ -378,7 +465,7 @@ Changelog (Pillow)
- Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.) - Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.)
[fluggo] [fluggo]
- Add PyPy support (experimental, please see: https://github.com/python-imaging/Pillow/issues/67) - Add PyPy support (experimental, please see: https://github.com/python-pillow/Pillow/issues/67)
- Add WebP support. - Add WebP support.
[lqs] [lqs]

View File

@ -9,6 +9,7 @@ include tox.ini
recursive-include Images *.bdf recursive-include Images *.bdf
recursive-include Images *.fli recursive-include Images *.fli
recursive-include Images *.gif recursive-include Images *.gif
recursive-include Images *.icns
recursive-include Images *.ico recursive-include Images *.ico
recursive-include Images *.jpg recursive-include Images *.jpg
recursive-include Images *.pbm recursive-include Images *.pbm
@ -34,7 +35,9 @@ recursive-include Tests *.gif
recursive-include Tests *.gnuplot recursive-include Tests *.gnuplot
recursive-include Tests *.html recursive-include Tests *.html
recursive-include Tests *.icm recursive-include Tests *.icm
recursive-include Tests *.icns
recursive-include Tests *.ico recursive-include Tests *.ico
recursive-include Tests *.jp2
recursive-include Tests *.jpg recursive-include Tests *.jpg
recursive-include Tests *.pcf recursive-include Tests *.pcf
recursive-include Tests *.pcx recursive-include Tests *.pcx
@ -47,6 +50,7 @@ recursive-include Tests *.ttf
recursive-include Tests *.txt recursive-include Tests *.txt
recursive-include Tk *.c recursive-include Tk *.c
recursive-include Tk *.txt recursive-include Tk *.txt
recursive-include depends *.sh
recursive-include docs *.bat recursive-include docs *.bat
recursive-include docs *.gitignore recursive-include docs *.gitignore
recursive-include docs *.html recursive-include docs *.html

View File

@ -370,7 +370,7 @@ class ArgStream(ChunkStream):
im1 = im1.chop_add_modulo(im0.crop(bbox)) im1 = im1.chop_add_modulo(im0.crop(bbox))
im0.paste(im1, bbox) im0.paste(im1, bbox)
self.count = self.count - 1 self.count -= 1
if self.count == 0 and self.show: if self.count == 0 and self.show:
self.im = self.images[self.id] self.im = self.images[self.id]

View File

@ -128,5 +128,5 @@ class BdfFontFile(FontFile.FontFile):
if not c: if not c:
break break
id, ch, (xy, dst, src), im = c id, ch, (xy, dst, src), im = c
if ch >= 0 and ch < len(self.glyph): if 0 <= ch < len(self.glyph):
self.glyph[ch] = xy, dst, src, im self.glyph[ch] = xy, dst, src, im

View File

@ -11,6 +11,7 @@
# 1996-08-23 fl Handle files from Macintosh (0.3) # 1996-08-23 fl Handle files from Macintosh (0.3)
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4) # 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5) # 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution resizing
# #
# Copyright (c) 1997-2003 by Secret Labs AB. # Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2003 by Fredrik Lundh # Copyright (c) 1995-2003 by Fredrik Lundh
@ -50,20 +51,36 @@ if sys.platform.startswith('win'):
else: else:
gs_windows_binary = False gs_windows_binary = False
def has_ghostscript():
if gs_windows_binary:
return True
if not sys.platform.startswith('win'):
import subprocess
try:
gs = subprocess.Popen(['gs','--version'], stdout=subprocess.PIPE)
gs.stdout.read()
return True
except OSError:
# no ghostscript
pass
return False
def Ghostscript(tile, size, fp, scale=1): def Ghostscript(tile, size, fp, scale=1):
"""Render an image using Ghostscript""" """Render an image using Ghostscript"""
# Unpack decoder tile # Unpack decoder tile
decoder, tile, offset, data = tile[0] decoder, tile, offset, data = tile[0]
length, bbox = data length, bbox = data
#Hack to support hi-res rendering #Hack to support hi-res rendering
scale = int(scale) or 1 scale = int(scale) or 1
orig_size = size orig_size = size
orig_bbox = bbox orig_bbox = bbox
size = (size[0] * scale, size[1] * scale) size = (size[0] * scale, size[1] * scale)
bbox = [bbox[0], bbox[1], bbox[2] * scale, bbox[3] * scale] # resolution is dependend on bbox and size
#print("Ghostscript", scale, size, orig_size, bbox, orig_bbox) res = ( float((72.0 * size[0]) / (bbox[2]-bbox[0])), float((72.0 * size[1]) / (bbox[3]-bbox[1])) )
#print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
import tempfile, os, subprocess import tempfile, os, subprocess
@ -71,21 +88,30 @@ def Ghostscript(tile, size, fp, scale=1):
os.close(out_fd) os.close(out_fd)
in_fd, infile = tempfile.mkstemp() in_fd, infile = tempfile.mkstemp()
os.close(in_fd) os.close(in_fd)
# ignore length and offset!
# ghostscript can read it
# copy whole file to read in ghostscript
with open(infile, 'wb') as f: with open(infile, 'wb') as f:
fp.seek(offset) # fetch length of fp
while length >0: fp.seek(0, 2)
s = fp.read(100*1024) fsize = fp.tell()
# ensure start position
# go back
fp.seek(0)
lengthfile = fsize
while lengthfile > 0:
s = fp.read(min(lengthfile, 100*1024))
if not s: if not s:
break break
length = length - len(s) length -= len(s)
f.write(s) f.write(s)
# Build ghostscript command # Build ghostscript command
command = ["gs", command = ["gs",
"-q", # quiet mode "-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels) "-g%dx%d" % size, # set output geometry (pixels)
"-r%d" % (72*scale), # set input DPI (dots per inch) "-r%fx%f" % res, # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode "-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
"-sDEVICE=ppmraw", # ppm driver "-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file "-sOutputFile=%s" % outfile, # output file
@ -93,7 +119,7 @@ def Ghostscript(tile, size, fp, scale=1):
# adjust for image origin # adjust for image origin
"-f", infile, # input file "-f", infile, # input file
] ]
if gs_windows_binary is not None: if gs_windows_binary is not None:
if not gs_windows_binary: if not gs_windows_binary:
raise WindowsError('Unable to locate Ghostscript on paths') raise WindowsError('Unable to locate Ghostscript on paths')
@ -112,7 +138,7 @@ def Ghostscript(tile, size, fp, scale=1):
os.unlink(outfile) os.unlink(outfile)
os.unlink(infile) os.unlink(infile)
except: pass except: pass
return im return im
@ -130,10 +156,12 @@ class PSFile:
self.fp.seek(offset, whence) self.fp.seek(offset, whence)
def read(self, count): def read(self, count):
return self.fp.read(count).decode('latin-1') return self.fp.read(count).decode('latin-1')
def readbinary(self, count):
return self.fp.read(count)
def tell(self): def tell(self):
pos = self.fp.tell() pos = self.fp.tell()
if self.char: if self.char:
pos = pos - 1 pos -= 1
return pos return pos
def readline(self): def readline(self):
s = b"" s = b""
@ -167,26 +195,34 @@ class EpsImageFile(ImageFile.ImageFile):
def _open(self): def _open(self):
# FIXME: should check the first 512 bytes to see if this
# really is necessary (platform-dependent, though...)
fp = PSFile(self.fp) fp = PSFile(self.fp)
# HEAD # FIX for: Some EPS file not handled correctly / issue #302
s = fp.read(512) # EPS can contain binary data
# or start directly with latin coding
# read header in both ways to handle both
# file types
# more info see http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
# for HEAD without binary preview
s = fp.read(4)
# for HEAD with binary preview
fp.seek(0)
sb = fp.readbinary(160)
if s[:4] == "%!PS": if s[:4] == "%!PS":
offset = 0
fp.seek(0, 2) fp.seek(0, 2)
length = fp.tell() length = fp.tell()
elif i32(s) == 0xC6D3D0C5: offset = 0
offset = i32(s[4:]) elif i32(sb[0:4]) == 0xC6D3D0C5:
length = i32(s[8:]) offset = i32(sb[4:8])
fp.seek(offset) length = i32(sb[8:12])
else: else:
raise SyntaxError("not an EPS file") raise SyntaxError("not an EPS file")
# go to offset - start of "%!PS"
fp.seek(offset) fp.seek(offset)
box = None box = None
self.mode = "RGB" self.mode = "RGB"
@ -196,7 +232,7 @@ class EpsImageFile(ImageFile.ImageFile):
# Load EPS header # Load EPS header
s = fp.readline() s = fp.readline()
while s: while s:
if len(s) > 255: if len(s) > 255:

View File

@ -63,15 +63,12 @@ TAGS = {
0x0201: "JpegIFOffset", 0x0201: "JpegIFOffset",
0x0202: "JpegIFByteCount", 0x0202: "JpegIFByteCount",
0x0211: "YCbCrCoefficients", 0x0211: "YCbCrCoefficients",
0x0211: "YCbCrCoefficients",
0x0212: "YCbCrSubSampling", 0x0212: "YCbCrSubSampling",
0x0213: "YCbCrPositioning", 0x0213: "YCbCrPositioning",
0x0213: "YCbCrPositioning",
0x0214: "ReferenceBlackWhite",
0x0214: "ReferenceBlackWhite", 0x0214: "ReferenceBlackWhite",
0x1000: "RelatedImageFileFormat", 0x1000: "RelatedImageFileFormat",
0x1001: "RelatedImageLength", 0x1001: "RelatedImageLength", # FIXME / Dictionary contains duplicate keys
0x1001: "RelatedImageWidth", 0x1001: "RelatedImageWidth", # FIXME \ Dictionary contains duplicate keys
0x828d: "CFARepeatPatternDim", 0x828d: "CFARepeatPatternDim",
0x828e: "CFAPattern", 0x828e: "CFAPattern",
0x828f: "BatteryLevel", 0x828f: "BatteryLevel",

View File

@ -105,7 +105,7 @@ class FliImageFile(ImageFile.ImageFile):
g = i8(s[n+1]) << shift g = i8(s[n+1]) << shift
b = i8(s[n+2]) << shift b = i8(s[n+2]) << shift
palette[i] = (r, g, b) palette[i] = (r, g, b)
i = i + 1 i += 1
def seek(self, frame): def seek(self, frame):

View File

@ -30,7 +30,7 @@ def puti16(fp, values):
# write network order (big-endian) 16-bit sequence # write network order (big-endian) 16-bit sequence
for v in values: for v in values:
if v < 0: if v < 0:
v = v + 65536 v += 65536
fp.write(_binary.o16be(v)) fp.write(_binary.o16be(v))
## ##
@ -63,7 +63,7 @@ class FontFile:
h = max(h, src[3] - src[1]) h = max(h, src[3] - src[1])
w = w + (src[2] - src[0]) w = w + (src[2] - src[0])
if w > WIDTH: if w > WIDTH:
lines = lines + 1 lines += 1
w = (src[2] - src[0]) w = (src[2] - src[0])
maxwidth = max(maxwidth, w) maxwidth = max(maxwidth, w)

View File

@ -84,7 +84,7 @@ class FpxImageFile(ImageFile.ImageFile):
i = 1 i = 1
while size > 64: while size > 64:
size = size / 2 size = size / 2
i = i + 1 i += 1
self.maxid = i - 1 self.maxid = i - 1
# mode. instead of using a single field for this, flashpix # mode. instead of using a single field for this, flashpix

View File

@ -68,7 +68,7 @@ class GradientFile:
x = i / float(entries-1) x = i / float(entries-1)
while x1 < x: while x1 < x:
ix = ix + 1 ix += 1
x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix] x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
w = x1 - x0 w = x1 - x0

View File

@ -52,7 +52,7 @@ class GimpPaletteFile:
if 0 <= i <= 255: if 0 <= i <= 255:
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2]) self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
i = i + 1 i += 1
self.palette = b"".join(self.palette) self.palette = b"".join(self.palette)

View File

@ -70,7 +70,7 @@ def read_32(fobj, start_length, size):
else: else:
blocksize = byte + 1 blocksize = byte + 1
data.append(fobj.read(blocksize)) data.append(fobj.read(blocksize))
bytesleft = bytesleft - blocksize bytesleft -= blocksize
if bytesleft <= 0: if bytesleft <= 0:
break break
if bytesleft != 0: if bytesleft != 0:
@ -179,11 +179,11 @@ class IcnsFile:
i = HEADERSIZE i = HEADERSIZE
while i < filesize: while i < filesize:
sig, blocksize = nextheader(fobj) sig, blocksize = nextheader(fobj)
i = i + HEADERSIZE i += HEADERSIZE
blocksize = blocksize - HEADERSIZE blocksize -= HEADERSIZE
dct[sig] = (i, blocksize) dct[sig] = (i, blocksize)
fobj.seek(blocksize, 1) fobj.seek(blocksize, 1)
i = i + blocksize i += blocksize
def itersizes(self): def itersizes(self):
sizes = [] sizes = []

View File

@ -182,7 +182,7 @@ class ImImageFile(ImageFile.ImageFile):
self.info[k] = v self.info[k] = v
if k in TAGS: if k in TAGS:
n = n + 1 n += 1
else: else:

View File

@ -30,11 +30,19 @@ from PIL import VERSION, PILLOW_VERSION, _plugins
import warnings import warnings
class DecompressionBombWarning(RuntimeWarning):
pass
class _imaging_not_installed: class _imaging_not_installed:
# module placeholder # module placeholder
def __getattr__(self, id): def __getattr__(self, id):
raise ImportError("The _imaging C module is not installed") raise ImportError("The _imaging C module is not installed")
# Limit to around a quarter gigabyte for a 24 bit (3 bpp) image
MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 / 4 / 3)
try: try:
# give Tk a chance to set up the environment, in case we're # give Tk a chance to set up the environment, in case we're
# using an _imaging module linked against libtcl/libtk (use # using an _imaging module linked against libtcl/libtk (use
@ -52,8 +60,8 @@ try:
# directly; import Image and use the Image.core variable instead. # directly; import Image and use the Image.core variable instead.
from PIL import _imaging as core from PIL import _imaging as core
if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None): if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
raise ImportError("The _imaging extension was built for another " raise ImportError("The _imaging extension was built for another "
" version of Pillow or PIL") " version of Pillow or PIL")
except ImportError as v: except ImportError as v:
core = _imaging_not_installed() core = _imaging_not_installed()
@ -91,22 +99,26 @@ except ImportError:
builtins = __builtin__ builtins = __builtin__
from PIL import ImageMode from PIL import ImageMode
from PIL._binary import i8, o8 from PIL._binary import i8
from PIL._util import isPath, isStringType from PIL._util import isPath
from PIL._util import isStringType
from PIL._util import deferred_error
import os, sys import os
import sys
# type stuff # type stuff
import collections import collections
import numbers import numbers
# works everywhere, win for pypy, not cpython # works everywhere, win for pypy, not cpython
USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info') USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info')
try: try:
import cffi import cffi
HAS_CFFI=True HAS_CFFI = True
except: except:
HAS_CFFI=False HAS_CFFI = False
def isImageType(t): def isImageType(t):
""" """
@ -148,16 +160,16 @@ MESH = 4
# resampling filters # resampling filters
NONE = 0 NONE = 0
NEAREST = 0 NEAREST = 0
ANTIALIAS = 1 # 3-lobed lanczos ANTIALIAS = 1 # 3-lobed lanczos
LINEAR = BILINEAR = 2 LINEAR = BILINEAR = 2
CUBIC = BICUBIC = 3 CUBIC = BICUBIC = 3
# dithers # dithers
NONE = 0 NONE = 0
NEAREST = 0 NEAREST = 0
ORDERED = 1 # Not yet implemented ORDERED = 1 # Not yet implemented
RASTERIZE = 2 # Not yet implemented RASTERIZE = 2 # Not yet implemented
FLOYDSTEINBERG = 3 # default FLOYDSTEINBERG = 3 # default
# palettes/quantizers # palettes/quantizers
WEB = 0 WEB = 0
@ -222,7 +234,7 @@ else:
_MODE_CONV = { _MODE_CONV = {
# official modes # official modes
"1": ('|b1', None), # broken "1": ('|b1', None), # broken
"L": ('|u1', None), "L": ('|u1', None),
"I": (_ENDIAN + 'i4', None), "I": (_ENDIAN + 'i4', None),
"F": (_ENDIAN + 'f4', None), "F": (_ENDIAN + 'f4', None),
@ -232,8 +244,8 @@ _MODE_CONV = {
"RGBA": ('|u1', 4), "RGBA": ('|u1', 4),
"CMYK": ('|u1', 4), "CMYK": ('|u1', 4),
"YCbCr": ('|u1', 3), "YCbCr": ('|u1', 3),
"LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1 "LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1
# I;16 == I;16L, and I;32 == I;32L # I;16 == I;16L, and I;32 == I;32L
"I;16": ('<u2', None), "I;16": ('<u2', None),
"I;16B": ('>u2', None), "I;16B": ('>u2', None),
"I;16L": ('<u2', None), "I;16L": ('<u2', None),
@ -248,6 +260,7 @@ _MODE_CONV = {
"I;32LS": ('<i4', None), "I;32LS": ('<i4', None),
} }
def _conv_type_shape(im): def _conv_type_shape(im):
shape = im.size[1], im.size[0] shape = im.size[1], im.size[0]
typ, extra = _MODE_CONV[im.mode] typ, extra = _MODE_CONV[im.mode]
@ -368,8 +381,8 @@ def init():
for plugin in _plugins: for plugin in _plugins:
try: try:
if DEBUG: if DEBUG:
print ("Importing %s"%plugin) print ("Importing %s" % plugin)
__import__("PIL.%s"%plugin, globals(), locals(), []) __import__("PIL.%s" % plugin, globals(), locals(), [])
except ImportError: except ImportError:
if DEBUG: if DEBUG:
print("Image: failed to import", end=' ') print("Image: failed to import", end=' ')
@ -379,6 +392,7 @@ def init():
_initialized = 2 _initialized = 2
return 1 return 1
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Codec factories (used by tobytes/frombytes and ImageFile.load) # Codec factories (used by tobytes/frombytes and ImageFile.load)
@ -398,6 +412,7 @@ def _getdecoder(mode, decoder_name, args, extra=()):
except AttributeError: except AttributeError:
raise IOError("decoder %s not available" % decoder_name) raise IOError("decoder %s not available" % decoder_name)
def _getencoder(mode, encoder_name, args, extra=()): def _getencoder(mode, encoder_name, args, extra=()):
# tweak arguments # tweak arguments
@ -421,30 +436,36 @@ def _getencoder(mode, encoder_name, args, extra=()):
def coerce_e(value): def coerce_e(value):
return value if isinstance(value, _E) else _E(value) return value if isinstance(value, _E) else _E(value)
class _E: class _E:
def __init__(self, data): def __init__(self, data):
self.data = data self.data = data
def __add__(self, other): def __add__(self, other):
return _E((self.data, "__add__", coerce_e(other).data)) return _E((self.data, "__add__", coerce_e(other).data))
def __mul__(self, other): def __mul__(self, other):
return _E((self.data, "__mul__", coerce_e(other).data)) return _E((self.data, "__mul__", coerce_e(other).data))
def _getscaleoffset(expr): def _getscaleoffset(expr):
stub = ["stub"] stub = ["stub"]
data = expr(_E(stub)).data data = expr(_E(stub)).data
try: try:
(a, b, c) = data # simplified syntax (a, b, c) = data # simplified syntax
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)): if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)):
return c, 0.0 return c, 0.0
if (a is stub and b == "__add__" and isinstance(c, numbers.Number)): if a is stub and b == "__add__" and isinstance(c, numbers.Number):
return 1.0, c return 1.0, c
except TypeError: pass except TypeError:
pass
try: try:
((a, b, c), d, e) = data # full syntax ((a, b, c), d, e) = data # full syntax
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number) and if (a is stub and b == "__mul__" and isinstance(c, numbers.Number) and
d == "__add__" and isinstance(e, numbers.Number)): d == "__add__" and isinstance(e, numbers.Number)):
return c, e return c, e
except TypeError: pass except TypeError:
pass
raise ValueError("illegal expression") raise ValueError("illegal expression")
@ -495,7 +516,36 @@ class Image:
new.info[k] = v new.info[k] = v
return new return new
_makeself = _new # compatibility _makeself = _new # compatibility
# Context Manager Support
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def close(self):
"""
Closes the file pointer, if possible.
This operation will destroy the image core and release it's memory.
The image data will be unusable afterward.
This function is only required to close images that have not
had their file read and closed by the
:py:meth:`~PIL.Image.Image.load` method.
"""
try:
self.fp.close()
except Exception as msg:
if Image.DEBUG:
print ("Error closing: %s" % msg)
# Instead of simply setting to None, we're setting up a
# deferred error that will better explain that the core image
# object is gone.
self.im = deferred_error(ValueError("Operation on closed image"))
def _copy(self): def _copy(self):
self.load() self.load()
@ -504,20 +554,38 @@ class Image:
self.readonly = 0 self.readonly = 0
def _dump(self, file=None, format=None): def _dump(self, file=None, format=None):
import tempfile, os import os
import tempfile
suffix = ''
if format:
suffix = '.'+format
if not file: if not file:
f, file = tempfile.mkstemp(format or '') f, file = tempfile.mkstemp(suffix)
os.close(f) os.close(f)
self.load() self.load()
if not format or format == "PPM": if not format or format == "PPM":
self.im.save_ppm(file) self.im.save_ppm(file)
else: else:
if file.endswith(format): if not file.endswith(format):
file = file + "." + format file = file + "." + format
self.save(file, format) self.save(file, format)
return file return file
def __eq__(self, other):
a = (self.mode == other.mode)
b = (self.size == other.size)
c = (self.getpalette() == other.getpalette())
d = (self.info == other.info)
e = (self.category == other.category)
f = (self.readonly == other.readonly)
g = (self.tobytes() == other.tobytes())
return a and b and c and d and e and f and g
def __ne__(self, other):
eq = (self == other)
return not eq
def __repr__(self): def __repr__(self):
return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % ( return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (
self.__class__.__module__, self.__class__.__name__, self.__class__.__module__, self.__class__.__name__,
@ -536,6 +604,26 @@ class Image:
return new return new
raise AttributeError(name) raise AttributeError(name)
def __getstate__(self):
return [
self.info,
self.mode,
self.size,
self.getpalette(),
self.tobytes()]
def __setstate__(self, state):
Image.__init__(self)
self.tile = []
info, mode, size, palette, data = state
self.info = info
self.mode = mode
self.size = size
self.im = core.new(mode, size)
if mode in ("L", "P"):
self.putpalette(palette)
self.frombytes(data)
def tobytes(self, encoder_name="raw", *args): def tobytes(self, encoder_name="raw", *args):
""" """
Return image as a bytes object Return image as a bytes object
@ -559,7 +647,7 @@ class Image:
e = _getencoder(self.mode, encoder_name, args) e = _getencoder(self.mode, encoder_name, args)
e.setimage(self.im) e.setimage(self.im)
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
data = [] data = []
while True: while True:
@ -596,9 +684,11 @@ class Image:
if self.mode != "1": if self.mode != "1":
raise ValueError("not a bitmap") raise ValueError("not a bitmap")
data = self.tobytes("xbm") data = self.tobytes("xbm")
return b"".join([("#define %s_width %d\n" % (name, self.size[0])).encode('ascii'), return b"".join([
("#define %s_height %d\n"% (name, self.size[1])).encode('ascii'), ("#define %s_width %d\n" % (name, self.size[0])).encode('ascii'),
("static char %s_bits[] = {\n" % name).encode('ascii'), data, b"};"]) ("#define %s_height %d\n" % (name, self.size[1])).encode('ascii'),
("static char %s_bits[] = {\n" % name).encode('ascii'), data, b"};"
])
def frombytes(self, data, decoder_name="raw", *args): def frombytes(self, data, decoder_name="raw", *args):
""" """
@ -631,7 +721,9 @@ class Image:
.. deprecated:: 2.0 .. deprecated:: 2.0
""" """
warnings.warn('fromstring() is deprecated. Please call frombytes() instead.', DeprecationWarning) warnings.warn(
'fromstring() is deprecated. Please call frombytes() instead.',
DeprecationWarning)
return self.frombytes(*args, **kw) return self.frombytes(*args, **kw)
def load(self): def load(self):
@ -639,7 +731,8 @@ class Image:
Allocates storage for the image and loads the pixel data. In Allocates storage for the image and loads the pixel data. In
normal cases, you don't need to call this method, since the normal cases, you don't need to call this method, since the
Image class automatically loads an opened image when it is Image class automatically loads an opened image when it is
accessed for the first time. accessed for the first time. This method will close the file
associated with the image.
:returns: An image access object. :returns: An image access object.
""" """
@ -741,36 +834,39 @@ class Image:
trns = None trns = None
delete_trns = False delete_trns = False
# transparency handling # transparency handling
if "transparency" in self.info and self.info['transparency'] is not None: if "transparency" in self.info and \
self.info['transparency'] is not None:
if self.mode in ('L', 'RGB') and mode == 'RGBA': if self.mode in ('L', 'RGB') and mode == 'RGBA':
# Use transparent conversion to promote from transparent # Use transparent conversion to promote from transparent
# color to an alpha channel. # color to an alpha channel.
return self._new(self.im.convert_transparent( return self._new(self.im.convert_transparent(
mode, self.info['transparency'])) mode, self.info['transparency']))
elif self.mode in ('L', 'RGB', 'P') and mode in ('L', 'RGB', 'P'): elif self.mode in ('L', 'RGB', 'P') and mode in ('L', 'RGB', 'P'):
t = self.info['transparency'] t = self.info['transparency']
if isinstance(t, bytes): if isinstance(t, bytes):
# Dragons. This can't be represented by a single color # Dragons. This can't be represented by a single color
warnings.warn('Palette images with Transparency expressed '+ warnings.warn('Palette images with Transparency expressed ' +
' in bytes should be converted to RGBA images') ' in bytes should be converted to RGBA images')
delete_trns = True delete_trns = True
else: else:
# get the new transparency color. # get the new transparency color.
# use existing conversions # use existing conversions
trns_im = Image()._new(core.new(self.mode, (1,1))) trns_im = Image()._new(core.new(self.mode, (1, 1)))
if self.mode == 'P': if self.mode == 'P':
trns_im.putpalette(self.palette) trns_im.putpalette(self.palette)
trns_im.putpixel((0,0), t) trns_im.putpixel((0, 0), t)
if mode in ('L','RGB'): if mode in ('L', 'RGB'):
trns_im = trns_im.convert(mode) trns_im = trns_im.convert(mode)
else: else:
# can't just retrieve the palette number, got to do it # can't just retrieve the palette number, got to do it
# after quantization. # after quantization.
trns_im = trns_im.convert('RGB') trns_im = trns_im.convert('RGB')
trns = trns_im.getpixel((0,0)) trns = trns_im.getpixel((0,0))
elif self.mode == 'P' and mode == 'RGBA':
delete_trns = True
if mode == "P" and palette == ADAPTIVE: if mode == "P" and palette == ADAPTIVE:
im = self.im.quantize(colors) im = self.im.quantize(colors)
new = self._new(im) new = self._new(im)
@ -778,7 +874,7 @@ class Image:
new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB")) new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB"))
if delete_trns: if delete_trns:
# This could possibly happen if we requantize to fewer colors. # This could possibly happen if we requantize to fewer colors.
# The transparency would be totally off in that case. # The transparency would be totally off in that case.
del(new.info['transparency']) del(new.info['transparency'])
if trns is not None: if trns is not None:
try: try:
@ -787,13 +883,14 @@ class Image:
# if we can't make a transparent color, don't leave the old # if we can't make a transparent color, don't leave the old
# transparency hanging around to mess us up. # transparency hanging around to mess us up.
del(new.info['transparency']) del(new.info['transparency'])
warnings.warn("Couldn't allocate palette entry for transparency") warnings.warn("Couldn't allocate palette entry " +
"for transparency")
return new return new
# colorspace conversion # colorspace conversion
if dither is None: if dither is None:
dither = FLOYDSTEINBERG dither = FLOYDSTEINBERG
try: try:
im = self.im.convert(mode, dither) im = self.im.convert(mode, dither)
except ValueError: except ValueError:
@ -806,15 +903,16 @@ class Image:
new_im = self._new(im) new_im = self._new(im)
if delete_trns: if delete_trns:
#crash fail if we leave a bytes transparency in an rgb/l mode. # crash fail if we leave a bytes transparency in an rgb/l mode.
del(new.info['transparency']) del(new_im.info['transparency'])
if trns is not None: if trns is not None:
if new_im.mode == 'P': if new_im.mode == 'P':
try: try:
new_im.info['transparency'] = new_im.palette.getcolor(trns) new_im.info['transparency'] = new_im.palette.getcolor(trns)
except: except:
del(new_im.info['transparency']) del(new_im.info['transparency'])
warnings.warn("Couldn't allocate palette entry for transparency") warnings.warn("Couldn't allocate palette entry " +
"for transparency")
else: else:
new_im.info['transparency'] = trns new_im.info['transparency'] = trns
return new_im return new_im
@ -830,7 +928,7 @@ class Image:
# quantizer interface in a later version of PIL. # quantizer interface in a later version of PIL.
self.load() self.load()
if method is None: if method is None:
# defaults: # defaults:
method = 0 method = 0
@ -838,10 +936,10 @@ class Image:
method = 2 method = 2
if self.mode == 'RGBA' and method != 2: if self.mode == 'RGBA' and method != 2:
# Caller specified an invalid mode. # Caller specified an invalid mode.
raise ValueError('Fast Octree (method == 2) is the ' + raise ValueError('Fast Octree (method == 2) is the ' +
' only valid method for quantizing RGBA images') ' only valid method for quantizing RGBA images')
if palette: if palette:
# use palette from reference image # use palette from reference image
palette.load() palette.load()
@ -895,7 +993,7 @@ class Image:
def draft(self, mode, size): def draft(self, mode, size):
""" """
NYI NYI
Configures the image file loader so it returns a version of the Configures the image file loader so it returns a version of the
image that as closely as possible matches the given mode and image that as closely as possible matches the given mode and
size. For example, you can use this method to convert a color size. For example, you can use this method to convert a color
@ -930,7 +1028,8 @@ class Image:
if isinstance(filter, collections.Callable): if isinstance(filter, collections.Callable):
filter = filter() filter = filter()
if not hasattr(filter, "filter"): if not hasattr(filter, "filter"):
raise TypeError("filter argument should be ImageFilter.Filter instance or class") raise TypeError("filter argument should be ImageFilter.Filter " +
"instance or class")
if self.im.bands == 1: if self.im.bands == 1:
return self._new(filter.filter(self.im)) return self._new(filter.filter(self.im))
@ -986,7 +1085,7 @@ class Image:
return out return out
return self.im.getcolors(maxcolors) return self.im.getcolors(maxcolors)
def getdata(self, band = None): def getdata(self, band=None):
""" """
Returns the contents of this image as a sequence object Returns the contents of this image as a sequence object
containing pixel values. The sequence object is flattened, so containing pixel values. The sequence object is flattened, so
@ -1007,7 +1106,7 @@ class Image:
self.load() self.load()
if band is not None: if band is not None:
return self.im.getband(band) return self.im.getband(band)
return self.im # could be abused return self.im # could be abused
def getextrema(self): def getextrema(self):
""" """
@ -1037,7 +1136,6 @@ class Image:
self.load() self.load()
return self.im.ptr return self.im.ptr
def getpalette(self): def getpalette(self):
""" """
Returns the image palette as a list. Returns the image palette as a list.
@ -1053,8 +1151,7 @@ class Image:
else: else:
return list(self.im.getpalette()) return list(self.im.getpalette())
except ValueError: except ValueError:
return None # no palette return None # no palette
def getpixel(self, xy): def getpixel(self, xy):
""" """
@ -1176,7 +1273,8 @@ class Image:
if isImageType(box) and mask is None: if isImageType(box) and mask is None:
# abbreviated paste(im, mask) syntax # abbreviated paste(im, mask) syntax
mask = box; box = None mask = box
box = None
if box is None: if box is None:
# cover all of self # cover all of self
@ -1244,7 +1342,7 @@ class Image:
if self.mode in ("I", "I;16", "F"): if self.mode in ("I", "I;16", "F"):
# check if the function can be used with point_transform # check if the function can be used with point_transform
# UNDONE wiredfool -- I think this prevents us from ever doing # UNDONE wiredfool -- I think this prevents us from ever doing
# a gamma function point transform on > 8bit images. # a gamma function point transform on > 8bit images.
scale, offset = _getscaleoffset(lut) scale, offset = _getscaleoffset(lut)
return self._new(self.im.point_transform(scale, offset)) return self._new(self.im.point_transform(scale, offset))
# for other modes, convert the function to a table # for other modes, convert the function to a table
@ -1282,7 +1380,7 @@ class Image:
# do things the hard way # do things the hard way
im = self.im.convert(mode) im = self.im.convert(mode)
if im.mode not in ("LA", "RGBA"): if im.mode not in ("LA", "RGBA"):
raise ValueError # sanity check raise ValueError # sanity check
self.im = im self.im = im
self.pyaccess = None self.pyaccess = None
self.mode = self.im.mode self.mode = self.im.mode
@ -1360,7 +1458,7 @@ class Image:
self.mode = "P" self.mode = "P"
self.palette = palette self.palette = palette
self.palette.mode = "RGB" self.palette.mode = "RGB"
self.load() # install new palette self.load() # install new palette
def putpixel(self, xy, value): def putpixel(self, xy, value):
""" """
@ -1387,9 +1485,9 @@ class Image:
self._copy() self._copy()
self.pyaccess = None self.pyaccess = None
self.load() self.load()
if self.pyaccess: if self.pyaccess:
return self.pyaccess.putpixel(xy,value) return self.pyaccess.putpixel(xy, value)
return self.im.putpixel(xy, value) return self.im.putpixel(xy, value)
def resize(self, size, resample=NEAREST): def resize(self, size, resample=NEAREST):
@ -1438,7 +1536,7 @@ class Image:
clockwise around its centre. clockwise around its centre.
:param angle: In degrees counter clockwise. :param angle: In degrees counter clockwise.
:param filter: An optional resampling filter. This can be :param resample: An optional resampling filter. This can be
one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
:py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
environment), or :py:attr:`PIL.Image.BICUBIC` environment), or :py:attr:`PIL.Image.BICUBIC`
@ -1456,9 +1554,10 @@ class Image:
import math import math
angle = -angle * math.pi / 180 angle = -angle * math.pi / 180
matrix = [ matrix = [
math.cos(angle), math.sin(angle), 0.0, math.cos(angle), math.sin(angle), 0.0,
-math.sin(angle), math.cos(angle), 0.0 -math.sin(angle), math.cos(angle), 0.0
] ]
def transform(x, y, matrix=matrix): def transform(x, y, matrix=matrix):
(a, b, c, d, e, f) = matrix (a, b, c, d, e, f) = matrix
return a*x + b*y + c, d*x + e*y + f return a*x + b*y + c, d*x + e*y + f
@ -1546,13 +1645,13 @@ class Image:
try: try:
format = EXTENSION[ext] format = EXTENSION[ext]
except KeyError: except KeyError:
raise KeyError(ext) # unknown extension raise KeyError(ext) # unknown extension
try: try:
save_handler = SAVE[format.upper()] save_handler = SAVE[format.upper()]
except KeyError: except KeyError:
init() init()
save_handler = SAVE[format.upper()] # unknown format save_handler = SAVE[format.upper()] # unknown format
if isPath(fp): if isPath(fp):
fp = builtins.open(fp, "wb") fp = builtins.open(fp, "wb")
@ -1634,7 +1733,7 @@ class Image:
""" """
return 0 return 0
def thumbnail(self, size, resample=NEAREST): def thumbnail(self, size, resample=ANTIALIAS):
""" """
Make this image into a thumbnail. This method modifies the Make this image into a thumbnail. This method modifies the
image to contain a thumbnail version of itself, no larger than image to contain a thumbnail version of itself, no larger than
@ -1649,26 +1748,28 @@ class Image:
important than quality. important than quality.
Also note that this function modifies the :py:class:`~PIL.Image.Image` Also note that this function modifies the :py:class:`~PIL.Image.Image`
object in place. If you need to use the full resolution image as well, apply object in place. If you need to use the full resolution image as well,
this method to a :py:meth:`~PIL.Image.Image.copy` of the original image. apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
image.
:param size: Requested size. :param size: Requested size.
:param resample: Optional resampling filter. This can be one :param resample: Optional resampling filter. This can be one
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`, of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS` :py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS`
(best quality). If omitted, it defaults to (best quality). If omitted, it defaults to
:py:attr:`PIL.Image.NEAREST` (this will be changed to ANTIALIAS in a :py:attr:`PIL.Image.ANTIALIAS`. (was :py:attr:`PIL.Image.NEAREST`
future version). prior to version 2.5.0)
:returns: None :returns: None
""" """
# FIXME: the default resampling filter will be changed
# to ANTIALIAS in future versions
# preserve aspect ratio # preserve aspect ratio
x, y = self.size x, y = self.size
if x > size[0]: y = int(max(y * size[0] / x, 1)); x = int(size[0]) if x > size[0]:
if y > size[1]: x = int(max(x * size[1] / y, 1)); y = int(size[1]) y = int(max(y * size[0] / x, 1))
x = int(size[0])
if y > size[1]:
x = int(max(x * size[1] / y, 1))
y = int(size[1])
size = x, y size = x, y
if size == self.size: if size == self.size:
@ -1683,7 +1784,7 @@ class Image:
except ValueError: except ValueError:
if resample != ANTIALIAS: if resample != ANTIALIAS:
raise raise
im = self.resize(size, NEAREST) # fallback im = self.resize(size, NEAREST) # fallback
self.im = im.im self.im = im.im
self.mode = im.mode self.mode = im.mode
@ -1719,7 +1820,8 @@ class Image:
""" """
if self.mode == 'RGBA': if self.mode == 'RGBA':
return self.convert('RGBa').transform(size, method, data, resample, fill).convert('RGBA') return self.convert('RGBa').transform(
size, method, data, resample, fill).convert('RGBA')
if isinstance(method, ImageTransformHandler): if isinstance(method, ImageTransformHandler):
return method.transform(size, self, resample=resample, fill=fill) return method.transform(size, self, resample=resample, fill=fill)
@ -1766,8 +1868,13 @@ class Image:
elif method == QUAD: elif method == QUAD:
# quadrilateral warp. data specifies the four corners # quadrilateral warp. data specifies the four corners
# given as NW, SW, SE, and NE. # given as NW, SW, SE, and NE.
nw = data[0:2]; sw = data[2:4]; se = data[4:6]; ne = data[6:8] nw = data[0:2]
x0, y0 = nw; As = 1.0 / w; At = 1.0 / h sw = data[2:4]
se = data[4:6]
ne = data[6:8]
x0, y0 = nw
As = 1.0 / w
At = 1.0 / h
data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At, data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At,
(se[0]-sw[0]-ne[0]+x0)*As*At, (se[0]-sw[0]-ne[0]+x0)*As*At,
y0, (ne[1]-y0)*As, (sw[1]-y0)*At, y0, (ne[1]-y0)*As, (sw[1]-y0)*At,
@ -1801,6 +1908,7 @@ class Image:
im = self.im.transpose(method) im = self.im.transpose(method)
return self._new(im) return self._new(im)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Lazy operations # Lazy operations
@ -1836,6 +1944,7 @@ class _ImageCrop(Image):
# FIXME: future versions should optimize crop/paste # FIXME: future versions should optimize crop/paste
# sequences! # sequences!
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Abstract handlers. # Abstract handlers.
@ -1843,10 +1952,12 @@ class ImagePointHandler:
# used as a mixin by point transforms (for use with im.point) # used as a mixin by point transforms (for use with im.point)
pass pass
class ImageTransformHandler: class ImageTransformHandler:
# used as a mixin by geometry transforms (for use with im.transform) # used as a mixin by geometry transforms (for use with im.transform)
pass pass
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Factories # Factories
@ -1922,6 +2033,7 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
im.frombytes(data, decoder_name, args) im.frombytes(data, decoder_name, args)
return im return im
def fromstring(*args, **kw): def fromstring(*args, **kw):
"""Deprecated alias to frombytes. """Deprecated alias to frombytes.
@ -1969,7 +2081,6 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
.. versionadded:: 1.1.4 .. versionadded:: 1.1.4
""" """
"Load image from bytes or buffer"
# may pass tuple instead of argument list # may pass tuple instead of argument list
if len(args) == 1 and isinstance(args[0], tuple): if len(args) == 1 and isinstance(args[0], tuple):
@ -1984,9 +2095,9 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
" frombuffer(mode, size, data, 'raw', mode, 0, 1)", " frombuffer(mode, size, data, 'raw', mode, 0, 1)",
RuntimeWarning, stacklevel=2 RuntimeWarning, stacklevel=2
) )
args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6 args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6
if args[0] in _MAPMODES: if args[0] in _MAPMODES:
im = new(mode, (1,1)) im = new(mode, (1, 1))
im = im._new( im = im._new(
core.map_buffer(data, size, decoder_name, None, 0, args) core.map_buffer(data, size, decoder_name, None, 0, args)
) )
@ -2067,14 +2178,29 @@ _fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I")
_fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F") _fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F")
def _decompression_bomb_check(size):
if MAX_IMAGE_PIXELS is None:
return
pixels = size[0] * size[1]
if pixels > MAX_IMAGE_PIXELS:
warnings.warn(
"Image size (%d pixels) exceeds limit of %d pixels, "
"could be decompression bomb DOS attack." %
(pixels, MAX_IMAGE_PIXELS),
DecompressionBombWarning)
def open(fp, mode="r"): def open(fp, mode="r"):
""" """
Opens and identifies the given image file. Opens and identifies the given image file.
This is a lazy operation; this function identifies the file, but the This is a lazy operation; this function identifies the file, but
actual image data is not read from the file until you try to process the file remains open and the actual image data is not read from
the data (or call the :py:meth:`~PIL.Image.Image.load` method). the file until you try to process the data (or call the
See :py:func:`~PIL.Image.new`. :py:meth:`~PIL.Image.Image.load` method). See
:py:func:`~PIL.Image.new`.
:param file: A filename (string) or a file object. The file object :param file: A filename (string) or a file object. The file object
must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and
@ -2103,10 +2229,12 @@ def open(fp, mode="r"):
factory, accept = OPEN[i] factory, accept = OPEN[i]
if not accept or accept(prefix): if not accept or accept(prefix):
fp.seek(0) fp.seek(0)
return factory(fp, filename) im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError): except (SyntaxError, IndexError, TypeError):
#import traceback # import traceback
#traceback.print_exc() # traceback.print_exc()
pass pass
if init(): if init():
@ -2116,15 +2244,18 @@ def open(fp, mode="r"):
factory, accept = OPEN[i] factory, accept = OPEN[i]
if not accept or accept(prefix): if not accept or accept(prefix):
fp.seek(0) fp.seek(0)
return factory(fp, filename) im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError): except (SyntaxError, IndexError, TypeError):
#import traceback # import traceback
#traceback.print_exc() # traceback.print_exc()
pass pass
raise IOError("cannot identify image file %r" raise IOError("cannot identify image file %r"
% (filename if filename else fp)) % (filename if filename else fp))
# #
# Image processing. # Image processing.
@ -2223,6 +2354,7 @@ def merge(mode, bands):
im.putband(bands[i].im, i) im.putband(bands[i].im, i)
return bands[0]._new(im) return bands[0]._new(im)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Plugin registry # Plugin registry
@ -2281,6 +2413,7 @@ def _show(image, **options):
# override me, as necessary # override me, as necessary
_showxv(image, **options) _showxv(image, **options)
def _showxv(image, title=None, **options): def _showxv(image, title=None, **options):
from PIL import ImageShow from PIL import ImageShow
ImageShow.show(image, title, **options) ImageShow.show(image, title, **options)

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
# #
# For a background, see "Image Processing By Interpolation and # For a background, see "Image Processing By Interpolation and
# Extrapolation", Paul Haeberli and Douglas Voorhies. Available # Extrapolation", Paul Haeberli and Douglas Voorhies. Available
# at http://www.sgi.com/grafica/interp/index.html # at http://www.graficaobscura.com/interp/index.html
# #
# History: # History:
# 1996-03-23 fl Created # 1996-03-23 fl Created

View File

@ -502,5 +502,5 @@ def _safe_read(fp, size):
if not block: if not block:
break break
data.append(block) data.append(block)
size = size - len(block) size -= len(block)
return b"".join(data) return b"".join(data)

View File

@ -396,7 +396,7 @@ w7IkEbzhVQAAAABJRU5ErkJggg==
if __name__ == "__main__": if __name__ == "__main__":
# create font data chunk for embedding # create font data chunk for embedding
import base64, os, sys import base64, os, sys
font = "../Images/courB08" font = "../Tests/images/courB08"
print(" f._load_pilfont_data(") print(" f._load_pilfont_data(")
print(" # %s" % os.path.basename(font)) print(" # %s" % os.path.basename(font))
print(" BytesIO(base64.decodestring(b'''") print(" BytesIO(base64.decodestring(b'''")

View File

@ -17,7 +17,6 @@
from PIL import Image from PIL import Image
from PIL import _imagingmath from PIL import _imagingmath
import sys
try: try:
import builtins import builtins

244
PIL/ImageMorph.py Normal file
View File

@ -0,0 +1,244 @@
# A binary morphology add-on for the Python Imaging Library
#
# History:
# 2014-06-04 Initial version.
#
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
from PIL import Image
from PIL import _imagingmorph
import re
LUT_SIZE = 1 << 9
class LutBuilder:
"""A class for building a MorphLut from a descriptive language
The input patterns is a list of a strings sequences like these:
4:(...
.1.
111)->1
(whitespaces including linebreaks are ignored). The option 4
describes a series of symmetry operations (in this case a
4-rotation), the pattern is described by:
. or X - Ignore
1 - Pixel is on
0 - Pixel is off
The result of the operation is described after "->" string.
The default is to return the current pixel value, which is
returned if no other match is found.
Operations:
4 - 4 way rotation
N - Negate
1 - Dummy op for no other operation (an op must always be given)
M - Mirroring
Example:
lb = LutBuilder(patterns = ["4:(... .1. 111)->1"])
lut = lb.build_lut()
"""
def __init__(self, patterns=None, op_name=None):
if patterns is not None:
self.patterns = patterns
else:
self.patterns = []
self.lut = None
if op_name is not None:
known_patterns = {
'corner': ['1:(... ... ...)->0',
'4:(00. 01. ...)->1'],
'dilation4': ['4:(... .0. .1.)->1'],
'dilation8': ['4:(... .0. .1.)->1',
'4:(... .0. ..1)->1'],
'erosion4': ['4:(... .1. .0.)->0'],
'erosion8': ['4:(... .1. .0.)->0',
'4:(... .1. ..0)->0'],
'edge': ['1:(... ... ...)->0',
'4:(.0. .1. ...)->1',
'4:(01. .1. ...)->1']
}
if op_name not in known_patterns:
raise Exception('Unknown pattern '+op_name+'!')
self.patterns = known_patterns[op_name]
def add_patterns(self, patterns):
self.patterns += patterns
def build_default_lut(self):
symbols = [0, 1]
m = 1 << 4 # pos of current pixel
self.lut = bytearray([symbols[(i & m) > 0] for i in range(LUT_SIZE)])
def get_lut(self):
return self.lut
def _string_permute(self, pattern, permutation):
"""string_permute takes a pattern and a permutation and returns the
string permuted according to the permutation list.
"""
assert(len(permutation) == 9)
return ''.join([pattern[p] for p in permutation])
def _pattern_permute(self, basic_pattern, options, basic_result):
"""pattern_permute takes a basic pattern and its result and clones
the pattern according to the modifications described in the $options
parameter. It returns a list of all cloned patterns."""
patterns = [(basic_pattern, basic_result)]
# rotations
if '4' in options:
res = patterns[-1][1]
for i in range(4):
patterns.append(
(self._string_permute(patterns[-1][0], [6, 3, 0,
7, 4, 1,
8, 5, 2]), res))
# mirror
if 'M' in options:
n = len(patterns)
for pattern, res in patterns[0:n]:
patterns.append(
(self._string_permute(pattern, [2, 1, 0,
5, 4, 3,
8, 7, 6]), res))
# negate
if 'N' in options:
n = len(patterns)
for pattern, res in patterns[0:n]:
# Swap 0 and 1
pattern = (pattern
.replace('0', 'Z')
.replace('1', '0')
.replace('Z', '1'))
res = '%d' % (1-int(res))
patterns.append((pattern, res))
return patterns
def build_lut(self):
"""Compile all patterns into a morphology lut.
TBD :Build based on (file) morphlut:modify_lut
"""
self.build_default_lut()
patterns = []
# Parse and create symmetries of the patterns strings
for p in self.patterns:
m = re.search(
r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n', ''))
if not m:
raise Exception('Syntax error in pattern "'+p+'"')
options = m.group(1)
pattern = m.group(2)
result = int(m.group(3))
# Get rid of spaces
pattern = pattern.replace(' ', '').replace('\n', '')
patterns += self._pattern_permute(pattern, options, result)
# # Debugging
# for p,r in patterns:
# print p,r
# print '--'
# compile the patterns into regular expressions for speed
for i in range(len(patterns)):
p = patterns[i][0].replace('.', 'X').replace('X', '[01]')
p = re.compile(p)
patterns[i] = (p, patterns[i][1])
# Step through table and find patterns that match.
# Note that all the patterns are searched. The last one
# caught overrides
for i in range(LUT_SIZE):
# Build the bit pattern
bitpattern = bin(i)[2:]
bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1]
for p, r in patterns:
if p.match(bitpattern):
self.lut[i] = [0, 1][r]
return self.lut
class MorphOp:
"""A class for binary morphological operators"""
def __init__(self,
lut=None,
op_name=None,
patterns=None):
"""Create a binary morphological operator"""
self.lut = lut
if op_name is not None:
self.lut = LutBuilder(op_name=op_name).build_lut()
elif patterns is not None:
self.lut = LutBuilder(patterns=patterns).build_lut()
def apply(self, image):
"""Run a single morphological operation on an image
Returns a tuple of the number of changed pixels and the
morphed image"""
if self.lut is None:
raise Exception('No operator loaded')
outimage = Image.new(image.mode, image.size, None)
count = _imagingmorph.apply(
bytes(self.lut), image.im.id, outimage.im.id)
return count, outimage
def match(self, image):
"""Get a list of coordinates matching the morphological operation on
an image.
Returns a list of tuples of (x,y) coordinates
of all matching pixels."""
if self.lut is None:
raise Exception('No operator loaded')
return _imagingmorph.match(bytes(self.lut), image.im.id)
def get_on_pixels(self, image):
"""Get a list of all turned on pixels in a binary image
Returns a list of tuples of (x,y) coordinates
of all matching pixels."""
return _imagingmorph.get_on_pixels(image.im.id)
def load_lut(self, filename):
"""Load an operator from an mrl file"""
with open(filename, 'rb') as f:
self.lut = bytearray(f.read())
if len(self.lut) != 8192:
self.lut = None
raise Exception('Wrong size operator file!')
def save_lut(self, filename):
"""Save an operator to an mrl file"""
if self.lut is None:
raise Exception('No operator loaded')
with open(filename, 'wb') as f:
f.write(self.lut)
def set_lut(self, lut):
"""Set the lut from an external source"""
self.lut = lut
# End of file

View File

@ -94,7 +94,7 @@ def autocontrast(image, cutoff=0, ignore=None):
cut = cut - h[lo] cut = cut - h[lo]
h[lo] = 0 h[lo] = 0
else: else:
h[lo] = h[lo] - cut h[lo] -= cut
cut = 0 cut = 0
if cut <= 0: if cut <= 0:
break break
@ -105,7 +105,7 @@ def autocontrast(image, cutoff=0, ignore=None):
cut = cut - h[hi] cut = cut - h[hi]
h[hi] = 0 h[hi] = 0
else: else:
h[hi] = h[hi] - cut h[hi] -= cut
cut = 0 cut = 0
if cut <= 0: if cut <= 0:
break break

View File

@ -101,8 +101,11 @@ class ImagePalette:
fp.write("# Mode: %s\n" % self.mode) fp.write("# Mode: %s\n" % self.mode)
for i in range(256): for i in range(256):
fp.write("%d" % i) fp.write("%d" % i)
for j in range(i, len(self.palette), 256): for j in range(i*len(self.mode), (i+1)*len(self.mode)):
fp.write(" %d" % self.palette[j]) try:
fp.write(" %d" % self.palette[j])
except IndexError:
fp.write(" 0")
fp.write("\n") fp.write("\n")
fp.close() fp.close()

View File

@ -17,7 +17,7 @@ from __future__ import print_function
from PIL import Image from PIL import Image
import os, sys import os, sys
if(sys.version_info >= (3, 3)): if sys.version_info >= (3, 3):
from shlex import quote from shlex import quote
else: else:
from pipes import quote from pipes import quote
@ -160,7 +160,7 @@ else:
# imagemagick's display command instead. # imagemagick's display command instead.
command = executable = "xv" command = executable = "xv"
if title: if title:
command = command + " -name %s" % quote(title) command += " -name %s" % quote(title)
return command, executable return command, executable
if which("xv"): if which("xv"):

View File

@ -21,7 +21,6 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from PIL import Image
import operator, math import operator, math
from functools import reduce from functools import reduce
@ -81,7 +80,7 @@ class Stat:
for i in range(0, len(self.h), 256): for i in range(0, len(self.h), 256):
sum = 0.0 sum = 0.0
for j in range(256): for j in range(256):
sum = sum + j * self.h[i+j] sum += j * self.h[i + j]
v.append(sum) v.append(sum)
return v return v
@ -92,7 +91,7 @@ class Stat:
for i in range(0, len(self.h), 256): for i in range(0, len(self.h), 256):
sum2 = 0.0 sum2 = 0.0
for j in range(256): for j in range(256):
sum2 = sum2 + (j ** 2) * float(self.h[i+j]) sum2 += (j ** 2) * float(self.h[i + j])
v.append(sum2) v.append(sum2)
return v return v

View File

@ -103,7 +103,7 @@ class IptcImageFile(ImageFile.ImageFile):
break break
if s != sz: if s != sz:
return 0 return 0
y = y + 1 y += 1
return y == size[1] return y == size[1]
def _open(self): def _open(self):
@ -187,7 +187,7 @@ class IptcImageFile(ImageFile.ImageFile):
if not s: if not s:
break break
o.write(s) o.write(s)
size = size - len(s) size -= len(s)
o.close() o.close()
try: try:
@ -235,26 +235,26 @@ def getiptcinfo(im):
# parse the image resource block # parse the image resource block
offset = 0 offset = 0
while app[offset:offset+4] == "8BIM": while app[offset:offset+4] == "8BIM":
offset = offset + 4 offset += 4
# resource code # resource code
code = JpegImagePlugin.i16(app, offset) code = JpegImagePlugin.i16(app, offset)
offset = offset + 2 offset += 2
# resource name (usually empty) # resource name (usually empty)
name_len = i8(app[offset]) name_len = i8(app[offset])
name = app[offset+1:offset+1+name_len] name = app[offset+1:offset+1+name_len]
offset = 1 + offset + name_len offset = 1 + offset + name_len
if offset & 1: if offset & 1:
offset = offset + 1 offset += 1
# resource data block # resource data block
size = JpegImagePlugin.i32(app, offset) size = JpegImagePlugin.i32(app, offset)
offset = offset + 4 offset += 4
if code == 0x0404: if code == 0x0404:
# 0x0404 contains IPTC/NAA data # 0x0404 contains IPTC/NAA data
data = app[offset:offset+size] data = app[offset:offset+size]
break break
offset = offset + size offset = offset + size
if offset & 1: if offset & 1:
offset = offset + 1 offset += 1
except (AttributeError, KeyError): except (AttributeError, KeyError):
pass pass

View File

@ -15,20 +15,21 @@
__version__ = "0.1" __version__ = "0.1"
from PIL import Image, ImageFile, _binary from PIL import Image, ImageFile
import struct import struct
import os import os
import io import io
def _parse_codestream(fp): def _parse_codestream(fp):
"""Parse the JPEG 2000 codestream to extract the size and component """Parse the JPEG 2000 codestream to extract the size and component
count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
hdr = fp.read(2) hdr = fp.read(2)
lsiz = struct.unpack('>H', hdr)[0] lsiz = struct.unpack('>H', hdr)[0]
siz = hdr + fp.read(lsiz - 2) siz = hdr + fp.read(lsiz - 2)
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \ lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
xtosiz, ytosiz, csiz \ xtosiz, ytosiz, csiz \
= struct.unpack('>HHIIIIIIIIH', siz[:38]) = struct.unpack('>HHIIIIIIIIH', siz[:38])
ssiz = [None]*csiz ssiz = [None]*csiz
xrsiz = [None]*csiz xrsiz = [None]*csiz
@ -45,16 +46,17 @@ def _parse_codestream(fp):
elif csiz == 3: elif csiz == 3:
mode = 'RGB' mode = 'RGB'
elif csiz == 4: elif csiz == 4:
mode == 'RGBA' mode = 'RGBA'
else: else:
mode = None mode = None
return (size, mode) return (size, mode)
def _parse_jp2_header(fp): def _parse_jp2_header(fp):
"""Parse the JP2 header box to extract size, component count and """Parse the JP2 header box to extract size, component count and
color space information, returning a PIL (size, mode) tuple.""" color space information, returning a PIL (size, mode) tuple."""
# Find the JP2 header box # Find the JP2 header box
header = None header = None
while True: while True:
@ -76,7 +78,7 @@ def _parse_jp2_header(fp):
size = None size = None
mode = None mode = None
hio = io.BytesIO(header) hio = io.BytesIO(header)
while True: while True:
lbox, tbox = struct.unpack('>I4s', hio.read(8)) lbox, tbox = struct.unpack('>I4s', hio.read(8))
@ -90,7 +92,7 @@ def _parse_jp2_header(fp):
if tbox == b'ihdr': if tbox == b'ihdr':
height, width, nc, bpc, c, unkc, ipr \ height, width, nc, bpc, c, unkc, ipr \
= struct.unpack('>IIHBBBB', content) = struct.unpack('>IIHBBBB', content)
size = (width, height) size = (width, height)
if unkc: if unkc:
if nc == 1: if nc == 1:
@ -112,21 +114,22 @@ def _parse_jp2_header(fp):
elif nc == 4: elif nc == 4:
mode = 'RGBA' mode = 'RGBA'
break break
elif cs == 17: # grayscale elif cs == 17: # grayscale
if nc == 1: if nc == 1:
mode = 'L' mode = 'L'
elif nc == 2: elif nc == 2:
mode = 'LA' mode = 'LA'
break break
elif cs == 18: # sYCC elif cs == 18: # sYCC
if nc == 3: if nc == 3:
mode = 'RGB' mode = 'RGB'
elif nc == 4: elif nc == 4:
mode == 'RGBA' mode = 'RGBA'
break break
return (size, mode) return (size, mode)
## ##
# Image plugin for JPEG2000 images. # Image plugin for JPEG2000 images.
@ -141,29 +144,39 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
self.size, self.mode = _parse_codestream(self.fp) self.size, self.mode = _parse_codestream(self.fp)
else: else:
sig = sig + self.fp.read(8) sig = sig + self.fp.read(8)
if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
self.codec = "jp2" self.codec = "jp2"
self.size, self.mode = _parse_jp2_header(self.fp) self.size, self.mode = _parse_jp2_header(self.fp)
else: else:
raise SyntaxError('not a JPEG 2000 file') raise SyntaxError('not a JPEG 2000 file')
if self.size is None or self.mode is None: if self.size is None or self.mode is None:
raise SyntaxError('unable to determine size/mode') raise SyntaxError('unable to determine size/mode')
self.reduce = 0 self.reduce = 0
self.layers = 0 self.layers = 0
fd = -1 fd = -1
length = -1
if hasattr(self.fp, "fileno"): if hasattr(self.fp, "fileno"):
try: try:
fd = self.fp.fileno() fd = self.fp.fileno()
length = os.fstat(fd).st_size
except: except:
fd = -1 fd = -1
elif hasattr(self.fp, "seek"):
try:
pos = f.tell()
f.seek(0, 2)
length = f.tell()
f.seek(pos, 0)
except:
length = -1
self.tile = [('jpeg2k', (0, 0) + self.size, 0, self.tile = [('jpeg2k', (0, 0) + self.size, 0,
(self.codec, self.reduce, self.layers, fd))] (self.codec, self.reduce, self.layers, fd, length))]
def load(self): def load(self):
if self.reduce: if self.reduce:
@ -175,15 +188,17 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
if self.tile: if self.tile:
# Update the reduce and layers settings # Update the reduce and layers settings
t = self.tile[0] t = self.tile[0]
t3 = (t[3][0], self.reduce, self.layers, t[3][3]) t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4])
self.tile = [(t[0], (0, 0) + self.size, t[2], t3)] self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
ImageFile.ImageFile.load(self) ImageFile.ImageFile.load(self)
def _accept(prefix): def _accept(prefix):
return (prefix[:4] == b'\xff\x4f\xff\x51' return (prefix[:4] == b'\xff\x4f\xff\x51'
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a') or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
# ------------------------------------------------------------ # ------------------------------------------------------------
# Save support # Save support
@ -214,7 +229,7 @@ def _save(im, fp, filename):
fd = fp.fileno() fd = fp.fileno()
except: except:
fd = -1 fd = -1
im.encoderconfig = ( im.encoderconfig = (
offset, offset,
tile_offset, tile_offset,
@ -228,10 +243,10 @@ def _save(im, fp, filename):
progression, progression,
cinema_mode, cinema_mode,
fd fd
) )
ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)]) ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
# ------------------------------------------------------------ # ------------------------------------------------------------
# Registry stuff # Registry stuff

View File

@ -34,7 +34,8 @@
__version__ = "0.6" __version__ = "0.6"
import array, struct import array
import struct
from PIL import Image, ImageFile, _binary from PIL import Image, ImageFile, _binary
from PIL.JpegPresets import presets from PIL.JpegPresets import presets
from PIL._util import isStringType from PIL._util import isStringType
@ -44,6 +45,7 @@ o8 = _binary.o8
i16 = _binary.i16be i16 = _binary.i16be
i32 = _binary.i32be i32 = _binary.i32be
# #
# Parser # Parser
@ -51,6 +53,7 @@ def Skip(self, marker):
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2))-2
ImageFile._safe_read(self.fp, n) ImageFile._safe_read(self.fp, n)
def APP(self, marker): def APP(self, marker):
# #
# Application marker. Store these in the APP dictionary. # Application marker. Store these in the APP dictionary.
@ -59,14 +62,14 @@ def APP(self, marker):
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2))-2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
app = "APP%d" % (marker&15) app = "APP%d" % (marker & 15)
self.app[app] = s # compatibility self.app[app] = s # compatibility
self.applist.append((app, s)) self.applist.append((app, s))
if marker == 0xFFE0 and s[:4] == b"JFIF": if marker == 0xFFE0 and s[:4] == b"JFIF":
# extract JFIF information # extract JFIF information
self.info["jfif"] = version = i16(s, 5) # version self.info["jfif"] = version = i16(s, 5) # version
self.info["jfif_version"] = divmod(version, 256) self.info["jfif_version"] = divmod(version, 256)
# extract JFIF properties # extract JFIF properties
try: try:
@ -81,10 +84,10 @@ def APP(self, marker):
self.info["jfif_density"] = jfif_density self.info["jfif_density"] = jfif_density
elif marker == 0xFFE1 and s[:5] == b"Exif\0": elif marker == 0xFFE1 and s[:5] == b"Exif\0":
# extract Exif information (incomplete) # extract Exif information (incomplete)
self.info["exif"] = s # FIXME: value will change self.info["exif"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:5] == b"FPXR\0": elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
# extract FlashPix information (incomplete) # extract FlashPix information (incomplete)
self.info["flashpix"] = s # FIXME: value will change self.info["flashpix"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0": elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0":
# Since an ICC profile can be larger than the maximum size of # Since an ICC profile can be larger than the maximum size of
# a JPEG marker (64K), we need provisions to split it into # a JPEG marker (64K), we need provisions to split it into
@ -108,16 +111,17 @@ def APP(self, marker):
else: else:
self.info["adobe_transform"] = adobe_transform self.info["adobe_transform"] = adobe_transform
def COM(self, marker): def COM(self, marker):
# #
# Comment marker. Store these in the APP dictionary. # Comment marker. Store these in the APP dictionary.
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2))-2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
self.app["COM"] = s # compatibility self.app["COM"] = s # compatibility
self.applist.append(("COM", s)) self.applist.append(("COM", s))
def SOF(self, marker): def SOF(self, marker):
# #
# Start of frame marker. Defines the size and mode of the # Start of frame marker. Defines the size and mode of the
@ -149,21 +153,22 @@ def SOF(self, marker):
if self.icclist: if self.icclist:
# fixup icc profile # fixup icc profile
self.icclist.sort() # sort by sequence number self.icclist.sort() # sort by sequence number
if i8(self.icclist[0][13]) == len(self.icclist): if i8(self.icclist[0][13]) == len(self.icclist):
profile = [] profile = []
for p in self.icclist: for p in self.icclist:
profile.append(p[14:]) profile.append(p[14:])
icc_profile = b"".join(profile) icc_profile = b"".join(profile)
else: else:
icc_profile = None # wrong number of fragments icc_profile = None # wrong number of fragments
self.info["icc_profile"] = icc_profile self.info["icc_profile"] = icc_profile
self.icclist = None self.icclist = None
for i in range(6, len(s), 3): for i in range(6, len(s), 3):
t = s[i:i+3] t = s[i:i+3]
# 4-tuples: id, vsamp, hsamp, qtable # 4-tuples: id, vsamp, hsamp, qtable
self.layer.append((t[0], i8(t[1])//16, i8(t[1])&15, i8(t[2]))) self.layer.append((t[0], i8(t[1])//16, i8(t[1]) & 15, i8(t[2])))
def DQT(self, marker): def DQT(self, marker):
# #
@ -181,10 +186,10 @@ def DQT(self, marker):
raise SyntaxError("bad quantization table marker") raise SyntaxError("bad quantization table marker")
v = i8(s[0]) v = i8(s[0])
if v//16 == 0: if v//16 == 0:
self.quantization[v&15] = array.array("b", s[1:65]) self.quantization[v & 15] = array.array("b", s[1:65])
s = s[65:] s = s[65:]
else: else:
return # FIXME: add code to read 16-bit tables! return # FIXME: add code to read 16-bit tables!
# raise SyntaxError, "bad quantization table element size" # raise SyntaxError, "bad quantization table element size"
@ -261,6 +266,7 @@ MARKER = {
def _accept(prefix): def _accept(prefix):
return prefix[0:1] == b"\377" return prefix[0:1] == b"\377"
## ##
# Image plugin for JPEG and JFIF images. # Image plugin for JPEG and JFIF images.
@ -284,32 +290,37 @@ class JpegImageFile(ImageFile.ImageFile):
self.huffman_dc = {} self.huffman_dc = {}
self.huffman_ac = {} self.huffman_ac = {}
self.quantization = {} self.quantization = {}
self.app = {} # compatibility self.app = {} # compatibility
self.applist = [] self.applist = []
self.icclist = [] self.icclist = []
while True: while True:
s = s + self.fp.read(1) i = i8(s)
if i == 0xFF:
i = i16(s) s = s + self.fp.read(1)
i = i16(s)
else:
# Skip non-0xFF junk
s = b"\xff"
continue
if i in MARKER: if i in MARKER:
name, description, handler = MARKER[i] name, description, handler = MARKER[i]
# print hex(i), name, description # print hex(i), name, description
if handler is not None: if handler is not None:
handler(self, i) handler(self, i)
if i == 0xFFDA: # start of scan if i == 0xFFDA: # start of scan
rawmode = self.mode rawmode = self.mode
if self.mode == "CMYK": if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0,0) + self.size, 0, (rawmode, ""))] self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
# self.__offset = self.fp.tell() # self.__offset = self.fp.tell()
break break
s = self.fp.read(1) s = self.fp.read(1)
elif i == 0 or i == 65535: elif i == 0 or i == 0xFFFF:
# padded marker or junk; move on # padded marker or junk; move on
s = "\xff" s = b"\xff"
else: else:
raise SyntaxError("no marker found") raise SyntaxError("no marker found")
@ -343,7 +354,8 @@ class JpegImageFile(ImageFile.ImageFile):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities # ALTERNATIVE: handle JPEGs via the IJG command line utilities
import tempfile, os import tempfile
import os
f, path = tempfile.mkstemp() f, path = tempfile.mkstemp()
os.close(f) os.close(f)
if os.path.exists(self.filename): if os.path.exists(self.filename):
@ -354,8 +366,10 @@ class JpegImageFile(ImageFile.ImageFile):
try: try:
self.im = Image.core.open_ppm(path) self.im = Image.core.open_ppm(path)
finally: finally:
try: os.unlink(path) try:
except: pass os.unlink(path)
except:
pass
self.mode = self.im.mode self.mode = self.im.mode
self.size = self.im.size self.size = self.im.size
@ -372,6 +386,7 @@ def _getexif(self):
# version. # version.
from PIL import TiffImagePlugin from PIL import TiffImagePlugin
import io import io
def fixup(value): def fixup(value):
if len(value) == 1: if len(value) == 1:
return value[0] return value[0]
@ -422,7 +437,7 @@ RAWMODE = {
"RGB": "RGB", "RGB": "RGB",
"RGBA": "RGB", "RGBA": "RGB",
"RGBX": "RGB", "RGBX": "RGB",
"CMYK": "CMYK;I", # assume adobe conventions "CMYK": "CMYK;I", # assume adobe conventions
"YCbCr": "YCbCr", "YCbCr": "YCbCr",
} }
@ -441,16 +456,19 @@ samplings = {
(2, 2, 1, 1, 1, 1): 2, (2, 2, 1, 1, 1, 1): 2,
} }
def convert_dict_qtables(qtables): def convert_dict_qtables(qtables):
qtables = [qtables[key] for key in range(len(qtables)) if key in qtables] qtables = [qtables[key] for key in range(len(qtables)) if key in qtables]
for idx, table in enumerate(qtables): for idx, table in enumerate(qtables):
qtables[idx] = [table[i] for i in zigzag_index] qtables[idx] = [table[i] for i in zigzag_index]
return qtables return qtables
def get_sampling(im): def get_sampling(im):
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
return samplings.get(sampling, -1) return samplings.get(sampling, -1)
def _save(im, fp, filename): def _save(im, fp, filename):
try: try:
@ -480,7 +498,7 @@ def _save(im, fp, filename):
else: else:
if subsampling in presets: if subsampling in presets:
subsampling = presets[subsampling].get('subsampling', -1) subsampling = presets[subsampling].get('subsampling', -1)
if qtables in presets: if isStringType(qtables) and qtables in presets:
qtables = presets[qtables].get('quantization') qtables = presets[qtables].get('quantization')
if subsampling == "4:4:4": if subsampling == "4:4:4":
@ -543,8 +561,8 @@ def _save(im, fp, filename):
i = 1 i = 1
for marker in markers: for marker in markers:
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker)) size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
extra = extra + (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker) extra += b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker
i = i + 1 i += 1
# get keyword arguments # get keyword arguments
im.encoderconfig = ( im.encoderconfig = (
@ -563,12 +581,11 @@ def _save(im, fp, filename):
info.get("exif", b"") info.get("exif", b"")
) )
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
# if we optimize, libjpeg needs a buffer big enough to hold the whole image in a shot. # in a shot. Guessing on the size, at im.size bytes. (raw pizel size is
# Guessing on the size, at im.size bytes. (raw pizel size is channels*size, this # channels*size, this is a value that's been used in a django patch.
# is a value that's been used in a django patch.
# https://github.com/jdriscoll/django-imagekit/issues/50 # https://github.com/jdriscoll/django-imagekit/issues/50
bufsize=0 bufsize = 0
if "optimize" in info or "progressive" in info or "progression" in info: if "optimize" in info or "progressive" in info or "progression" in info:
if quality >= 95: if quality >= 95:
bufsize = 2 * im.size[0] * im.size[1] bufsize = 2 * im.size[0] * im.size[1]
@ -577,17 +594,20 @@ def _save(im, fp, filename):
# The exif info needs to be written as one block, + APP1, + one spare byte. # The exif info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough # Ensure that our buffer is big enough
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif",b"")) + 5 ) bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5)
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)], bufsize)
def _save_cjpeg(im, fp, filename): def _save_cjpeg(im, fp, filename):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities. # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
import os import os
file = im._dump() file = im._dump()
os.system("cjpeg %s >%s" % (file, filename)) os.system("cjpeg %s >%s" % (file, filename))
try: os.unlink(file) try:
except: pass os.unlink(file)
except:
pass
# -------------------------------------------------------------------q- # -------------------------------------------------------------------q-
# Registry stuff # Registry stuff

View File

@ -38,13 +38,13 @@ class BitStream:
self.bits = 0 self.bits = 0
continue continue
self.bitbuffer = (self.bitbuffer << 8) + c self.bitbuffer = (self.bitbuffer << 8) + c
self.bits = self.bits + 8 self.bits += 8
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1 return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
def skip(self, bits): def skip(self, bits):
while self.bits < bits: while self.bits < bits:
self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1)) self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1))
self.bits = self.bits + 8 self.bits += 8
self.bits = self.bits - bits self.bits = self.bits - bits
def read(self, bits): def read(self, bits):

View File

@ -1,27 +1,22 @@
OleFileIO_PL OleFileIO_PL
============ ============
[OleFileIO_PL](http://www.decalage.info/python/olefileio) is a Python module to read [Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format)](http://en.wikipedia.org/wiki/Compound_File_Binary_Format), such as Microsoft Office documents, Image Composer and FlashPix files, Outlook messages, ... [OleFileIO_PL](http://www.decalage.info/python/olefileio) is a Python module to parse and read [Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format)](http://en.wikipedia.org/wiki/Compound_File_Binary_Format), such as Microsoft Office documents, Image Composer and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats ...
This is an improved version of the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent Python Imaging Library, created and maintained by Fredrik Lundh. The API is still compatible with PIL, but I have improved the internal implementation significantly, with new features, bugfixes and a more robust design. This is an improved version of the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent Python Imaging Library, created and maintained by Fredrik Lundh. The API is still compatible with PIL, but since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust design.
As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules) As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules)
WARNING: THIS IS (STILL) WORK IN PROGRESS. OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-pillow.github.io/), the friendly fork of PIL.
Main improvements over PIL version of OleFileIO: OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL.
------------------------------------------------
- Better compatibility with Python 2.6 (also compatible with Python 3.0+)
- Support for files larger than 6.8MB
- Robust: many checks to detect malformed files
- Improved API
- New features: metadata extraction, stream/storage timestamps
- Added setup.py and install.bat to ease installation
News News
---- ----
Follow all updates and news on Twitter: <https://twitter.com/decalage2>
- **2014-02-04 v0.30**: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work.
- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed parsing of direntry timestamps - 2013-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-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 - 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved getproperties to convert timestamps to Python datetime
@ -34,47 +29,224 @@ News
- 2009-12-10 v0.19: fixed support for 64 bits platforms (thanks to Ben G. and Martijn for reporting the bug) - 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. - see changelog in source code for more info.
Download: Download
--------- --------
The archive is available on [the project page](https://bitbucket.org/decalage/olefileio_pl/downloads). The archive is available on [the project page](https://bitbucket.org/decalage/olefileio_pl/downloads).
Features
--------
How to use this module: - Parse and read any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls, PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes, Zeiss AxioVision ZVI files, Olympus FluoView OIB files, ...
----------------------- - 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
See sample code at the end of the module, and also docstrings.
Here are a few examples: 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
How to use this module
----------------------
OleFileIO_PL can be used as an independent module or with PIL. The main functions and methods are explained below.
For more information, see also the file **OleFileIO_PL.html**, sample code at the end of the module itself, and docstrings within the code.
### About the structure of OLE files ###
An OLE file can be seen as a mini file system or a Zip archive: It contains **streams** of data that look like files embedded within the OLE file. Each stream has a name. For example, the main stream of a MS Word document containing its text is named "WordDocument".
An OLE file can also contain **storages**. A storage is a folder that contains streams or other storages. For example, a MS Word document with VBA macros has a storage called "Macros".
Special streams can contain **properties**. A property is a specific value that can be used to store information such as the metadata of a document (title, author, creation date, etc). Property stream names usually start with the character '\x05'.
For example, a typical MS Word document may look like this:
\x05DocumentSummaryInformation (stream)
\x05SummaryInformation (stream)
WordDocument (stream)
Macros (storage)
PROJECT (stream)
PROJECTwm (stream)
VBA (storage)
Module1 (stream)
ThisDocument (stream)
_VBA_PROJECT (stream)
dir (stream)
ObjectPool (storage)
### Import OleFileIO_PL ###
:::python :::python
import OleFileIO_PL import OleFileIO_PL
# Test if a file is an OLE container: As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. If your application needs to be compatible with Python 2.5 or older, you may use the following code to load the old version when needed:
:::python
try:
import OleFileIO_PL
except:
import OleFileIO_PL2 as OleFileIO_PL
If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
### Test if a file is an OLE container ###
Use isOleFile to check if the first bytes of the file contain the Magic for OLE files, before opening it. isOleFile returns True if it is an OLE file, False otherwise (new in v0.16).
:::python
assert OleFileIO_PL.isOleFile('myfile.doc') assert OleFileIO_PL.isOleFile('myfile.doc')
# Open an OLE file from disk:
### Open an OLE file from disk ###
Create an OleFileIO object with the file path as parameter:
:::python
ole = OleFileIO_PL.OleFileIO('myfile.doc') ole = OleFileIO_PL.OleFileIO('myfile.doc')
# Get list of streams: ### Open an OLE file from a file-like object ###
This is useful if the file is not on disk, e.g. already stored in a string or as a file-like object.
:::python
ole = OleFileIO_PL.OleFileIO(f)
For example the code below reads a file into a string, then uses BytesIO to turn it into a file-like object.
:::python
data = open('myfile.doc', 'rb').read()
f = io.BytesIO(data) # or StringIO.StringIO for Python 2.x
ole = OleFileIO_PL.OleFileIO(f)
### How to handle malformed OLE files ###
By default, the parser is configured to be as robust and permissive as possible, allowing to parse most malformed OLE files. Only fatal errors will raise an exception. It is possible to tell the parser to be more strict in order to raise exceptions for files that do not fully conform to the OLE specifications, using the raise_defect option (new in v0.14):
:::python
ole = OleFileIO_PL.OleFileIO('myfile.doc', raise_defects=DEFECT_INCORRECT)
When the parsing is done, the list of non-fatal issues detected is available as a list in the parsing_issues attribute of the OleFileIO object (new in 0.25):
:::python
print('Non-fatal issues raised during parsing:')
if ole.parsing_issues:
for exctype, msg in ole.parsing_issues:
print('- %s: %s' % (exctype.__name__, msg))
else:
print('None')
### Syntax for stream and storage path ###
Two different syntaxes are allowed for methods that need or return the path of streams and storages:
1) Either a **list of strings** including all the storages from the root up to the stream/storage name. For example a stream called "WordDocument" at the root will have ['WordDocument'] as full path. A stream called "ThisDocument" located in the storage "Macros/VBA" will be ['Macros', 'VBA', 'ThisDocument']. This is the original syntax from PIL. While hard to read and not very convenient, this syntax works in all cases.
2) Or a **single string with slashes** to separate storage and stream names (similar to the Unix path syntax). The previous examples would be 'WordDocument' and 'Macros/VBA/ThisDocument'. This syntax is easier, but may fail if a stream or storage name contains a slash. (new in v0.15)
Both are case-insensitive.
Switching between the two is easy:
:::python
slash_path = '/'.join(list_path)
list_path = slash_path.split('/')
### Get the list of streams ###
listdir() returns a list of all the streams contained in the OLE file, including those stored in storages. Each stream is listed itself as a list, as described above.
:::python
print(ole.listdir()) print(ole.listdir())
# Test if known streams/storages exist: Sample result:
:::python
[['\x01CompObj'], ['\x05DocumentSummaryInformation'], ['\x05SummaryInformation']
, ['1Table'], ['Macros', 'PROJECT'], ['Macros', 'PROJECTwm'], ['Macros', 'VBA',
'Module1'], ['Macros', 'VBA', 'ThisDocument'], ['Macros', 'VBA', '_VBA_PROJECT']
, ['Macros', 'VBA', 'dir'], ['ObjectPool'], ['WordDocument']]
As an option it is possible to choose if storages should also be listed, with or without streams (new in v0.26):
:::python
ole.listdir (streams=False, storages=True)
### Test if known streams/storages exist: ###
exists(path) checks if a given stream or storage exists in the OLE file (new in v0.16).
:::python
if ole.exists('worddocument'): if ole.exists('worddocument'):
print("This is a Word document.") print("This is a Word document.")
print("size :", ole.get_size('worddocument'))
if ole.exists('macros/vba'): if ole.exists('macros/vba'):
print("This document seems to contain VBA macros.") print("This document seems to contain VBA macros.")
# Extract the "Pictures" stream from a PPT file:
if ole.exists('Pictures'):
pics = ole.openstream('Pictures')
data = pics.read()
f = open('Pictures.bin', 'wb')
f.write(data)
f.close()
# Extract metadata (new in v0.24) - see source code for all attributes: ### Read data from a stream ###
openstream(path) opens a stream as a file-like object.
The following example extracts the "Pictures" stream from a PPT file:
:::python
pics = ole.openstream('Pictures')
data = pics.read()
### Get information about a stream/storage ###
Several methods can provide the size, type and timestamps of a given stream/storage:
get_size(path) returns the size of a stream in bytes (new in v0.16):
:::python
s = ole.get_size('WordDocument')
get_type(path) returns the type of a stream/storage, as one of the following constants: STGTY\_STREAM for a stream, STGTY\_STORAGE for a storage, STGTY\_ROOT for the root entry, and False for a non existing path (new in v0.15).
:::python
t = ole.get_type('WordDocument')
get\_ctime(path) and get\_mtime(path) return the creation and modification timestamps of a stream/storage, as a Python datetime object with UTC timezone. Please note that these timestamps are only present if the application that created the OLE file explicitly stored them, which is rarely the case. When not present, these methods return None (new in v0.26).
:::python
c = ole.get_ctime('WordDocument')
m = ole.get_mtime('WordDocument')
The root storage is a special case: You can get its creation and modification timestamps using the OleFileIO.root attribute (new in v0.26):
:::python
c = ole.root.getctime()
m = ole.root.getmtime()
### Extract metadata ###
get_metadata() will check if standard property streams exist, parse all the properties they contain, and return an OleMetadata object with the found properties as attributes (new in v0.24).
:::python
meta = ole.get_metadata() meta = ole.get_metadata()
print('Author:', meta.author) print('Author:', meta.author)
print('Title:', meta.title) print('Title:', meta.title)
@ -82,29 +254,67 @@ Here are a few examples:
# print all metadata: # print all metadata:
meta.dump() meta.dump()
# Close the OLE file: Available attributes include:
codepage, title, subject, author, keywords, comments, template,
last_saved_by, revision_number, total_edit_time, last_printed, create_time,
last_saved_time, num_pages, num_words, num_chars, thumbnail,
creating_application, security, codepage_doc, category, presentation_target,
bytes, lines, paragraphs, slides, notes, hidden_slides, mm_clips,
scale_crop, heading_pairs, titles_of_parts, manager, company, links_dirty,
chars_with_spaces, unused, shared_doc, link_base, hlinks, hlinks_changed,
version, dig_sig, content_type, content_status, language, doc_version
See the source code of the OleMetadata class for more information.
### Parse a property stream ###
get\_properties(path) can be used to parse any property stream that is not handled by get\_metadata. It returns a dictionary indexed by integers. Each integer is the index of the property, pointing to its value. For example in the standard property stream '\x05SummaryInformation', the document title is property #2, and the subject is #3.
:::python
p = ole.getproperties('specialprops')
By default as in the original PIL version, timestamp properties are converted into a number of seconds since Jan 1,1601. With the option convert\_time, you can obtain more convenient Python datetime objects (UTC timezone). If some time properties should not be converted (such as total editing time in '\x05SummaryInformation'), the list of indexes can be passed as no_conversion (new in v0.25):
:::python
p = ole.getproperties('specialprops', convert_time=True, no_conversion=[10])
### Close the OLE file ###
Unless your application is a simple script that terminates after processing an OLE file, do not forget to close each OleFileIO object after parsing to close the file on disk. (new in v0.22)
:::python
ole.close() ole.close()
# Work with a file-like object (e.g. StringIO) instead of a file on disk: ### Use OleFileIO_PL as a script ###
data = open('myfile.doc', 'rb').read()
f = io.BytesIO(data) OleFileIO_PL can also be used as a script from the command-line to display the structure of an OLE file and its metadata, for example:
ole = OleFileIO_PL.OleFileIO(f)
print(ole.listdir())
ole.close()
It can also be used as a script from the command-line to display the structure of an OLE file, for example:
OleFileIO_PL.py myfile.doc OleFileIO_PL.py myfile.doc
You can use the option -c to check that all streams can be read fully, and -d to generate very verbose debugging information.
## Real-life examples ##
A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/). A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/).
How to contribute: See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features OleFileIO_PL.
------------------
About Python 2 and 3
--------------------
OleFileIO\_PL used to support only Python 2.x. As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. See above the "import" section for a workaround.
If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
How to contribute
-----------------
The code is available in [a Mercurial repository on bitbucket](https://bitbucket.org/decalage/olefileio_pl). You may use it to submit enhancements or to report any issue. The code is available in [a Mercurial repository on bitbucket](https://bitbucket.org/decalage/olefileio_pl). You may use it to submit enhancements or to report any issue.
If you would like to help us improve this module, or simply provide feedback, you may also send an e-mail to decalage(at)laposte.net. You can help in many ways: If you would like to help us improve this module, or simply provide feedback, please [contact me](http://decalage.info/contact). You can help in many ways:
- test this module on different platforms / Python versions - test this module on different platforms / Python versions
- find and report bugs - find and report bugs
@ -112,21 +322,21 @@ If you would like to help us improve this module, or simply provide feedback, yo
- write unittest test cases - write unittest test cases
- provide tricky malformed files - provide tricky malformed files
How to report bugs: How to report bugs
------------------- ------------------
To report a bug, for example a normal file which is not parsed correctly, please use the [issue reporting page](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open), or send an e-mail with an attachment containing the debugging output of OleFileIO_PL. To report a bug, for example a normal file which is not parsed correctly, please use the [issue reporting page](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open), or if you prefer to do it privately, use this [contact form](http://decalage.info/contact). Please provide all the information about the context and how to reproduce the bug.
For this, launch the following command : If possible please join the debugging output of OleFileIO_PL. For this, launch the following command :
OleFileIO_PL.py -d -c file >debug.txt OleFileIO_PL.py -d -c file >debug.txt
License License
------- -------
OleFileIO_PL is open-source. OleFileIO_PL is open-source.
OleFileIO_PL changes are Copyright (c) 2005-2013 by Philippe Lagadec. OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec.
The Python Imaging Library (PIL) is The Python Imaging Library (PIL) is

View File

@ -2,11 +2,12 @@
# -*- coding: latin-1 -*- # -*- coding: latin-1 -*-
""" """
OleFileIO_PL: OleFileIO_PL:
Module to read Microsoft OLE2 files (also called Structured Storage or Module to read Microsoft OLE2 files (also called Structured Storage or
Microsoft Compound Document File Format), such as Microsoft Office Microsoft Compound Document File Format), such as Microsoft Office
documents, Image Composer and FlashPix files, Outlook messages, ... documents, Image Composer and FlashPix files, Outlook messages, ...
This version is compatible with Python 2.6+ and 3.x
version 0.26 2013-07-24 Philippe Lagadec - http://www.decalage.info version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info
Project website: http://www.decalage.info/python/olefileio Project website: http://www.decalage.info/python/olefileio
@ -16,25 +17,30 @@ See: http://www.pythonware.com/products/pil/index.htm
The Python Imaging Library (PIL) is The Python Imaging Library (PIL) is
Copyright (c) 1997-2005 by Secret Labs AB Copyright (c) 1997-2005 by Secret Labs AB
Copyright (c) 1995-2005 by Fredrik Lundh Copyright (c) 1995-2005 by Fredrik Lundh
OleFileIO_PL changes are Copyright (c) 2005-2013 by Philippe Lagadec OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
See source code and LICENSE.txt for information on usage and redistribution. See source code and LICENSE.txt for information on usage and redistribution.
WARNING: THIS IS (STILL) WORK IN PROGRESS. WARNING: THIS IS (STILL) WORK IN PROGRESS.
""" """
from __future__ import print_function # Starting with OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported
# This import enables print() as a function rather than a keyword
# (main requirement to be compatible with Python 3.x)
# The comment on the line below should be printed on Python 2.5 or older:
from __future__ import print_function # This version of OleFileIO_PL requires Python 2.6+ or 3.x.
__author__ = "Philippe Lagadec, Fredrik Lundh (Secret Labs AB)" __author__ = "Philippe Lagadec, Fredrik Lundh (Secret Labs AB)"
__date__ = "2013-07-24" __date__ = "2014-02-04"
__version__ = '0.26' __version__ = '0.30'
#--- LICENSE ------------------------------------------------------------------ #--- LICENSE ------------------------------------------------------------------
# OleFileIO_PL is an improved version of the OleFileIO module from the # OleFileIO_PL is an improved version of the OleFileIO module from the
# Python Imaging Library (PIL). # Python Imaging Library (PIL).
# OleFileIO_PL changes are Copyright (c) 2005-2013 by Philippe Lagadec # OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
# #
# The Python Imaging Library (PIL) is # The Python Imaging Library (PIL) is
# Copyright (c) 1997-2005 by Secret Labs AB # Copyright (c) 1997-2005 by Secret Labs AB
@ -133,9 +139,14 @@ __version__ = '0.26'
# of a directory entry or a storage/stream # of a directory entry or a storage/stream
# - fixed parsing of direntry timestamps # - fixed parsing of direntry timestamps
# 2013-07-24 PL: - new options in listdir to list storages and/or streams # 2013-07-24 PL: - new options in listdir to list storages and/or streams
# 2014-02-04 v0.30 PL: - upgraded code to support Python 3.x by Martin Panter
# - several fixes for Python 2.6 (xrange, MAGIC)
# - reused i32 from Pillow's _binary
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# TODO (for version 1.0): # TODO (for version 1.0):
# + isOleFile should accept file-like objects like open
# + fix how all the methods handle unicode str and/or bytes as arguments
# + add path attrib to _OleDirEntry, set it once and for all in init or # + add path attrib to _OleDirEntry, set it once and for all in init or
# append_kids (then listdir/_list can be simplified) # append_kids (then listdir/_list can be simplified)
# - TESTS with Linux, MacOSX, Python 1.5.2, various files, PIL, ... # - TESTS with Linux, MacOSX, Python 1.5.2, various files, PIL, ...
@ -220,17 +231,26 @@ __version__ = '0.26'
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
import io import io
import sys import sys
from PIL import _binary
import struct, array, os.path, datetime import struct, array, os.path, datetime
#[PL] Define explicitly the public API to avoid private objects in pydoc: #[PL] Define explicitly the public API to avoid private objects in pydoc:
__all__ = ['OleFileIO', 'isOleFile', 'MAGIC'] __all__ = ['OleFileIO', 'isOleFile', 'MAGIC']
# For Python 3.x, need to redefine long as int:
if str is not bytes: if str is not bytes:
long = int long = int
# Need to make sure we use xrange both on Python 2 and 3.x:
try:
# on Python 2 we need xrange:
iterrange = xrange
except:
# no xrange, for Python 3 it was renamed as range:
iterrange = range
#[PL] workaround to fix an issue with array item size on 64 bits systems: #[PL] workaround to fix an issue with array item size on 64 bits systems:
if array.array('L').itemsize == 4: if array.array('L').itemsize == 4:
# on 32 bits platforms, long integers in an array are 32 bits: # on 32 bits platforms, long integers in an array are 32 bits:
@ -281,8 +301,7 @@ def set_debug_mode(debug_mode):
else: else:
debug = debug_pass debug = debug_pass
#TODO: convert this to hex MAGIC = b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'
MAGIC = b'\320\317\021\340\241\261\032\341'
#[PL]: added constants for Sector IDs (from AAF specifications) #[PL]: added constants for Sector IDs (from AAF specifications)
MAXREGSECT = 0xFFFFFFFA; # maximum SECT MAXREGSECT = 0xFFFFFFFA; # maximum SECT
@ -362,9 +381,39 @@ def isOleFile (filename):
return False return False
i8 = _binary.i8 if bytes is str:
i16 = _binary.i16le # version for Python 2.x
i32 = _binary.i32le def i8(c):
return ord(c)
else:
# version for Python 3.x
def i8(c):
return c if c.__class__ is int else c[0]
#TODO: replace i16 and i32 with more readable struct.unpack equivalent?
def i16(c, o = 0):
"""
Converts a 2-bytes (16 bits) string to an integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
"""
return i8(c[o]) | (i8(c[o+1])<<8)
def i32(c, o = 0):
"""
Converts a 4-bytes (32 bits) string to an integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
"""
## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24))
## # [PL]: added int() because "<<" gives long int since Python 2.4
# copied from Pillow's _binary:
return i8(c[o]) | (i8(c[o+1])<<8) | (i8(c[o+2])<<16) | (i8(c[o+3])<<24)
def _clsid(clsid): def _clsid(clsid):
@ -373,7 +422,9 @@ def _clsid(clsid):
clsid: string of length 16. clsid: string of length 16.
""" """
assert len(clsid) == 16 assert len(clsid) == 16
if clsid == bytearray(16): # if clsid is only made of null bytes, return an empty string:
# (PL: why not simply return the string with zeroes?)
if not clsid.strip(b"\0"):
return "" return ""
return (("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) % return (("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) %
((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) + ((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) +
@ -902,18 +953,22 @@ class _OleDirectoryEntry:
def __eq__(self, other): def __eq__(self, other):
"Compare entries by name" "Compare entries by name"
return self.name == other.name return self.name == other.name
def __lt__(self, other): def __lt__(self, other):
"Compare entries by name" "Compare entries by name"
return self.name < other.name return self.name < other.name
#TODO: replace by the same function as MS implementation ?
# (order by name length first, then case-insensitive order)
def __ne__(self, other): def __ne__(self, other):
return not self.__eq__(other) return not self.__eq__(other)
def __le__(self, other): def __le__(self, other):
return self.__eq__(other) or self.__lt__(other) return self.__eq__(other) or self.__lt__(other)
# Reflected __lt__() and __le__() will be used for __gt__() and __ge__() # Reflected __lt__() and __le__() will be used for __gt__() and __ge__()
#TODO: replace by the same function as MS implementation ?
# (order by name length first, then case-insensitive order)
def dump(self, tab = 0): def dump(self, tab = 0):
"Dump this entry, and all its subentries (for debug purposes only)" "Dump this entry, and all its subentries (for debug purposes only)"
@ -1046,7 +1101,7 @@ class OleFileIO:
#TODO: if larger than 1024 bytes, this could be the actual data => BytesIO #TODO: if larger than 1024 bytes, this could be the actual data => BytesIO
self.fp = open(filename, "rb") self.fp = open(filename, "rb")
# old code fails if filename is not a plain string: # old code fails if filename is not a plain string:
#if isPath(filename): #if isinstance(filename, (bytes, basestring)):
# self.fp = open(filename, "rb") # self.fp = open(filename, "rb")
#else: #else:
# self.fp = filename # self.fp = filename
@ -1133,7 +1188,7 @@ class OleFileIO:
) = struct.unpack(fmt_header, header1) ) = struct.unpack(fmt_header, header1)
debug( struct.unpack(fmt_header, header1)) debug( struct.unpack(fmt_header, header1))
if self.Sig != b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1': if self.Sig != MAGIC:
# OLE signature should always be present # OLE signature should always be present
self._raise_defect(DEFECT_FATAL, "incorrect OLE signature") self._raise_defect(DEFECT_FATAL, "incorrect OLE signature")
if self.clsid != bytearray(16): if self.clsid != bytearray(16):
@ -1385,7 +1440,7 @@ class OleFileIO:
if self.csectDif != nb_difat: if self.csectDif != nb_difat:
raise IOError('incorrect DIFAT') raise IOError('incorrect DIFAT')
isect_difat = self.sectDifStart isect_difat = self.sectDifStart
for i in range(nb_difat): for i in iterrange(nb_difat):
debug( "DIFAT block %d, sector %X" % (i, isect_difat) ) debug( "DIFAT block %d, sector %X" % (i, isect_difat) )
#TODO: check if corresponding FAT SID = DIFSECT #TODO: check if corresponding FAT SID = DIFSECT
sector_difat = self.getsect(isect_difat) sector_difat = self.getsect(isect_difat)
@ -1494,7 +1549,7 @@ class OleFileIO:
#self.direntries = [] #self.direntries = []
# We start with a list of "None" object # We start with a list of "None" object
self.direntries = [None] * max_entries self.direntries = [None] * max_entries
## for sid in range(max_entries): ## for sid in iterrange(max_entries):
## entry = fp.read(128) ## entry = fp.read(128)
## if not entry: ## if not entry:
## break ## break

View File

@ -172,21 +172,21 @@ def _save(im, fp, filename, check=0):
cols = im.size[0] cols = im.size[0]
rows = im.size[1] rows = im.size[1]
rowbytes = ((cols + (16//bpp - 1)) / (16 // bpp)) * 2; rowbytes = ((cols + (16//bpp - 1)) / (16 // bpp)) * 2
transparent_index = 0 transparent_index = 0
compression_type = _COMPRESSION_TYPES["none"] compression_type = _COMPRESSION_TYPES["none"]
flags = 0; flags = 0
if im.mode == "P" and "custom-colormap" in im.info: if im.mode == "P" and "custom-colormap" in im.info:
flags = flags & _FLAGS["custom-colormap"] flags = flags & _FLAGS["custom-colormap"]
colormapsize = 4 * 256 + 2; colormapsize = 4 * 256 + 2
colormapmode = im.palette.mode colormapmode = im.palette.mode
colormap = im.getdata().getpalette() colormap = im.getdata().getpalette()
else: else:
colormapsize = 0 colormapsize = 0
if "offset" in im.info: if "offset" in im.info:
offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4; offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4
else: else:
offset = 0 offset = 0

View File

@ -135,7 +135,7 @@ def _save(im, fp, filename, check=0):
# bytes per plane # bytes per plane
stride = (im.size[0] * bits + 7) // 8 stride = (im.size[0] * bits + 7) // 8
# stride should be even # stride should be even
stride = stride + (stride % 2) stride += stride % 2
# Stride needs to be kept in sync with the PcxEncode.c version. # Stride needs to be kept in sync with the PcxEncode.c version.
# Ideally it should be passed in in the state, but the bytes value # Ideally it should be passed in in the state, but the bytes value
# gets overwritten. # gets overwritten.

View File

@ -46,9 +46,11 @@ def _obj(fp, obj, **dict):
fp.write("/%s %s\n" % (k, v)) fp.write("/%s %s\n" % (k, v))
fp.write(">>\n") fp.write(">>\n")
def _endobj(fp): def _endobj(fp):
fp.write("endobj\n") fp.write("endobj\n")
## ##
# (Internal) Image save plugin for the PDF format. # (Internal) Image save plugin for the PDF format.
@ -59,13 +61,15 @@ def _save(im, fp, filename):
# make sure image data is available # make sure image data is available
im.load() im.load()
xref = [0]*(5+1) # placeholders xref = [0]*(5+1) # placeholders
class TextWriter: class TextWriter:
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.fp, name) return getattr(self.fp, name)
def write(self, value): def write(self, value):
self.fp.write(value.encode('latin-1')) self.fp.write(value.encode('latin-1'))
@ -89,13 +93,13 @@ def _save(im, fp, filename):
if im.mode == "1": if im.mode == "1":
filter = "/ASCIIHexDecode" filter = "/ASCIIHexDecode"
colorspace = "/DeviceGray" colorspace = "/DeviceGray"
procset = "/ImageB" # grayscale procset = "/ImageB" # grayscale
bits = 1 bits = 1
elif im.mode == "L": elif im.mode == "L":
filter = "/DCTDecode" filter = "/DCTDecode"
# params = "<< /Predictor 15 /Columns %d >>" % (width-2) # params = "<< /Predictor 15 /Columns %d >>" % (width-2)
colorspace = "/DeviceGray" colorspace = "/DeviceGray"
procset = "/ImageB" # grayscale procset = "/ImageB" # grayscale
elif im.mode == "P": elif im.mode == "P":
filter = "/ASCIIHexDecode" filter = "/ASCIIHexDecode"
colorspace = "[ /Indexed /DeviceRGB 255 <" colorspace = "[ /Indexed /DeviceRGB 255 <"
@ -104,17 +108,17 @@ def _save(im, fp, filename):
r = i8(palette[i*3]) r = i8(palette[i*3])
g = i8(palette[i*3+1]) g = i8(palette[i*3+1])
b = i8(palette[i*3+2]) b = i8(palette[i*3+2])
colorspace = colorspace + "%02x%02x%02x " % (r, g, b) colorspace += "%02x%02x%02x " % (r, g, b)
colorspace = colorspace + b"> ]" colorspace += "> ]"
procset = "/ImageI" # indexed color procset = "/ImageI" # indexed color
elif im.mode == "RGB": elif im.mode == "RGB":
filter = "/DCTDecode" filter = "/DCTDecode"
colorspace = "/DeviceRGB" colorspace = "/DeviceRGB"
procset = "/ImageC" # color images procset = "/ImageC" # color images
elif im.mode == "CMYK": elif im.mode == "CMYK":
filter = "/DCTDecode" filter = "/DCTDecode"
colorspace = "/DeviceCMYK" colorspace = "/DeviceCMYK"
procset = "/ImageC" # color images procset = "/ImageC" # color images
else: else:
raise ValueError("cannot save mode %s" % im.mode) raise ValueError("cannot save mode %s" % im.mode)
@ -122,17 +126,21 @@ def _save(im, fp, filename):
# catalogue # catalogue
xref[1] = fp.tell() xref[1] = fp.tell()
_obj(fp, 1, Type = "/Catalog", _obj(
Pages = "2 0 R") fp, 1,
Type="/Catalog",
Pages="2 0 R")
_endobj(fp) _endobj(fp)
# #
# pages # pages
xref[2] = fp.tell() xref[2] = fp.tell()
_obj(fp, 2, Type = "/Pages", _obj(
Count = 1, fp, 2,
Kids = "[4 0 R]") Type="/Pages",
Count=1,
Kids="[4 0 R]")
_endobj(fp) _endobj(fp)
# #
@ -144,29 +152,31 @@ def _save(im, fp, filename):
if bits == 1: if bits == 1:
# FIXME: the hex encoder doesn't support packed 1-bit # FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way... # images; do things the hard way...
data = im.tostring("raw", "1") data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None) im = Image.new("L", (len(data), 1), None)
im.putdata(data) im.putdata(data)
ImageFile._save(im, op, [("hex", (0,0)+im.size, 0, im.mode)]) ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode": elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename) Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode": elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0,0)+im.size, 0, im.mode)]) ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode": elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0,0)+im.size, 0, im.mode)]) ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else: else:
raise ValueError("unsupported PDF filter (%s)" % filter) raise ValueError("unsupported PDF filter (%s)" % filter)
xref[3] = fp.tell() xref[3] = fp.tell()
_obj(fp, 3, Type = "/XObject", _obj(
Subtype = "/Image", fp, 3,
Width = width, # * 72.0 / resolution, Type="/XObject",
Height = height, # * 72.0 / resolution, Subtype="/Image",
Length = len(op.getvalue()), Width=width, # * 72.0 / resolution,
Filter = filter, Height=height, # * 72.0 / resolution,
BitsPerComponent = bits, Length=len(op.getvalue()),
DecodeParams = params, Filter=filter,
ColorSpace = colorspace) BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
fp.write("stream\n") fp.write("stream\n")
fp.fp.write(op.getvalue()) fp.fp.write(op.getvalue())
@ -179,11 +189,14 @@ def _save(im, fp, filename):
xref[4] = fp.tell() xref[4] = fp.tell()
_obj(fp, 4) _obj(fp, 4)
fp.write("<<\n/Type /Page\n/Parent 2 0 R\n"\ fp.write(
"/Resources <<\n/ProcSet [ /PDF %s ]\n"\ "<<\n/Type /Page\n/Parent 2 0 R\n"
"/XObject << /image 3 0 R >>\n>>\n"\ "/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" %\ "/XObject << /image 3 0 R >>\n>>\n"
(procset, int(width * 72.0 /resolution) , int(height * 72.0 / resolution))) "/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % (
procset,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
_endobj(fp) _endobj(fp)
# #
@ -191,10 +204,13 @@ def _save(im, fp, filename):
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[5] = fp.tell() xref[5] = fp.tell()
_obj(fp, 5, Length = len(op.fp.getvalue())) _obj(fp, 5, Length=len(op.fp.getvalue()))
fp.write("stream\n") fp.write("stream\n")
fp.fp.write(op.fp.getvalue()) fp.fp.write(op.fp.getvalue())

View File

@ -89,33 +89,33 @@ class ChunkStream:
"Fetch a new chunk. Returns header information." "Fetch a new chunk. Returns header information."
if self.queue: if self.queue:
cid, pos, len = self.queue[-1] cid, pos, length = self.queue[-1]
del self.queue[-1] del self.queue[-1]
self.fp.seek(pos) self.fp.seek(pos)
else: else:
s = self.fp.read(8) s = self.fp.read(8)
cid = s[4:] cid = s[4:]
pos = self.fp.tell() pos = self.fp.tell()
len = i32(s) length = i32(s)
if not is_cid(cid): if not is_cid(cid):
raise SyntaxError("broken PNG file (chunk %s)" % repr(cid)) raise SyntaxError("broken PNG file (chunk %s)" % repr(cid))
return cid, pos, len return cid, pos, length
def close(self): def close(self):
self.queue = self.crc = self.fp = None self.queue = self.crc = self.fp = None
def push(self, cid, pos, len): def push(self, cid, pos, length):
self.queue.append((cid, pos, len)) self.queue.append((cid, pos, length))
def call(self, cid, pos, len): def call(self, cid, pos, length):
"Call the appropriate chunk handler" "Call the appropriate chunk handler"
if Image.DEBUG: if Image.DEBUG:
print("STREAM", cid, pos, len) print("STREAM", cid, pos, length)
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, len) return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length)
def crc(self, cid, data): def crc(self, cid, data):
"Read and verify checksum" "Read and verify checksum"
@ -139,10 +139,10 @@ class ChunkStream:
cids = [] cids = []
while True: while True:
cid, pos, len = self.read() cid, pos, length = self.read()
if cid == endchunk: if cid == endchunk:
break break
self.crc(cid, ImageFile._safe_read(self.fp, len)) self.crc(cid, ImageFile._safe_read(self.fp, length))
cids.append(cid) cids.append(cid)
return cids return cids
@ -190,10 +190,10 @@ class PngStream(ChunkStream):
self.im_tile = None self.im_tile = None
self.im_palette = None self.im_palette = None
def chunk_iCCP(self, pos, len): def chunk_iCCP(self, pos, length):
# ICC profile # ICC profile
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
# according to PNG spec, the iCCP chunk contains: # according to PNG spec, the iCCP chunk contains:
# Profile name 1-79 bytes (character string) # Profile name 1-79 bytes (character string)
# Null separator 1 byte (null character) # Null separator 1 byte (null character)
@ -213,10 +213,10 @@ class PngStream(ChunkStream):
self.im_info["icc_profile"] = icc_profile self.im_info["icc_profile"] = icc_profile
return s return s
def chunk_IHDR(self, pos, len): def chunk_IHDR(self, pos, length):
# image header # image header
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
self.im_size = i32(s), i32(s[4:]) self.im_size = i32(s), i32(s[4:])
try: try:
self.im_mode, self.im_rawmode = _MODES[(i8(s[8]), i8(s[9]))] self.im_mode, self.im_rawmode = _MODES[(i8(s[8]), i8(s[9]))]
@ -228,30 +228,30 @@ class PngStream(ChunkStream):
raise SyntaxError("unknown filter category") raise SyntaxError("unknown filter category")
return s return s
def chunk_IDAT(self, pos, len): def chunk_IDAT(self, pos, length):
# image data # image data
self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)] self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
self.im_idat = len self.im_idat = length
raise EOFError raise EOFError
def chunk_IEND(self, pos, len): def chunk_IEND(self, pos, length):
# end of PNG image # end of PNG image
raise EOFError raise EOFError
def chunk_PLTE(self, pos, len): def chunk_PLTE(self, pos, length):
# palette # palette
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P": if self.im_mode == "P":
self.im_palette = "RGB", s self.im_palette = "RGB", s
return s return s
def chunk_tRNS(self, pos, len): def chunk_tRNS(self, pos, length):
# transparency # transparency
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P": if self.im_mode == "P":
if _simple_palette.match(s): if _simple_palette.match(s):
i = s.find(b"\0") i = s.find(b"\0")
@ -265,17 +265,17 @@ class PngStream(ChunkStream):
self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:]) self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
return s return s
def chunk_gAMA(self, pos, len): def chunk_gAMA(self, pos, length):
# gamma setting # gamma setting
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
self.im_info["gamma"] = i32(s) / 100000.0 self.im_info["gamma"] = i32(s) / 100000.0
return s return s
def chunk_pHYs(self, pos, len): def chunk_pHYs(self, pos, length):
# pixels per unit # pixels per unit
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
px, py = i32(s), i32(s[4:]) px, py = i32(s), i32(s[4:])
unit = i8(s[8]) unit = i8(s[8])
if unit == 1: # meter if unit == 1: # meter
@ -285,10 +285,10 @@ class PngStream(ChunkStream):
self.im_info["aspect"] = px, py self.im_info["aspect"] = px, py
return s return s
def chunk_tEXt(self, pos, len): def chunk_tEXt(self, pos, length):
# text # text
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
@ -301,10 +301,10 @@ class PngStream(ChunkStream):
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
return s return s
def chunk_zTXt(self, pos, len): def chunk_zTXt(self, pos, length):
# compressed text # compressed text
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
@ -358,16 +358,16 @@ class PngImageFile(ImageFile.ImageFile):
# #
# get next chunk # get next chunk
cid, pos, len = self.png.read() cid, pos, length = self.png.read()
try: try:
s = self.png.call(cid, pos, len) s = self.png.call(cid, pos, length)
except EOFError: except EOFError:
break break
except AttributeError: except AttributeError:
if Image.DEBUG: if Image.DEBUG:
print(cid, pos, len, "(unknown)") print(cid, pos, length, "(unknown)")
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
self.png.crc(cid, s) self.png.crc(cid, s)
@ -388,7 +388,7 @@ class PngImageFile(ImageFile.ImageFile):
rawmode, data = self.png.im_palette rawmode, data = self.png.im_palette
self.palette = ImagePalette.raw(rawmode, data) self.palette = ImagePalette.raw(rawmode, data)
self.__idat = len # used by load_read() self.__idat = length # used by load_read()
def verify(self): def verify(self):
@ -413,7 +413,7 @@ class PngImageFile(ImageFile.ImageFile):
ImageFile.ImageFile.load_prepare(self) ImageFile.ImageFile.load_prepare(self)
def load_read(self, bytes): def load_read(self, read_bytes):
"internal: read more image data" "internal: read more image data"
while self.__idat == 0: while self.__idat == 0:
@ -421,23 +421,23 @@ class PngImageFile(ImageFile.ImageFile):
self.fp.read(4) # CRC self.fp.read(4) # CRC
cid, pos, len = self.png.read() cid, pos, length = self.png.read()
if cid not in [b"IDAT", b"DDAT"]: if cid not in [b"IDAT", b"DDAT"]:
self.png.push(cid, pos, len) self.png.push(cid, pos, length)
return b"" return b""
self.__idat = len # empty chunks are allowed self.__idat = length # empty chunks are allowed
# read more data from this chunk # read more data from this chunk
if bytes <= 0: if read_bytes <= 0:
bytes = self.__idat read_bytes = self.__idat
else: else:
bytes = min(bytes, self.__idat) read_bytes = min(read_bytes, self.__idat)
self.__idat = self.__idat - bytes self.__idat = self.__idat - read_bytes
return self.fp.read(bytes) return self.fp.read(read_bytes)
def load_end(self): def load_end(self):
@ -560,7 +560,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
chunk(fp, b"PLTE", palette_bytes) chunk(fp, b"PLTE", palette_bytes)
transparency = im.encoderinfo.get('transparency',im.info.get('transparency', None)) transparency = im.encoderinfo.get('transparency',im.info.get('transparency', None))
if transparency or transparency == 0: if transparency or transparency == 0:
if im.mode == "P": if im.mode == "P":
# limit to actual palette size # limit to actual palette size
@ -580,7 +580,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
else: else:
if "transparency" in im.encoderinfo: if "transparency" in im.encoderinfo:
# don't bother with transparency if it's an RGBA # don't bother with transparency if it's an RGBA
# and it's in the info dict. It's probably just stale. # and it's in the info dict. It's probably just stale.
raise IOError("cannot use transparency for this mode") raise IOError("cannot use transparency for this mode")
else: else:
if im.mode == "P" and im.im.getpalettemode() == "RGBA": if im.mode == "P" and im.im.getpalettemode() == "RGBA":

View File

@ -63,7 +63,11 @@ class PpmImageFile(ImageFile.ImageFile):
c = self.fp.read(1) c = self.fp.read(1)
if not c or c in b_whitespace: if not c or c in b_whitespace:
break break
if c > b'\x79':
raise ValueError("Expected ASCII value, found binary")
s = s + c s = s + c
if (len(s) > 9):
raise ValueError("Expected int, got > 9 digits")
return s return s
def _open(self): def _open(self):
@ -96,7 +100,18 @@ class PpmImageFile(ImageFile.ImageFile):
ysize = s ysize = s
if mode == "1": if mode == "1":
break break
elif ix == 2:
# maxgrey
if s > 255:
if not mode == 'L':
raise ValueError("Too many colors for band: %s" %s)
if s < 2**16:
self.mode = 'I'
rawmode = 'I;16B'
else:
self.mode = 'I';
rawmode = 'I;32B'
self.size = xsize, ysize self.size = xsize, ysize
self.tile = [("raw", self.tile = [("raw",
(0, 0, xsize, ysize), (0, 0, xsize, ysize),
@ -116,6 +131,11 @@ def _save(im, fp, filename):
rawmode, head = "1;I", b"P4" rawmode, head = "1;I", b"P4"
elif im.mode == "L": elif im.mode == "L":
rawmode, head = "L", b"P5" rawmode, head = "L", b"P5"
elif im.mode == "I":
if im.getextrema()[1] < 2**16:
rawmode, head = "I;16B", b"P5"
else:
rawmode, head = "I;32B", b"P5"
elif im.mode == "RGB": elif im.mode == "RGB":
rawmode, head = "RGB", b"P6" rawmode, head = "RGB", b"P6"
elif im.mode == "RGBA": elif im.mode == "RGBA":
@ -123,8 +143,15 @@ def _save(im, fp, filename):
else: else:
raise IOError("cannot write mode %s as PPM" % im.mode) raise IOError("cannot write mode %s as PPM" % im.mode)
fp.write(head + ("\n%d %d\n" % im.size).encode('ascii')) fp.write(head + ("\n%d %d\n" % im.size).encode('ascii'))
if head != b"P4": if head == b"P6":
fp.write(b"255\n") fp.write(b"255\n")
if head == b"P5":
if rawmode == "L":
fp.write(b"255\n")
elif rawmode == "I;16B":
fp.write(b"65535\n")
elif rawmode == "I;32B":
fp.write(b"2147483648\n")
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))]) ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))])
# ALTERNATIVE: save via builtin debug function # ALTERNATIVE: save via builtin debug function

View File

@ -235,7 +235,7 @@ def _layerinfo(file):
if t: if t:
tile.extend(t) tile.extend(t)
layers[i] = name, mode, bbox, tile layers[i] = name, mode, bbox, tile
i = i + 1 i += 1
return layers return layers
@ -258,7 +258,7 @@ def _maketile(file, mode, bbox, channels):
for channel in range(channels): for channel in range(channels):
layer = mode[channel] layer = mode[channel]
if mode == "CMYK": if mode == "CMYK":
layer = layer + ";I" layer += ";I"
tile.append(("raw", bbox, offset, layer)) tile.append(("raw", bbox, offset, layer))
offset = offset + xsize*ysize offset = offset + xsize*ysize
@ -272,13 +272,13 @@ def _maketile(file, mode, bbox, channels):
for channel in range(channels): for channel in range(channels):
layer = mode[channel] layer = mode[channel]
if mode == "CMYK": if mode == "CMYK":
layer = layer + ";I" layer += ";I"
tile.append( tile.append(
("packbits", bbox, offset, layer) ("packbits", bbox, offset, layer)
) )
for y in range(ysize): for y in range(ysize):
offset = offset + i16(bytecount[i:i+2]) offset = offset + i16(bytecount[i:i+2])
i = i + 2 i += 2
file.seek(offset) file.seek(offset)

View File

@ -36,17 +36,23 @@
from __future__ import print_function from __future__ import print_function
from PIL import Image, ImageFile from PIL import Image, ImageFile
import os, struct, sys import os
import struct
import sys
def isInt(f): def isInt(f):
try: try:
i = int(f) i = int(f)
if f-i == 0: return 1 if f-i == 0:
else: return 0 return 1
else:
return 0
except: except:
return 0 return 0
iforms = [1,3,-11,-12,-21,-22] iforms = [1, 3, -11, -12, -21, -22]
# There is no magic number to identify Spider files, so just check a # There is no magic number to identify Spider files, so just check a
# series of header locations to see if they have reasonable values. # series of header locations to see if they have reasonable values.
@ -56,30 +62,32 @@ iforms = [1,3,-11,-12,-21,-22]
def isSpiderHeader(t): def isSpiderHeader(t):
h = (99,) + t # add 1 value so can use spider header index start=1 h = (99,) + t # add 1 value so can use spider header index start=1
# header values 1,2,5,12,13,22,23 should be integers # header values 1,2,5,12,13,22,23 should be integers
for i in [1,2,5,12,13,22,23]: for i in [1, 2, 5, 12, 13, 22, 23]:
if not isInt(h[i]): return 0 if not isInt(h[i]):
return 0
# check iform # check iform
iform = int(h[5]) iform = int(h[5])
if not iform in iforms: return 0 if iform not in iforms:
return 0
# check other header values # check other header values
labrec = int(h[13]) # no. records in file header labrec = int(h[13]) # no. records in file header
labbyt = int(h[22]) # total no. of bytes in header labbyt = int(h[22]) # total no. of bytes in header
lenbyt = int(h[23]) # record length in bytes lenbyt = int(h[23]) # record length in bytes
#print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt) # print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt)
if labbyt != (labrec * lenbyt): return 0 if labbyt != (labrec * lenbyt):
return 0
# looks like a valid header # looks like a valid header
return labbyt return labbyt
def isSpiderImage(filename): def isSpiderImage(filename):
fp = open(filename,'rb') fp = open(filename, 'rb')
f = fp.read(92) # read 23 * 4 bytes f = fp.read(92) # read 23 * 4 bytes
fp.close() fp.close()
bigendian = 1 t = struct.unpack('>23f', f) # try big-endian first
t = struct.unpack('>23f',f) # try big-endian first
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
bigendian = 0 t = struct.unpack('<23f', f) # little-endian
t = struct.unpack('<23f',f) # little-endian
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
return hdrlen return hdrlen
@ -96,11 +104,11 @@ class SpiderImageFile(ImageFile.ImageFile):
try: try:
self.bigendian = 1 self.bigendian = 1
t = struct.unpack('>27f',f) # try big-endian first t = struct.unpack('>27f', f) # try big-endian first
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
self.bigendian = 0 self.bigendian = 0
t = struct.unpack('<27f',f) # little-endian t = struct.unpack('<27f', f) # little-endian
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
raise SyntaxError("not a valid Spider file") raise SyntaxError("not a valid Spider file")
@ -112,7 +120,7 @@ class SpiderImageFile(ImageFile.ImageFile):
if iform != 1: if iform != 1:
raise SyntaxError("not a Spider 2D image") raise SyntaxError("not a Spider 2D image")
self.size = int(h[12]), int(h[2]) # size in pixels (width, height) self.size = int(h[12]), int(h[2]) # size in pixels (width, height)
self.istack = int(h[24]) self.istack = int(h[24])
self.imgnumber = int(h[27]) self.imgnumber = int(h[27])
@ -141,9 +149,10 @@ class SpiderImageFile(ImageFile.ImageFile):
self.rawmode = "F;32F" self.rawmode = "F;32F"
self.mode = "F" self.mode = "F"
self.tile = [("raw", (0, 0) + self.size, offset, self.tile = [
(self.rawmode, 0, 1))] ("raw", (0, 0) + self.size, offset,
self.__fp = self.fp # FIXME: hack (self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack
# 1st image index is zero (although SPIDER imgnumber starts at 1) # 1st image index is zero (although SPIDER imgnumber starts at 1)
def tell(self): def tell(self):
@ -176,6 +185,7 @@ class SpiderImageFile(ImageFile.ImageFile):
from PIL import ImageTk from PIL import ImageTk
return ImageTk.PhotoImage(self.convert2byte(), palette=256) return ImageTk.PhotoImage(self.convert2byte(), palette=256)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image series # Image series
@ -200,17 +210,19 @@ def loadImageSeries(filelist=None):
imglist.append(im) imglist.append(im)
return imglist return imglist
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# For saving images in Spider format # For saving images in Spider format
def makeSpiderHeader(im): def makeSpiderHeader(im):
nsam,nrow = im.size nsam, nrow = im.size
lenbyt = nsam * 4 # There are labrec records in the header lenbyt = nsam * 4 # There are labrec records in the header
labrec = 1024 / lenbyt labrec = 1024 / lenbyt
if 1024%lenbyt != 0: labrec += 1 if 1024 % lenbyt != 0:
labrec += 1
labbyt = labrec * lenbyt labbyt = labrec * lenbyt
hdr = [] hdr = []
nvalues = labbyt / 4 nvalues = int(labbyt / 4)
for i in range(nvalues): for i in range(nvalues):
hdr.append(0.0) hdr.append(0.0)
@ -218,13 +230,13 @@ def makeSpiderHeader(im):
return [] return []
# NB these are Fortran indices # NB these are Fortran indices
hdr[1] = 1.0 # nslice (=1 for an image) hdr[1] = 1.0 # nslice (=1 for an image)
hdr[2] = float(nrow) # number of rows per slice hdr[2] = float(nrow) # number of rows per slice
hdr[5] = 1.0 # iform for 2D image hdr[5] = 1.0 # iform for 2D image
hdr[12] = float(nsam) # number of pixels per line hdr[12] = float(nsam) # number of pixels per line
hdr[13] = float(labrec) # number of records in file header hdr[13] = float(labrec) # number of records in file header
hdr[22] = float(labbyt) # total number of bytes in header hdr[22] = float(labbyt) # total number of bytes in header
hdr[23] = float(lenbyt) # record length in bytes hdr[23] = float(lenbyt) # record length in bytes
# adjust for Fortran indexing # adjust for Fortran indexing
hdr = hdr[1:] hdr = hdr[1:]
@ -232,9 +244,10 @@ def makeSpiderHeader(im):
# pack binary data into a string # pack binary data into a string
hdrstr = [] hdrstr = []
for v in hdr: for v in hdr:
hdrstr.append(struct.pack('f',v)) hdrstr.append(struct.pack('f', v))
return hdrstr return hdrstr
def _save(im, fp, filename): def _save(im, fp, filename):
if im.mode[0] != "F": if im.mode[0] != "F":
im = im.convert('F') im = im.convert('F')
@ -250,11 +263,12 @@ def _save(im, fp, filename):
raise IOError("Unable to open %s for writing" % filename) raise IOError("Unable to open %s for writing" % filename)
fp.writelines(hdr) fp.writelines(hdr)
rawmode = "F;32NF" #32-bit native floating point rawmode = "F;32NF" # 32-bit native floating point
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode,0,1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
fp.close() fp.close()
def _save_spider(im, fp, filename): def _save_spider(im, fp, filename):
# get the filename extension and register it with Image # get the filename extension and register it with Image
fn, ext = os.path.splitext(filename) fn, ext = os.path.splitext(filename)
@ -292,5 +306,7 @@ if __name__ == "__main__":
if outfile != "": if outfile != "":
# perform some image operation # perform some image operation
im = im.transpose(Image.FLIP_LEFT_RIGHT) im = im.transpose(Image.FLIP_LEFT_RIGHT)
print("saving a flipped version of %s as %s " % (os.path.basename(filename), outfile)) print(
"saving a flipped version of %s as %s " %
(os.path.basename(filename), outfile))
im.save(outfile, "SPIDER") im.save(outfile, "SPIDER")

View File

@ -54,7 +54,7 @@ import collections
import itertools import itertools
import os import os
# Set these to true to force use of libtiff for reading or writing. # Set these to true to force use of libtiff for reading or writing.
READ_LIBTIFF = False READ_LIBTIFF = False
WRITE_LIBTIFF= False WRITE_LIBTIFF= False
@ -146,6 +146,7 @@ OPEN_INFO = {
(II, 0, 1, 2, (1,), ()): ("1", "1;IR"), (II, 0, 1, 2, (1,), ()): ("1", "1;IR"),
(II, 0, 1, 1, (8,), ()): ("L", "L;I"), (II, 0, 1, 1, (8,), ()): ("L", "L;I"),
(II, 0, 1, 2, (8,), ()): ("L", "L;IR"), (II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
(II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
(II, 1, 1, 1, (1,), ()): ("1", "1"), (II, 1, 1, 1, (1,), ()): ("1", "1"),
(II, 1, 1, 2, (1,), ()): ("1", "1;R"), (II, 1, 1, 2, (1,), ()): ("1", "1;R"),
(II, 1, 1, 1, (8,), ()): ("L", "L"), (II, 1, 1, 1, (8,), ()): ("L", "L"),
@ -237,7 +238,7 @@ class ImageFileDirectory(collections.MutableMapping):
Value: integer corresponding to the data type from Value: integer corresponding to the data type from
`TiffTags.TYPES` `TiffTags.TYPES`
'internal' 'internal'
* self.tags = {} Key: numerical tiff tag number * self.tags = {} Key: numerical tiff tag number
Value: Decoded data, Generally a tuple. Value: Decoded data, Generally a tuple.
* If set from __setval__ -- always a tuple * If set from __setval__ -- always a tuple
@ -488,10 +489,10 @@ class ImageFileDirectory(collections.MutableMapping):
if tag in self.tagtype: if tag in self.tagtype:
typ = self.tagtype[tag] typ = self.tagtype[tag]
if Image.DEBUG: if Image.DEBUG:
print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
if typ == 1: if typ == 1:
# byte data # byte data
if isinstance(value, tuple): if isinstance(value, tuple):
@ -511,7 +512,7 @@ class ImageFileDirectory(collections.MutableMapping):
# and doesn't match the tiff spec: 8-bit byte that # and doesn't match the tiff spec: 8-bit byte that
# contains a 7-bit ASCII code; the last byte must be # contains a 7-bit ASCII code; the last byte must be
# NUL (binary zero). Also, I don't think this was well # NUL (binary zero). Also, I don't think this was well
# excersized before. # excersized before.
data = value = b"" + value.encode('ascii', 'replace') + b"\0" data = value = b"" + value.encode('ascii', 'replace') + b"\0"
else: else:
# integer data # integer data
@ -557,9 +558,9 @@ class ImageFileDirectory(collections.MutableMapping):
count = count // 2 # adjust for rational data field count = count // 2 # adjust for rational data field
append((tag, typ, count, o32(offset), data)) append((tag, typ, count, o32(offset), data))
offset = offset + len(data) offset += len(data)
if offset & 1: if offset & 1:
offset = offset + 1 # word padding offset += 1 # word padding
# update strip offset data to point beyond auxiliary data # update strip offset data to point beyond auxiliary data
if stripoffsets is not None: if stripoffsets is not None:
@ -643,7 +644,7 @@ class TiffImageFile(ImageFile.ImageFile):
self.fp.seek(self.__next) self.fp.seek(self.__next)
self.tag.load(self.fp) self.tag.load(self.fp)
self.__next = self.tag.next self.__next = self.tag.next
self.__frame = self.__frame + 1 self.__frame += 1
self._setup() self._setup()
def _tell(self): def _tell(self):
@ -858,7 +859,7 @@ class TiffImageFile(ImageFile.ImageFile):
# libtiff handles the fillmode for us, so 1;IR should # libtiff handles the fillmode for us, so 1;IR should
# actually be 1;I. Including the R double reverses the # actually be 1;I. Including the R double reverses the
# bits, so stripes of the image are reversed. See # bits, so stripes of the image are reversed. See
# https://github.com/python-imaging/Pillow/issues/279 # https://github.com/python-pillow/Pillow/issues/279
if fillorder == 2: if fillorder == 2:
key = ( key = (
self.tag.prefix, photo, format, 1, self.tag.prefix, photo, format, 1,
@ -899,7 +900,7 @@ class TiffImageFile(ImageFile.ImageFile):
y = y + h y = y + h
if y >= self.size[1]: if y >= self.size[1]:
x = y = 0 x = y = 0
l = l + 1 l += 1
a = None a = None
elif TILEOFFSETS in self.tag: elif TILEOFFSETS in self.tag:
# tiled image # tiled image
@ -920,7 +921,7 @@ class TiffImageFile(ImageFile.ImageFile):
x, y = 0, y + h x, y = 0, y + h
if y >= self.size[1]: if y >= self.size[1]:
x = y = 0 x = y = 0
l = l + 1 l += 1
a = None a = None
else: else:
if Image.DEBUG: if Image.DEBUG:
@ -983,15 +984,11 @@ def _save(im, fp, filename):
compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) compression = im.encoderinfo.get('compression',im.info.get('compression','raw'))
libtiff = WRITE_LIBTIFF or compression in ["tiff_ccitt", "group3", "group4", libtiff = WRITE_LIBTIFF or compression != 'raw'
"tiff_jpeg", "tiff_adobe_deflate",
"tiff_thunderscan", "tiff_deflate",
"tiff_sgilog", "tiff_sgilog24",
"tiff_raw_16"]
# required for color libtiff images # required for color libtiff images
ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1)
# -- multi-page -- skip TIFF header on subsequent pages # -- multi-page -- skip TIFF header on subsequent pages
if not libtiff and fp.tell() == 0: if not libtiff and fp.tell() == 0:
# tiff header (write via IFD to get everything right) # tiff header (write via IFD to get everything right)
@ -1028,7 +1025,7 @@ def _save(im, fp, filename):
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
if "icc_profile" in im.info: if "icc_profile" in im.info:
ifd[ICCPROFILE] = im.info["icc_profile"] ifd[ICCPROFILE] = im.info["icc_profile"]
if "description" in im.encoderinfo: if "description" in im.encoderinfo:
ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"]
if "resolution" in im.encoderinfo: if "resolution" in im.encoderinfo:
@ -1092,9 +1089,9 @@ def _save(im, fp, filename):
fp.seek(0) fp.seek(0)
_fp = os.dup(fp.fileno()) _fp = os.dup(fp.fileno())
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes.
atts={} atts={}
# bits per sample is a single short in the tiff directory, not a list. # bits per sample is a single short in the tiff directory, not a list.
atts[BITSPERSAMPLE] = bits[0] atts[BITSPERSAMPLE] = bits[0]
# Merge the ones that we have with (optional) more bits from # Merge the ones that we have with (optional) more bits from
# the original file, e.g x,y resolution so that we can # the original file, e.g x,y resolution so that we can

View File

@ -161,7 +161,7 @@ TAGS = {
50716: "BlackLevelDeltaV", 50716: "BlackLevelDeltaV",
50717: "WhiteLevel", 50717: "WhiteLevel",
50718: "DefaultScale", 50718: "DefaultScale",
50741: "BestQualityScale", 50741: "BestQualityScale", # FIXME! Dictionary contains duplicate keys 50741
50719: "DefaultCropOrigin", 50719: "DefaultCropOrigin",
50720: "DefaultCropSize", 50720: "DefaultCropSize",
50778: "CalibrationIlluminant1", 50778: "CalibrationIlluminant1",
@ -185,7 +185,7 @@ TAGS = {
50737: "ChromaBlurRadius", 50737: "ChromaBlurRadius",
50738: "AntiAliasStrength", 50738: "AntiAliasStrength",
50740: "DNGPrivateData", 50740: "DNGPrivateData",
50741: "MakerNoteSafety", 50741: "MakerNoteSafety", # FIXME! Dictionary contains duplicate keys 50741
#ImageJ #ImageJ
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe 50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe

View File

@ -14,11 +14,11 @@
# #
# NOTE: This format cannot be automatically recognized, so the reader # NOTE: This format cannot be automatically recognized, so the reader
# is not registered for use with Image.open(). To open a WEL file, use # is not registered for use with Image.open(). To open a WAL file, use
# the WalImageFile.open() function instead. # the WalImageFile.open() function instead.
# This reader is based on the specification available from: # This reader is based on the specification available from:
# http://www.flipcode.com/tutorials/tut_q2levels.shtml # http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
# and has been tested with a few sample files found using google. # and has been tested with a few sample files found using google.
from __future__ import print_function from __future__ import print_function

View File

@ -59,7 +59,7 @@ word = _binary.i16le
def short(c, o=0): def short(c, o=0):
v = word(c, o) v = word(c, o)
if v >= 32768: if v >= 32768:
v = v - 65536 v -= 65536
return v return v
dword = _binary.i32le dword = _binary.i32le

View File

@ -12,7 +12,7 @@
# ;-) # ;-)
VERSION = '1.1.7' # PIL version VERSION = '1.1.7' # PIL version
PILLOW_VERSION = '2.3.0' # Pillow PILLOW_VERSION = '2.4.0' # Pillow
_plugins = ['ArgImagePlugin', _plugins = ['ArgImagePlugin',
'BmpImagePlugin', 'BmpImagePlugin',

View File

@ -14,3 +14,9 @@ else:
# Checks if an object is a string, and that it points to a directory. # Checks if an object is a string, and that it points to a directory.
def isDirectory(f): def isDirectory(f):
return isPath(f) and os.path.isdir(f) return isPath(f) and os.path.isdir(f)
class deferred_error(object):
def __init__(self, ex):
self.ex = ex
def __getattr__(self, elt):
raise self.ex

View File

@ -5,8 +5,9 @@ Pillow
Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
.. image:: https://travis-ci.org/python-imaging/Pillow.png .. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
:target: https://travis-ci.org/python-imaging/Pillow :target: https://travis-ci.org/python-pillow/Pillow
:alt: Travis CI build status
.. image:: https://pypip.in/v/Pillow/badge.png .. image:: https://pypip.in/v/Pillow/badge.png
:target: https://pypi.python.org/pypi/Pillow/ :target: https://pypi.python.org/pypi/Pillow/
@ -16,4 +17,7 @@ Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Pyt
:target: https://pypi.python.org/pypi/Pillow/ :target: https://pypi.python.org/pypi/Pillow/
:alt: Number of PyPI downloads :alt: Number of PyPI downloads
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details, and more. The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details, and more.

View File

@ -1,5 +1,5 @@
Python SANE module V1.1 (30 Sep. 2004) Python SANE module V1.1 (30 Sep. 2004)
================================================================================
The SANE module provides an interface to the SANE scanner and frame The SANE module provides an interface to the SANE scanner and frame
grabber interface for Linux. This module was contributed by Andrew grabber interface for Linux. This module was contributed by Andrew
@ -9,11 +9,11 @@ word 'SANE' or 'sane' in the subject of your mail, otherwise it might
be classified as spam in the future. be classified as spam in the future.
To build this module, type (in the Sane directory): To build this module, type (in the Sane directory)::
python setup.py build python setup.py build
In order to install the module type: In order to install the module type::
python setup.py install python setup.py install

View File

@ -1,6 +1,5 @@
-------
Scripts Scripts
------- =======
This directory contains a number of more or less trivial utilities This directory contains a number of more or less trivial utilities
and demo programs. and demo programs.
@ -9,50 +8,50 @@ Comments and contributions are welcome.
</F> </F>
--------------------------------------------------------------------
pildriver.py (by Eric S. Raymond) pildriver.py (by Eric S. Raymond)
--------------------------------------------------------------------
A class implementing an image-processing calculator for scripts. A class implementing an image-processing calculator for scripts.
Parses lists of commnds (or, called interactively, command-line Parses lists of commnds (or, called interactively, command-line
arguments) into image loads, transformations, and saves. arguments) into image loads, transformations, and saves.
--------------------------------------------------------------------
viewer.py viewer.py
--------------------------------------------------------------------
A simple image viewer. Can display all file formats handled by A simple image viewer. Can display all file formats handled by
PIL. Transparent images are properly handled. PIL. Transparent images are properly handled.
--------------------------------------------------------------------
thresholder.py thresholder.py
--------------------------------------------------------------------
A simple utility that demonstrates how a transparent 1-bit overlay A simple utility that demonstrates how a transparent 1-bit overlay
can be used to show the current thresholding of an 8-bit image. can be used to show the current thresholding of an 8-bit image.
--------------------------------------------------------------------
enhancer.py enhancer.py
--------------------------------------------------------------------
Illustrates the ImageEnhance module. Drag the sliders to modify the Illustrates the ImageEnhance module. Drag the sliders to modify the
images. This might be very slow on some platforms, depending on the images. This might be very slow on some platforms, depending on the
Tk version. Tk version.
--------------------------------------------------------------------
painter.py painter.py
--------------------------------------------------------------------
Illustrates how a painting program could be based on PIL and Tk. Illustrates how a painting program could be based on PIL and Tk.
Press the left mouse button and drag over the image to remove the Press the left mouse button and drag over the image to remove the
colour. Some clever tricks have been used to get decent performance colour. Some clever tricks have been used to get decent performance
when updating the screen; see the sources for details. when updating the screen; see the sources for details.
--------------------------------------------------------------------
player.py player.py
--------------------------------------------------------------------
A simple image sequence player. You can use either a sequence format A simple image sequence player. You can use either a sequence format
like FLI/FLC, GIF, or ARG, or give a number of images which are like FLI/FLC, GIF, or ARG, or give a number of images which are
interpreted as frames in a sequence. All frames must have the same interpreted as frames in a sequence. All frames must have the same
size. size.
--------------------------------------------------------------------
gifmaker.py gifmaker.py
--------------------------------------------------------------------
Convert a sequence file to a GIF animation. Convert a sequence file to a GIF animation.
@ -60,20 +59,20 @@ Note that the GIF encoder provided with this release of PIL writes
uncompressed GIF files only, so the resulting animations are rather uncompressed GIF files only, so the resulting animations are rather
large compared with these created by other tools. large compared with these created by other tools.
--------------------------------------------------------------------
explode.py explode.py
--------------------------------------------------------------------
Split a sequence file into individual frames. Split a sequence file into individual frames.
--------------------------------------------------------------------
image2py.py image2py.py
--------------------------------------------------------------------
Convert an image to a Python module containing an IMAGE variable. Convert an image to a Python module containing an IMAGE variable.
Note that the module using the module must include JPEG and ZIP Note that the module using the module must include JPEG and ZIP
decoders, unless the -u option is used. decoders, unless the -u option is used.
--------------------------------------------------------------------
olesummary.py olesummary.py
--------------------------------------------------------------------
Uses the OleFileIO module to dump the summary information from an OLE Uses the OleFileIO module to dump the summary information from an OLE
structured storage file. This works with most OLE files, including structured storage file. This works with most OLE files, including

View File

@ -104,7 +104,7 @@ while True:
except EOFError: except EOFError:
break break
ix = ix + 1 ix += 1
if html: if html:
html.write("</body>\n</html>\n") html.write("</body>\n</html>\n")

View File

@ -100,7 +100,7 @@ def makedelta(fp, sequence):
previous = im.copy() previous = im.copy()
frames = frames + 1 frames += 1
fp.write(";") fp.write(";")

View File

@ -486,7 +486,7 @@ class PILDriver:
print("Stack: " + repr(self.stack)) print("Stack: " + repr(self.stack))
top = self.top() top = self.top()
if not isinstance(top, str): if not isinstance(top, str):
continue; continue
funcname = "do_" + top funcname = "do_" + top
if not hasattr(self, funcname): if not hasattr(self, funcname):
continue continue
@ -513,9 +513,9 @@ if __name__ == '__main__':
while True: while True:
try: try:
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3:
line = input('pildriver> '); line = input('pildriver> ')
else: else:
line = raw_input('pildriver> '); line = raw_input('pildriver> ')
except EOFError: except EOFError:
print("\nPILDriver says goodbye.") print("\nPILDriver says goodbye.")
break break

View File

@ -57,7 +57,7 @@ for o, a in opt:
elif o == "-v": elif o == "-v":
verify = 1 verify = 1
elif o == "-D": elif o == "-D":
Image.DEBUG = Image.DEBUG + 1 Image.DEBUG += 1
def globfix(files): def globfix(files):
# expand wildcards where necessary # expand wildcards where necessary

341
Tests/helper.py Normal file
View File

@ -0,0 +1,341 @@
"""
Helper functions.
"""
from __future__ import print_function
import sys
if sys.version_info[:2] <= (2, 6):
import unittest2 as unittest
else:
import unittest
def tearDownModule():
import glob
import os
import tempfile
temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
tempfiles = glob.glob(os.path.join(temp_root, "temp_*"))
if tempfiles:
print("===", "remaining temporary files")
for file in tempfiles:
print(file)
print("-"*68)
class PillowTestCase(unittest.TestCase):
currentResult = None # holds last result object passed to run method
_tempfiles = []
def run(self, result=None):
self.addCleanup(self.delete_tempfiles)
self.currentResult = result # remember result for use later
unittest.TestCase.run(self, result) # call superclass run method
def delete_tempfiles(self):
try:
ok = self.currentResult.wasSuccessful()
except AttributeError: # for nosetests
proxy = self.currentResult
ok = (len(proxy.errors) + len(proxy.failures) == 0)
if ok:
# only clean out tempfiles if test passed
import os
import os.path
import tempfile
for file in self._tempfiles:
try:
os.remove(file)
except OSError:
pass # report?
temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
try:
os.rmdir(temp_root)
except OSError:
pass
def assert_almost_equal(self, a, b, msg=None, eps=1e-6):
self.assertLess(
abs(a-b), eps,
msg or "got %r, expected %r" % (a, b))
def assert_deep_equal(self, a, b, msg=None):
try:
self.assertEqual(
len(a), len(b),
msg or "got length %s, expected %s" % (len(a), len(b)))
self.assertTrue(
all([x == y for x, y in zip(a, b)]),
msg or "got %s, expected %s" % (a, b))
except:
self.assertEqual(a, b, msg)
def assert_image(self, im, mode, size, msg=None):
if mode is not None:
self.assertEqual(
im.mode, mode,
msg or "got mode %r, expected %r" % (im.mode, mode))
if size is not None:
self.assertEqual(
im.size, size,
msg or "got size %r, expected %r" % (im.size, size))
def assert_image_equal(self, a, b, msg=None):
self.assertEqual(
a.mode, b.mode,
msg or "got mode %r, expected %r" % (a.mode, b.mode))
self.assertEqual(
a.size, b.size,
msg or "got size %r, expected %r" % (a.size, b.size))
self.assertEqual(
a.tobytes(), b.tobytes(),
msg or "got different content")
def assert_image_similar(self, a, b, epsilon, msg=None):
epsilon = float(epsilon)
self.assertEqual(
a.mode, b.mode,
msg or "got mode %r, expected %r" % (a.mode, b.mode))
self.assertEqual(
a.size, b.size,
msg or "got size %r, expected %r" % (a.size, b.size))
diff = 0
try:
ord(b'0')
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
diff += abs(ord(abyte)-ord(bbyte))
except:
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
diff += abs(abyte-bbyte)
ave_diff = float(diff)/(a.size[0]*a.size[1])
self.assertGreaterEqual(
epsilon, ave_diff,
msg or "average pixel value difference %.4f > epsilon %.4f" % (
ave_diff, epsilon))
def assert_warning(self, warn_class, func):
import warnings
result = None
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
# Hopefully trigger a warning.
result = func()
# Verify some things.
self.assertGreaterEqual(len(w), 1)
found = False
for v in w:
if issubclass(v.category, warn_class):
found = True
break
self.assertTrue(found)
return result
def tempfile(self, template, *extra):
import os
import os.path
import sys
import tempfile
files = []
root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
try:
os.mkdir(root)
except OSError:
pass
for temp in (template,) + extra:
assert temp[:5] in ("temp.", "temp_")
name = os.path.basename(sys.argv[0])
name = temp[:4] + os.path.splitext(name)[0][4:]
name = name + "_%d" % len(self._tempfiles) + temp[4:]
name = os.path.join(root, name)
files.append(name)
self._tempfiles.extend(files)
return files[0]
# # require that deprecation warnings are triggered
# import warnings
# warnings.simplefilter('default')
# # temporarily turn off resource warnings that warn about unclosed
# # files in the test scripts.
# try:
# warnings.filterwarnings("ignore", category=ResourceWarning)
# except NameError:
# # we expect a NameError on py2.x, since it doesn't have ResourceWarnings.
# pass
import sys
py3 = (sys.version_info >= (3, 0))
# # some test helpers
#
# _target = None
# _tempfiles = []
# _logfile = None
#
#
# def success():
# import sys
# success.count += 1
# if _logfile:
# print(sys.argv[0], success.count, failure.count, file=_logfile)
# return True
#
#
# def failure(msg=None, frame=None):
# import sys
# import linecache
# failure.count += 1
# if _target:
# if frame is None:
# frame = sys._getframe()
# while frame.f_globals.get("__name__") != _target.__name__:
# frame = frame.f_back
# location = (frame.f_code.co_filename, frame.f_lineno)
# prefix = "%s:%d: " % location
# line = linecache.getline(*location)
# print(prefix + line.strip() + " failed:")
# if msg:
# print("- " + msg)
# if _logfile:
# print(sys.argv[0], success.count, failure.count, file=_logfile)
# return False
#
# success.count = failure.count = 0
#
# helpers
def fromstring(data):
from io import BytesIO
from PIL import Image
return Image.open(BytesIO(data))
def tostring(im, format, **options):
from io import BytesIO
out = BytesIO()
im.save(out, format, **options)
return out.getvalue()
def lena(mode="RGB", cache={}):
from PIL import Image
im = None
# im = cache.get(mode)
if im is None:
if mode == "RGB":
im = Image.open("Tests/images/lena.ppm")
elif mode == "F":
im = lena("L").convert(mode)
elif mode[:4] == "I;16":
im = lena("I").convert(mode)
else:
im = lena("RGB").convert(mode)
# cache[mode] = im
return im
# def assert_image_completely_equal(a, b, msg=None):
# if a != b:
# failure(msg or "images different")
# else:
# success()
#
#
# # test runner
#
# def run():
# global _target, _tests, run
# import sys
# import traceback
# _target = sys.modules["__main__"]
# run = None # no need to run twice
# tests = []
# for name, value in list(vars(_target).items()):
# if name[:5] == "test_" and type(value) is type(success):
# tests.append((value.__code__.co_firstlineno, name, value))
# tests.sort() # sort by line
# for lineno, name, func in tests:
# try:
# _tests = []
# func()
# for func, args in _tests:
# func(*args)
# except:
# t, v, tb = sys.exc_info()
# tb = tb.tb_next
# if tb:
# failure(frame=tb.tb_frame)
# traceback.print_exception(t, v, tb)
# else:
# print("%s:%d: cannot call test function: %s" % (
# sys.argv[0], lineno, v))
# failure.count += 1
#
#
# def yield_test(function, *args):
# # collect delayed/generated tests
# _tests.append((function, args))
#
#
# def skip(msg=None):
# import os
# print("skip")
# os._exit(0) # don't run exit handlers
#
#
# def ignore(pattern):
# """Tells the driver to ignore messages matching the pattern, for the
# duration of the current test."""
# print('ignore: %s' % pattern)
#
#
# def _setup():
# global _logfile
#
# import sys
# if "--coverage" in sys.argv:
# # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4)
# with warnings.catch_warnings():
# warnings.simplefilter("ignore")
# import coverage
# cov = coverage.coverage(auto_data=True, include="PIL/*")
# cov.start()
#
# def report():
# if run:
# run()
# if success.count and not failure.count:
# print("ok")
# # only clean out tempfiles if test passed
# import os
# import os.path
# import tempfile
# for file in _tempfiles:
# try:
# os.remove(file)
# except OSError:
# pass # report?
# temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
# try:
# os.rmdir(temp_root)
# except OSError:
# pass
#
# import atexit
# atexit.register(report)
#
# if "--log" in sys.argv:
# _logfile = open("test.log", "a")
#
#
# _setup()

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

BIN
Tests/images/corner.lut Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
Tests/images/dilation4.lut Normal file

Binary file not shown.

BIN
Tests/images/dilation8.lut Normal file

Binary file not shown.

BIN
Tests/images/edge.lut Normal file

Binary file not shown.

BIN
Tests/images/erosion4.lut Normal file

Binary file not shown.

BIN
Tests/images/erosion8.lut Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

BIN
Tests/images/lena.spider Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
Tests/images/morph_a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 B

View File

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

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