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
*.egg-info
build
dist
.tox
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.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
.*.swp
@ -13,3 +59,4 @@ docs/_build
*~
\#*#
.#*

View File

@ -1,35 +1,46 @@
language: python
# for python-qt4
virtualenv:
system_site_packages: true
notifications:
irc: "chat.freenode.net#pil"
python:
- "pypy"
- 2.6
- 2.7
- 3.2
- 3.3
- "pypy"
- 3.4
install:
install:
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake"
- "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
# openjpeg
- pushd depends && ./install_openjpeg.sh && popd
script:
- coverage erase
- python setup.py clean
- python setup.py build_ext --inplace
- python selftest.py
- python Tests/run.py
matrix:
allow_failures:
- python: "pypy"
# Don't cover PyPy: it fails intermittently and is x5.8 slower (#640)
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi
- 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)
==================
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
@ -22,7 +109,7 @@ Changelog (Pillow)
- Added support for JPEG 2000
[al45tair]
- Add more detailed error messages to Image.py
- Add more detailed error messages to Image.py
[larsmans]
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
@ -50,7 +137,7 @@ Changelog (Pillow)
[wiredfool]
- 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
[d-schmidt]
@ -60,7 +147,7 @@ Changelog (Pillow)
- Fixed DOS with invalid palette size or invalid image size in BMP file
[wiredfool]
- Added support for BMP version 4 and 5
[eddwardo, wiredfool]
@ -93,7 +180,7 @@ Changelog (Pillow)
- Prefer homebrew freetype over X11 freetype (but still allow both)
[dmckeone]
2.3.1 (2014-03-14)
------------------
@ -218,7 +305,7 @@ Changelog (Pillow)
[nikmolnar]
- 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
[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.)
[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.
[lqs]

View File

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

View File

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

View File

@ -128,5 +128,5 @@ class BdfFontFile(FontFile.FontFile):
if not c:
break
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

View File

@ -11,6 +11,7 @@
# 1996-08-23 fl Handle files from Macintosh (0.3)
# 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)
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution resizing
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2003 by Fredrik Lundh
@ -50,20 +51,36 @@ if sys.platform.startswith('win'):
else:
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):
"""Render an image using Ghostscript"""
# Unpack decoder tile
decoder, tile, offset, data = tile[0]
length, bbox = data
#Hack to support hi-res rendering
scale = int(scale) or 1
orig_size = size
orig_bbox = bbox
size = (size[0] * scale, size[1] * scale)
bbox = [bbox[0], bbox[1], bbox[2] * scale, bbox[3] * scale]
#print("Ghostscript", scale, size, orig_size, bbox, orig_bbox)
# resolution is dependend on bbox and size
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
@ -71,21 +88,30 @@ def Ghostscript(tile, size, fp, scale=1):
os.close(out_fd)
in_fd, infile = tempfile.mkstemp()
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:
fp.seek(offset)
while length >0:
s = fp.read(100*1024)
# fetch length of fp
fp.seek(0, 2)
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:
break
length = length - len(s)
length -= len(s)
f.write(s)
# Build ghostscript command
command = ["gs",
"-q", # quiet mode
"-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
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
@ -93,7 +119,7 @@ def Ghostscript(tile, size, fp, scale=1):
# adjust for image origin
"-f", infile, # input file
]
if gs_windows_binary is not None:
if not gs_windows_binary:
raise WindowsError('Unable to locate Ghostscript on paths')
@ -112,7 +138,7 @@ def Ghostscript(tile, size, fp, scale=1):
os.unlink(outfile)
os.unlink(infile)
except: pass
return im
@ -130,10 +156,12 @@ class PSFile:
self.fp.seek(offset, whence)
def read(self, count):
return self.fp.read(count).decode('latin-1')
def readbinary(self, count):
return self.fp.read(count)
def tell(self):
pos = self.fp.tell()
if self.char:
pos = pos - 1
pos -= 1
return pos
def readline(self):
s = b""
@ -167,26 +195,34 @@ class EpsImageFile(ImageFile.ImageFile):
def _open(self):
# FIXME: should check the first 512 bytes to see if this
# really is necessary (platform-dependent, though...)
fp = PSFile(self.fp)
# HEAD
s = fp.read(512)
# FIX for: Some EPS file not handled correctly / issue #302
# 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":
offset = 0
fp.seek(0, 2)
length = fp.tell()
elif i32(s) == 0xC6D3D0C5:
offset = i32(s[4:])
length = i32(s[8:])
fp.seek(offset)
offset = 0
elif i32(sb[0:4]) == 0xC6D3D0C5:
offset = i32(sb[4:8])
length = i32(sb[8:12])
else:
raise SyntaxError("not an EPS file")
# go to offset - start of "%!PS"
fp.seek(offset)
box = None
self.mode = "RGB"
@ -196,7 +232,7 @@ class EpsImageFile(ImageFile.ImageFile):
# Load EPS header
s = fp.readline()
while s:
if len(s) > 255:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,6 @@
from PIL import Image
from PIL import _imagingmath
import sys
try:
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]
h[lo] = 0
else:
h[lo] = h[lo] - cut
h[lo] -= cut
cut = 0
if cut <= 0:
break
@ -105,7 +105,7 @@ def autocontrast(image, cutoff=0, ignore=None):
cut = cut - h[hi]
h[hi] = 0
else:
h[hi] = h[hi] - cut
h[hi] -= cut
cut = 0
if cut <= 0:
break

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +1,22 @@
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)
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:
------------------------------------------------
- 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
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.
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-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
@ -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)
- 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).
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
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')
# 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')
# 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())
# 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'):
print("This is a Word document.")
print("size :", ole.get_size('worddocument'))
if ole.exists('macros/vba'):
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()
print('Author:', meta.author)
print('Title:', meta.title)
@ -82,29 +254,67 @@ Here are a few examples:
# print all metadata:
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()
# Work with a file-like object (e.g. StringIO) instead of a file on disk:
data = open('myfile.doc', 'rb').read()
f = io.BytesIO(data)
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:
### Use OleFileIO_PL as a script ###
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:
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/).
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.
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
- 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
- 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
-------
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

View File

@ -2,11 +2,12 @@
# -*- coding: latin-1 -*-
"""
OleFileIO_PL:
Module to read Microsoft OLE2 files (also called Structured Storage or
Microsoft Compound Document File Format), such as Microsoft Office
documents, Image Composer and FlashPix files, Outlook messages, ...
Module to read Microsoft OLE2 files (also called Structured Storage or
Microsoft Compound Document File Format), such as Microsoft Office
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
@ -16,25 +17,30 @@ See: http://www.pythonware.com/products/pil/index.htm
The Python Imaging Library (PIL) is
Copyright (c) 1997-2005 by Secret Labs AB
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.
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)"
__date__ = "2013-07-24"
__version__ = '0.26'
__date__ = "2014-02-04"
__version__ = '0.30'
#--- LICENSE ------------------------------------------------------------------
# OleFileIO_PL is an improved version of the OleFileIO module from the
# 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
# Copyright (c) 1997-2005 by Secret Labs AB
@ -133,9 +139,14 @@ __version__ = '0.26'
# of a directory entry or a storage/stream
# - fixed parsing of direntry timestamps
# 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):
# + 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
# append_kids (then listdir/_list can be simplified)
# - TESTS with Linux, MacOSX, Python 1.5.2, various files, PIL, ...
@ -220,17 +231,26 @@ __version__ = '0.26'
#------------------------------------------------------------------------------
import io
import sys
from PIL import _binary
import struct, array, os.path, datetime
#[PL] Define explicitly the public API to avoid private objects in pydoc:
__all__ = ['OleFileIO', 'isOleFile', 'MAGIC']
# For Python 3.x, need to redefine long as int:
if str is not bytes:
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:
if array.array('L').itemsize == 4:
# on 32 bits platforms, long integers in an array are 32 bits:
@ -281,8 +301,7 @@ def set_debug_mode(debug_mode):
else:
debug = debug_pass
#TODO: convert this to hex
MAGIC = b'\320\317\021\340\241\261\032\341'
MAGIC = b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'
#[PL]: added constants for Sector IDs (from AAF specifications)
MAXREGSECT = 0xFFFFFFFA; # maximum SECT
@ -362,9 +381,39 @@ def isOleFile (filename):
return False
i8 = _binary.i8
i16 = _binary.i16le
i32 = _binary.i32le
if bytes is str:
# version for Python 2.x
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):
@ -373,7 +422,9 @@ def _clsid(clsid):
clsid: string of length 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 (("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) %
((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) +
@ -902,18 +953,22 @@ class _OleDirectoryEntry:
def __eq__(self, other):
"Compare entries by name"
return self.name == other.name
def __lt__(self, other):
"Compare entries by 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):
return not self.__eq__(other)
def __le__(self, other):
return self.__eq__(other) or self.__lt__(other)
# 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):
"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
self.fp = open(filename, "rb")
# old code fails if filename is not a plain string:
#if isPath(filename):
#if isinstance(filename, (bytes, basestring)):
# self.fp = open(filename, "rb")
#else:
# self.fp = filename
@ -1133,7 +1188,7 @@ class OleFileIO:
) = 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
self._raise_defect(DEFECT_FATAL, "incorrect OLE signature")
if self.clsid != bytearray(16):
@ -1385,7 +1440,7 @@ class OleFileIO:
if self.csectDif != nb_difat:
raise IOError('incorrect DIFAT')
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) )
#TODO: check if corresponding FAT SID = DIFSECT
sector_difat = self.getsect(isect_difat)
@ -1494,7 +1549,7 @@ class OleFileIO:
#self.direntries = []
# We start with a list of "None" object
self.direntries = [None] * max_entries
## for sid in range(max_entries):
## for sid in iterrange(max_entries):
## entry = fp.read(128)
## if not entry:
## break

View File

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

View File

@ -135,7 +135,7 @@ def _save(im, fp, filename, check=0):
# bytes per plane
stride = (im.size[0] * bits + 7) // 8
# stride should be even
stride = stride + (stride % 2)
stride += stride % 2
# 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
# gets overwritten.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,11 +14,11 @@
#
# 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.
# 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.
from __future__ import print_function

View File

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

View File

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

View File

@ -14,3 +14,9 @@ else:
# Checks if an object is a string, and that it points to a directory.
def isDirectory(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.
.. image:: https://travis-ci.org/python-imaging/Pillow.png
:target: https://travis-ci.org/python-imaging/Pillow
.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
:target: https://travis-ci.org/python-pillow/Pillow
:alt: Travis CI build status
.. image:: https://pypip.in/v/Pillow/badge.png
: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/
: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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -57,7 +57,7 @@ for o, a in opt:
elif o == "-v":
verify = 1
elif o == "-D":
Image.DEBUG = Image.DEBUG + 1
Image.DEBUG += 1
def globfix(files):
# 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