Merge from master
11
.coveragerc
Normal 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
|
@ -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
|
|||
*~
|
||||
\#*#
|
||||
.#*
|
||||
|
||||
|
|
31
.travis.yml
|
@ -1,22 +1,21 @@
|
|||
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:
|
||||
- "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
|
||||
- pushd depends && ./install_webp.sh && popd
|
||||
|
@ -25,11 +24,23 @@ install:
|
|||
- 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)
|
||||
|
|
91
CHANGES.rst
|
@ -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
|
||||
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,6 +51,21 @@ 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"""
|
||||
|
||||
|
@ -62,8 +78,9 @@ def Ghostscript(tile, size, fp, scale=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
|
||||
|
||||
|
@ -72,20 +89,29 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
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
|
||||
|
@ -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,24 +195,32 @@ 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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -182,7 +182,7 @@ class ImImageFile(ImageFile.ImageFile):
|
|||
self.info[k] = v
|
||||
|
||||
if k in TAGS:
|
||||
n = n + 1
|
||||
n += 1
|
||||
|
||||
else:
|
||||
|
||||
|
|
217
PIL/Image.py
|
@ -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
|
||||
|
@ -91,10 +99,13 @@ 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
|
||||
|
@ -108,6 +119,7 @@ try:
|
|||
except:
|
||||
HAS_CFFI = False
|
||||
|
||||
|
||||
def isImageType(t):
|
||||
"""
|
||||
Checks if an object is an image object.
|
||||
|
@ -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]
|
||||
|
@ -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,14 +436,18 @@ 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
|
||||
|
@ -436,15 +455,17 @@ def _getscaleoffset(expr):
|
|||
(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
|
||||
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number) and
|
||||
d == "__add__" and isinstance(e, numbers.Number)):
|
||||
return c, e
|
||||
except TypeError: pass
|
||||
except TypeError:
|
||||
pass
|
||||
raise ValueError("illegal expression")
|
||||
|
||||
|
||||
|
@ -497,6 +518,35 @@ class Image:
|
|||
|
||||
_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()
|
||||
self.im = self.im.copy()
|
||||
|
@ -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
|
||||
|
@ -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'),
|
||||
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"};"])
|
||||
("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,7 +834,8 @@ 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.
|
||||
|
@ -770,6 +864,8 @@ class Image:
|
|||
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)
|
||||
|
@ -787,7 +883,8 @@ 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
|
||||
|
@ -807,14 +904,15 @@ 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'])
|
||||
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
|
||||
|
@ -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))
|
||||
|
@ -1037,7 +1136,6 @@ class Image:
|
|||
self.load()
|
||||
return self.im.ptr
|
||||
|
||||
|
||||
def getpalette(self):
|
||||
"""
|
||||
Returns the image palette as a list.
|
||||
|
@ -1055,7 +1153,6 @@ class Image:
|
|||
except ValueError:
|
||||
return None # no palette
|
||||
|
||||
|
||||
def getpixel(self, xy):
|
||||
"""
|
||||
Returns the pixel value at a given position.
|
||||
|
@ -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
|
||||
|
@ -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`
|
||||
|
@ -1459,6 +1557,7 @@ class Image:
|
|||
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
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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,7 +2229,9 @@ 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()
|
||||
|
@ -2116,7 +2244,9 @@ 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()
|
||||
|
@ -2125,6 +2255,7 @@ def open(fp, mode="r"):
|
|||
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)
|
||||
|
|
988
PIL/ImageCms.py
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'''")
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
from PIL import Image
|
||||
from PIL import _imagingmath
|
||||
import sys
|
||||
|
||||
try:
|
||||
import builtins
|
||||
|
|
244
PIL/ImageMorph.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
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()
|
||||
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -15,11 +15,12 @@
|
|||
|
||||
__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."""
|
||||
|
@ -45,12 +46,13 @@ 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."""
|
||||
|
@ -122,11 +124,12 @@ def _parse_jp2_header(fp):
|
|||
if nc == 3:
|
||||
mode = 'RGB'
|
||||
elif nc == 4:
|
||||
mode == 'RGBA'
|
||||
mode = 'RGBA'
|
||||
break
|
||||
|
||||
return (size, mode)
|
||||
|
||||
|
||||
##
|
||||
# Image plugin for JPEG2000 images.
|
||||
|
||||
|
@ -155,15 +158,25 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
|||
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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -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.applist.append(("COM", s))
|
||||
|
||||
|
||||
def SOF(self, marker):
|
||||
#
|
||||
# Start of frame marker. Defines the size and mode of the
|
||||
|
@ -165,6 +169,7 @@ def SOF(self, marker):
|
|||
# 4-tuples: id, vsamp, hsamp, qtable
|
||||
self.layer.append((t[0], i8(t[1])//16, i8(t[1]) & 15, i8(t[2])))
|
||||
|
||||
|
||||
def DQT(self, marker):
|
||||
#
|
||||
# Define quantization table. Support baseline 8-bit tables
|
||||
|
@ -261,6 +266,7 @@ MARKER = {
|
|||
def _accept(prefix):
|
||||
return prefix[0:1] == b"\377"
|
||||
|
||||
|
||||
##
|
||||
# Image plugin for JPEG and JFIF images.
|
||||
|
||||
|
@ -290,9 +296,14 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
|
||||
while True:
|
||||
|
||||
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]
|
||||
|
@ -307,9 +318,9 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
# 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]
|
||||
|
@ -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,10 +581,9 @@ 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
|
||||
if "optimize" in info or "progressive" in info or "progression" in info:
|
||||
|
@ -581,13 +598,16 @@ def _save(im, fp, filename):
|
|||
|
||||
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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'):
|
||||
|
||||
### 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()
|
||||
f = open('Pictures.bin', 'wb')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
# Extract metadata (new in v0.24) - see source code for all attributes:
|
||||
|
||||
### 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()
|
||||
### Use OleFileIO_PL as a script ###
|
||||
|
||||
|
||||
It can also be used as a script from the command-line to display the structure of an OLE file, for example:
|
||||
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,12 +322,12 @@ 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
|
||||
|
||||
|
@ -126,7 +336,7 @@ 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
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@ 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, ...
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
@ -64,8 +66,10 @@ def _save(im, fp, filename):
|
|||
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'))
|
||||
|
||||
|
@ -104,8 +108,8 @@ 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"> ]"
|
||||
colorspace += "%02x%02x%02x " % (r, g, b)
|
||||
colorspace += "> ]"
|
||||
procset = "/ImageI" # indexed color
|
||||
elif im.mode == "RGB":
|
||||
filter = "/DCTDecode"
|
||||
|
@ -122,7 +126,9 @@ def _save(im, fp, filename):
|
|||
# catalogue
|
||||
|
||||
xref[1] = fp.tell()
|
||||
_obj(fp, 1, Type = "/Catalog",
|
||||
_obj(
|
||||
fp, 1,
|
||||
Type="/Catalog",
|
||||
Pages="2 0 R")
|
||||
_endobj(fp)
|
||||
|
||||
|
@ -130,7 +136,9 @@ def _save(im, fp, filename):
|
|||
# pages
|
||||
|
||||
xref[2] = fp.tell()
|
||||
_obj(fp, 2, Type = "/Pages",
|
||||
_obj(
|
||||
fp, 2,
|
||||
Type="/Pages",
|
||||
Count=1,
|
||||
Kids="[4 0 R]")
|
||||
_endobj(fp)
|
||||
|
@ -144,7 +152,7 @@ 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)])
|
||||
|
@ -158,7 +166,9 @@ def _save(im, fp, filename):
|
|||
raise ValueError("unsupported PDF filter (%s)" % filter)
|
||||
|
||||
xref[3] = fp.tell()
|
||||
_obj(fp, 3, Type = "/XObject",
|
||||
_obj(
|
||||
fp, 3,
|
||||
Type="/XObject",
|
||||
Subtype="/Image",
|
||||
Width=width, # * 72.0 / resolution,
|
||||
Height=height, # * 72.0 / resolution,
|
||||
|
@ -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,7 +204,10 @@ 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()))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,6 +100,17 @@ 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",
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -36,18 +36,24 @@
|
|||
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]
|
||||
|
||||
|
||||
# There is no magic number to identify Spider files, so just check a
|
||||
# series of header locations to see if they have reasonable values.
|
||||
# Returns no.of bytes in the header, if it is a valid Spider header,
|
||||
|
@ -57,28 +63,30 @@ 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
|
||||
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
|
||||
if labbyt != (labrec * lenbyt):
|
||||
return 0
|
||||
# looks like a valid header
|
||||
return labbyt
|
||||
|
||||
|
||||
def isSpiderImage(filename):
|
||||
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
|
||||
hdrlen = isSpiderHeader(t)
|
||||
if hdrlen == 0:
|
||||
bigendian = 0
|
||||
t = struct.unpack('<23f', f) # little-endian
|
||||
hdrlen = isSpiderHeader(t)
|
||||
return hdrlen
|
||||
|
@ -141,7 +149,8 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
self.rawmode = "F;32F"
|
||||
self.mode = "F"
|
||||
|
||||
self.tile = [("raw", (0, 0) + self.size, offset,
|
||||
self.tile = [
|
||||
("raw", (0, 0) + self.size, offset,
|
||||
(self.rawmode, 0, 1))]
|
||||
self.__fp = self.fp # FIXME: hack
|
||||
|
||||
|
@ -176,6 +185,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
from PIL import ImageTk
|
||||
return ImageTk.PhotoImage(self.convert2byte(), palette=256)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Image series
|
||||
|
||||
|
@ -200,6 +210,7 @@ def loadImageSeries(filelist=None):
|
|||
imglist.append(im)
|
||||
return imglist
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# For saving images in Spider format
|
||||
|
||||
|
@ -207,10 +218,11 @@ def makeSpiderHeader(im):
|
|||
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)
|
||||
|
||||
|
@ -235,6 +247,7 @@ def makeSpiderHeader(im):
|
|||
hdrstr.append(struct.pack('f', v))
|
||||
return hdrstr
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
if im.mode[0] != "F":
|
||||
im = im.convert('F')
|
||||
|
@ -255,6 +268,7 @@ def _save(im, fp, filename):
|
|||
|
||||
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")
|
||||
|
|
|
@ -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"),
|
||||
|
@ -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,11 +984,7 @@ 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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -104,7 +104,7 @@ while True:
|
|||
except EOFError:
|
||||
break
|
||||
|
||||
ix = ix + 1
|
||||
ix += 1
|
||||
|
||||
if html:
|
||||
html.write("</body>\n</html>\n")
|
||||
|
|
|
@ -100,7 +100,7 @@ def makedelta(fp, sequence):
|
|||
|
||||
previous = im.copy()
|
||||
|
||||
frames = frames + 1
|
||||
frames += 1
|
||||
|
||||
fp.write(";")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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()
|
BIN
Tests/images/10ct_32bit_128.tiff
Normal file
BIN
Tests/images/16_bit_binary.pgm
Normal file
BIN
Tests/images/16_bit_binary_pgm.png
Normal file
After Width: | Height: | Size: 578 B |
BIN
Tests/images/binary_preview_map.eps
Executable file
BIN
Tests/images/corner.lut
Normal file
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
Tests/images/dilation4.lut
Normal file
BIN
Tests/images/dilation8.lut
Normal file
BIN
Tests/images/edge.lut
Normal file
BIN
Tests/images/erosion4.lut
Normal file
BIN
Tests/images/erosion8.lut
Normal file
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
BIN
Tests/images/imagedraw_arc.png
Normal file
After Width: | Height: | Size: 284 B |
BIN
Tests/images/imagedraw_bitmap.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
Tests/images/imagedraw_chord.png
Normal file
After Width: | Height: | Size: 326 B |
BIN
Tests/images/imagedraw_ellipse.png
Normal file
After Width: | Height: | Size: 491 B |
BIN
Tests/images/imagedraw_floodfill.png
Normal file
After Width: | Height: | Size: 232 B |
BIN
Tests/images/imagedraw_floodfill2.png
Normal file
After Width: | Height: | Size: 212 B |
BIN
Tests/images/imagedraw_line.png
Normal file
After Width: | Height: | Size: 286 B |
BIN
Tests/images/imagedraw_pieslice.png
Normal file
After Width: | Height: | Size: 405 B |
BIN
Tests/images/imagedraw_point.png
Normal file
After Width: | Height: | Size: 124 B |
BIN
Tests/images/imagedraw_polygon.png
Normal file
After Width: | Height: | Size: 292 B |
BIN
Tests/images/imagedraw_rectangle.png
Normal file
After Width: | Height: | Size: 228 B |
BIN
Tests/images/junk_jpeg_header.jpg
Normal file
After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
BIN
Tests/images/lena.spider
Normal file
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
BIN
Tests/images/morph_a.png
Normal file
After Width: | Height: | Size: 83 B |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
BIN
Tests/images/rgb_trns_ycbc.j2k
Normal file
BIN
Tests/images/rgb_trns_ycbc.jp2
Normal file
BIN
Tests/images/test-ole-file.doc
Normal file
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |