mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-04 21:50:54 +03:00
Merge branch 'master' of https://github.com/python-pillow/Pillow
This commit is contained in:
commit
6605bf22e5
|
@ -9,6 +9,7 @@ python:
|
||||||
- "pypy"
|
- "pypy"
|
||||||
- 2.6
|
- 2.6
|
||||||
- 2.7
|
- 2.7
|
||||||
|
- "2.7_with_system_site_packages" # For PyQt4
|
||||||
- 3.2
|
- 3.2
|
||||||
- 3.3
|
- 3.3
|
||||||
- 3.4
|
- 3.4
|
||||||
|
@ -33,11 +34,11 @@ script:
|
||||||
|
|
||||||
# Don't cover PyPy: it fails intermittently and is x5.8 slower (#640)
|
# 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 python selftest.py; fi
|
||||||
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests Tests/test_*.py; fi
|
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests -vx Tests/test_*.py; fi
|
||||||
|
|
||||||
# Cover the others
|
# 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/* selftest.py; fi
|
||||||
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose Tests/test_*.py; fi
|
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py; fi
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- coverage report
|
- coverage report
|
||||||
|
|
14
CHANGES.rst
14
CHANGES.rst
|
@ -4,10 +4,22 @@ Changelog (Pillow)
|
||||||
2.6.0 (unreleased)
|
2.6.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Removed unusable ImagePalette.new()
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Fix Scrambled XPM #808
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Doc cleanup
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix `ImageStat` docs
|
||||||
|
[akx]
|
||||||
|
|
||||||
- Added docs for ExifTags
|
- Added docs for ExifTags
|
||||||
[Wintermute3]
|
[Wintermute3]
|
||||||
|
|
||||||
- More tests for ImageFont, ImageMath, and _util
|
- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- Fix return value of FreeTypeFont.textsize() does not include font offsets
|
- Fix return value of FreeTypeFont.textsize() does not include font offsets
|
||||||
|
|
21
Makefile
21
Makefile
|
@ -1,4 +1,16 @@
|
||||||
|
.PHONY: pre clean install test inplace coverage test-dep help docs livedocs
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " clean remove build products"
|
||||||
|
@echo " install make and install"
|
||||||
|
@echo " test run tests on installed pillow"
|
||||||
|
@echo " inplace make inplace extension"
|
||||||
|
@echo " coverage run coverage test (in progress)"
|
||||||
|
@echo " docs make html docs"
|
||||||
|
@echo " docserver run an http server on the docs directory"
|
||||||
|
@echo " test-dep install coveraget and test dependencies"
|
||||||
|
|
||||||
pre:
|
pre:
|
||||||
virtualenv .
|
virtualenv .
|
||||||
|
@ -18,12 +30,11 @@ clean:
|
||||||
rm -r build || true
|
rm -r build || true
|
||||||
find . -name __pycache__ | xargs rm -r || true
|
find . -name __pycache__ | xargs rm -r || true
|
||||||
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
python setup.py install
|
python setup.py install
|
||||||
python selftest.py --installed
|
python selftest.py --installed
|
||||||
|
|
||||||
test: install
|
test:
|
||||||
python test-installed.py
|
python test-installed.py
|
||||||
|
|
||||||
inplace: clean
|
inplace: clean
|
||||||
|
@ -42,3 +53,9 @@ coverage:
|
||||||
|
|
||||||
test-dep:
|
test-dep:
|
||||||
pip install coveralls nose nose-cov pep8 pyflakes
|
pip install coveralls nose nose-cov pep8 pyflakes
|
||||||
|
|
||||||
|
docs:
|
||||||
|
$(MAKE) -C docs html
|
||||||
|
|
||||||
|
docserver:
|
||||||
|
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
|
|
@ -33,6 +33,7 @@ i32 = _binary.i32le
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] == b"\0\0\2\0"
|
return prefix[:4] == b"\0\0\2\0"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Windows Cursor files.
|
# Image plugin for Windows Cursor files.
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
# check magic
|
# check magic
|
||||||
s = self.fp.read(6)
|
s = self.fp.read(6)
|
||||||
if not _accept(s):
|
if not _accept(s):
|
||||||
raise SyntaxError("not an CUR file")
|
raise SyntaxError("not a CUR file")
|
||||||
|
|
||||||
# pick the largest cursor in the file
|
# pick the largest cursor in the file
|
||||||
m = b""
|
m = b""
|
||||||
|
@ -58,14 +59,14 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
m = s
|
m = s
|
||||||
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
||||||
m = s
|
m = s
|
||||||
#print "width", i8(s[0])
|
# print "width", i8(s[0])
|
||||||
#print "height", i8(s[1])
|
# print "height", i8(s[1])
|
||||||
#print "colors", i8(s[2])
|
# print "colors", i8(s[2])
|
||||||
#print "reserved", i8(s[3])
|
# print "reserved", i8(s[3])
|
||||||
#print "hotspot x", i16(s[4:])
|
# print "hotspot x", i16(s[4:])
|
||||||
#print "hotspot y", i16(s[6:])
|
# print "hotspot y", i16(s[6:])
|
||||||
#print "bytes", i32(s[8:])
|
# print "bytes", i32(s[8:])
|
||||||
#print "offset", i32(s[12:])
|
# print "offset", i32(s[12:])
|
||||||
|
|
||||||
# load as bitmap
|
# load as bitmap
|
||||||
self._bitmap(i32(m[12:]) + offset)
|
self._bitmap(i32(m[12:]) + offset)
|
||||||
|
@ -73,7 +74,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
# patch up the bitmap height
|
# patch up the bitmap height
|
||||||
self.size = self.size[0], self.size[1]//2
|
self.size = self.size[0], self.size[1]//2
|
||||||
d, e, o, a = self.tile[0]
|
d, e, o, a = self.tile[0]
|
||||||
self.tile[0] = d, (0,0)+self.size, o, a
|
self.tile[0] = d, (0, 0)+self.size, o, a
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -27,13 +27,15 @@ from PIL import Image, _binary
|
||||||
|
|
||||||
from PIL.PcxImagePlugin import PcxImageFile
|
from PIL.PcxImagePlugin import PcxImageFile
|
||||||
|
|
||||||
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
||||||
|
|
||||||
i32 = _binary.i32le
|
i32 = _binary.i32le
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i32(prefix) == MAGIC
|
return i32(prefix) == MAGIC
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for the Intel DCX format.
|
# Image plugin for the Intel DCX format.
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
"""
|
## The Python Imaging Library.
|
||||||
The Python Imaging Library.
|
## $Id$
|
||||||
$Id$
|
|
||||||
|
|
||||||
Optional color managment support, based on Kevin Cazabon's PyCMS
|
## Optional color managment support, based on Kevin Cazabon's PyCMS
|
||||||
library.
|
## library.
|
||||||
|
|
||||||
History:
|
## History:
|
||||||
2009-03-08 fl Added to PIL.
|
|
||||||
|
|
||||||
Copyright (C) 2002-2003 Kevin Cazabon
|
## 2009-03-08 fl Added to PIL.
|
||||||
Copyright (c) 2009 by Fredrik Lundh
|
|
||||||
|
|
||||||
See the README file for information on usage and redistribution. See
|
## Copyright (C) 2002-2003 Kevin Cazabon
|
||||||
below for the original description.
|
## Copyright (c) 2009 by Fredrik Lundh
|
||||||
"""
|
## Copyright (c) 2013 by Eric Soroos
|
||||||
|
|
||||||
|
## See the README file for information on usage and redistribution. See
|
||||||
|
## below for the original description.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
@ -637,7 +637,7 @@ def getProfileName(profile):
|
||||||
|
|
||||||
(pyCMS) Gets the internal product name for the given profile.
|
(pyCMS) Gets the internal product name for the given profile.
|
||||||
|
|
||||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||||
a PyCMSError is raised If an error occurs while trying to obtain the
|
a PyCMSError is raised If an error occurs while trying to obtain the
|
||||||
name tag, a PyCMSError is raised.
|
name tag, a PyCMSError is raised.
|
||||||
|
|
||||||
|
@ -876,7 +876,7 @@ def isIntentSupported(profile, intent, direction):
|
||||||
input/output/proof profile as you desire.
|
input/output/proof profile as you desire.
|
||||||
|
|
||||||
Some profiles are created specifically for one "direction", can cannot
|
Some profiles are created specifically for one "direction", can cannot
|
||||||
be used for others. Some profiles can only be used for certain
|
be used for others. Some profiles can only be used for certain
|
||||||
rendering intents... so it's best to either verify this before trying
|
rendering intents... so it's best to either verify this before trying
|
||||||
to create a transform with them (using this function), or catch the
|
to create a transform with them (using this function), or catch the
|
||||||
potential PyCMSError that will occur if they don't support the modes
|
potential PyCMSError that will occur if they don't support the modes
|
||||||
|
|
|
@ -133,11 +133,27 @@ class ImageFile(Image.Image):
|
||||||
return pixel
|
return pixel
|
||||||
|
|
||||||
self.map = None
|
self.map = None
|
||||||
|
use_mmap = self.filename and len(self.tile) == 1
|
||||||
|
# As of pypy 2.1.0, memory mapping was failing here.
|
||||||
|
use_mmap = use_mmap and not hasattr(sys, 'pypy_version_info')
|
||||||
|
|
||||||
readonly = 0
|
readonly = 0
|
||||||
|
|
||||||
if self.filename and len(self.tile) == 1 and not hasattr(sys, 'pypy_version_info'):
|
# look for read/seek overrides
|
||||||
# As of pypy 2.1.0, memory mapping was failing here.
|
try:
|
||||||
|
read = self.load_read
|
||||||
|
# don't use mmap if there are custom read/seek functions
|
||||||
|
use_mmap = False
|
||||||
|
except AttributeError:
|
||||||
|
read = self.fp.read
|
||||||
|
|
||||||
|
try:
|
||||||
|
seek = self.load_seek
|
||||||
|
use_mmap = False
|
||||||
|
except AttributeError:
|
||||||
|
seek = self.fp.seek
|
||||||
|
|
||||||
|
if use_mmap:
|
||||||
# try memory mapping
|
# try memory mapping
|
||||||
d, e, o, a = self.tile[0]
|
d, e, o, a = self.tile[0]
|
||||||
if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
|
if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
|
||||||
|
@ -165,19 +181,7 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
self.load_prepare()
|
self.load_prepare()
|
||||||
|
|
||||||
# look for read/seek overrides
|
|
||||||
try:
|
|
||||||
read = self.load_read
|
|
||||||
except AttributeError:
|
|
||||||
read = self.fp.read
|
|
||||||
|
|
||||||
try:
|
|
||||||
seek = self.load_seek
|
|
||||||
except AttributeError:
|
|
||||||
seek = self.fp.seek
|
|
||||||
|
|
||||||
if not self.map:
|
if not self.map:
|
||||||
|
|
||||||
# sort tiles in file order
|
# sort tiles in file order
|
||||||
self.tile.sort(key=_tilesort)
|
self.tile.sort(key=_tilesort)
|
||||||
|
|
||||||
|
|
|
@ -17,19 +17,20 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import array
|
import array
|
||||||
from PIL import Image, ImageColor
|
import warnings
|
||||||
|
from PIL import ImageColor
|
||||||
|
|
||||||
|
|
||||||
class ImagePalette:
|
class ImagePalette:
|
||||||
"Color palette for palette mapped images"
|
"Color palette for palette mapped images"
|
||||||
|
|
||||||
def __init__(self, mode = "RGB", palette = None, size = 0):
|
def __init__(self, mode="RGB", palette=None, size=0):
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.rawmode = None # if set, palette contains raw data
|
self.rawmode = None # if set, palette contains raw data
|
||||||
self.palette = palette or list(range(256))*len(self.mode)
|
self.palette = palette or list(range(256))*len(self.mode)
|
||||||
self.colors = {}
|
self.colors = {}
|
||||||
self.dirty = None
|
self.dirty = None
|
||||||
if ((size == 0 and len(self.mode)*256 != len(self.palette)) or
|
if ((size == 0 and len(self.mode)*256 != len(self.palette)) or
|
||||||
(size != 0 and size != len(self.palette))):
|
(size != 0 and size != len(self.palette))):
|
||||||
raise ValueError("wrong palette size")
|
raise ValueError("wrong palette size")
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ class ImagePalette:
|
||||||
return self.palette
|
return self.palette
|
||||||
arr = array.array("B", self.palette)
|
arr = array.array("B", self.palette)
|
||||||
if hasattr(arr, 'tobytes'):
|
if hasattr(arr, 'tobytes'):
|
||||||
#py3k has a tobytes, tostring is deprecated.
|
# py3k has a tobytes, tostring is deprecated.
|
||||||
return arr.tobytes()
|
return arr.tobytes()
|
||||||
return arr.tostring()
|
return arr.tostring()
|
||||||
|
|
||||||
|
@ -109,6 +110,7 @@ class ImagePalette:
|
||||||
fp.write("\n")
|
fp.write("\n")
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Internal
|
# Internal
|
||||||
|
|
||||||
|
@ -119,32 +121,53 @@ def raw(rawmode, data):
|
||||||
palette.dirty = 1
|
palette.dirty = 1
|
||||||
return palette
|
return palette
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Factories
|
# Factories
|
||||||
|
|
||||||
def _make_linear_lut(black, white):
|
def _make_linear_lut(black, white):
|
||||||
|
warnings.warn(
|
||||||
|
'_make_linear_lut() is deprecated. '
|
||||||
|
'Please call make_linear_lut() instead.',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
|
return make_linear_lut(black, white)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_gamma_lut(exp):
|
||||||
|
warnings.warn(
|
||||||
|
'_make_gamma_lut() is deprecated. '
|
||||||
|
'Please call make_gamma_lut() instead.',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
|
return make_gamma_lut(exp)
|
||||||
|
|
||||||
|
|
||||||
|
def make_linear_lut(black, white):
|
||||||
lut = []
|
lut = []
|
||||||
if black == 0:
|
if black == 0:
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
lut.append(white*i//255)
|
lut.append(white*i//255)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError # FIXME
|
raise NotImplementedError # FIXME
|
||||||
return lut
|
return lut
|
||||||
|
|
||||||
def _make_gamma_lut(exp, mode="RGB"):
|
|
||||||
|
def make_gamma_lut(exp):
|
||||||
lut = []
|
lut = []
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
|
lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
|
||||||
return lut
|
return lut
|
||||||
|
|
||||||
def new(mode, data):
|
|
||||||
return Image.core.new_palette(mode, data)
|
|
||||||
|
|
||||||
def negative(mode="RGB"):
|
def negative(mode="RGB"):
|
||||||
palette = list(range(256))
|
palette = list(range(256))
|
||||||
palette.reverse()
|
palette.reverse()
|
||||||
return ImagePalette(mode, palette * len(mode))
|
return ImagePalette(mode, palette * len(mode))
|
||||||
|
|
||||||
|
|
||||||
def random(mode="RGB"):
|
def random(mode="RGB"):
|
||||||
from random import randint
|
from random import randint
|
||||||
palette = []
|
palette = []
|
||||||
|
@ -152,16 +175,19 @@ def random(mode="RGB"):
|
||||||
palette.append(randint(0, 255))
|
palette.append(randint(0, 255))
|
||||||
return ImagePalette(mode, palette)
|
return ImagePalette(mode, palette)
|
||||||
|
|
||||||
|
|
||||||
def sepia(white="#fff0c0"):
|
def sepia(white="#fff0c0"):
|
||||||
r, g, b = ImageColor.getrgb(white)
|
r, g, b = ImageColor.getrgb(white)
|
||||||
r = _make_linear_lut(0, r)
|
r = make_linear_lut(0, r)
|
||||||
g = _make_linear_lut(0, g)
|
g = make_linear_lut(0, g)
|
||||||
b = _make_linear_lut(0, b)
|
b = make_linear_lut(0, b)
|
||||||
return ImagePalette("RGB", r + g + b)
|
return ImagePalette("RGB", r + g + b)
|
||||||
|
|
||||||
|
|
||||||
def wedge(mode="RGB"):
|
def wedge(mode="RGB"):
|
||||||
return ImagePalette(mode, list(range(256)) * len(mode))
|
return ImagePalette(mode, list(range(256)) * len(mode))
|
||||||
|
|
||||||
|
|
||||||
def load(filename):
|
def load(filename):
|
||||||
|
|
||||||
# FIXME: supports GIMP gradients only
|
# FIXME: supports GIMP gradients only
|
||||||
|
@ -177,8 +203,8 @@ def load(filename):
|
||||||
p = GimpPaletteFile.GimpPaletteFile(fp)
|
p = GimpPaletteFile.GimpPaletteFile(fp)
|
||||||
lut = p.getpalette()
|
lut = p.getpalette()
|
||||||
except (SyntaxError, ValueError):
|
except (SyntaxError, ValueError):
|
||||||
#import traceback
|
# import traceback
|
||||||
#traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not lut:
|
if not lut:
|
||||||
|
@ -188,8 +214,8 @@ def load(filename):
|
||||||
p = GimpGradientFile.GimpGradientFile(fp)
|
p = GimpGradientFile.GimpGradientFile(fp)
|
||||||
lut = p.getpalette()
|
lut = p.getpalette()
|
||||||
except (SyntaxError, ValueError):
|
except (SyntaxError, ValueError):
|
||||||
#import traceback
|
# import traceback
|
||||||
#traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not lut:
|
if not lut:
|
||||||
|
@ -206,4 +232,4 @@ def load(filename):
|
||||||
if not lut:
|
if not lut:
|
||||||
raise IOError("cannot load palette")
|
raise IOError("cannot load palette")
|
||||||
|
|
||||||
return lut # data, rawmode
|
return lut # data, rawmode
|
||||||
|
|
197
PIL/OleFileIO.py
197
PIL/OleFileIO.py
|
@ -1,28 +1,29 @@
|
||||||
#!/usr/local/bin/python
|
#!/usr/local/bin/python
|
||||||
# -*- coding: latin-1 -*-
|
# -*- coding: latin-1 -*-
|
||||||
"""
|
## OleFileIO_PL:
|
||||||
OleFileIO_PL:
|
## Module to read Microsoft OLE2 files (also called Structured Storage or
|
||||||
Module to read Microsoft OLE2 files (also called Structured Storage or
|
## Microsoft Compound Document File Format), such as Microsoft Office
|
||||||
Microsoft Compound Document File Format), such as Microsoft Office
|
## documents, Image Composer and FlashPix files, Outlook messages, ...
|
||||||
documents, Image Composer and FlashPix files, Outlook messages, ...
|
## This version is compatible with Python 2.6+ and 3.x
|
||||||
This version is compatible with Python 2.6+ and 3.x
|
|
||||||
|
|
||||||
version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info
|
## version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info
|
||||||
|
|
||||||
Project website: http://www.decalage.info/python/olefileio
|
## Project website: http://www.decalage.info/python/olefileio
|
||||||
|
|
||||||
Improved version of the OleFileIO module from PIL library v1.1.6
|
## Improved version of the OleFileIO module from PIL library v1.1.6
|
||||||
See: http://www.pythonware.com/products/pil/index.htm
|
## See: http://www.pythonware.com/products/pil/index.htm
|
||||||
|
|
||||||
The Python Imaging Library (PIL) is
|
## The Python Imaging Library (PIL) is
|
||||||
Copyright (c) 1997-2005 by Secret Labs AB
|
|
||||||
Copyright (c) 1995-2005 by Fredrik Lundh
|
|
||||||
OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
|
|
||||||
|
|
||||||
See source code and LICENSE.txt for information on usage and redistribution.
|
## Copyright (c) 1997-2005 by Secret Labs AB
|
||||||
|
## Copyright (c) 1995-2005 by Fredrik Lundh
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
WARNING: THIS IS (STILL) WORK IN PROGRESS.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Starting with OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported
|
# 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
|
# This import enables print() as a function rather than a keyword
|
||||||
|
@ -370,8 +371,9 @@ for key in list(vars().keys()):
|
||||||
def isOleFile (filename):
|
def isOleFile (filename):
|
||||||
"""
|
"""
|
||||||
Test if file is an OLE container (according to its header).
|
Test if file is an OLE container (according to its header).
|
||||||
filename: file name or path (str, unicode)
|
|
||||||
return: True if OLE, False otherwise.
|
:param filename: file name or path (str, unicode)
|
||||||
|
:returns: True if OLE, False otherwise.
|
||||||
"""
|
"""
|
||||||
f = open(filename, 'rb')
|
f = open(filename, 'rb')
|
||||||
header = f.read(len(MAGIC))
|
header = f.read(len(MAGIC))
|
||||||
|
@ -397,8 +399,8 @@ def i16(c, o = 0):
|
||||||
"""
|
"""
|
||||||
Converts a 2-bytes (16 bits) string to an integer.
|
Converts a 2-bytes (16 bits) string to an integer.
|
||||||
|
|
||||||
c: string containing bytes to convert
|
:param c: string containing bytes to convert
|
||||||
o: offset of bytes to convert in string
|
:param o: offset of bytes to convert in string
|
||||||
"""
|
"""
|
||||||
return i8(c[o]) | (i8(c[o+1])<<8)
|
return i8(c[o]) | (i8(c[o+1])<<8)
|
||||||
|
|
||||||
|
@ -407,8 +409,8 @@ def i32(c, o = 0):
|
||||||
"""
|
"""
|
||||||
Converts a 4-bytes (32 bits) string to an integer.
|
Converts a 4-bytes (32 bits) string to an integer.
|
||||||
|
|
||||||
c: string containing bytes to convert
|
:param c: string containing bytes to convert
|
||||||
o: offset of bytes to convert in string
|
:param 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))
|
## 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
|
## # [PL]: added int() because "<<" gives long int since Python 2.4
|
||||||
|
@ -419,7 +421,8 @@ def i32(c, o = 0):
|
||||||
def _clsid(clsid):
|
def _clsid(clsid):
|
||||||
"""
|
"""
|
||||||
Converts a CLSID to a human-readable string.
|
Converts a CLSID to a human-readable string.
|
||||||
clsid: string of length 16.
|
|
||||||
|
:param clsid: string of length 16.
|
||||||
"""
|
"""
|
||||||
assert len(clsid) == 16
|
assert len(clsid) == 16
|
||||||
# if clsid is only made of null bytes, return an empty string:
|
# if clsid is only made of null bytes, return an empty string:
|
||||||
|
@ -439,8 +442,8 @@ def _unicode(s, errors='replace'):
|
||||||
"""
|
"""
|
||||||
Map unicode string to Latin 1. (Python with Unicode support)
|
Map unicode string to Latin 1. (Python with Unicode support)
|
||||||
|
|
||||||
s: UTF-16LE unicode string to convert to Latin-1
|
:param s: UTF-16LE unicode string to convert to Latin-1
|
||||||
errors: 'replace', 'ignore' or 'strict'.
|
:param errors: 'replace', 'ignore' or 'strict'.
|
||||||
"""
|
"""
|
||||||
#TODO: test if it OleFileIO works with Unicode strings, instead of
|
#TODO: test if it OleFileIO works with Unicode strings, instead of
|
||||||
# converting to Latin-1.
|
# converting to Latin-1.
|
||||||
|
@ -650,14 +653,14 @@ class _OleStream(io.BytesIO):
|
||||||
"""
|
"""
|
||||||
Constructor for _OleStream class.
|
Constructor for _OleStream class.
|
||||||
|
|
||||||
fp : file object, the OLE container or the MiniFAT stream
|
:param fp : file object, the OLE container or the MiniFAT stream
|
||||||
sect : sector index of first sector in the stream
|
:param sect : sector index of first sector in the stream
|
||||||
size : total size of the stream
|
:param size : total size of the stream
|
||||||
offset : offset in bytes for the first FAT or MiniFAT sector
|
:param offset : offset in bytes for the first FAT or MiniFAT sector
|
||||||
sectorsize: size of one sector
|
:param sectorsize: size of one sector
|
||||||
fat : array/list of sector indexes (FAT or MiniFAT)
|
:param fat : array/list of sector indexes (FAT or MiniFAT)
|
||||||
filesize : size of OLE file (for debugging)
|
:param filesize : size of OLE file (for debugging)
|
||||||
return : a BytesIO instance containing the OLE stream
|
:returns : a BytesIO instance containing the OLE stream
|
||||||
"""
|
"""
|
||||||
debug('_OleStream.__init__:')
|
debug('_OleStream.__init__:')
|
||||||
debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s'
|
debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s'
|
||||||
|
@ -793,9 +796,9 @@ class _OleDirectoryEntry:
|
||||||
Constructor for an _OleDirectoryEntry object.
|
Constructor for an _OleDirectoryEntry object.
|
||||||
Parses a 128-bytes entry from the OLE Directory stream.
|
Parses a 128-bytes entry from the OLE Directory stream.
|
||||||
|
|
||||||
entry : string (must be 128 bytes long)
|
:param entry : string (must be 128 bytes long)
|
||||||
sid : index of this directory entry in the OLE file directory
|
:param sid : index of this directory entry in the OLE file directory
|
||||||
olefile: OleFileIO containing this directory entry
|
:param olefile: OleFileIO containing this directory entry
|
||||||
"""
|
"""
|
||||||
self.sid = sid
|
self.sid = sid
|
||||||
# ref to olefile is stored for future use
|
# ref to olefile is stored for future use
|
||||||
|
@ -989,7 +992,7 @@ class _OleDirectoryEntry:
|
||||||
"""
|
"""
|
||||||
Return modification time of a directory entry.
|
Return modification time of a directory entry.
|
||||||
|
|
||||||
return: None if modification time is null, a python datetime object
|
:returns: None if modification time is null, a python datetime object
|
||||||
otherwise (UTC timezone)
|
otherwise (UTC timezone)
|
||||||
|
|
||||||
new in version 0.26
|
new in version 0.26
|
||||||
|
@ -1003,7 +1006,7 @@ class _OleDirectoryEntry:
|
||||||
"""
|
"""
|
||||||
Return creation time of a directory entry.
|
Return creation time of a directory entry.
|
||||||
|
|
||||||
return: None if modification time is null, a python datetime object
|
:returns: None if modification time is null, a python datetime object
|
||||||
otherwise (UTC timezone)
|
otherwise (UTC timezone)
|
||||||
|
|
||||||
new in version 0.26
|
new in version 0.26
|
||||||
|
@ -1020,7 +1023,8 @@ class OleFileIO:
|
||||||
OLE container object
|
OLE container object
|
||||||
|
|
||||||
This class encapsulates the interface to an OLE 2 structured
|
This class encapsulates the interface to an OLE 2 structured
|
||||||
storage file. Use the {@link listdir} and {@link openstream} methods to
|
storage file. Use the :py:meth:`~PIL.OleFileIO.OleFileIO.listdir` and
|
||||||
|
:py:meth:`~PIL.OleFileIO.OleFileIO.openstream` methods to
|
||||||
access the contents of this file.
|
access the contents of this file.
|
||||||
|
|
||||||
Object names are given as a list of strings, one for each subentry
|
Object names are given as a list of strings, one for each subentry
|
||||||
|
@ -1048,8 +1052,8 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Constructor for OleFileIO class.
|
Constructor for OleFileIO class.
|
||||||
|
|
||||||
filename: file to open.
|
:param filename: file to open.
|
||||||
raise_defects: minimal level for defects to be raised as exceptions.
|
:param raise_defects: minimal level for defects to be raised as exceptions.
|
||||||
(use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a
|
(use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a
|
||||||
security-oriented application, see source code for details)
|
security-oriented application, see source code for details)
|
||||||
"""
|
"""
|
||||||
|
@ -1068,13 +1072,13 @@ class OleFileIO:
|
||||||
It may raise an IOError exception according to the minimal level chosen
|
It may raise an IOError exception according to the minimal level chosen
|
||||||
for the OleFileIO object.
|
for the OleFileIO object.
|
||||||
|
|
||||||
defect_level: defect level, possible values are:
|
:param defect_level: defect level, possible values are:
|
||||||
DEFECT_UNSURE : a case which looks weird, but not sure it's a defect
|
DEFECT_UNSURE : a case which looks weird, but not sure it's a defect
|
||||||
DEFECT_POTENTIAL : a potential defect
|
DEFECT_POTENTIAL : a potential defect
|
||||||
DEFECT_INCORRECT : an error according to specifications, but parsing can go on
|
DEFECT_INCORRECT : an error according to specifications, but parsing can go on
|
||||||
DEFECT_FATAL : an error which cannot be ignored, parsing is impossible
|
DEFECT_FATAL : an error which cannot be ignored, parsing is impossible
|
||||||
message: string describing the defect, used with raised exception.
|
:param message: string describing the defect, used with raised exception.
|
||||||
exception_type: exception class to be raised, IOError by default
|
:param exception_type: exception class to be raised, IOError by default
|
||||||
"""
|
"""
|
||||||
# added by [PL]
|
# added by [PL]
|
||||||
if defect_level >= self._raise_defects_level:
|
if defect_level >= self._raise_defects_level:
|
||||||
|
@ -1089,7 +1093,7 @@ class OleFileIO:
|
||||||
Open an OLE2 file.
|
Open an OLE2 file.
|
||||||
Reads the header, FAT and directory.
|
Reads the header, FAT and directory.
|
||||||
|
|
||||||
filename: string-like or file-like object
|
:param filename: string-like or file-like object
|
||||||
"""
|
"""
|
||||||
#[PL] check if filename is a string-like or file-like object:
|
#[PL] check if filename is a string-like or file-like object:
|
||||||
# (it is better to check for a read() method)
|
# (it is better to check for a read() method)
|
||||||
|
@ -1276,8 +1280,8 @@ class OleFileIO:
|
||||||
Checks if a stream has not been already referenced elsewhere.
|
Checks if a stream has not been already referenced elsewhere.
|
||||||
This method should only be called once for each known stream, and only
|
This method should only be called once for each known stream, and only
|
||||||
if stream size is not null.
|
if stream size is not null.
|
||||||
first_sect: index of first sector of the stream in FAT
|
:param first_sect: index of first sector of the stream in FAT
|
||||||
minifat: if True, stream is located in the MiniFAT, else in the FAT
|
:param minifat: if True, stream is located in the MiniFAT, else in the FAT
|
||||||
"""
|
"""
|
||||||
if minifat:
|
if minifat:
|
||||||
debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect)
|
debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect)
|
||||||
|
@ -1371,8 +1375,9 @@ class OleFileIO:
|
||||||
def loadfat_sect(self, sect):
|
def loadfat_sect(self, sect):
|
||||||
"""
|
"""
|
||||||
Adds the indexes of the given sector to the FAT
|
Adds the indexes of the given sector to the FAT
|
||||||
sect: string containing the first FAT sector, or array of long integers
|
|
||||||
return: index of last FAT sector.
|
:param sect: string containing the first FAT sector, or array of long integers
|
||||||
|
:returns: index of last FAT sector.
|
||||||
"""
|
"""
|
||||||
# a FAT sector is an array of ulong integers.
|
# a FAT sector is an array of ulong integers.
|
||||||
if isinstance(sect, array.array):
|
if isinstance(sect, array.array):
|
||||||
|
@ -1505,8 +1510,9 @@ class OleFileIO:
|
||||||
def getsect(self, sect):
|
def getsect(self, sect):
|
||||||
"""
|
"""
|
||||||
Read given sector from file on disk.
|
Read given sector from file on disk.
|
||||||
sect: sector index
|
|
||||||
returns a string containing the sector data.
|
:param sect: sector index
|
||||||
|
:returns: a string containing the sector data.
|
||||||
"""
|
"""
|
||||||
# [PL] this original code was wrong when sectors are 4KB instead of
|
# [PL] this original code was wrong when sectors are 4KB instead of
|
||||||
# 512 bytes:
|
# 512 bytes:
|
||||||
|
@ -1530,7 +1536,8 @@ class OleFileIO:
|
||||||
def loaddirectory(self, sect):
|
def loaddirectory(self, sect):
|
||||||
"""
|
"""
|
||||||
Load the directory.
|
Load the directory.
|
||||||
sect: sector index of directory stream.
|
|
||||||
|
:param sect: sector index of directory stream.
|
||||||
"""
|
"""
|
||||||
# The directory is stored in a standard
|
# The directory is stored in a standard
|
||||||
# substream, independent of its size.
|
# substream, independent of its size.
|
||||||
|
@ -1567,9 +1574,10 @@ class OleFileIO:
|
||||||
Load a directory entry from the directory.
|
Load a directory entry from the directory.
|
||||||
This method should only be called once for each storage/stream when
|
This method should only be called once for each storage/stream when
|
||||||
loading the directory.
|
loading the directory.
|
||||||
sid: index of storage/stream in the directory.
|
|
||||||
return: a _OleDirectoryEntry object
|
:param sid: index of storage/stream in the directory.
|
||||||
raise: IOError if the entry has always been referenced.
|
:returns: a _OleDirectoryEntry object
|
||||||
|
:exception IOError: if the entry has always been referenced.
|
||||||
"""
|
"""
|
||||||
# check if SID is OK:
|
# check if SID is OK:
|
||||||
if sid<0 or sid>=len(self.direntries):
|
if sid<0 or sid>=len(self.direntries):
|
||||||
|
@ -1598,9 +1606,9 @@ class OleFileIO:
|
||||||
Open a stream, either in FAT or MiniFAT according to its size.
|
Open a stream, either in FAT or MiniFAT according to its size.
|
||||||
(openstream helper)
|
(openstream helper)
|
||||||
|
|
||||||
start: index of first sector
|
:param start: index of first sector
|
||||||
size: size of stream (or nothing if size is unknown)
|
:param size: size of stream (or nothing if size is unknown)
|
||||||
force_FAT: if False (default), stream will be opened in FAT or MiniFAT
|
:param force_FAT: if False (default), stream will be opened in FAT or MiniFAT
|
||||||
according to size. If True, it will always be opened in FAT.
|
according to size. If True, it will always be opened in FAT.
|
||||||
"""
|
"""
|
||||||
debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' %
|
debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' %
|
||||||
|
@ -1630,11 +1638,11 @@ class OleFileIO:
|
||||||
def _list(self, files, prefix, node, streams=True, storages=False):
|
def _list(self, files, prefix, node, streams=True, storages=False):
|
||||||
"""
|
"""
|
||||||
(listdir helper)
|
(listdir helper)
|
||||||
files: list of files to fill in
|
:param files: list of files to fill in
|
||||||
prefix: current location in storage tree (list of names)
|
:param prefix: current location in storage tree (list of names)
|
||||||
node: current node (_OleDirectoryEntry object)
|
:param node: current node (_OleDirectoryEntry object)
|
||||||
streams: bool, include streams if True (True by default) - new in v0.26
|
:param streams: bool, include streams if True (True by default) - new in v0.26
|
||||||
storages: bool, include storages if True (False by default) - new in v0.26
|
:param storages: bool, include storages if True (False by default) - new in v0.26
|
||||||
(note: the root storage is never included)
|
(note: the root storage is never included)
|
||||||
"""
|
"""
|
||||||
prefix = prefix + [node.name]
|
prefix = prefix + [node.name]
|
||||||
|
@ -1657,9 +1665,9 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return a list of streams stored in this file
|
Return a list of streams stored in this file
|
||||||
|
|
||||||
streams: bool, include streams if True (True by default) - new in v0.26
|
:param streams: bool, include streams if True (True by default) - new in v0.26
|
||||||
storages: bool, include storages if True (False by default) - new in v0.26
|
:param storages: bool, include storages if True (False by default) - new in v0.26
|
||||||
(note: the root storage is never included)
|
(note: the root storage is never included)
|
||||||
"""
|
"""
|
||||||
files = []
|
files = []
|
||||||
self._list(files, [], self.root, streams, storages)
|
self._list(files, [], self.root, streams, storages)
|
||||||
|
@ -1671,12 +1679,13 @@ class OleFileIO:
|
||||||
Returns directory entry of given filename. (openstream helper)
|
Returns directory entry of given filename. (openstream helper)
|
||||||
Note: this method is case-insensitive.
|
Note: this method is case-insensitive.
|
||||||
|
|
||||||
filename: path of stream in storage tree (except root entry), either:
|
:param filename: path of stream in storage tree (except root entry), either:
|
||||||
|
|
||||||
- a string using Unix path syntax, for example:
|
- a string using Unix path syntax, for example:
|
||||||
'storage_1/storage_1.2/stream'
|
'storage_1/storage_1.2/stream'
|
||||||
- a list of storage filenames, path to the desired stream/storage.
|
- a list of storage filenames, path to the desired stream/storage.
|
||||||
Example: ['storage_1', 'storage_1.2', 'stream']
|
Example: ['storage_1', 'storage_1.2', 'stream']
|
||||||
return: sid of requested filename
|
:returns: sid of requested filename
|
||||||
raise IOError if file not found
|
raise IOError if file not found
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -1700,13 +1709,15 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Open a stream as a read-only file object (BytesIO).
|
Open a stream as a read-only file object (BytesIO).
|
||||||
|
|
||||||
filename: path of stream in storage tree (except root entry), either:
|
:param filename: path of stream in storage tree (except root entry), either:
|
||||||
|
|
||||||
- a string using Unix path syntax, for example:
|
- a string using Unix path syntax, for example:
|
||||||
'storage_1/storage_1.2/stream'
|
'storage_1/storage_1.2/stream'
|
||||||
- a list of storage filenames, path to the desired stream/storage.
|
- a list of storage filenames, path to the desired stream/storage.
|
||||||
Example: ['storage_1', 'storage_1.2', 'stream']
|
Example: ['storage_1', 'storage_1.2', 'stream']
|
||||||
return: file object (read-only)
|
|
||||||
raise IOError if filename not found, or if this is not a stream.
|
:returns: file object (read-only)
|
||||||
|
:exception IOError: if filename not found, or if this is not a stream.
|
||||||
"""
|
"""
|
||||||
sid = self._find(filename)
|
sid = self._find(filename)
|
||||||
entry = self.direntries[sid]
|
entry = self.direntries[sid]
|
||||||
|
@ -1720,8 +1731,9 @@ class OleFileIO:
|
||||||
Test if given filename exists as a stream or a storage in the OLE
|
Test if given filename exists as a stream or a storage in the OLE
|
||||||
container, and return its type.
|
container, and return its type.
|
||||||
|
|
||||||
filename: path of stream in storage tree. (see openstream for syntax)
|
:param filename: path of stream in storage tree. (see openstream for syntax)
|
||||||
return: False if object does not exist, its entry type (>0) otherwise:
|
:returns: False if object does not exist, its entry type (>0) otherwise:
|
||||||
|
|
||||||
- STGTY_STREAM: a stream
|
- STGTY_STREAM: a stream
|
||||||
- STGTY_STORAGE: a storage
|
- STGTY_STORAGE: a storage
|
||||||
- STGTY_ROOT: the root entry
|
- STGTY_ROOT: the root entry
|
||||||
|
@ -1738,10 +1750,10 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return modification time of a stream/storage.
|
Return modification time of a stream/storage.
|
||||||
|
|
||||||
filename: path of stream/storage in storage tree. (see openstream for
|
:param filename: path of stream/storage in storage tree. (see openstream for
|
||||||
syntax)
|
syntax)
|
||||||
return: None if modification time is null, a python datetime object
|
:returns: None if modification time is null, a python datetime object
|
||||||
otherwise (UTC timezone)
|
otherwise (UTC timezone)
|
||||||
|
|
||||||
new in version 0.26
|
new in version 0.26
|
||||||
"""
|
"""
|
||||||
|
@ -1754,10 +1766,10 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return creation time of a stream/storage.
|
Return creation time of a stream/storage.
|
||||||
|
|
||||||
filename: path of stream/storage in storage tree. (see openstream for
|
:param filename: path of stream/storage in storage tree. (see openstream for
|
||||||
syntax)
|
syntax)
|
||||||
return: None if creation time is null, a python datetime object
|
:returns: None if creation time is null, a python datetime object
|
||||||
otherwise (UTC timezone)
|
otherwise (UTC timezone)
|
||||||
|
|
||||||
new in version 0.26
|
new in version 0.26
|
||||||
"""
|
"""
|
||||||
|
@ -1771,8 +1783,8 @@ class OleFileIO:
|
||||||
Test if given filename exists as a stream or a storage in the OLE
|
Test if given filename exists as a stream or a storage in the OLE
|
||||||
container.
|
container.
|
||||||
|
|
||||||
filename: path of stream in storage tree. (see openstream for syntax)
|
:param filename: path of stream in storage tree. (see openstream for syntax)
|
||||||
return: True if object exist, else False.
|
:returns: True if object exist, else False.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sid = self._find(filename)
|
sid = self._find(filename)
|
||||||
|
@ -1785,9 +1797,10 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return size of a stream in the OLE container, in bytes.
|
Return size of a stream in the OLE container, in bytes.
|
||||||
|
|
||||||
filename: path of stream in storage tree (see openstream for syntax)
|
:param filename: path of stream in storage tree (see openstream for syntax)
|
||||||
return: size in bytes (long integer)
|
:returns: size in bytes (long integer)
|
||||||
raise: IOError if file not found, TypeError if this is not a stream.
|
:exception IOError: if file not found
|
||||||
|
:exception TypeError: if this is not a stream
|
||||||
"""
|
"""
|
||||||
sid = self._find(filename)
|
sid = self._find(filename)
|
||||||
entry = self.direntries[sid]
|
entry = self.direntries[sid]
|
||||||
|
@ -1809,11 +1822,11 @@ class OleFileIO:
|
||||||
"""
|
"""
|
||||||
Return properties described in substream.
|
Return properties described in substream.
|
||||||
|
|
||||||
filename: path of stream in storage tree (see openstream for syntax)
|
:param filename: path of stream in storage tree (see openstream for syntax)
|
||||||
convert_time: bool, if True timestamps will be converted to Python datetime
|
:param convert_time: bool, if True timestamps will be converted to Python datetime
|
||||||
no_conversion: None or list of int, timestamps not to be converted
|
:param no_conversion: None or list of int, timestamps not to be converted
|
||||||
(for example total editing time is not a real timestamp)
|
(for example total editing time is not a real timestamp)
|
||||||
return: a dictionary of values indexed by id (integer)
|
:returns: a dictionary of values indexed by id (integer)
|
||||||
"""
|
"""
|
||||||
# make sure no_conversion is a list, just to simplify code below:
|
# make sure no_conversion is a list, just to simplify code below:
|
||||||
if no_conversion == None:
|
if no_conversion == None:
|
||||||
|
|
|
@ -73,9 +73,8 @@ class PSDraw:
|
||||||
|
|
||||||
def setink(self, ink):
|
def setink(self, ink):
|
||||||
"""
|
"""
|
||||||
.. warning::
|
.. warning:: This has been in the PIL API for ages but was never implemented.
|
||||||
|
|
||||||
This has been in the PIL API for ages but was never implemented.
|
|
||||||
"""
|
"""
|
||||||
print("*** NOT YET IMPLEMENTED ***")
|
print("*** NOT YET IMPLEMENTED ***")
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ i32 = _binary.i32be
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i16(prefix) == 474
|
return i16(prefix) == 474
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for SGI images.
|
# Image plugin for SGI images.
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ class SgiImageFile(ImageFile.ImageFile):
|
||||||
# HEAD
|
# HEAD
|
||||||
s = self.fp.read(512)
|
s = self.fp.read(512)
|
||||||
if i16(s) != 474:
|
if i16(s) != 474:
|
||||||
raise SyntaxError("not an SGI image file")
|
raise ValueError("Not an SGI image file")
|
||||||
|
|
||||||
# relevant header entries
|
# relevant header entries
|
||||||
compression = i8(s[2])
|
compression = i8(s[2])
|
||||||
|
@ -60,22 +61,22 @@ class SgiImageFile(ImageFile.ImageFile):
|
||||||
elif layout == (1, 3, 4):
|
elif layout == (1, 3, 4):
|
||||||
self.mode = "RGBA"
|
self.mode = "RGBA"
|
||||||
else:
|
else:
|
||||||
raise SyntaxError("unsupported SGI image mode")
|
raise ValueError("Unsupported SGI image mode")
|
||||||
|
|
||||||
# size
|
# size
|
||||||
self.size = i16(s[6:]), i16(s[8:])
|
self.size = i16(s[6:]), i16(s[8:])
|
||||||
|
|
||||||
|
|
||||||
# decoder info
|
# decoder info
|
||||||
if compression == 0:
|
if compression == 0:
|
||||||
offset = 512
|
offset = 512
|
||||||
pagesize = self.size[0]*self.size[1]*layout[0]
|
pagesize = self.size[0]*self.size[1]*layout[0]
|
||||||
self.tile = []
|
self.tile = []
|
||||||
for layer in self.mode:
|
for layer in self.mode:
|
||||||
self.tile.append(("raw", (0,0)+self.size, offset, (layer,0,-1)))
|
self.tile.append(
|
||||||
|
("raw", (0, 0)+self.size, offset, (layer, 0, -1)))
|
||||||
offset = offset + pagesize
|
offset = offset + pagesize
|
||||||
elif compression == 1:
|
elif compression == 1:
|
||||||
self.tile = [("sgi_rle", (0,0)+self.size, 512, (self.mode, 0, -1))]
|
raise ValueError("SGI RLE encoding not supported")
|
||||||
|
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
@ -85,5 +86,6 @@ Image.register_open("SGI", SgiImageFile, _accept)
|
||||||
Image.register_extension("SGI", ".bw")
|
Image.register_extension("SGI", ".bw")
|
||||||
Image.register_extension("SGI", ".rgb")
|
Image.register_extension("SGI", ".rgb")
|
||||||
Image.register_extension("SGI", ".rgba")
|
Image.register_extension("SGI", ".rgba")
|
||||||
|
Image.register_extension("SGI", ".sgi")
|
||||||
|
|
||||||
Image.register_extension("SGI", ".sgi") # really?
|
# End of file
|
||||||
|
|
|
@ -29,6 +29,7 @@ i32 = _binary.i32be
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return i32(prefix) == 0x59a66a95
|
return i32(prefix) == 0x59a66a95
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for Sun raster files.
|
# Image plugin for Sun raster files.
|
||||||
|
|
||||||
|
@ -70,9 +71,9 @@ class SunImageFile(ImageFile.ImageFile):
|
||||||
stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3)
|
stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3)
|
||||||
|
|
||||||
if compression == 1:
|
if compression == 1:
|
||||||
self.tile = [("raw", (0,0)+self.size, offset, (rawmode, stride))]
|
self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))]
|
||||||
elif compression == 2:
|
elif compression == 2:
|
||||||
self.tile = [("sun_rle", (0,0)+self.size, offset, rawmode)]
|
self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)]
|
||||||
|
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
|
@ -29,6 +29,7 @@ xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)")
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:9] == b"/* XPM */"
|
return prefix[:9] == b"/* XPM */"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for X11 pixel maps.
|
# Image plugin for X11 pixel maps.
|
||||||
|
|
||||||
|
@ -86,9 +87,9 @@ class XpmImageFile(ImageFile.ImageFile):
|
||||||
elif rgb[0:1] == b"#":
|
elif rgb[0:1] == b"#":
|
||||||
# FIXME: handle colour names (see ImagePalette.py)
|
# FIXME: handle colour names (see ImagePalette.py)
|
||||||
rgb = int(rgb[1:], 16)
|
rgb = int(rgb[1:], 16)
|
||||||
palette[c] = o8((rgb >> 16) & 255) +\
|
palette[c] = (o8((rgb >> 16) & 255) +
|
||||||
o8((rgb >> 8) & 255) +\
|
o8((rgb >> 8) & 255) +
|
||||||
o8(rgb & 255)
|
o8(rgb & 255))
|
||||||
else:
|
else:
|
||||||
# unknown colour
|
# unknown colour
|
||||||
raise ValueError("cannot read this XPM file")
|
raise ValueError("cannot read this XPM file")
|
||||||
|
|
|
@ -19,6 +19,10 @@ class PillowTestCase(unittest.TestCase):
|
||||||
# holds last result object passed to run method:
|
# holds last result object passed to run method:
|
||||||
self.currentResult = None
|
self.currentResult = None
|
||||||
|
|
||||||
|
# Nicer output for --verbose
|
||||||
|
def __str__(self):
|
||||||
|
return self.__class__.__name__ + "." + self._testMethodName
|
||||||
|
|
||||||
def run(self, result=None):
|
def run(self, result=None):
|
||||||
self.currentResult = result # remember result for use later
|
self.currentResult = result # remember result for use later
|
||||||
unittest.TestCase.run(self, result) # call superclass run method
|
unittest.TestCase.run(self, result) # call superclass run method
|
||||||
|
|
BIN
Tests/images/deerstalker.cur
Normal file
BIN
Tests/images/deerstalker.cur
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
Tests/images/lena.bw
Normal file
BIN
Tests/images/lena.bw
Normal file
Binary file not shown.
BIN
Tests/images/lena.dcx
Normal file
BIN
Tests/images/lena.dcx
Normal file
Binary file not shown.
BIN
Tests/images/lena.ras
Normal file
BIN
Tests/images/lena.ras
Normal file
Binary file not shown.
BIN
Tests/images/lena.rgb
Normal file
BIN
Tests/images/lena.rgb
Normal file
Binary file not shown.
BIN
Tests/images/transparent.sgi
Normal file
BIN
Tests/images/transparent.sgi
Normal file
Binary file not shown.
27
Tests/test_file_cur.py
Normal file
27
Tests/test_file_cur.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
|
from PIL import Image, CurImagePlugin
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileCur(PillowTestCase):
|
||||||
|
|
||||||
|
def test_sanity(self):
|
||||||
|
# Arrange
|
||||||
|
test_file = "Tests/images/deerstalker.cur"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im = Image.open(test_file)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(im.size, (32, 32))
|
||||||
|
self.assertIsInstance(im, CurImagePlugin.CurImageFile)
|
||||||
|
# Check some pixel colors to ensure image is loaded properly
|
||||||
|
self.assertEqual(im.getpixel((10, 1)), (0, 0, 0))
|
||||||
|
self.assertEqual(im.getpixel((11, 1)), (253, 254, 254))
|
||||||
|
self.assertEqual(im.getpixel((16, 16)), (84, 87, 86))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
45
Tests/test_file_dcx.py
Normal file
45
Tests/test_file_dcx.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
from helper import unittest, PillowTestCase, lena
|
||||||
|
|
||||||
|
from PIL import Image, DcxImagePlugin
|
||||||
|
|
||||||
|
# Created with ImageMagick: convert lena.ppm lena.dcx
|
||||||
|
TEST_FILE = "Tests/images/lena.dcx"
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileDcx(PillowTestCase):
|
||||||
|
|
||||||
|
def test_sanity(self):
|
||||||
|
# Arrange
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im = Image.open(TEST_FILE)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(im.size, (128, 128))
|
||||||
|
self.assertIsInstance(im, DcxImagePlugin.DcxImageFile)
|
||||||
|
orig = lena()
|
||||||
|
self.assert_image_equal(im, orig)
|
||||||
|
|
||||||
|
def test_tell(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.open(TEST_FILE)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
frame = im.tell()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(frame, 0)
|
||||||
|
|
||||||
|
def test_seek_too_far(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.open(TEST_FILE)
|
||||||
|
frame = 999 # too big on purpose
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
self.assertRaises(EOFError, lambda: im.seek(frame))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
39
Tests/test_file_sgi.py
Normal file
39
Tests/test_file_sgi.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileSgi(PillowTestCase):
|
||||||
|
|
||||||
|
def test_rgb(self):
|
||||||
|
# Arrange
|
||||||
|
# Created with ImageMagick then renamed:
|
||||||
|
# convert lena.ppm lena.sgi
|
||||||
|
test_file = "Tests/images/lena.rgb"
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
self.assertRaises(ValueError, lambda: Image.open(test_file))
|
||||||
|
|
||||||
|
def test_l(self):
|
||||||
|
# Arrange
|
||||||
|
# Created with ImageMagick then renamed:
|
||||||
|
# convert lena.ppm -monochrome lena.sgi
|
||||||
|
test_file = "Tests/images/lena.bw"
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
self.assertRaises(ValueError, lambda: Image.open(test_file))
|
||||||
|
|
||||||
|
def test_rgba(self):
|
||||||
|
# Arrange
|
||||||
|
# Created with ImageMagick:
|
||||||
|
# convert transparent.png transparent.sgi
|
||||||
|
test_file = "Tests/images/transparent.sgi"
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
self.assertRaises(ValueError, lambda: Image.open(test_file))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
|
@ -3,13 +3,13 @@ from helper import unittest, PillowTestCase, lena
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL import SpiderImagePlugin
|
from PIL import SpiderImagePlugin
|
||||||
|
|
||||||
test_file = "Tests/images/lena.spider"
|
TEST_FILE = "Tests/images/lena.spider"
|
||||||
|
|
||||||
|
|
||||||
class TestImageSpider(PillowTestCase):
|
class TestImageSpider(PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
im = Image.open(test_file)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertEqual(im.mode, "F")
|
self.assertEqual(im.mode, "F")
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
|
@ -30,7 +30,50 @@ class TestImageSpider(PillowTestCase):
|
||||||
self.assertEqual(im2.format, "SPIDER")
|
self.assertEqual(im2.format, "SPIDER")
|
||||||
|
|
||||||
def test_isSpiderImage(self):
|
def test_isSpiderImage(self):
|
||||||
self.assertTrue(SpiderImagePlugin.isSpiderImage(test_file))
|
self.assertTrue(SpiderImagePlugin.isSpiderImage(TEST_FILE))
|
||||||
|
|
||||||
|
def test_tell(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.open(TEST_FILE)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
index = im.tell()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(index, 0)
|
||||||
|
|
||||||
|
def test_loadImageSeries(self):
|
||||||
|
# Arrange
|
||||||
|
not_spider_file = "Tests/images/lena.ppm"
|
||||||
|
file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"]
|
||||||
|
|
||||||
|
# Act
|
||||||
|
img_list = SpiderImagePlugin.loadImageSeries(file_list)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(len(img_list), 1)
|
||||||
|
self.assertIsInstance(img_list[0], Image.Image)
|
||||||
|
self.assertEqual(img_list[0].size, (128, 128))
|
||||||
|
|
||||||
|
def test_loadImageSeries_no_input(self):
|
||||||
|
# Arrange
|
||||||
|
file_list = None
|
||||||
|
|
||||||
|
# Act
|
||||||
|
img_list = SpiderImagePlugin.loadImageSeries(file_list)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(img_list, None)
|
||||||
|
|
||||||
|
def test_isInt_not_a_number(self):
|
||||||
|
# Arrange
|
||||||
|
not_a_number = "a"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ret = SpiderImagePlugin.isInt(not_a_number)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(ret, 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
23
Tests/test_file_sun.py
Normal file
23
Tests/test_file_sun.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileSun(PillowTestCase):
|
||||||
|
|
||||||
|
def test_sanity(self):
|
||||||
|
# Arrange
|
||||||
|
# Created with ImageMagick: convert lena.ppm lena.ras
|
||||||
|
test_file = "Tests/images/lena.ras"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im = Image.open(test_file)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(im.size, (128, 128))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
|
@ -1,21 +1,34 @@
|
||||||
from helper import unittest, PillowTestCase
|
from helper import unittest, PillowTestCase, lena
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
# sample ppm stream
|
# sample ppm stream
|
||||||
file = "Tests/images/lena.xpm"
|
TEST_FILE = "Tests/images/lena.xpm"
|
||||||
data = open(file, "rb").read()
|
|
||||||
|
|
||||||
|
|
||||||
class TestFileXpm(PillowTestCase):
|
class TestFileXpm(PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
im = Image.open(file)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertEqual(im.mode, "P")
|
self.assertEqual(im.mode, "P")
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
self.assertEqual(im.format, "XPM")
|
self.assertEqual(im.format, "XPM")
|
||||||
|
|
||||||
|
#large error due to quantization->44 colors.
|
||||||
|
self.assert_image_similar(im.convert('RGB'), lena('RGB'), 60)
|
||||||
|
|
||||||
|
def test_load_read(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.open(TEST_FILE)
|
||||||
|
dummy_bytes = 1
|
||||||
|
|
||||||
|
# Act
|
||||||
|
data = im.load_read(dummy_bytes)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(len(data), 16384)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from helper import unittest, PillowTestCase
|
from helper import unittest, PillowTestCase, lena
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
@ -55,6 +55,91 @@ class TestImage(PillowTestCase):
|
||||||
self.assertFalse(item == None)
|
self.assertFalse(item == None)
|
||||||
self.assertFalse(item == num)
|
self.assertFalse(item == num)
|
||||||
|
|
||||||
|
def test_expand_x(self):
|
||||||
|
# Arrange
|
||||||
|
im = lena()
|
||||||
|
orig_size = im.size
|
||||||
|
xmargin = 5
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im = im._expand(xmargin)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(im.size[0], orig_size[0] + 2*xmargin)
|
||||||
|
self.assertEqual(im.size[1], orig_size[1] + 2*xmargin)
|
||||||
|
|
||||||
|
def test_expand_xy(self):
|
||||||
|
# Arrange
|
||||||
|
im = lena()
|
||||||
|
orig_size = im.size
|
||||||
|
xmargin = 5
|
||||||
|
ymargin = 3
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im = im._expand(xmargin, ymargin)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(im.size[0], orig_size[0] + 2*xmargin)
|
||||||
|
self.assertEqual(im.size[1], orig_size[1] + 2*ymargin)
|
||||||
|
|
||||||
|
def test_getbands(self):
|
||||||
|
# Arrange
|
||||||
|
im = lena()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
bands = im.getbands()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(bands, ('R', 'G', 'B'))
|
||||||
|
|
||||||
|
def test_getbbox(self):
|
||||||
|
# Arrange
|
||||||
|
im = lena()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
bbox = im.getbbox()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(bbox, (0, 0, 128, 128))
|
||||||
|
|
||||||
|
def test_ne(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.new('RGB', (25, 25), 'black')
|
||||||
|
im2 = Image.new('RGB', (25, 25), 'white')
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
self.assertTrue(im1 != im2)
|
||||||
|
|
||||||
|
def test_alpha_composite(self):
|
||||||
|
# http://stackoverflow.com/questions/3374878
|
||||||
|
# Arrange
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
expected_colors = sorted([
|
||||||
|
(1122, (128, 127, 0, 255)),
|
||||||
|
(1089, (0, 255, 0, 255)),
|
||||||
|
(3300, (255, 0, 0, 255)),
|
||||||
|
(1156, (170, 85, 0, 192)),
|
||||||
|
(1122, (0, 255, 0, 128)),
|
||||||
|
(1122, (255, 0, 0, 128)),
|
||||||
|
(1089, (0, 255, 0, 0))])
|
||||||
|
|
||||||
|
dst = Image.new('RGBA', size=(100, 100), color=(0, 255, 0, 255))
|
||||||
|
draw = ImageDraw.Draw(dst)
|
||||||
|
draw.rectangle((0, 33, 100, 66), fill=(0, 255, 0, 128))
|
||||||
|
draw.rectangle((0, 67, 100, 100), fill=(0, 255, 0, 0))
|
||||||
|
src = Image.new('RGBA', size=(100, 100), color=(255, 0, 0, 255))
|
||||||
|
draw = ImageDraw.Draw(src)
|
||||||
|
draw.rectangle((33, 0, 66, 100), fill=(255, 0, 0, 128))
|
||||||
|
draw.rectangle((67, 0, 100, 100), fill=(255, 0, 0, 0))
|
||||||
|
|
||||||
|
# Act
|
||||||
|
img = Image.alpha_composite(dst, src)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
img_colors = sorted(img.getcolors())
|
||||||
|
self.assertEqual(img_colors, expected_colors)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -5,7 +5,7 @@ from PIL import Image
|
||||||
im = lena().resize((128, 100))
|
im = lena().resize((128, 100))
|
||||||
|
|
||||||
|
|
||||||
class TestImageCrop(PillowTestCase):
|
class TestImageArray(PillowTestCase):
|
||||||
|
|
||||||
def test_toarray(self):
|
def test_toarray(self):
|
||||||
def test(mode):
|
def test(mode):
|
||||||
|
|
|
@ -29,6 +29,10 @@ class TestImageFilter(PillowTestCase):
|
||||||
filter(ImageFilter.MinFilter)
|
filter(ImageFilter.MinFilter)
|
||||||
filter(ImageFilter.ModeFilter)
|
filter(ImageFilter.ModeFilter)
|
||||||
filter(ImageFilter.Kernel((3, 3), list(range(9))))
|
filter(ImageFilter.Kernel((3, 3), list(range(9))))
|
||||||
|
filter(ImageFilter.GaussianBlur)
|
||||||
|
filter(ImageFilter.GaussianBlur(5))
|
||||||
|
filter(ImageFilter.UnsharpMask)
|
||||||
|
filter(ImageFilter.UnsharpMask(10))
|
||||||
|
|
||||||
self.assertRaises(TypeError, lambda: filter("hello"))
|
self.assertRaises(TypeError, lambda: filter("hello"))
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,27 @@ class TestImageMode(PillowTestCase):
|
||||||
im = lena()
|
im = lena()
|
||||||
im.mode
|
im.mode
|
||||||
|
|
||||||
|
from PIL import ImageMode
|
||||||
|
|
||||||
|
ImageMode.getmode("1")
|
||||||
|
ImageMode.getmode("L")
|
||||||
|
ImageMode.getmode("P")
|
||||||
|
ImageMode.getmode("RGB")
|
||||||
|
ImageMode.getmode("I")
|
||||||
|
ImageMode.getmode("F")
|
||||||
|
|
||||||
|
m = ImageMode.getmode("1")
|
||||||
|
self.assertEqual(m.mode, "1")
|
||||||
|
self.assertEqual(m.bands, ("1",))
|
||||||
|
self.assertEqual(m.basemode, "L")
|
||||||
|
self.assertEqual(m.basetype, "L")
|
||||||
|
|
||||||
|
m = ImageMode.getmode("RGB")
|
||||||
|
self.assertEqual(m.mode, "RGB")
|
||||||
|
self.assertEqual(m.bands, ("R", "G", "B"))
|
||||||
|
self.assertEqual(m.basemode, "RGB")
|
||||||
|
self.assertEqual(m.basetype, "L")
|
||||||
|
|
||||||
def test_properties(self):
|
def test_properties(self):
|
||||||
def check(mode, *result):
|
def check(mode, *result):
|
||||||
signature = (
|
signature = (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from helper import unittest, lena
|
from helper import unittest, PillowTestCase, lena
|
||||||
|
|
||||||
|
|
||||||
class TestImageToBytes(unittest.TestCase):
|
class TestImageToBytes(PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
data = lena().tobytes()
|
data = lena().tobytes()
|
||||||
|
|
|
@ -5,6 +5,22 @@ from PIL import Image
|
||||||
|
|
||||||
class TestImageTransform(PillowTestCase):
|
class TestImageTransform(PillowTestCase):
|
||||||
|
|
||||||
|
def test_sanity(self):
|
||||||
|
from PIL import ImageTransform
|
||||||
|
|
||||||
|
im = Image.new("L", (100, 100))
|
||||||
|
|
||||||
|
seq = tuple(range(10))
|
||||||
|
|
||||||
|
transform = ImageTransform.AffineTransform(seq[:6])
|
||||||
|
im.transform((100, 100), transform)
|
||||||
|
transform = ImageTransform.ExtentTransform(seq[:4])
|
||||||
|
im.transform((100, 100), transform)
|
||||||
|
transform = ImageTransform.QuadTransform(seq[:8])
|
||||||
|
im.transform((100, 100), transform)
|
||||||
|
transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])])
|
||||||
|
im.transform((100, 100), transform)
|
||||||
|
|
||||||
def test_extent(self):
|
def test_extent(self):
|
||||||
im = lena('RGB')
|
im = lena('RGB')
|
||||||
(w, h) = im.size
|
(w, h) = im.size
|
||||||
|
|
|
@ -14,7 +14,7 @@ MAXBLOCK = ImageFile.MAXBLOCK
|
||||||
SAFEBLOCK = ImageFile.SAFEBLOCK
|
SAFEBLOCK = ImageFile.SAFEBLOCK
|
||||||
|
|
||||||
|
|
||||||
class TestImagePutData(PillowTestCase):
|
class TestImageFile(PillowTestCase):
|
||||||
|
|
||||||
def test_parser(self):
|
def test_parser(self):
|
||||||
|
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
from helper import unittest, PillowTestCase
|
|
||||||
|
|
||||||
from PIL import ImageFilter
|
|
||||||
|
|
||||||
|
|
||||||
class TestImageFilter(PillowTestCase):
|
|
||||||
|
|
||||||
def test_sanity(self):
|
|
||||||
# see test_image_filter for more tests
|
|
||||||
|
|
||||||
# Check these run. Exceptions cause failures.
|
|
||||||
ImageFilter.MaxFilter
|
|
||||||
ImageFilter.MedianFilter
|
|
||||||
ImageFilter.MinFilter
|
|
||||||
ImageFilter.ModeFilter
|
|
||||||
ImageFilter.Kernel((3, 3), list(range(9)))
|
|
||||||
ImageFilter.GaussianBlur
|
|
||||||
ImageFilter.GaussianBlur(5)
|
|
||||||
ImageFilter.UnsharpMask
|
|
||||||
ImageFilter.UnsharpMask(10)
|
|
||||||
|
|
||||||
ImageFilter.BLUR
|
|
||||||
ImageFilter.CONTOUR
|
|
||||||
ImageFilter.DETAIL
|
|
||||||
ImageFilter.EDGE_ENHANCE
|
|
||||||
ImageFilter.EDGE_ENHANCE_MORE
|
|
||||||
ImageFilter.EMBOSS
|
|
||||||
ImageFilter.FIND_EDGES
|
|
||||||
ImageFilter.SMOOTH
|
|
||||||
ImageFilter.SMOOTH_MORE
|
|
||||||
ImageFilter.SHARPEN
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
|
|
||||||
# End of file
|
|
|
@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase
|
||||||
try:
|
try:
|
||||||
from PIL import ImageGrab
|
from PIL import ImageGrab
|
||||||
|
|
||||||
class TestImageCopy(PillowTestCase):
|
class TestImageGrab(PillowTestCase):
|
||||||
|
|
||||||
def test_grab(self):
|
def test_grab(self):
|
||||||
im = ImageGrab.grab()
|
im = ImageGrab.grab()
|
||||||
|
@ -14,7 +14,7 @@ try:
|
||||||
self.assert_image(im, im.mode, im.size)
|
self.assert_image(im, im.mode, im.size)
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
class TestImageCopy(PillowTestCase):
|
class TestImageGrab(PillowTestCase):
|
||||||
def test_skip(self):
|
def test_skip(self):
|
||||||
self.skipTest("ImportError")
|
self.skipTest("ImportError")
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
from helper import unittest, PillowTestCase
|
|
||||||
|
|
||||||
from PIL import ImageMode
|
|
||||||
|
|
||||||
|
|
||||||
class TestImageMode(PillowTestCase):
|
|
||||||
|
|
||||||
def test_sanity(self):
|
|
||||||
ImageMode.getmode("1")
|
|
||||||
ImageMode.getmode("L")
|
|
||||||
ImageMode.getmode("P")
|
|
||||||
ImageMode.getmode("RGB")
|
|
||||||
ImageMode.getmode("I")
|
|
||||||
ImageMode.getmode("F")
|
|
||||||
|
|
||||||
m = ImageMode.getmode("1")
|
|
||||||
self.assertEqual(m.mode, "1")
|
|
||||||
self.assertEqual(m.bands, ("1",))
|
|
||||||
self.assertEqual(m.basemode, "L")
|
|
||||||
self.assertEqual(m.basetype, "L")
|
|
||||||
|
|
||||||
m = ImageMode.getmode("RGB")
|
|
||||||
self.assertEqual(m.mode, "RGB")
|
|
||||||
self.assertEqual(m.bands, ("R", "G", "B"))
|
|
||||||
self.assertEqual(m.basemode, "RGB")
|
|
||||||
self.assertEqual(m.basetype, "L")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
|
|
||||||
# End of file
|
|
|
@ -44,6 +44,109 @@ class TestImagePalette(PillowTestCase):
|
||||||
self.assertIsInstance(p, ImagePalette)
|
self.assertIsInstance(p, ImagePalette)
|
||||||
self.assertEqual(p.palette, palette.tobytes())
|
self.assertEqual(p.palette, palette.tobytes())
|
||||||
|
|
||||||
|
def test_make_linear_lut(self):
|
||||||
|
# Arrange
|
||||||
|
from PIL.ImagePalette import make_linear_lut
|
||||||
|
black = 0
|
||||||
|
white = 255
|
||||||
|
|
||||||
|
# Act
|
||||||
|
lut = make_linear_lut(black, white)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertIsInstance(lut, list)
|
||||||
|
self.assertEqual(len(lut), 256)
|
||||||
|
# Check values
|
||||||
|
for i in range(0, len(lut)):
|
||||||
|
self.assertEqual(lut[i], i)
|
||||||
|
|
||||||
|
def test_make_linear_lut_not_yet_implemented(self):
|
||||||
|
# Update after FIXME
|
||||||
|
# Arrange
|
||||||
|
from PIL.ImagePalette import make_linear_lut
|
||||||
|
black = 1
|
||||||
|
white = 255
|
||||||
|
|
||||||
|
# Act
|
||||||
|
self.assertRaises(
|
||||||
|
NotImplementedError,
|
||||||
|
lambda: make_linear_lut(black, white))
|
||||||
|
|
||||||
|
def test_make_gamma_lut(self):
|
||||||
|
# Arrange
|
||||||
|
from PIL.ImagePalette import make_gamma_lut
|
||||||
|
exp = 5
|
||||||
|
|
||||||
|
# Act
|
||||||
|
lut = make_gamma_lut(exp)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertIsInstance(lut, list)
|
||||||
|
self.assertEqual(len(lut), 256)
|
||||||
|
# Check a few values
|
||||||
|
self.assertEqual(lut[0], 0)
|
||||||
|
self.assertEqual(lut[63], 0)
|
||||||
|
self.assertEqual(lut[127], 8)
|
||||||
|
self.assertEqual(lut[191], 60)
|
||||||
|
self.assertEqual(lut[255], 255)
|
||||||
|
|
||||||
|
def test_private_make_linear_lut_warning(self):
|
||||||
|
# Arrange
|
||||||
|
from PIL.ImagePalette import _make_linear_lut
|
||||||
|
black = 0
|
||||||
|
white = 255
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
self.assert_warning(
|
||||||
|
DeprecationWarning,
|
||||||
|
lambda: _make_linear_lut(black, white))
|
||||||
|
|
||||||
|
def test_private_make_gamma_lut_warning(self):
|
||||||
|
# Arrange
|
||||||
|
from PIL.ImagePalette import _make_gamma_lut
|
||||||
|
exp = 5
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
self.assert_warning(
|
||||||
|
DeprecationWarning,
|
||||||
|
lambda: _make_gamma_lut(exp))
|
||||||
|
|
||||||
|
def test_rawmode_valueerrors(self):
|
||||||
|
# Arrange
|
||||||
|
from PIL.ImagePalette import raw
|
||||||
|
palette = raw("RGB", list(range(256))*3)
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
self.assertRaises(ValueError, lambda: palette.tobytes())
|
||||||
|
self.assertRaises(ValueError, lambda: palette.getcolor((1, 2, 3)))
|
||||||
|
f = self.tempfile("temp.lut")
|
||||||
|
self.assertRaises(ValueError, lambda: palette.save(f))
|
||||||
|
|
||||||
|
def test_getdata(self):
|
||||||
|
# Arrange
|
||||||
|
data_in = list(range(256))*3
|
||||||
|
palette = ImagePalette("RGB", data_in)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
mode, data_out = palette.getdata()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(mode, "RGB;L")
|
||||||
|
|
||||||
|
def test_rawmode_getdata(self):
|
||||||
|
# Arrange
|
||||||
|
from PIL.ImagePalette import raw
|
||||||
|
data_in = list(range(256))*3
|
||||||
|
palette = raw("RGB", data_in)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
rawmode, data_out = palette.getdata()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(rawmode, "RGB")
|
||||||
|
self.assertEqual(data_in, data_out)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
from helper import unittest, PillowTestCase
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
from PIL import ImageTransform
|
|
||||||
|
|
||||||
|
|
||||||
class TestImageTransform(PillowTestCase):
|
|
||||||
|
|
||||||
def test_sanity(self):
|
|
||||||
im = Image.new("L", (100, 100))
|
|
||||||
|
|
||||||
seq = tuple(range(10))
|
|
||||||
|
|
||||||
transform = ImageTransform.AffineTransform(seq[:6])
|
|
||||||
im.transform((100, 100), transform)
|
|
||||||
transform = ImageTransform.ExtentTransform(seq[:4])
|
|
||||||
im.transform((100, 100), transform)
|
|
||||||
transform = ImageTransform.QuadTransform(seq[:8])
|
|
||||||
im.transform((100, 100), transform)
|
|
||||||
transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])])
|
|
||||||
im.transform((100, 100), transform)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
|
|
||||||
# End of file
|
|
|
@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
class TestSanity(PillowTestCase):
|
class TestLibImage(PillowTestCase):
|
||||||
|
|
||||||
def test_setmode(self):
|
def test_setmode(self):
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestPyroma(unittest.TestCase):
|
class TestPyroma(PillowTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
* http://www.cazabon.com
|
* http://www.cazabon.com
|
||||||
* Adapted/reworked for PIL by Fredrik Lundh
|
* Adapted/reworked for PIL by Fredrik Lundh
|
||||||
* Copyright (c) 2009 Fredrik Lundh
|
* Copyright (c) 2009 Fredrik Lundh
|
||||||
|
* Updated to LCMS2
|
||||||
|
* Copyright (c) 2013 Eric Soroos
|
||||||
*
|
*
|
||||||
* pyCMS home page: http://www.cazabon.com/pyCMS
|
* pyCMS home page: http://www.cazabon.com/pyCMS
|
||||||
* littleCMS home page: http://www.littlecms.com
|
* littleCMS home page: http://www.littlecms.com
|
||||||
|
|
25
docs/PIL.rst
25
docs/PIL.rst
|
@ -52,15 +52,8 @@ can be found here.
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
:mod:`ImageCms` Module
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
.. automodule:: PIL.ImageCms
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
.. intentionally skipped documenting this because it's not documented anywhere
|
.. intentionally skipped documenting this because it's not documented anywhere
|
||||||
|
|
||||||
:mod:`ImageDraw2` Module
|
:mod:`ImageDraw2` Module
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
@ -70,6 +63,7 @@ can be found here.
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
.. intentionally skipped documenting this because it's deprecated
|
.. intentionally skipped documenting this because it's deprecated
|
||||||
|
|
||||||
:mod:`ImageFileIO` Module
|
:mod:`ImageFileIO` Module
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -78,13 +72,6 @@ can be found here.
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
:mod:`ImageMorph` Module
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
.. automodule:: PIL.ImageMorph
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
:mod:`ImageShow` Module
|
:mod:`ImageShow` Module
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -110,14 +97,6 @@ can be found here.
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
:mod:`OleFileIO` Module
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
.. automodule:: PIL.OleFileIO
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
:mod:`PaletteFile` Module
|
:mod:`PaletteFile` Module
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,10 @@ Functions
|
||||||
|
|
||||||
.. autofunction:: open
|
.. autofunction:: open
|
||||||
|
|
||||||
.. warning:: > To protect against potential DOS attacks caused by "`decompression bombs<https://en.wikipedia.org/wiki/Zip_bomb>`_" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also `the logging documentation<https://docs.python.org/2/library/logging.html?highlight=logging#integration-with-the-warnings-module>`_ to have warnings output to the logging facility instead of stderr.
|
.. warning:: To protect against potential DOS attacks caused by "`decompression bombs`_" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also `the logging documentation`_ to have warnings output to the logging facility instead of stderr.
|
||||||
|
|
||||||
|
.. _decompression bombs: https://en.wikipedia.org/wiki/Zip_bomb
|
||||||
|
.. _the logging documentation: https://docs.python.org/2/library/logging.html?highlight=logging#integration-with-the-warnings-module
|
||||||
|
|
||||||
Image processing
|
Image processing
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
13
docs/reference/ImageCms.rst
Normal file
13
docs/reference/ImageCms.rst
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
.. py:module:: PIL.ImageCms
|
||||||
|
.. py:currentmodule:: PIL.ImageCms
|
||||||
|
|
||||||
|
:py:mod:`ImageCms` Module
|
||||||
|
=========================
|
||||||
|
|
||||||
|
The :py:mod:`ImageCms` module provides color profile management
|
||||||
|
support using the LittleCMS2 color management engine, based on Kevin
|
||||||
|
Cazabon's PyCMS library.
|
||||||
|
|
||||||
|
.. automodule:: PIL.ImageCms
|
||||||
|
:members:
|
||||||
|
:noindex:
|
|
@ -74,6 +74,34 @@ To load a OpenType/TrueType font, use the truetype function in the
|
||||||
:py:mod:`~PIL.ImageFont` module. Note that this function depends on third-party
|
:py:mod:`~PIL.ImageFont` module. Note that this function depends on third-party
|
||||||
libraries, and may not available in all PIL builds.
|
libraries, and may not available in all PIL builds.
|
||||||
|
|
||||||
|
Example: Draw Partial Opacity Text
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
# get an image
|
||||||
|
base = Image.open('Pillow/Tests/images/lena.png').convert('RGBA')
|
||||||
|
|
||||||
|
# make a blank image for the text, initialized to transparent text color
|
||||||
|
txt = Image.new('RGBA', base.size, (255,255,255,0))
|
||||||
|
|
||||||
|
# get a font
|
||||||
|
fnt = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 40)
|
||||||
|
# get a drawing context
|
||||||
|
d = ImageDraw.Draw(txt)
|
||||||
|
|
||||||
|
# draw text, half opacity
|
||||||
|
d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128))
|
||||||
|
# draw text, full opacity
|
||||||
|
d.text((10,60), "World", font=fnt, fill=(255,255,255,255))
|
||||||
|
|
||||||
|
out = Image.alpha_composite(base, txt)
|
||||||
|
|
||||||
|
out.show()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Functions
|
Functions
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
@ -83,6 +111,13 @@ Functions
|
||||||
|
|
||||||
Note that the image will be modified in place.
|
Note that the image will be modified in place.
|
||||||
|
|
||||||
|
:param im: The image to draw in.
|
||||||
|
:param mode: Optional mode to use for color values. For RGB
|
||||||
|
images, this argument can be RGB or RGBA (to blend the
|
||||||
|
drawing into the image). For all other modes, this argument
|
||||||
|
must be the same as the image mode. If omitted, the mode
|
||||||
|
defaults to the mode of the image.
|
||||||
|
|
||||||
Methods
|
Methods
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
13
docs/reference/ImageMorph.rst
Normal file
13
docs/reference/ImageMorph.rst
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
.. py:module:: PIL.ImageMorph
|
||||||
|
.. py:currentmodule:: PIL.ImageMorph
|
||||||
|
|
||||||
|
:py:mod:`ImageMorph` Module
|
||||||
|
===========================
|
||||||
|
|
||||||
|
The :py:mod:`ImageMorph` module provides morphology operations on images.
|
||||||
|
|
||||||
|
.. automodule:: PIL.ImageMorph
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
:noindex:
|
|
@ -22,32 +22,32 @@ for a region of an image.
|
||||||
|
|
||||||
.. py:attribute:: count
|
.. py:attribute:: count
|
||||||
|
|
||||||
Total number of pixels.
|
Total number of pixels for each band in the image.
|
||||||
|
|
||||||
.. py:attribute:: sum
|
.. py:attribute:: sum
|
||||||
|
|
||||||
Sum of all pixels.
|
Sum of all pixels for each band in the image.
|
||||||
|
|
||||||
.. py:attribute:: sum2
|
.. py:attribute:: sum2
|
||||||
|
|
||||||
Squared sum of all pixels.
|
Squared sum of all pixels for each band in the image.
|
||||||
|
|
||||||
.. py:attribute:: pixel
|
.. py:attribute:: mean
|
||||||
|
|
||||||
Average pixel level.
|
Average (arithmetic mean) pixel level for each band in the image.
|
||||||
|
|
||||||
.. py:attribute:: median
|
.. py:attribute:: median
|
||||||
|
|
||||||
Median pixel level.
|
Median pixel level for each band in the image.
|
||||||
|
|
||||||
.. py:attribute:: rms
|
.. py:attribute:: rms
|
||||||
|
|
||||||
RMS (root-mean-square).
|
RMS (root-mean-square) for each band in the image.
|
||||||
|
|
||||||
.. py:attribute:: var
|
.. py:attribute:: var
|
||||||
|
|
||||||
Variance.
|
Variance for each band in the image.
|
||||||
|
|
||||||
.. py:attribute:: stddev
|
.. py:attribute:: stddev
|
||||||
|
|
||||||
Standard deviation.
|
Standard deviation for each band in the image.
|
||||||
|
|
364
docs/reference/OleFileIO.rst
Normal file
364
docs/reference/OleFileIO.rst
Normal file
|
@ -0,0 +1,364 @@
|
||||||
|
.. py:module:: PIL.OleFileIO
|
||||||
|
.. py:currentmodule:: PIL.OleFileIO
|
||||||
|
|
||||||
|
:py:mod:`OleFileIO` Module
|
||||||
|
===========================
|
||||||
|
|
||||||
|
The :py:mod:`OleFileIO` module reads Microsoft OLE2 files (also called
|
||||||
|
Structured Storage or Microsoft Compound Document File Format), such
|
||||||
|
as Microsoft Office documents, Image Composer and FlashPix files, and
|
||||||
|
Outlook messages.
|
||||||
|
|
||||||
|
This module is the `OleFileIO\_PL`_ project by Philippe Lagadec, v0.30,
|
||||||
|
merged back into Pillow.
|
||||||
|
|
||||||
|
.. _OleFileIO\_PL: http://www.decalage.info/python/olefileio
|
||||||
|
|
||||||
|
How to use this module
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
For more information, see also the file **PIL/OleFileIO.py**, 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 '05'.
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
assert OleFileIO.isOleFile('myfile.doc')
|
||||||
|
|
||||||
|
Open an OLE file from disk
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Create an OleFileIO object with the file path as parameter:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
ole = OleFileIO.OleFileIO('myfile.doc')
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
ole = OleFileIO.OleFileIO(f)
|
||||||
|
|
||||||
|
For example the code below reads a file into a string, then uses BytesIO
|
||||||
|
to turn it into a file-like object.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
data = open('myfile.doc', 'rb').read()
|
||||||
|
f = io.BytesIO(data) # or StringIO.StringIO for Python 2.x
|
||||||
|
ole = OleFileIO.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:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
ole = OleFileIO.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:
|
||||||
|
|
||||||
|
.. code-block:: 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.
|
||||||
|
|
||||||
|
Both are case-insensitive.
|
||||||
|
|
||||||
|
Switching between the two is easy:
|
||||||
|
|
||||||
|
.. code-block:: 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.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
print(ole.listdir())
|
||||||
|
|
||||||
|
Sample result:
|
||||||
|
|
||||||
|
.. code-block:: 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:
|
||||||
|
|
||||||
|
.. code-block:: 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.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
if ole.exists('worddocument'):
|
||||||
|
print("This is a Word document.")
|
||||||
|
if ole.exists('macros/vba'):
|
||||||
|
print("This document seems to contain VBA macros.")
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
pics = ole.openstream('Pictures')
|
||||||
|
data = pics.read()
|
||||||
|
|
||||||
|
|
||||||
|
Get information about a stream/storage
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Several methods can provide the size, type and timestamps of a given
|
||||||
|
stream/storage:
|
||||||
|
|
||||||
|
get\_size(path) returns the size of a stream in bytes:
|
||||||
|
|
||||||
|
.. code-block:: 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.
|
||||||
|
|
||||||
|
.. code-block:: 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.
|
||||||
|
|
||||||
|
.. code-block:: 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:
|
||||||
|
|
||||||
|
.. code-block:: 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.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
meta = ole.get_metadata()
|
||||||
|
print('Author:', meta.author)
|
||||||
|
print('Title:', meta.title)
|
||||||
|
print('Creation date:', meta.create_time)
|
||||||
|
# print all metadata:
|
||||||
|
meta.dump()
|
||||||
|
|
||||||
|
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
|
||||||
|
'05SummaryInformation', the document title is property #2, and the
|
||||||
|
subject is #3.
|
||||||
|
|
||||||
|
.. code-block:: 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 '05SummaryInformation'), the list of indexes can
|
||||||
|
be passed as no\_conversion:
|
||||||
|
|
||||||
|
.. code-block:: 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.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
ole.close()
|
||||||
|
|
||||||
|
Use OleFileIO as a script
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
OleFileIO can also be used as a script from the command-line to
|
||||||
|
display the structure of an OLE file and its metadata, for example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
PIL/OleFileIO.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.
|
||||||
|
|
||||||
|
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, 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
|
||||||
|
- improve documentation, code samples, docstrings
|
||||||
|
- write unittest test cases
|
||||||
|
- provide tricky malformed files
|
||||||
|
|
||||||
|
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 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.
|
||||||
|
|
||||||
|
If possible please join the debugging output of OleFileIO. For this,
|
||||||
|
launch the following command :
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
PIL/OleFileIO.py -d -c file >debug.txt
|
||||||
|
|
||||||
|
|
||||||
|
Classes and Methods
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: PIL.OleFileIO
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
:noindex:
|
|
@ -8,6 +8,7 @@ Reference
|
||||||
Image
|
Image
|
||||||
ImageChops
|
ImageChops
|
||||||
ImageColor
|
ImageColor
|
||||||
|
ImageCms
|
||||||
ImageDraw
|
ImageDraw
|
||||||
ImageEnhance
|
ImageEnhance
|
||||||
ImageFile
|
ImageFile
|
||||||
|
@ -15,6 +16,7 @@ Reference
|
||||||
ImageFont
|
ImageFont
|
||||||
ImageGrab
|
ImageGrab
|
||||||
ImageMath
|
ImageMath
|
||||||
|
ImageMorph
|
||||||
ImageOps
|
ImageOps
|
||||||
ImagePalette
|
ImagePalette
|
||||||
ImagePath
|
ImagePath
|
||||||
|
@ -24,5 +26,6 @@ Reference
|
||||||
ImageTk
|
ImageTk
|
||||||
ImageWin
|
ImageWin
|
||||||
ExifTags
|
ExifTags
|
||||||
|
OleFileIO
|
||||||
PSDraw
|
PSDraw
|
||||||
../PIL
|
../PIL
|
||||||
|
|
Loading…
Reference in New Issue
Block a user