mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
merge from master
This commit is contained in:
commit
a631eef872
59
.gitignore
vendored
59
.gitignore
vendored
|
@ -1,11 +1,56 @@
|
|||
*.pyc
|
||||
*.egg-info
|
||||
build
|
||||
dist
|
||||
.tox
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
docs/_build
|
||||
*~
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
bin/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Rope
|
||||
.ropeproject
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
*.pot
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# Vim cruft
|
||||
.*.swp
|
||||
|
|
21
.travis.yml
21
.travis.yml
|
@ -1,22 +1,20 @@
|
|||
language: python
|
||||
|
||||
# for python-qt4
|
||||
virtualenv:
|
||||
system_site_packages: true
|
||||
|
||||
notifications:
|
||||
irc: "chat.freenode.net#pil"
|
||||
|
||||
python:
|
||||
- "pypy"
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.2
|
||||
- 3.3
|
||||
- "pypy"
|
||||
- 3.4
|
||||
|
||||
install:
|
||||
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake"
|
||||
- "pip install cffi"
|
||||
- "pip install coveralls"
|
||||
|
||||
# webp
|
||||
- pushd depends && ./install_webp.sh && popd
|
||||
|
@ -25,11 +23,20 @@ install:
|
|||
- pushd depends && ./install_openjpeg.sh && popd
|
||||
|
||||
script:
|
||||
- coverage erase
|
||||
- python setup.py clean
|
||||
- python setup.py build_ext --inplace
|
||||
- python selftest.py
|
||||
- python Tests/run.py
|
||||
- coverage run --append --include=PIL/* selftest.py
|
||||
- python Tests/run.py --coverage
|
||||
|
||||
after_success:
|
||||
- coverage report
|
||||
- coveralls
|
||||
- pip install pep8 pyflakes
|
||||
- pep8 PIL/*.py
|
||||
- pyflakes PIL/*.py
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: "pypy"
|
||||
- python: 3.4
|
||||
|
|
21
CHANGES.rst
21
CHANGES.rst
|
@ -1,6 +1,27 @@
|
|||
Changelog (Pillow)
|
||||
==================
|
||||
|
||||
2.5.0 (unreleased)
|
||||
------------------
|
||||
|
||||
- Added Image.close, context manager support.
|
||||
[wiredfool]
|
||||
|
||||
- Added support for 16 bit PGM files.
|
||||
[wiredfool]
|
||||
|
||||
- Updated OleFileIO to version 0.30 from upstream
|
||||
[hugovk]
|
||||
|
||||
- Added support for additional TIFF floating point format
|
||||
[Hijackal]
|
||||
|
||||
- Have the tempfile use a suffix with a dot
|
||||
[wiredfool]
|
||||
|
||||
- Fix variable name used for transparency manipulations
|
||||
[nijel]
|
||||
|
||||
2.4.0 (2014-04-01)
|
||||
------------------
|
||||
|
||||
|
|
52
PIL/Image.py
52
PIL/Image.py
|
@ -92,7 +92,7 @@ except ImportError:
|
|||
|
||||
from PIL import ImageMode
|
||||
from PIL._binary import i8, o8
|
||||
from PIL._util import isPath, isStringType
|
||||
from PIL._util import isPath, isStringType, deferred_error
|
||||
|
||||
import os, sys
|
||||
|
||||
|
@ -497,6 +497,35 @@ class Image:
|
|||
|
||||
_makeself = _new # compatibility
|
||||
|
||||
# Context Manager Support
|
||||
def __enter__(self):
|
||||
return self
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Closes the file pointer, if possible.
|
||||
|
||||
This operation will destroy the image core and release it's memory.
|
||||
The image data will be unusable afterward.
|
||||
|
||||
This function is only required to close images that have not
|
||||
had their file read and closed by the
|
||||
:py:meth:`~PIL.Image.Image.load` method.
|
||||
"""
|
||||
try:
|
||||
self.fp.close()
|
||||
except Exception as msg:
|
||||
if Image.DEBUG:
|
||||
print ("Error closing: %s" %msg)
|
||||
|
||||
# Instead of simply setting to None, we're setting up a
|
||||
# deferred error that will better explain that the core image
|
||||
# object is gone.
|
||||
self.im = deferred_error(ValueError("Operation on closed image"))
|
||||
|
||||
|
||||
def _copy(self):
|
||||
self.load()
|
||||
self.im = self.im.copy()
|
||||
|
@ -505,15 +534,18 @@ class Image:
|
|||
|
||||
def _dump(self, file=None, format=None):
|
||||
import tempfile, os
|
||||
suffix = ''
|
||||
if format:
|
||||
suffix = '.'+format
|
||||
if not file:
|
||||
f, file = tempfile.mkstemp(format or '')
|
||||
f, file = tempfile.mkstemp(suffix)
|
||||
os.close(f)
|
||||
|
||||
self.load()
|
||||
if not format or format == "PPM":
|
||||
self.im.save_ppm(file)
|
||||
else:
|
||||
if file.endswith(format):
|
||||
if not file.endswith(format):
|
||||
file = file + "." + format
|
||||
self.save(file, format)
|
||||
return file
|
||||
|
@ -639,7 +671,8 @@ class Image:
|
|||
Allocates storage for the image and loads the pixel data. In
|
||||
normal cases, you don't need to call this method, since the
|
||||
Image class automatically loads an opened image when it is
|
||||
accessed for the first time.
|
||||
accessed for the first time. This method will close the file
|
||||
associated with the image.
|
||||
|
||||
:returns: An image access object.
|
||||
"""
|
||||
|
@ -807,7 +840,7 @@ class Image:
|
|||
new_im = self._new(im)
|
||||
if delete_trns:
|
||||
#crash fail if we leave a bytes transparency in an rgb/l mode.
|
||||
del(new.info['transparency'])
|
||||
del(new_im.info['transparency'])
|
||||
if trns is not None:
|
||||
if new_im.mode == 'P':
|
||||
try:
|
||||
|
@ -2071,10 +2104,11 @@ def open(fp, mode="r"):
|
|||
"""
|
||||
Opens and identifies the given image file.
|
||||
|
||||
This is a lazy operation; this function identifies the file, but the
|
||||
actual image data is not read from the file until you try to process
|
||||
the data (or call the :py:meth:`~PIL.Image.Image.load` method).
|
||||
See :py:func:`~PIL.Image.new`.
|
||||
This is a lazy operation; this function identifies the file, but
|
||||
the file remains open and the actual image data is not read from
|
||||
the file until you try to process the data (or call the
|
||||
:py:meth:`~PIL.Image.Image.load` method). See
|
||||
:py:func:`~PIL.Image.new`.
|
||||
|
||||
:param file: A filename (string) or a file object. The file object
|
||||
must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and
|
||||
|
|
|
@ -89,8 +89,8 @@ try:
|
|||
except ImportError as ex:
|
||||
# Allow error import for doc purposes, but error out when accessing
|
||||
# anything in core.
|
||||
from _util import import_err
|
||||
_imagingcms = import_err(ex)
|
||||
from _util import deferred_error
|
||||
_imagingcms = deferred_error(ex)
|
||||
from PIL._util import isStringType
|
||||
|
||||
core = _imagingcms
|
||||
|
|
|
@ -1,27 +1,22 @@
|
|||
OleFileIO_PL
|
||||
============
|
||||
|
||||
[OleFileIO_PL](http://www.decalage.info/python/olefileio) is a Python module to read [Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format)](http://en.wikipedia.org/wiki/Compound_File_Binary_Format), such as Microsoft Office documents, Image Composer and FlashPix files, Outlook messages, ...
|
||||
[OleFileIO_PL](http://www.decalage.info/python/olefileio) is a Python module to parse and read [Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format)](http://en.wikipedia.org/wiki/Compound_File_Binary_Format), such as Microsoft Office documents, Image Composer and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats ...
|
||||
|
||||
This is an improved version of the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent Python Imaging Library, created and maintained by Fredrik Lundh. The API is still compatible with PIL, but I have improved the internal implementation significantly, with new features, bugfixes and a more robust design.
|
||||
This is an improved version of the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent Python Imaging Library, created and maintained by Fredrik Lundh. The API is still compatible with PIL, but since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust design.
|
||||
|
||||
As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules)
|
||||
|
||||
WARNING: THIS IS (STILL) WORK IN PROGRESS.
|
||||
OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-imaging.github.io/), the friendly fork of PIL.
|
||||
|
||||
Main improvements over PIL version of OleFileIO:
|
||||
------------------------------------------------
|
||||
|
||||
- Better compatibility with Python 2.6 (also compatible with Python 3.0+)
|
||||
- Support for files larger than 6.8MB
|
||||
- Robust: many checks to detect malformed files
|
||||
- Improved API
|
||||
- New features: metadata extraction, stream/storage timestamps
|
||||
- Added setup.py and install.bat to ease installation
|
||||
OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL.
|
||||
|
||||
News
|
||||
----
|
||||
|
||||
Follow all updates and news on Twitter: <https://twitter.com/decalage2>
|
||||
|
||||
- **2014-02-04 v0.30**: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work.
|
||||
- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed parsing of direntry timestamps
|
||||
- 2013-05-27 v0.25: improved metadata extraction, properties parsing and exception handling, fixed [issue #12](https://bitbucket.org/decalage/olefileio_pl/issue/12/error-when-converting-timestamps-in-ole)
|
||||
- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved getproperties to convert timestamps to Python datetime
|
||||
|
@ -34,47 +29,224 @@ News
|
|||
- 2009-12-10 v0.19: fixed support for 64 bits platforms (thanks to Ben G. and Martijn for reporting the bug)
|
||||
- see changelog in source code for more info.
|
||||
|
||||
Download:
|
||||
---------
|
||||
Download
|
||||
--------
|
||||
|
||||
The archive is available on [the project page](https://bitbucket.org/decalage/olefileio_pl/downloads).
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
How to use this module:
|
||||
-----------------------
|
||||
- Parse and read any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls, PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes, Zeiss AxioVision ZVI files, Olympus FluoView OIB files, ...
|
||||
- List all the streams and storages contained in an OLE file
|
||||
- Open streams as files
|
||||
- Parse and read property streams, containing metadata of the file
|
||||
- Portable, pure Python module, no dependency
|
||||
|
||||
See sample code at the end of the module, and also docstrings.
|
||||
|
||||
Here are a few examples:
|
||||
Main improvements over the original version of OleFileIO in PIL:
|
||||
----------------------------------------------------------------
|
||||
|
||||
- Compatible with Python 3.x and 2.6+
|
||||
- Many bug fixes
|
||||
- Support for files larger than 6.8MB
|
||||
- Support for 64 bits platforms and big-endian CPUs
|
||||
- Robust: many checks to detect malformed files
|
||||
- Runtime option to choose if malformed files should be parsed or raise exceptions
|
||||
- Improved API
|
||||
- Metadata extraction, stream/storage timestamps (e.g. for document forensics)
|
||||
- Can open file-like objects
|
||||
- Added setup.py and install.bat to ease installation
|
||||
- More convenient slash-based syntax for stream paths
|
||||
|
||||
|
||||
|
||||
How to use this module
|
||||
----------------------
|
||||
|
||||
OleFileIO_PL can be used as an independent module or with PIL. The main functions and methods are explained below.
|
||||
|
||||
For more information, see also the file **OleFileIO_PL.html**, sample code at the end of the module itself, and docstrings within the code.
|
||||
|
||||
### About the structure of OLE files ###
|
||||
|
||||
An OLE file can be seen as a mini file system or a Zip archive: It contains **streams** of data that look like files embedded within the OLE file. Each stream has a name. For example, the main stream of a MS Word document containing its text is named "WordDocument".
|
||||
|
||||
An OLE file can also contain **storages**. A storage is a folder that contains streams or other storages. For example, a MS Word document with VBA macros has a storage called "Macros".
|
||||
|
||||
Special streams can contain **properties**. A property is a specific value that can be used to store information such as the metadata of a document (title, author, creation date, etc). Property stream names usually start with the character '\x05'.
|
||||
|
||||
For example, a typical MS Word document may look like this:
|
||||
|
||||
\x05DocumentSummaryInformation (stream)
|
||||
\x05SummaryInformation (stream)
|
||||
WordDocument (stream)
|
||||
Macros (storage)
|
||||
PROJECT (stream)
|
||||
PROJECTwm (stream)
|
||||
VBA (storage)
|
||||
Module1 (stream)
|
||||
ThisDocument (stream)
|
||||
_VBA_PROJECT (stream)
|
||||
dir (stream)
|
||||
ObjectPool (storage)
|
||||
|
||||
|
||||
|
||||
### Import OleFileIO_PL ###
|
||||
|
||||
:::python
|
||||
import OleFileIO_PL
|
||||
|
||||
# Test if a file is an OLE container:
|
||||
As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. If your application needs to be compatible with Python 2.5 or older, you may use the following code to load the old version when needed:
|
||||
|
||||
:::python
|
||||
try:
|
||||
import OleFileIO_PL
|
||||
except:
|
||||
import OleFileIO_PL2 as OleFileIO_PL
|
||||
|
||||
If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
|
||||
|
||||
|
||||
### Test if a file is an OLE container ###
|
||||
|
||||
Use isOleFile to check if the first bytes of the file contain the Magic for OLE files, before opening it. isOleFile returns True if it is an OLE file, False otherwise (new in v0.16).
|
||||
|
||||
:::python
|
||||
assert OleFileIO_PL.isOleFile('myfile.doc')
|
||||
|
||||
# Open an OLE file from disk:
|
||||
|
||||
### Open an OLE file from disk ###
|
||||
|
||||
Create an OleFileIO object with the file path as parameter:
|
||||
|
||||
:::python
|
||||
ole = OleFileIO_PL.OleFileIO('myfile.doc')
|
||||
|
||||
# Get list of streams:
|
||||
### Open an OLE file from a file-like object ###
|
||||
|
||||
This is useful if the file is not on disk, e.g. already stored in a string or as a file-like object.
|
||||
|
||||
:::python
|
||||
ole = OleFileIO_PL.OleFileIO(f)
|
||||
|
||||
For example the code below reads a file into a string, then uses BytesIO to turn it into a file-like object.
|
||||
|
||||
:::python
|
||||
data = open('myfile.doc', 'rb').read()
|
||||
f = io.BytesIO(data) # or StringIO.StringIO for Python 2.x
|
||||
ole = OleFileIO_PL.OleFileIO(f)
|
||||
|
||||
### How to handle malformed OLE files ###
|
||||
|
||||
By default, the parser is configured to be as robust and permissive as possible, allowing to parse most malformed OLE files. Only fatal errors will raise an exception. It is possible to tell the parser to be more strict in order to raise exceptions for files that do not fully conform to the OLE specifications, using the raise_defect option (new in v0.14):
|
||||
|
||||
:::python
|
||||
ole = OleFileIO_PL.OleFileIO('myfile.doc', raise_defects=DEFECT_INCORRECT)
|
||||
|
||||
When the parsing is done, the list of non-fatal issues detected is available as a list in the parsing_issues attribute of the OleFileIO object (new in 0.25):
|
||||
|
||||
:::python
|
||||
print('Non-fatal issues raised during parsing:')
|
||||
if ole.parsing_issues:
|
||||
for exctype, msg in ole.parsing_issues:
|
||||
print('- %s: %s' % (exctype.__name__, msg))
|
||||
else:
|
||||
print('None')
|
||||
|
||||
|
||||
### Syntax for stream and storage path ###
|
||||
|
||||
Two different syntaxes are allowed for methods that need or return the path of streams and storages:
|
||||
|
||||
1) Either a **list of strings** including all the storages from the root up to the stream/storage name. For example a stream called "WordDocument" at the root will have ['WordDocument'] as full path. A stream called "ThisDocument" located in the storage "Macros/VBA" will be ['Macros', 'VBA', 'ThisDocument']. This is the original syntax from PIL. While hard to read and not very convenient, this syntax works in all cases.
|
||||
|
||||
2) Or a **single string with slashes** to separate storage and stream names (similar to the Unix path syntax). The previous examples would be 'WordDocument' and 'Macros/VBA/ThisDocument'. This syntax is easier, but may fail if a stream or storage name contains a slash. (new in v0.15)
|
||||
|
||||
Both are case-insensitive.
|
||||
|
||||
Switching between the two is easy:
|
||||
|
||||
:::python
|
||||
slash_path = '/'.join(list_path)
|
||||
list_path = slash_path.split('/')
|
||||
|
||||
|
||||
### Get the list of streams ###
|
||||
|
||||
listdir() returns a list of all the streams contained in the OLE file, including those stored in storages. Each stream is listed itself as a list, as described above.
|
||||
|
||||
:::python
|
||||
print(ole.listdir())
|
||||
|
||||
# Test if known streams/storages exist:
|
||||
Sample result:
|
||||
|
||||
:::python
|
||||
[['\x01CompObj'], ['\x05DocumentSummaryInformation'], ['\x05SummaryInformation']
|
||||
, ['1Table'], ['Macros', 'PROJECT'], ['Macros', 'PROJECTwm'], ['Macros', 'VBA',
|
||||
'Module1'], ['Macros', 'VBA', 'ThisDocument'], ['Macros', 'VBA', '_VBA_PROJECT']
|
||||
, ['Macros', 'VBA', 'dir'], ['ObjectPool'], ['WordDocument']]
|
||||
|
||||
As an option it is possible to choose if storages should also be listed, with or without streams (new in v0.26):
|
||||
|
||||
:::python
|
||||
ole.listdir (streams=False, storages=True)
|
||||
|
||||
|
||||
### Test if known streams/storages exist: ###
|
||||
|
||||
exists(path) checks if a given stream or storage exists in the OLE file (new in v0.16).
|
||||
|
||||
:::python
|
||||
if ole.exists('worddocument'):
|
||||
print("This is a Word document.")
|
||||
print("size :", ole.get_size('worddocument'))
|
||||
if ole.exists('macros/vba'):
|
||||
print("This document seems to contain VBA macros.")
|
||||
|
||||
# Extract the "Pictures" stream from a PPT file:
|
||||
if ole.exists('Pictures'):
|
||||
pics = ole.openstream('Pictures')
|
||||
data = pics.read()
|
||||
f = open('Pictures.bin', 'wb')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
# Extract metadata (new in v0.24) - see source code for all attributes:
|
||||
### Read data from a stream ###
|
||||
|
||||
openstream(path) opens a stream as a file-like object.
|
||||
|
||||
The following example extracts the "Pictures" stream from a PPT file:
|
||||
|
||||
:::python
|
||||
pics = ole.openstream('Pictures')
|
||||
data = pics.read()
|
||||
|
||||
|
||||
### Get information about a stream/storage ###
|
||||
|
||||
Several methods can provide the size, type and timestamps of a given stream/storage:
|
||||
|
||||
get_size(path) returns the size of a stream in bytes (new in v0.16):
|
||||
|
||||
:::python
|
||||
s = ole.get_size('WordDocument')
|
||||
|
||||
get_type(path) returns the type of a stream/storage, as one of the following constants: STGTY\_STREAM for a stream, STGTY\_STORAGE for a storage, STGTY\_ROOT for the root entry, and False for a non existing path (new in v0.15).
|
||||
|
||||
:::python
|
||||
t = ole.get_type('WordDocument')
|
||||
|
||||
get\_ctime(path) and get\_mtime(path) return the creation and modification timestamps of a stream/storage, as a Python datetime object with UTC timezone. Please note that these timestamps are only present if the application that created the OLE file explicitly stored them, which is rarely the case. When not present, these methods return None (new in v0.26).
|
||||
|
||||
:::python
|
||||
c = ole.get_ctime('WordDocument')
|
||||
m = ole.get_mtime('WordDocument')
|
||||
|
||||
The root storage is a special case: You can get its creation and modification timestamps using the OleFileIO.root attribute (new in v0.26):
|
||||
|
||||
:::python
|
||||
c = ole.root.getctime()
|
||||
m = ole.root.getmtime()
|
||||
|
||||
### Extract metadata ###
|
||||
|
||||
get_metadata() will check if standard property streams exist, parse all the properties they contain, and return an OleMetadata object with the found properties as attributes (new in v0.24).
|
||||
|
||||
:::python
|
||||
meta = ole.get_metadata()
|
||||
print('Author:', meta.author)
|
||||
print('Title:', meta.title)
|
||||
|
@ -82,29 +254,67 @@ Here are a few examples:
|
|||
# print all metadata:
|
||||
meta.dump()
|
||||
|
||||
# Close the OLE file:
|
||||
Available attributes include:
|
||||
|
||||
codepage, title, subject, author, keywords, comments, template,
|
||||
last_saved_by, revision_number, total_edit_time, last_printed, create_time,
|
||||
last_saved_time, num_pages, num_words, num_chars, thumbnail,
|
||||
creating_application, security, codepage_doc, category, presentation_target,
|
||||
bytes, lines, paragraphs, slides, notes, hidden_slides, mm_clips,
|
||||
scale_crop, heading_pairs, titles_of_parts, manager, company, links_dirty,
|
||||
chars_with_spaces, unused, shared_doc, link_base, hlinks, hlinks_changed,
|
||||
version, dig_sig, content_type, content_status, language, doc_version
|
||||
|
||||
See the source code of the OleMetadata class for more information.
|
||||
|
||||
|
||||
### Parse a property stream ###
|
||||
|
||||
get\_properties(path) can be used to parse any property stream that is not handled by get\_metadata. It returns a dictionary indexed by integers. Each integer is the index of the property, pointing to its value. For example in the standard property stream '\x05SummaryInformation', the document title is property #2, and the subject is #3.
|
||||
|
||||
:::python
|
||||
p = ole.getproperties('specialprops')
|
||||
|
||||
By default as in the original PIL version, timestamp properties are converted into a number of seconds since Jan 1,1601. With the option convert\_time, you can obtain more convenient Python datetime objects (UTC timezone). If some time properties should not be converted (such as total editing time in '\x05SummaryInformation'), the list of indexes can be passed as no_conversion (new in v0.25):
|
||||
|
||||
:::python
|
||||
p = ole.getproperties('specialprops', convert_time=True, no_conversion=[10])
|
||||
|
||||
|
||||
### Close the OLE file ###
|
||||
|
||||
Unless your application is a simple script that terminates after processing an OLE file, do not forget to close each OleFileIO object after parsing to close the file on disk. (new in v0.22)
|
||||
|
||||
:::python
|
||||
ole.close()
|
||||
|
||||
# Work with a file-like object (e.g. StringIO) instead of a file on disk:
|
||||
data = open('myfile.doc', 'rb').read()
|
||||
f = io.BytesIO(data)
|
||||
ole = OleFileIO_PL.OleFileIO(f)
|
||||
print(ole.listdir())
|
||||
ole.close()
|
||||
### Use OleFileIO_PL as a script ###
|
||||
|
||||
|
||||
It can also be used as a script from the command-line to display the structure of an OLE file, for example:
|
||||
OleFileIO_PL can also be used as a script from the command-line to display the structure of an OLE file and its metadata, for example:
|
||||
|
||||
OleFileIO_PL.py myfile.doc
|
||||
|
||||
You can use the option -c to check that all streams can be read fully, and -d to generate very verbose debugging information.
|
||||
|
||||
## Real-life examples ##
|
||||
|
||||
A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/).
|
||||
|
||||
How to contribute:
|
||||
------------------
|
||||
See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features OleFileIO_PL.
|
||||
|
||||
About Python 2 and 3
|
||||
--------------------
|
||||
|
||||
OleFileIO\_PL used to support only Python 2.x. As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. See above the "import" section for a workaround.
|
||||
|
||||
If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
|
||||
|
||||
How to contribute
|
||||
-----------------
|
||||
|
||||
The code is available in [a Mercurial repository on bitbucket](https://bitbucket.org/decalage/olefileio_pl). You may use it to submit enhancements or to report any issue.
|
||||
|
||||
If you would like to help us improve this module, or simply provide feedback, you may also send an e-mail to decalage(at)laposte.net. You can help in many ways:
|
||||
If you would like to help us improve this module, or simply provide feedback, please [contact me](http://decalage.info/contact). You can help in many ways:
|
||||
|
||||
- test this module on different platforms / Python versions
|
||||
- find and report bugs
|
||||
|
@ -112,12 +322,12 @@ If you would like to help us improve this module, or simply provide feedback, yo
|
|||
- write unittest test cases
|
||||
- provide tricky malformed files
|
||||
|
||||
How to report bugs:
|
||||
-------------------
|
||||
How to report bugs
|
||||
------------------
|
||||
|
||||
To report a bug, for example a normal file which is not parsed correctly, please use the [issue reporting page](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open), or send an e-mail with an attachment containing the debugging output of OleFileIO_PL.
|
||||
To report a bug, for example a normal file which is not parsed correctly, please use the [issue reporting page](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open), or if you prefer to do it privately, use this [contact form](http://decalage.info/contact). Please provide all the information about the context and how to reproduce the bug.
|
||||
|
||||
For this, launch the following command :
|
||||
If possible please join the debugging output of OleFileIO_PL. For this, launch the following command :
|
||||
|
||||
OleFileIO_PL.py -d -c file >debug.txt
|
||||
|
||||
|
@ -126,7 +336,7 @@ License
|
|||
|
||||
OleFileIO_PL is open-source.
|
||||
|
||||
OleFileIO_PL changes are Copyright (c) 2005-2013 by Philippe Lagadec.
|
||||
OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec.
|
||||
|
||||
The Python Imaging Library (PIL) is
|
||||
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
# -*- coding: latin-1 -*-
|
||||
"""
|
||||
OleFileIO_PL:
|
||||
Module to read Microsoft OLE2 files (also called Structured Storage or
|
||||
Microsoft Compound Document File Format), such as Microsoft Office
|
||||
documents, Image Composer and FlashPix files, Outlook messages, ...
|
||||
Module to read Microsoft OLE2 files (also called Structured Storage or
|
||||
Microsoft Compound Document File Format), such as Microsoft Office
|
||||
documents, Image Composer and FlashPix files, Outlook messages, ...
|
||||
This version is compatible with Python 2.6+ and 3.x
|
||||
|
||||
version 0.26 2013-07-24 Philippe Lagadec - http://www.decalage.info
|
||||
version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info
|
||||
|
||||
Project website: http://www.decalage.info/python/olefileio
|
||||
|
||||
|
@ -16,25 +17,30 @@ See: http://www.pythonware.com/products/pil/index.htm
|
|||
The Python Imaging Library (PIL) is
|
||||
Copyright (c) 1997-2005 by Secret Labs AB
|
||||
Copyright (c) 1995-2005 by Fredrik Lundh
|
||||
OleFileIO_PL changes are Copyright (c) 2005-2013 by Philippe Lagadec
|
||||
OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
|
||||
|
||||
See source code and LICENSE.txt for information on usage and redistribution.
|
||||
|
||||
WARNING: THIS IS (STILL) WORK IN PROGRESS.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
# Starting with OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported
|
||||
# This import enables print() as a function rather than a keyword
|
||||
# (main requirement to be compatible with Python 3.x)
|
||||
# The comment on the line below should be printed on Python 2.5 or older:
|
||||
from __future__ import print_function # This version of OleFileIO_PL requires Python 2.6+ or 3.x.
|
||||
|
||||
|
||||
__author__ = "Philippe Lagadec, Fredrik Lundh (Secret Labs AB)"
|
||||
__date__ = "2013-07-24"
|
||||
__version__ = '0.26'
|
||||
__date__ = "2014-02-04"
|
||||
__version__ = '0.30'
|
||||
|
||||
#--- LICENSE ------------------------------------------------------------------
|
||||
|
||||
# OleFileIO_PL is an improved version of the OleFileIO module from the
|
||||
# Python Imaging Library (PIL).
|
||||
|
||||
# OleFileIO_PL changes are Copyright (c) 2005-2013 by Philippe Lagadec
|
||||
# OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
|
||||
#
|
||||
# The Python Imaging Library (PIL) is
|
||||
# Copyright (c) 1997-2005 by Secret Labs AB
|
||||
|
@ -133,9 +139,14 @@ __version__ = '0.26'
|
|||
# of a directory entry or a storage/stream
|
||||
# - fixed parsing of direntry timestamps
|
||||
# 2013-07-24 PL: - new options in listdir to list storages and/or streams
|
||||
# 2014-02-04 v0.30 PL: - upgraded code to support Python 3.x by Martin Panter
|
||||
# - several fixes for Python 2.6 (xrange, MAGIC)
|
||||
# - reused i32 from Pillow's _binary
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# TODO (for version 1.0):
|
||||
# + isOleFile should accept file-like objects like open
|
||||
# + fix how all the methods handle unicode str and/or bytes as arguments
|
||||
# + add path attrib to _OleDirEntry, set it once and for all in init or
|
||||
# append_kids (then listdir/_list can be simplified)
|
||||
# - TESTS with Linux, MacOSX, Python 1.5.2, various files, PIL, ...
|
||||
|
@ -220,17 +231,26 @@ __version__ = '0.26'
|
|||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
|
||||
import io
|
||||
import sys
|
||||
from PIL import _binary
|
||||
import struct, array, os.path, datetime
|
||||
|
||||
#[PL] Define explicitly the public API to avoid private objects in pydoc:
|
||||
__all__ = ['OleFileIO', 'isOleFile', 'MAGIC']
|
||||
|
||||
# For Python 3.x, need to redefine long as int:
|
||||
if str is not bytes:
|
||||
long = int
|
||||
|
||||
# Need to make sure we use xrange both on Python 2 and 3.x:
|
||||
try:
|
||||
# on Python 2 we need xrange:
|
||||
iterrange = xrange
|
||||
except:
|
||||
# no xrange, for Python 3 it was renamed as range:
|
||||
iterrange = range
|
||||
|
||||
#[PL] workaround to fix an issue with array item size on 64 bits systems:
|
||||
if array.array('L').itemsize == 4:
|
||||
# on 32 bits platforms, long integers in an array are 32 bits:
|
||||
|
@ -281,8 +301,7 @@ def set_debug_mode(debug_mode):
|
|||
else:
|
||||
debug = debug_pass
|
||||
|
||||
#TODO: convert this to hex
|
||||
MAGIC = b'\320\317\021\340\241\261\032\341'
|
||||
MAGIC = b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'
|
||||
|
||||
#[PL]: added constants for Sector IDs (from AAF specifications)
|
||||
MAXREGSECT = 0xFFFFFFFA; # maximum SECT
|
||||
|
@ -362,9 +381,39 @@ def isOleFile (filename):
|
|||
return False
|
||||
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
if bytes is str:
|
||||
# version for Python 2.x
|
||||
def i8(c):
|
||||
return ord(c)
|
||||
else:
|
||||
# version for Python 3.x
|
||||
def i8(c):
|
||||
return c if c.__class__ is int else c[0]
|
||||
|
||||
|
||||
#TODO: replace i16 and i32 with more readable struct.unpack equivalent?
|
||||
|
||||
def i16(c, o = 0):
|
||||
"""
|
||||
Converts a 2-bytes (16 bits) string to an integer.
|
||||
|
||||
c: string containing bytes to convert
|
||||
o: offset of bytes to convert in string
|
||||
"""
|
||||
return i8(c[o]) | (i8(c[o+1])<<8)
|
||||
|
||||
|
||||
def i32(c, o = 0):
|
||||
"""
|
||||
Converts a 4-bytes (32 bits) string to an integer.
|
||||
|
||||
c: string containing bytes to convert
|
||||
o: offset of bytes to convert in string
|
||||
"""
|
||||
## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24))
|
||||
## # [PL]: added int() because "<<" gives long int since Python 2.4
|
||||
# copied from Pillow's _binary:
|
||||
return i8(c[o]) | (i8(c[o+1])<<8) | (i8(c[o+2])<<16) | (i8(c[o+3])<<24)
|
||||
|
||||
|
||||
def _clsid(clsid):
|
||||
|
@ -373,7 +422,9 @@ def _clsid(clsid):
|
|||
clsid: string of length 16.
|
||||
"""
|
||||
assert len(clsid) == 16
|
||||
if clsid == bytearray(16):
|
||||
# if clsid is only made of null bytes, return an empty string:
|
||||
# (PL: why not simply return the string with zeroes?)
|
||||
if not clsid.strip(b"\0"):
|
||||
return ""
|
||||
return (("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) %
|
||||
((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) +
|
||||
|
@ -902,18 +953,22 @@ class _OleDirectoryEntry:
|
|||
def __eq__(self, other):
|
||||
"Compare entries by name"
|
||||
return self.name == other.name
|
||||
|
||||
def __lt__(self, other):
|
||||
"Compare entries by name"
|
||||
return self.name < other.name
|
||||
#TODO: replace by the same function as MS implementation ?
|
||||
# (order by name length first, then case-insensitive order)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __le__(self, other):
|
||||
return self.__eq__(other) or self.__lt__(other)
|
||||
|
||||
# Reflected __lt__() and __le__() will be used for __gt__() and __ge__()
|
||||
|
||||
#TODO: replace by the same function as MS implementation ?
|
||||
# (order by name length first, then case-insensitive order)
|
||||
|
||||
|
||||
def dump(self, tab = 0):
|
||||
"Dump this entry, and all its subentries (for debug purposes only)"
|
||||
|
@ -1046,7 +1101,7 @@ class OleFileIO:
|
|||
#TODO: if larger than 1024 bytes, this could be the actual data => BytesIO
|
||||
self.fp = open(filename, "rb")
|
||||
# old code fails if filename is not a plain string:
|
||||
#if isPath(filename):
|
||||
#if isinstance(filename, (bytes, basestring)):
|
||||
# self.fp = open(filename, "rb")
|
||||
#else:
|
||||
# self.fp = filename
|
||||
|
@ -1133,7 +1188,7 @@ class OleFileIO:
|
|||
) = struct.unpack(fmt_header, header1)
|
||||
debug( struct.unpack(fmt_header, header1))
|
||||
|
||||
if self.Sig != b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1':
|
||||
if self.Sig != MAGIC:
|
||||
# OLE signature should always be present
|
||||
self._raise_defect(DEFECT_FATAL, "incorrect OLE signature")
|
||||
if self.clsid != bytearray(16):
|
||||
|
@ -1385,7 +1440,7 @@ class OleFileIO:
|
|||
if self.csectDif != nb_difat:
|
||||
raise IOError('incorrect DIFAT')
|
||||
isect_difat = self.sectDifStart
|
||||
for i in range(nb_difat):
|
||||
for i in iterrange(nb_difat):
|
||||
debug( "DIFAT block %d, sector %X" % (i, isect_difat) )
|
||||
#TODO: check if corresponding FAT SID = DIFSECT
|
||||
sector_difat = self.getsect(isect_difat)
|
||||
|
@ -1494,7 +1549,7 @@ class OleFileIO:
|
|||
#self.direntries = []
|
||||
# We start with a list of "None" object
|
||||
self.direntries = [None] * max_entries
|
||||
## for sid in range(max_entries):
|
||||
## for sid in iterrange(max_entries):
|
||||
## entry = fp.read(128)
|
||||
## if not entry:
|
||||
## break
|
||||
|
|
|
@ -63,7 +63,11 @@ class PpmImageFile(ImageFile.ImageFile):
|
|||
c = self.fp.read(1)
|
||||
if not c or c in b_whitespace:
|
||||
break
|
||||
if c > b'\x79':
|
||||
raise ValueError("Expected ASCII value, found binary")
|
||||
s = s + c
|
||||
if (len(s) > 9):
|
||||
raise ValueError("Expected int, got > 9 digits")
|
||||
return s
|
||||
|
||||
def _open(self):
|
||||
|
@ -96,6 +100,17 @@ class PpmImageFile(ImageFile.ImageFile):
|
|||
ysize = s
|
||||
if mode == "1":
|
||||
break
|
||||
elif ix == 2:
|
||||
# maxgrey
|
||||
if s > 255:
|
||||
if not mode == 'L':
|
||||
raise ValueError("Too many colors for band: %s" %s)
|
||||
if s < 2**16:
|
||||
self.mode = 'I'
|
||||
rawmode = 'I;16B'
|
||||
else:
|
||||
self.mode = 'I';
|
||||
rawmode = 'I;32B'
|
||||
|
||||
self.size = xsize, ysize
|
||||
self.tile = [("raw",
|
||||
|
@ -116,6 +131,11 @@ def _save(im, fp, filename):
|
|||
rawmode, head = "1;I", b"P4"
|
||||
elif im.mode == "L":
|
||||
rawmode, head = "L", b"P5"
|
||||
elif im.mode == "I":
|
||||
if im.getextrema()[1] < 2**16:
|
||||
rawmode, head = "I;16B", b"P5"
|
||||
else:
|
||||
rawmode, head = "I;32B", b"P5"
|
||||
elif im.mode == "RGB":
|
||||
rawmode, head = "RGB", b"P6"
|
||||
elif im.mode == "RGBA":
|
||||
|
@ -123,8 +143,15 @@ def _save(im, fp, filename):
|
|||
else:
|
||||
raise IOError("cannot write mode %s as PPM" % im.mode)
|
||||
fp.write(head + ("\n%d %d\n" % im.size).encode('ascii'))
|
||||
if head != b"P4":
|
||||
if head == b"P6":
|
||||
fp.write(b"255\n")
|
||||
if head == b"P5":
|
||||
if rawmode == "L":
|
||||
fp.write(b"255\n")
|
||||
elif rawmode == "I;16B":
|
||||
fp.write(b"65535\n")
|
||||
elif rawmode == "I;32B":
|
||||
fp.write(b"2147483648\n")
|
||||
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))])
|
||||
|
||||
# ALTERNATIVE: save via builtin debug function
|
||||
|
|
|
@ -146,6 +146,7 @@ OPEN_INFO = {
|
|||
(II, 0, 1, 2, (1,), ()): ("1", "1;IR"),
|
||||
(II, 0, 1, 1, (8,), ()): ("L", "L;I"),
|
||||
(II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
|
||||
(II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
|
||||
(II, 1, 1, 1, (1,), ()): ("1", "1"),
|
||||
(II, 1, 1, 2, (1,), ()): ("1", "1;R"),
|
||||
(II, 1, 1, 1, (8,), ()): ("L", "L"),
|
||||
|
|
|
@ -15,7 +15,7 @@ else:
|
|||
def isDirectory(f):
|
||||
return isPath(f) and os.path.isdir(f)
|
||||
|
||||
class import_err(object):
|
||||
class deferred_error(object):
|
||||
def __init__(self, ex):
|
||||
self.ex = ex
|
||||
def __getattr__(self, elt):
|
||||
|
|
|
@ -5,8 +5,9 @@ Pillow
|
|||
|
||||
Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
||||
|
||||
.. image:: https://travis-ci.org/python-imaging/Pillow.png
|
||||
.. image:: https://travis-ci.org/python-imaging/Pillow.svg?branch=master
|
||||
:target: https://travis-ci.org/python-imaging/Pillow
|
||||
:alt: Travis CI build status
|
||||
|
||||
.. image:: https://pypip.in/v/Pillow/badge.png
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
|
@ -16,4 +17,7 @@ Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Pyt
|
|||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Number of PyPI downloads
|
||||
|
||||
.. image:: https://coveralls.io/repos/python-imaging/Pillow/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/python-imaging/Pillow?branch=master
|
||||
|
||||
The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details, and more.
|
||||
|
|
BIN
Tests/images/10ct_32bit_128.tiff
Normal file
BIN
Tests/images/10ct_32bit_128.tiff
Normal file
Binary file not shown.
BIN
Tests/images/16_bit_binary.pgm
Normal file
BIN
Tests/images/16_bit_binary.pgm
Normal file
Binary file not shown.
BIN
Tests/images/16_bit_binary_pgm.png
Normal file
BIN
Tests/images/16_bit_binary_pgm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 578 B |
BIN
Tests/images/test-ole-file.doc
Normal file
BIN
Tests/images/test-ole-file.doc
Normal file
Binary file not shown.
32
Tests/run.py
32
Tests/run.py
|
@ -2,7 +2,12 @@ from __future__ import print_function
|
|||
|
||||
# minimal test runner
|
||||
|
||||
import glob, os, os.path, sys, tempfile, re
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
root = os.path.dirname(__file__)
|
||||
|
@ -34,6 +39,7 @@ files.sort()
|
|||
success = failure = 0
|
||||
include = [x for x in sys.argv[1:] if x[:2] != "--"]
|
||||
skipped = []
|
||||
failed = []
|
||||
|
||||
python_options = " ".join(python_options)
|
||||
tester_options = " ".join(tester_options)
|
||||
|
@ -48,10 +54,15 @@ for file in files:
|
|||
# 2>&1 works on unix and on modern windowses. we might care about
|
||||
# very old Python versions, but not ancient microsoft products :-)
|
||||
out = os.popen("%s %s -u %s %s 2>&1" % (
|
||||
sys.executable, python_options, file, tester_options
|
||||
))
|
||||
sys.executable, python_options, file, tester_options
|
||||
))
|
||||
result = out.read()
|
||||
|
||||
result_lines = result.splitlines()
|
||||
if len(result_lines):
|
||||
if result_lines[0] == "ignore_all_except_last_line":
|
||||
result = result_lines[-1]
|
||||
|
||||
# Extract any ignore patterns
|
||||
ignore_pats = ignore_re.findall(result)
|
||||
result = ignore_re.sub('', result)
|
||||
|
@ -77,7 +88,7 @@ for file in files:
|
|||
if result == "ok":
|
||||
result = None
|
||||
elif result == "skip":
|
||||
print("---", "skipped") # FIXME: driver should include a reason
|
||||
print("---", "skipped") # FIXME: driver should include a reason
|
||||
skipped.append(test)
|
||||
continue
|
||||
elif not result:
|
||||
|
@ -91,7 +102,7 @@ for file in files:
|
|||
# if there's an ok at the end, it's not really ok
|
||||
result = result[:-3]
|
||||
print(result)
|
||||
failure = failure + 1
|
||||
failed.append(test)
|
||||
else:
|
||||
success = success + 1
|
||||
|
||||
|
@ -105,6 +116,7 @@ if tempfiles:
|
|||
print(file)
|
||||
print("-"*68)
|
||||
|
||||
|
||||
def tests(n):
|
||||
if n == 1:
|
||||
return "1 test"
|
||||
|
@ -112,10 +124,12 @@ def tests(n):
|
|||
return "%d tests" % n
|
||||
|
||||
if skipped:
|
||||
print("---", tests(len(skipped)), "skipped.")
|
||||
print(skipped)
|
||||
if failure:
|
||||
print("***", tests(failure), "of", (success + failure), "failed.")
|
||||
print("---", tests(len(skipped)), "skipped:")
|
||||
print(", ".join(skipped))
|
||||
if failed:
|
||||
failure = len(failed)
|
||||
print("***", tests(failure), "of", (success + failure), "failed:")
|
||||
print(", ".join(failed))
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(tests(success), "passed.")
|
||||
|
|
|
@ -12,3 +12,25 @@ def test_sanity():
|
|||
assert_equal(im.mode, "RGB")
|
||||
assert_equal(im.size, (128, 128))
|
||||
assert_equal(im.format, "PPM")
|
||||
|
||||
def test_16bit_pgm():
|
||||
im = Image.open('Tests/images/16_bit_binary.pgm')
|
||||
im.load()
|
||||
assert_equal(im.mode, 'I')
|
||||
assert_equal(im.size, (20,100))
|
||||
|
||||
tgt = Image.open('Tests/images/16_bit_binary_pgm.png')
|
||||
assert_image_equal(im, tgt)
|
||||
|
||||
|
||||
def test_16bit_pgm_write():
|
||||
im = Image.open('Tests/images/16_bit_binary.pgm')
|
||||
im.load()
|
||||
|
||||
f = tempfile('temp.pgm')
|
||||
assert_no_exception(lambda: im.save(f, 'PPM'))
|
||||
|
||||
reloaded = Image.open(f)
|
||||
assert_image_equal(im, reloaded)
|
||||
|
||||
|
||||
|
|
|
@ -128,3 +128,14 @@ def test_12bit_rawmode():
|
|||
print (im2.getpixel((0,2)))
|
||||
|
||||
assert_image_equal(im, im2)
|
||||
|
||||
def test_32bit_float():
|
||||
# Issue 614, specific 32 bit float format
|
||||
path = 'Tests/images/10ct_32bit_128.tiff'
|
||||
im = Image.open(path)
|
||||
im.load()
|
||||
|
||||
assert_equal(im.getpixel((0,0)), -0.4526388943195343)
|
||||
assert_equal(im.getextrema(), (-3.140936851501465, 3.140684127807617))
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ from tester import *
|
|||
|
||||
from PIL import Image
|
||||
|
||||
import os
|
||||
|
||||
def test_sanity():
|
||||
|
||||
im = lena()
|
||||
|
@ -9,3 +11,17 @@ def test_sanity():
|
|||
pix = im.load()
|
||||
|
||||
assert_equal(pix[0, 0], (223, 162, 133))
|
||||
|
||||
def test_close():
|
||||
im = Image.open("Images/lena.gif")
|
||||
assert_no_exception(lambda: im.close())
|
||||
assert_exception(ValueError, lambda: im.load())
|
||||
assert_exception(ValueError, lambda: im.getpixel((0,0)))
|
||||
|
||||
def test_contextmanager():
|
||||
fn = None
|
||||
with Image.open("Images/lena.gif") as im:
|
||||
fn = im.fp.fileno()
|
||||
assert_no_exception(lambda: os.fstat(fn))
|
||||
|
||||
assert_exception(OSError, lambda: os.fstat(fn))
|
||||
|
|
|
@ -2,6 +2,7 @@ from tester import *
|
|||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def verify(im1):
|
||||
im2 = lena("I")
|
||||
assert_equal(im1.size, im2.size)
|
||||
|
@ -18,6 +19,7 @@ def verify(im1):
|
|||
return
|
||||
success()
|
||||
|
||||
|
||||
def test_basic():
|
||||
# PIL 1.1 has limited support for 16-bit image data. Check that
|
||||
# create/copy/transform and save works as expected.
|
||||
|
@ -30,10 +32,10 @@ def test_basic():
|
|||
w, h = imIn.size
|
||||
|
||||
imOut = imIn.copy()
|
||||
verify(imOut) # copy
|
||||
verify(imOut) # copy
|
||||
|
||||
imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h))
|
||||
verify(imOut) # transform
|
||||
verify(imOut) # transform
|
||||
|
||||
filename = tempfile("temp.im")
|
||||
imIn.save(filename)
|
||||
|
|
162
Tests/test_olefileio.py
Normal file
162
Tests/test_olefileio.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
from __future__ import print_function
|
||||
from tester import *
|
||||
import datetime
|
||||
|
||||
import PIL.OleFileIO as OleFileIO
|
||||
|
||||
|
||||
def test_isOleFile_false():
|
||||
# Arrange
|
||||
non_ole_file = "Tests/images/flower.jpg"
|
||||
|
||||
# Act
|
||||
is_ole = OleFileIO.isOleFile(non_ole_file)
|
||||
|
||||
# Assert
|
||||
assert_false(is_ole)
|
||||
|
||||
|
||||
def test_isOleFile_true():
|
||||
# Arrange
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
|
||||
# Act
|
||||
is_ole = OleFileIO.isOleFile(ole_file)
|
||||
|
||||
# Assert
|
||||
assert_true(is_ole)
|
||||
|
||||
|
||||
def test_exists_worddocument():
|
||||
# Arrange
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
ole = OleFileIO.OleFileIO(ole_file)
|
||||
|
||||
# Act
|
||||
exists = ole.exists('worddocument')
|
||||
|
||||
# Assert
|
||||
assert_true(exists)
|
||||
ole.close()
|
||||
|
||||
|
||||
def test_exists_no_vba_macros():
|
||||
# Arrange
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
ole = OleFileIO.OleFileIO(ole_file)
|
||||
|
||||
# Act
|
||||
exists = ole.exists('macros/vba')
|
||||
|
||||
# Assert
|
||||
assert_false(exists)
|
||||
ole.close()
|
||||
|
||||
|
||||
def test_get_type():
|
||||
# Arrange
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
ole = OleFileIO.OleFileIO(ole_file)
|
||||
|
||||
# Act
|
||||
type = ole.get_type('worddocument')
|
||||
|
||||
# Assert
|
||||
assert_equal(type, OleFileIO.STGTY_STREAM)
|
||||
ole.close()
|
||||
|
||||
|
||||
def test_get_size():
|
||||
# Arrange
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
ole = OleFileIO.OleFileIO(ole_file)
|
||||
|
||||
# Act
|
||||
size = ole.get_size('worddocument')
|
||||
|
||||
# Assert
|
||||
assert_greater(size, 0)
|
||||
ole.close()
|
||||
|
||||
|
||||
def test_get_rootentry_name():
|
||||
# Arrange
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
ole = OleFileIO.OleFileIO(ole_file)
|
||||
|
||||
# Act
|
||||
root = ole.get_rootentry_name()
|
||||
|
||||
# Assert
|
||||
assert_equal(root, "Root Entry")
|
||||
ole.close()
|
||||
|
||||
|
||||
def test_meta():
|
||||
# Arrange
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
ole = OleFileIO.OleFileIO(ole_file)
|
||||
|
||||
# Act
|
||||
meta = ole.get_metadata()
|
||||
|
||||
# Assert
|
||||
assert_equal(meta.author, b"Laurence Ipsum")
|
||||
assert_equal(meta.num_pages, 1)
|
||||
ole.close()
|
||||
|
||||
|
||||
def test_gettimes():
|
||||
# Arrange
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
ole = OleFileIO.OleFileIO(ole_file)
|
||||
root_entry = ole.direntries[0]
|
||||
|
||||
# Act
|
||||
ctime = root_entry.getctime()
|
||||
mtime = root_entry.getmtime()
|
||||
|
||||
# Assert
|
||||
assert_is_instance(ctime, type(None))
|
||||
assert_is_instance(mtime, datetime.datetime)
|
||||
assert_equal(ctime, None)
|
||||
assert_equal(mtime.year, 2014)
|
||||
ole.close()
|
||||
|
||||
|
||||
def test_listdir():
|
||||
# Arrange
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
ole = OleFileIO.OleFileIO(ole_file)
|
||||
|
||||
# Act
|
||||
dirlist = ole.listdir()
|
||||
|
||||
# Assert
|
||||
assert_in(['WordDocument'], dirlist)
|
||||
ole.close()
|
||||
|
||||
|
||||
def test_debug():
|
||||
# Arrange
|
||||
print("ignore_all_except_last_line")
|
||||
ole_file = "Tests/images/test-ole-file.doc"
|
||||
ole = OleFileIO.OleFileIO(ole_file)
|
||||
meta = ole.get_metadata()
|
||||
|
||||
# Act
|
||||
OleFileIO.set_debug_mode(True)
|
||||
ole.dumpdirectory()
|
||||
meta.dump()
|
||||
|
||||
OleFileIO.set_debug_mode(False)
|
||||
ole.dumpdirectory()
|
||||
meta.dump()
|
||||
|
||||
# Assert
|
||||
# No assert, just check they run ok
|
||||
print("ok")
|
||||
ole.close()
|
||||
|
||||
|
||||
# End of file
|
143
Tests/tester.py
143
Tests/tester.py
|
@ -6,15 +6,13 @@ warnings.simplefilter('default')
|
|||
# temporarily turn off resource warnings that warn about unclosed
|
||||
# files in the test scripts.
|
||||
try:
|
||||
warnings.filterwarnings("ignore", category=ResourceWarning)
|
||||
warnings.filterwarnings("ignore", category=ResourceWarning)
|
||||
except NameError:
|
||||
# we expect a NameError on py2.x, since it doesn't have ResourceWarnings.
|
||||
pass
|
||||
|
||||
|
||||
# we expect a NameError on py2.x, since it doesn't have ResourceWarnings.
|
||||
pass
|
||||
|
||||
import sys
|
||||
py3 = (sys.version_info >= (3,0))
|
||||
py3 = (sys.version_info >= (3, 0))
|
||||
|
||||
# some test helpers
|
||||
|
||||
|
@ -22,6 +20,7 @@ _target = None
|
|||
_tempfiles = []
|
||||
_logfile = None
|
||||
|
||||
|
||||
def success():
|
||||
import sys
|
||||
success.count += 1
|
||||
|
@ -29,8 +28,10 @@ def success():
|
|||
print(sys.argv[0], success.count, failure.count, file=_logfile)
|
||||
return True
|
||||
|
||||
|
||||
def failure(msg=None, frame=None):
|
||||
import sys, linecache
|
||||
import sys
|
||||
import linecache
|
||||
failure.count += 1
|
||||
if _target:
|
||||
if frame is None:
|
||||
|
@ -49,6 +50,7 @@ def failure(msg=None, frame=None):
|
|||
|
||||
success.count = failure.count = 0
|
||||
|
||||
|
||||
# predicates
|
||||
|
||||
def assert_true(v, msg=None):
|
||||
|
@ -57,35 +59,83 @@ def assert_true(v, msg=None):
|
|||
else:
|
||||
failure(msg or "got %r, expected true value" % v)
|
||||
|
||||
|
||||
def assert_false(v, msg=None):
|
||||
if v:
|
||||
failure(msg or "got %r, expected false value" % v)
|
||||
else:
|
||||
success()
|
||||
|
||||
|
||||
def assert_equal(a, b, msg=None):
|
||||
if a == b:
|
||||
success()
|
||||
else:
|
||||
failure(msg or "got %r, expected %r" % (a, b))
|
||||
|
||||
|
||||
def assert_almost_equal(a, b, msg=None, eps=1e-6):
|
||||
if abs(a-b) < eps:
|
||||
success()
|
||||
else:
|
||||
failure(msg or "got %r, expected %r" % (a, b))
|
||||
|
||||
|
||||
def assert_deep_equal(a, b, msg=None):
|
||||
try:
|
||||
if len(a) == len(b):
|
||||
if all([x==y for x,y in zip(a,b)]):
|
||||
if all([x == y for x, y in zip(a, b)]):
|
||||
success()
|
||||
else:
|
||||
failure(msg or "got %s, expected %s" % (a,b))
|
||||
failure(msg or "got %s, expected %s" % (a, b))
|
||||
else:
|
||||
failure(msg or "got length %s, expected %s" % (len(a), len(b)))
|
||||
except:
|
||||
assert_equal(a,b,msg)
|
||||
assert_equal(a, b, msg)
|
||||
|
||||
|
||||
def assert_greater(a, b, msg=None):
|
||||
if a > b:
|
||||
success()
|
||||
else:
|
||||
failure(msg or "%r unexpectedly not greater than %r" % (a, b))
|
||||
|
||||
|
||||
def assert_greater_equal(a, b, msg=None):
|
||||
if a >= b:
|
||||
success()
|
||||
else:
|
||||
failure(
|
||||
msg or "%r unexpectedly not greater than or equal to %r" % (a, b))
|
||||
|
||||
|
||||
def assert_less(a, b, msg=None):
|
||||
if a < b:
|
||||
success()
|
||||
else:
|
||||
failure(msg or "%r unexpectedly not less than %r" % (a, b))
|
||||
|
||||
|
||||
def assert_less_equal(a, b, msg=None):
|
||||
if a <= b:
|
||||
success()
|
||||
else:
|
||||
failure(
|
||||
msg or "%r unexpectedly not less than or equal to %r" % (a, b))
|
||||
|
||||
|
||||
def assert_is_instance(a, b, msg=None):
|
||||
if isinstance(a, b):
|
||||
success()
|
||||
else:
|
||||
failure(msg or "got %r, expected %r" % (type(a), b))
|
||||
|
||||
|
||||
def assert_in(a, b, msg=None):
|
||||
if a in b:
|
||||
success()
|
||||
else:
|
||||
failure(msg or "%r unexpectedly not in %r" % (a, b))
|
||||
|
||||
|
||||
def assert_match(v, pattern, msg=None):
|
||||
|
@ -95,8 +145,10 @@ def assert_match(v, pattern, msg=None):
|
|||
else:
|
||||
failure(msg or "got %r, doesn't match pattern %r" % (v, pattern))
|
||||
|
||||
|
||||
def assert_exception(exc_class, func):
|
||||
import sys, traceback
|
||||
import sys
|
||||
import traceback
|
||||
try:
|
||||
func()
|
||||
except exc_class:
|
||||
|
@ -108,8 +160,10 @@ def assert_exception(exc_class, func):
|
|||
else:
|
||||
failure("expected %r exception, got no exception" % exc_class.__name__)
|
||||
|
||||
|
||||
def assert_no_exception(func):
|
||||
import sys, traceback
|
||||
import sys
|
||||
import traceback
|
||||
try:
|
||||
func()
|
||||
except:
|
||||
|
@ -118,11 +172,14 @@ def assert_no_exception(func):
|
|||
else:
|
||||
success()
|
||||
|
||||
|
||||
def assert_warning(warn_class, func):
|
||||
# note: this assert calls func three times!
|
||||
import warnings
|
||||
|
||||
def warn_error(message, category=UserWarning, **options):
|
||||
raise category(message)
|
||||
|
||||
def warn_ignore(message, category=UserWarning, **options):
|
||||
pass
|
||||
warn = warnings.warn
|
||||
|
@ -134,22 +191,25 @@ def assert_warning(warn_class, func):
|
|||
warnings.warn = warn_error
|
||||
assert_exception(warn_class, func)
|
||||
finally:
|
||||
warnings.warn = warn # restore
|
||||
warnings.warn = warn # restore
|
||||
return result
|
||||
|
||||
# helpers
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
def fromstring(data):
|
||||
from PIL import Image
|
||||
return Image.open(BytesIO(data))
|
||||
|
||||
|
||||
def tostring(im, format, **options):
|
||||
out = BytesIO()
|
||||
im.save(out, format, **options)
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
def lena(mode="RGB", cache={}):
|
||||
from PIL import Image
|
||||
im = cache.get(mode)
|
||||
|
@ -165,6 +225,7 @@ def lena(mode="RGB", cache={}):
|
|||
cache[mode] = im
|
||||
return im
|
||||
|
||||
|
||||
def assert_image(im, mode, size, msg=None):
|
||||
if mode is not None and im.mode != mode:
|
||||
failure(msg or "got mode %r, expected %r" % (im.mode, mode))
|
||||
|
@ -173,6 +234,7 @@ def assert_image(im, mode, size, msg=None):
|
|||
else:
|
||||
success()
|
||||
|
||||
|
||||
def assert_image_equal(a, b, msg=None):
|
||||
if a.mode != b.mode:
|
||||
failure(msg or "got mode %r, expected %r" % (a.mode, b.mode))
|
||||
|
@ -184,6 +246,7 @@ def assert_image_equal(a, b, msg=None):
|
|||
else:
|
||||
success()
|
||||
|
||||
|
||||
def assert_image_similar(a, b, epsilon, msg=None):
|
||||
epsilon = float(epsilon)
|
||||
if a.mode != b.mode:
|
||||
|
@ -193,19 +256,25 @@ def assert_image_similar(a, b, epsilon, msg=None):
|
|||
diff = 0
|
||||
try:
|
||||
ord(b'0')
|
||||
for abyte,bbyte in zip(a.tobytes(),b.tobytes()):
|
||||
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
|
||||
diff += abs(ord(abyte)-ord(bbyte))
|
||||
except:
|
||||
for abyte,bbyte in zip(a.tobytes(),b.tobytes()):
|
||||
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
|
||||
diff += abs(abyte-bbyte)
|
||||
ave_diff = float(diff)/(a.size[0]*a.size[1])
|
||||
if epsilon < ave_diff:
|
||||
return failure(msg or "average pixel value difference %.4f > epsilon %.4f" %(ave_diff, epsilon))
|
||||
return failure(
|
||||
msg or "average pixel value difference %.4f > epsilon %.4f" % (
|
||||
ave_diff, epsilon))
|
||||
else:
|
||||
return success()
|
||||
|
||||
|
||||
def tempfile(template, *extra):
|
||||
import os, os.path, sys, tempfile
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import tempfile
|
||||
files = []
|
||||
root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
|
||||
try:
|
||||
|
@ -222,18 +291,20 @@ def tempfile(template, *extra):
|
|||
_tempfiles.extend(files)
|
||||
return files[0]
|
||||
|
||||
|
||||
# test runner
|
||||
|
||||
def run():
|
||||
global _target, _tests, run
|
||||
import sys, traceback
|
||||
import sys
|
||||
import traceback
|
||||
_target = sys.modules["__main__"]
|
||||
run = None # no need to run twice
|
||||
run = None # no need to run twice
|
||||
tests = []
|
||||
for name, value in list(vars(_target).items()):
|
||||
if name[:5] == "test_" and type(value) is type(success):
|
||||
tests.append((value.__code__.co_firstlineno, name, value))
|
||||
tests.sort() # sort by line
|
||||
tests.sort() # sort by line
|
||||
for lineno, name, func in tests:
|
||||
try:
|
||||
_tests = []
|
||||
|
@ -251,52 +322,56 @@ def run():
|
|||
sys.argv[0], lineno, v))
|
||||
failure.count += 1
|
||||
|
||||
|
||||
def yield_test(function, *args):
|
||||
# collect delayed/generated tests
|
||||
_tests.append((function, args))
|
||||
|
||||
|
||||
def skip(msg=None):
|
||||
import os
|
||||
print("skip")
|
||||
os._exit(0) # don't run exit handlers
|
||||
os._exit(0) # don't run exit handlers
|
||||
|
||||
|
||||
def ignore(pattern):
|
||||
"""Tells the driver to ignore messages matching the pattern, for the
|
||||
duration of the current test."""
|
||||
print('ignore: %s' % pattern)
|
||||
|
||||
|
||||
def _setup():
|
||||
global _logfile
|
||||
|
||||
import sys
|
||||
if "--coverage" in sys.argv:
|
||||
import coverage
|
||||
cov = coverage.coverage(auto_data=True, include="PIL/*")
|
||||
cov.start()
|
||||
|
||||
def report():
|
||||
if run:
|
||||
run()
|
||||
if success.count and not failure.count:
|
||||
print("ok")
|
||||
# only clean out tempfiles if test passed
|
||||
import os, os.path, tempfile
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
for file in _tempfiles:
|
||||
try:
|
||||
os.remove(file)
|
||||
except OSError:
|
||||
pass # report?
|
||||
pass # report?
|
||||
temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
|
||||
try:
|
||||
os.rmdir(temp_root)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if "--coverage" in sys.argv:
|
||||
import coverage
|
||||
coverage.stop()
|
||||
# The coverage module messes up when used from inside an
|
||||
# atexit handler. Do an explicit save to make sure that
|
||||
# we actually flush the coverage cache.
|
||||
coverage.the_coverage.save()
|
||||
import atexit, sys
|
||||
import atexit
|
||||
atexit.register(report)
|
||||
if "--coverage" in sys.argv:
|
||||
import coverage
|
||||
coverage.start()
|
||||
|
||||
if "--log" in sys.argv:
|
||||
_logfile = open("test.log", "a")
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ Why a fork?
|
|||
|
||||
PIL is not setuptools compatible. Please see `this Image-SIG post`_ for a more
|
||||
detailed explanation. Also, PIL's current bi-yearly (or greater) release
|
||||
schedule is too infrequent to accomodate the large number and frequency of
|
||||
schedule is too infrequent to accommodate the large number and frequency of
|
||||
issues reported.
|
||||
|
||||
.. _this Image-SIG post: https://mail.python.org/pipermail/image-sig/2010-August/006480.html
|
||||
|
@ -52,7 +52,7 @@ What about PIL?
|
|||
Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0
|
||||
added Python 3 support and includes many bug fixes from many contributors.
|
||||
|
||||
As more time passes since the last PIL release, the likelyhood of a new PIL
|
||||
As more time passes since the last PIL release, the likelihood of a new PIL
|
||||
release decreases. However, we've yet to hear an official "PIL is dead"
|
||||
announcement. So if you still want to support PIL, please
|
||||
`report issues here first`_, then
|
||||
|
|
|
@ -126,8 +126,8 @@ Identify Image Files
|
|||
|
||||
for infile in sys.argv[1:]:
|
||||
try:
|
||||
im = Image.open(infile)
|
||||
print(infile, im.format, "%dx%d" % im.size, im.mode)
|
||||
with Image.open(infile) as im:
|
||||
print(infile, im.format, "%dx%d" % im.size, im.mode)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@ Pillow
|
|||
Pillow is the 'friendly' PIL fork by Alex Clark and Contributors. PIL is the
|
||||
Python Imaging Library by Fredrik Lundh and Contributors.
|
||||
|
||||
.. image:: https://travis-ci.org/python-imaging/Pillow.png
|
||||
.. image:: https://travis-ci.org/python-imaging/Pillow.svg?branch=master
|
||||
:target: https://travis-ci.org/python-imaging/Pillow
|
||||
:alt: Travis CI build status
|
||||
|
||||
.. image:: https://pypip.in/v/Pillow/badge.png
|
||||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
|
@ -15,6 +16,10 @@ Python Imaging Library by Fredrik Lundh and Contributors.
|
|||
:target: https://pypi.python.org/pypi/Pillow/
|
||||
:alt: Number of PyPI downloads
|
||||
|
||||
.. image:: https://coveralls.io/repos/python-imaging/Pillow/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/python-imaging/Pillow?branch=master
|
||||
:alt: Test coverage
|
||||
|
||||
To start using Pillow, please read the :doc:`installation
|
||||
instructions <installation>`.
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ distribution. Otherwise, use whatever XCode you used to compile Python.)
|
|||
The easiest way to install the prerequisites is via `Homebrew
|
||||
<http://mxcl.github.com/homebrew/>`_. After you install Homebrew, run::
|
||||
|
||||
$ brew install libtiff libjpeg webp littlecms
|
||||
$ brew install libtiff libjpeg webp little-cms2
|
||||
|
||||
If you've built your own Python, then you should be able to install Pillow
|
||||
using::
|
||||
|
|
|
@ -136,9 +136,9 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
|
|||
.. automethod:: PIL.Image.Image.verify
|
||||
|
||||
.. automethod:: PIL.Image.Image.fromstring
|
||||
.. deprecated:: 2.0
|
||||
|
||||
.. automethod:: PIL.Image.Image.load
|
||||
.. automethod:: PIL.Image.Image.close
|
||||
|
||||
Attributes
|
||||
----------
|
||||
|
|
Loading…
Reference in New Issue
Block a user