merge from master

This commit is contained in:
wiredfool 2013-12-19 20:48:49 -08:00
commit aa1c0fdfb6
30 changed files with 506 additions and 104 deletions

View File

@ -6,7 +6,7 @@ python:
- 3.2 - 3.2
- 3.3 - 3.3
install: "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev" install: "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev ghostscript"
script: script:
- python setup.py clean - python setup.py clean

View File

@ -4,6 +4,30 @@ Changelog (Pillow)
2.3.0 (2014-01-01) 2.3.0 (2014-01-01)
------------------ ------------------
- 2gigapix image fixes
[wiredfool]
- Save arbitrary tags in Tiff image files
[wiredfool]
- Quote filenames and title before using on command line
[tmccombs]
- Fixed Viewer.show to return properly
[tmccombs]
- Documentation fixes
[wiredfool]
- Fixed memory leak saving images as webp when webpmux is available
[cezarsa]
- Fix compiling with FreeType 2.5.1
[stromnov]
- Adds directories for NetBSD.
[deepy]
- Support RGBA TIFF with missing ExtraSamples tag - Support RGBA TIFF with missing ExtraSamples tag
[cgohlke] [cgohlke]

View File

@ -1,36 +0,0 @@
Contributors (Pillow)
=====================
.. Note:: Contributors: please add your name here
- Alex Po <alex-86p __at__ yandex.ru>
- Anton Vlasenko <antares.spica __at__ gmail.com>
- Brian J. Crowell <brian __at__ fluggo.com>
- Bryant Mairs <bwmairs __at__ ucsc.edu>
- Christoph Gohlke <cgohlke __at__ uci.edu>
- Corey Richardson <corey __at__ octayn.net>
- Daniel Hahler <github __at__ thequod.de>
- David Schmidt <david.schmiddi.86 __at__ gmail.com>
- Eliot <saltycrane __at__ gmail.com>
- etienne <etienne.desautels __at__ gmail.com>
- Jannis Leidel <jannis __at__ leidel.info>
- Kyle MacFarlane <kyle __at__ deletethetrees.com>
- Lars Yencken <lars __at__ yencken.org>
- Liu Qishuai <lqs __at__ lqs.me>
- Manuel Ebert <Maebert __at__ UOS.de>
- Marc Abramowitz <marc __at__ marc-abramowitz.com>
- Matti Picus <gitmatti __at__ picus.org.il>
- Mikhail Korobov <kmike84 __at__ gmail.com>
- OCHIAI, Gouji <gjo.ext __at__ gmail.com>
- Oliver Tonnhofer <olt __at__ bogosoft.com>
- Phil Elson <pelson.pub __at__ gmail.com>
- Sandro Mani <manisandro __at__ gmail.com>
- Simon Law <simon.law __at__ ecometrica.com>
- Stéphane Klein <stephane __at__ harobed.org>
- Steve Johnson <steve __at__ steveasleep.com>
- Takeshi KOMIYA <i.tkomiya __at__ gmail.com>
- Tom Gross <tom __at__ toms-projekte.de>
- Tom Payne <twpayne __at__ gmail.com>
- Tyler Garner <garnertb __at__ gmail.com>
- tdesvenain <thomas.desvenain __at__ gmail.com>
- wiredfool <eric-github __at__ soroos.net>

View File

@ -27,6 +27,8 @@ recursive-include Sane README
recursive-include Scripts *.py recursive-include Scripts *.py
recursive-include Scripts README recursive-include Scripts README
recursive-include Tests *.bin recursive-include Tests *.bin
recursive-include Tests *.eps
recursive-include Tests *.gnuplot
recursive-include Tests *.icm recursive-include Tests *.icm
recursive-include Tests *.jpg recursive-include Tests *.jpg
recursive-include Tests *.pcf recursive-include Tests *.pcf
@ -42,8 +44,11 @@ recursive-include Tk *.c
recursive-include Tk *.txt recursive-include Tk *.txt
recursive-include docs *.bat recursive-include docs *.bat
recursive-include docs *.gitignore recursive-include docs *.gitignore
recursive-include docs *.html
recursive-include docs *.py recursive-include docs *.py
recursive-include docs *.rst recursive-include docs *.rst
recursive-include docs *.txt
recursive-include docs Guardfile
recursive-include docs Makefile recursive-include docs Makefile
recursive-include docs BUILDME recursive-include docs BUILDME
recursive-include docs COPYING recursive-include docs COPYING

View File

@ -50,14 +50,22 @@ if sys.platform.startswith('win'):
else: else:
gs_windows_binary = False gs_windows_binary = False
def Ghostscript(tile, size, fp): def Ghostscript(tile, size, fp, scale=1):
"""Render an image using Ghostscript""" """Render an image using Ghostscript"""
# Unpack decoder tile # Unpack decoder tile
decoder, tile, offset, data = tile[0] decoder, tile, offset, data = tile[0]
length, bbox = data length, bbox = data
import tempfile, os #Hack to support hi-res rendering
scale = int(scale) or 1
orig_size = size
orig_bbox = bbox
size = (size[0] * scale, size[1] * scale)
bbox = [bbox[0], bbox[1], bbox[2] * scale, bbox[3] * scale]
#print("Ghostscript", scale, size, orig_size, bbox, orig_bbox)
import tempfile, os, subprocess
file = tempfile.mktemp() file = tempfile.mktemp()
@ -65,33 +73,32 @@ def Ghostscript(tile, size, fp):
command = ["gs", command = ["gs",
"-q", # quite mode "-q", # quite mode
"-g%dx%d" % size, # set output geometry (pixels) "-g%dx%d" % size, # set output geometry (pixels)
"-r%d" % (72*scale), # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode "-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
"-sDEVICE=ppmraw", # ppm driver "-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % file,# output file "-sOutputFile=%s" % file,# output file
"- >/dev/null 2>/dev/null"] ]
if gs_windows_binary is not None: if gs_windows_binary is not None:
if gs_windows_binary is False: if gs_windows_binary is False:
raise WindowsError('Unable to locate Ghostscript on paths') raise WindowsError('Unable to locate Ghostscript on paths')
command[0] = gs_windows_binary command[0] = gs_windows_binary
command[-1] = '- >nul 2>nul'
command = " ".join(command)
# push data through ghostscript # push data through ghostscript
try: try:
gs = os.popen(command, "w") gs = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
# adjust for image origin # adjust for image origin
if bbox[0] != 0 or bbox[1] != 0: if bbox[0] != 0 or bbox[1] != 0:
gs.write("%d %d translate\n" % (-bbox[0], -bbox[1])) gs.stdin.write(("%d %d translate\n" % (-bbox[0], -bbox[1])).encode('ascii'))
fp.seek(offset) fp.seek(offset)
while length > 0: while length > 0:
s = fp.read(8192) s = fp.read(8192)
if not s: if not s:
break break
length = length - len(s) length = length - len(s)
gs.write(s) gs.stdin.write(s)
status = gs.close() gs.stdin.close()
status = gs.wait()
if status: if status:
raise IOError("gs failed (status %d)" % status) raise IOError("gs failed (status %d)" % status)
im = Image.core.open_ppm(file) im = Image.core.open_ppm(file)
@ -304,11 +311,11 @@ class EpsImageFile(ImageFile.ImageFile):
if not box: if not box:
raise IOError("cannot determine EPS bounding box") raise IOError("cannot determine EPS bounding box")
def load(self): def load(self, scale=1):
# Load EPS via Ghostscript # Load EPS via Ghostscript
if not self.tile: if not self.tile:
return return
self.im = Ghostscript(self.tile, self.size, self.fp) self.im = Ghostscript(self.tile, self.size, self.fp, scale)
self.mode = self.im.mode self.mode = self.im.mode
self.size = self.im.size self.size = self.im.size
self.tile = [] self.tile = []

View File

@ -675,15 +675,18 @@ class Image:
L = R * 299/1000 + G * 587/1000 + B * 114/1000 L = R * 299/1000 + G * 587/1000 + B * 114/1000
When translating a greyscale image into a bilevel image (mode The default method of converting a greyscale ("L") or "RGB"
"1"), all non-zero values are set to 255 (white). To use other image into a bilevel (mode "1") image uses Floyd-Steinberg
thresholds, use the :py:meth:`~PIL.Image.Image.point` method. dither to approximate the original image luminosity levels. If
dither is NONE, all non-zero values are set to 255 (white). To
use other thresholds, use the :py:meth:`~PIL.Image.Image.point`
method.
:param mode: The requested mode. :param mode: The requested mode.
:param matrix: An optional conversion matrix. If given, this :param matrix: An optional conversion matrix. If given, this
should be 4- or 16-tuple containing floating point values. should be 4- or 16-tuple containing floating point values.
:param dither: Dithering method, used when converting from :param dither: Dithering method, used when converting from
mode "RGB" to "P". mode "RGB" to "P" or from "RGB" or "L" to "1".
Available methods are NONE or FLOYDSTEINBERG (default). Available methods are NONE or FLOYDSTEINBERG (default).
:param palette: Palette to use when converting from mode "RGB" :param palette: Palette to use when converting from mode "RGB"
to "P". Available palettes are WEB or ADAPTIVE. to "P". Available palettes are WEB or ADAPTIVE.

View File

@ -17,6 +17,11 @@ from __future__ import print_function
from PIL import Image from PIL import Image
import os, sys import os, sys
if(sys.version_info >= (3, 3)):
from shlex import quote
else:
from pipes import quote
_viewers = [] _viewers = []
def register(viewer, order=1): def register(viewer, order=1):
@ -65,7 +70,7 @@ class Viewer:
if base != image.mode and image.mode != "1": if base != image.mode and image.mode != "1":
image = image.convert(base) image = image.convert(base)
self.show_image(image, **options) return self.show_image(image, **options)
# hook methods # hook methods
@ -99,7 +104,7 @@ if sys.platform == "win32":
format = "BMP" format = "BMP"
def get_command(self, file, **options): def get_command(self, file, **options):
return ("start /wait %s && ping -n 2 127.0.0.1 >NUL " return ("start /wait %s && ping -n 2 127.0.0.1 >NUL "
"&& del /f %s" % (file, file)) "&& del /f %s" % (quote(file), quote(file)))
register(WindowsViewer) register(WindowsViewer)
@ -111,7 +116,7 @@ elif sys.platform == "darwin":
# on darwin open returns immediately resulting in the temp # on darwin open returns immediately resulting in the temp
# file removal while app is opening # file removal while app is opening
command = "open -a /Applications/Preview.app" command = "open -a /Applications/Preview.app"
command = "(%s %s; sleep 20; rm -f %s)&" % (command, file, file) command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file), quote(file))
return command return command
register(MacViewer) register(MacViewer)
@ -134,7 +139,7 @@ else:
class UnixViewer(Viewer): class UnixViewer(Viewer):
def show_file(self, file, **options): def show_file(self, file, **options):
command, executable = self.get_command_ex(file, **options) command, executable = self.get_command_ex(file, **options)
command = "(%s %s; rm -f %s)&" % (command, file, file) command = "(%s %s; rm -f %s)&" % (command, quote(file), quote(file))
os.system(command) os.system(command)
return 1 return 1
@ -154,8 +159,7 @@ else:
# imagemagick's display command instead. # imagemagick's display command instead.
command = executable = "xv" command = executable = "xv"
if title: if title:
# FIXME: do full escaping command = command + " -name %s" % quote(title)
command = command + " -name \"%s\"" % title
return command, executable return command, executable
if which("xv"): if which("xv"):

View File

@ -262,7 +262,7 @@ def getiptcinfo(im):
# get raw data from the IPTC/NAA tag (PhotoShop tags the data # get raw data from the IPTC/NAA tag (PhotoShop tags the data
# as 4-byte integers, so we cannot use the get method...) # as 4-byte integers, so we cannot use the get method...)
try: try:
type, data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK] data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
except (AttributeError, KeyError): except (AttributeError, KeyError):
pass pass

View File

@ -220,11 +220,45 @@ def _accept(prefix):
# Wrapper for TIFF IFDs. # Wrapper for TIFF IFDs.
class ImageFileDirectory(collections.MutableMapping): class ImageFileDirectory(collections.MutableMapping):
""" This class represents a TIFF tag directory. To speed things
up, we don't decode tags unless they're asked for.
# represents a TIFF tag directory. to speed things up, Exposes a dictionary interface of the tags in the directory
# we don't decode tags unless they're asked for. ImageFileDirectory[key] = value
value = ImageFileDirectory[key]
def __init__(self, prefix): Also contains a dictionary of tag types as read from the tiff
image file, 'ImageFileDirectory.tagtype'
Data Structures:
'public'
* self.tagtype = {} Key: numerical tiff tag number
Value: integer corresponding to the data type from
`TiffTags.TYPES`
'internal'
* self.tags = {} Key: numerical tiff tag number
Value: Decoded data, Generally a tuple.
* If set from __setval__ -- always a tuple
* Numeric types -- always a tuple
* String type -- not a tuple, returned as string
* Undefined data -- not a tuple, returned as bytes
* Byte -- not a tuple, returned as byte.
* self.tagdata = {} Key: numerical tiff tag number
Value: undecoded byte string from file
Tags will be found in either self.tags or self.tagdata, but
not both. The union of the two should contain all the tags
from the Tiff image file. External classes shouldn't
reference these unless they're really sure what they're doing.
"""
def __init__(self, prefix=II):
"""
:prefix: 'II'|'MM' tiff endianness
"""
self.prefix = prefix[:2] self.prefix = prefix[:2]
if self.prefix == MM: if self.prefix == MM:
self.i16, self.i32 = ib16, ib32 self.i16, self.i32 = ib16, ib32
@ -270,7 +304,8 @@ class ImageFileDirectory(collections.MutableMapping):
try: try:
return self.tags[tag] return self.tags[tag]
except KeyError: except KeyError:
type, data = self.tagdata[tag] # unpack on the fly data = self.tagdata[tag] # unpack on the fly
type = self.tagtype[tag]
size, handler = self.load_dispatch[type] size, handler = self.load_dispatch[type]
self.tags[tag] = data = handler(self, data) self.tags[tag] = data = handler(self, data)
del self.tagdata[tag] del self.tagdata[tag]
@ -299,6 +334,9 @@ class ImageFileDirectory(collections.MutableMapping):
return tag in self return tag in self
def __setitem__(self, tag, value): def __setitem__(self, tag, value):
# tags are tuples for integers
# tags are not tuples for byte, string, and undefined data.
# see load_*
if not isinstance(value, tuple): if not isinstance(value, tuple):
value = (value,) value = (value,)
self.tags[tag] = value self.tags[tag] = value
@ -413,7 +451,7 @@ class ImageFileDirectory(collections.MutableMapping):
warnings.warn("Possibly corrupt EXIF data. Expecting to read %d bytes but only got %d. Skipping tag %s" % (size, len(data), tag)) warnings.warn("Possibly corrupt EXIF data. Expecting to read %d bytes but only got %d. Skipping tag %s" % (size, len(data), tag))
continue continue
self.tagdata[tag] = typ, data self.tagdata[tag] = data
self.tagtype[tag] = typ self.tagtype[tag] = typ
if Image.DEBUG: if Image.DEBUG:
@ -450,25 +488,42 @@ class ImageFileDirectory(collections.MutableMapping):
if tag in self.tagtype: if tag in self.tagtype:
typ = self.tagtype[tag] typ = self.tagtype[tag]
if Image.DEBUG:
print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
if typ == 1: if typ == 1:
# byte data # byte data
data = value if isinstance(value, tuple):
data = value = value[-1]
else:
data = value
elif typ == 7: elif typ == 7:
# untyped data # untyped data
data = value = b"".join(value) data = value = b"".join(value)
elif isinstance(value[0], str): elif isStringType(value[0]):
# string data # string data
if isinstance(value, tuple):
value = value[-1]
typ = 2 typ = 2
data = value = b"\0".join(value.encode('ascii', 'replace')) + b"\0" # was b'\0'.join(str), which led to \x00a\x00b sorts
# of strings which I don't see in in the wild tiffs
# and doesn't match the tiff spec: 8-bit byte that
# contains a 7-bit ASCII code; the last byte must be
# NUL (binary zero). Also, I don't think this was well
# excersized before.
data = value = b"" + value.encode('ascii', 'replace') + b"\0"
else: else:
# integer data # integer data
if tag == STRIPOFFSETS: if tag == STRIPOFFSETS:
stripoffsets = len(directory) stripoffsets = len(directory)
typ = 4 # to avoid catch-22 typ = 4 # to avoid catch-22
elif tag in (X_RESOLUTION, Y_RESOLUTION): elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ==5:
# identify rational data fields # identify rational data fields
typ = 5 typ = 5
if isinstance(value[0], tuple):
# long name for flatten
value = tuple(itertools.chain.from_iterable(value))
elif not typ: elif not typ:
typ = 3 typ = 3
for v in value: for v in value:
@ -500,6 +555,7 @@ class ImageFileDirectory(collections.MutableMapping):
count = len(value) count = len(value)
if typ == 5: if typ == 5:
count = count // 2 # adjust for rational data field count = count // 2 # adjust for rational data field
append((tag, typ, count, o32(offset), data)) append((tag, typ, count, o32(offset), data))
offset = offset + len(data) offset = offset + len(data)
if offset & 1: if offset & 1:
@ -941,23 +997,34 @@ def _save(im, fp, filename):
ifd[IMAGEWIDTH] = im.size[0] ifd[IMAGEWIDTH] = im.size[0]
ifd[IMAGELENGTH] = im.size[1] ifd[IMAGELENGTH] = im.size[1]
# write any arbitrary tags passed in as an ImageFileDirectory
info = im.encoderinfo.get("tiffinfo",{})
if Image.DEBUG:
print ("Tiffinfo Keys: %s"% info.keys)
keys = list(info.keys())
for key in keys:
ifd[key] = info.get(key)
try:
ifd.tagtype[key] = info.tagtype[key]
except:
pass # might not be an IFD, Might not have populated type
# additions written by Greg Couch, gregc@cgl.ucsf.edu # additions written by Greg Couch, gregc@cgl.ucsf.edu
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
if hasattr(im, 'tag'): if hasattr(im, 'tag'):
# preserve tags from original TIFF image file # preserve tags from original TIFF image file
for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION): for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION,
if key in im.tag.tagdata: IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
ifd[key] = im.tag.tagdata.get(key)
# preserve some more tags from original TIFF image file
# -- 2008-06-06 Florian Hoech
ifd.tagtype = im.tag.tagtype
for key in (IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
if key in im.tag: if key in im.tag:
ifd[key] = im.tag[key] ifd[key] = im.tag[key]
ifd.tagtype[key] = im.tag.tagtype.get(key, None)
# preserve ICC profile (should also work when saving other formats # preserve ICC profile (should also work when saving other formats
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
if "icc_profile" in im.info: if "icc_profile" in im.info:
ifd[ICCPROFILE] = im.info["icc_profile"] ifd[ICCPROFILE] = im.info["icc_profile"]
if "description" in im.encoderinfo: if "description" in im.encoderinfo:
ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"]
if "resolution" in im.encoderinfo: if "resolution" in im.encoderinfo:

View File

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

View File

@ -0,0 +1,30 @@
#!/usr/bin/gnuplot
#This is the script that was used to create our sample EPS files
#We used the following version of the gnuplot program
#G N U P L O T
#Version 4.6 patchlevel 3 last modified 2013-04-12
#Build System: Darwin x86_64
#This file will generate the non_zero_bb.eps variant, in order to get the
#zero_bb.eps variant you will need to edit line6 in the result file to
#be "%%BoundingBox: 0 0 460 352" instead of "%%BoundingBox: 50 50 410 302"
set t postscript eps color
set o "sample.eps"
set dummy u,v
set key bmargin center horizontal Right noreverse enhanced autotitles nobox
set parametric
set view 50, 30, 1, 1
set isosamples 10, 10
set hidden3d back offset 1 trianglepattern 3 undefined 1 altdiagonal bentover
set ticslevel 0
set title "Interlocking Tori"
set style line 1 lt 1 lw 1 pt 3 lc rgb "red"
set style line 2 lt 1 lw 1 pt 3 lc rgb "blue"
set urange [ -3.14159 : 3.14159 ] noreverse nowriteback
set vrange [ -3.14159 : 3.14159 ] noreverse nowriteback
splot cos(u)+.5*cos(u)*cos(v),sin(u)+.5*sin(u)*cos(v),.5*sin(v) ls 1,\
1+cos(u)+.5*cos(u)*cos(v),.5*sin(v),sin(u)+.5*sin(u)*cos(v) ls 2

BIN
Tests/images/lena.tif Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
Tests/images/zero_bb.eps Normal file

Binary file not shown.

BIN
Tests/images/zero_bb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,27 @@
from tester import *
# This test is not run automatically.
#
# It requires > 2gb memory for the >2 gigapixel image generated in the
# second test. Running this automatically would amount to a denial of
# service on our testing infrastructure. I expect this test to fail
# on any 32 bit machine, as well as any smallish things (like
# raspberrypis). It does succeed on a 3gb Ubuntu 12.04x64 VM on python
# 2.7 an 3.2
from PIL import Image
ydim = 32769
xdim = 48000
f = tempfile('temp.png')
def _write_png(xdim,ydim):
im = Image.new('L',(xdim,ydim),(0))
im.save(f)
success()
def test_large():
""" succeeded prepatch"""
_write_png(xdim,ydim)
def test_2gpx():
"""failed prepatch"""
_write_png(xdim,xdim)

82
Tests/test_file_eps.py Normal file
View File

@ -0,0 +1,82 @@
from tester import *
from PIL import Image
#Our two EPS test files (they are identical except for their bounding boxes)
file1 = "Tests/images/zero_bb.eps"
file2 = "Tests/images/non_zero_bb.eps"
#Due to palletization, we'll need to convert these to RGB after load
file1_compare = "Tests/images/zero_bb.png"
file1_compare_scale2 = "Tests/images/zero_bb_scale2.png"
file2_compare = "Tests/images/non_zero_bb.png"
file2_compare_scale2 = "Tests/images/non_zero_bb_scale2.png"
def test_sanity():
#Regular scale
image1 = Image.open(file1)
image1.load()
assert_equal(image1.mode, "RGB")
assert_equal(image1.size, (460, 352))
assert_equal(image1.format, "EPS")
image2 = Image.open(file2)
image2.load()
assert_equal(image2.mode, "RGB")
assert_equal(image2.size, (360, 252))
assert_equal(image2.format, "EPS")
#Double scale
image1_scale2 = Image.open(file1)
image1_scale2.load(scale=2)
assert_equal(image1_scale2.mode, "RGB")
assert_equal(image1_scale2.size, (920, 704))
assert_equal(image1_scale2.format, "EPS")
image2_scale2 = Image.open(file2)
image2_scale2.load(scale=2)
assert_equal(image2_scale2.mode, "RGB")
assert_equal(image2_scale2.size, (720, 504))
assert_equal(image2_scale2.format, "EPS")
def test_render_scale1():
#We need png support for these render test
codecs = dir(Image.core)
if "zip_encoder" not in codecs or "zip_decoder" not in codecs:
skip("zip/deflate support not available")
#Zero bounding box
image1_scale1 = Image.open(file1)
image1_scale1.load()
image1_scale1_compare = Image.open(file1_compare).convert("RGB")
image1_scale1_compare.load()
assert_image_similar(image1_scale1, image1_scale1_compare, 5)
#Non-Zero bounding box
image2_scale1 = Image.open(file2)
image2_scale1.load()
image2_scale1_compare = Image.open(file2_compare).convert("RGB")
image2_scale1_compare.load()
assert_image_similar(image2_scale1, image2_scale1_compare, 10)
def test_render_scale2():
#We need png support for these render test
codecs = dir(Image.core)
if "zip_encoder" not in codecs or "zip_decoder" not in codecs:
skip("zip/deflate support not available")
#Zero bounding box
image1_scale2 = Image.open(file1)
image1_scale2.load(scale=2)
image1_scale2_compare = Image.open(file1_compare_scale2).convert("RGB")
image1_scale2_compare.load()
assert_image_similar(image1_scale2, image1_scale2_compare, 5)
#Non-Zero bounding box
image2_scale2 = Image.open(file2)
image2_scale2.load(scale=2)
image2_scale2_compare = Image.open(file2_compare_scale2).convert("RGB")
image2_scale2_compare.load()
assert_image_similar(image2_scale2, image2_scale2_compare, 10)

View File

@ -107,6 +107,29 @@ def test_adobe_deflate_tiff():
assert_equal(im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0)) assert_equal(im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0))
assert_no_exception(lambda: im.load()) assert_no_exception(lambda: im.load())
def test_write_metadata():
""" Test metadata writing through libtiff """
img = Image.open('Tests/images/lena_g4.tif')
f = tempfile('temp.tiff')
img.save(f, tiffinfo = img.tag)
loaded = Image.open(f)
original = img.tag.named()
reloaded = loaded.tag.named()
# PhotometricInterpretation is set from SAVE_INFO, not the original image.
ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'PhotometricInterpretation']
for tag, value in reloaded.items():
if tag not in ignored:
assert_equal(original[tag], value, "%s didn't roundtrip" % tag)
for tag, value in original.items():
if tag not in ignored:
assert_equal(value, reloaded[tag], "%s didn't roundtrip" % tag)
def test_g3_compression(): def test_g3_compression():
i = Image.open('Tests/images/lena_g4_500.tif') i = Image.open('Tests/images/lena_g4_500.tif')
@ -116,7 +139,7 @@ def test_g3_compression():
reread = Image.open(out) reread = Image.open(out)
assert_equal(reread.info['compression'], 'group3') assert_equal(reread.info['compression'], 'group3')
assert_image_equal(reread, i) assert_image_equal(reread, i)
def test_little_endian(): def test_little_endian():
im = Image.open('Tests/images/16bit.deflate.tif') im = Image.open('Tests/images/16bit.deflate.tif')
assert_equal(im.getpixel((0,0)), 480) assert_equal(im.getpixel((0,0)), 480)

View File

@ -0,0 +1,80 @@
from tester import *
from PIL import Image, TiffImagePlugin, TiffTags
tag_ids = dict(zip(TiffTags.TAGS.values(), TiffTags.TAGS.keys()))
def test_rt_metadata():
""" Test writing arbitray metadata into the tiff image directory
Use case is ImageJ private tags, one numeric, one arbitrary
data. https://github.com/python-imaging/Pillow/issues/291
"""
img = lena()
textdata = "This is some arbitrary metadata for a text field"
info = TiffImagePlugin.ImageFileDirectory()
info[tag_ids['ImageJMetaDataByteCounts']] = len(textdata)
info[tag_ids['ImageJMetaData']] = textdata
f = tempfile("temp.tif")
img.save(f, tiffinfo=info)
loaded = Image.open(f)
assert_equal(loaded.tag[50838], (len(textdata),))
assert_equal(loaded.tag[50839], textdata)
def test_read_metadata():
img = Image.open('Tests/images/lena_g4.tif')
known = {'YResolution': ((1207959552, 16777216),),
'PlanarConfiguration': (1,),
'BitsPerSample': (1,),
'ImageLength': (128,),
'Compression': (4,),
'FillOrder': (1,),
'DocumentName': 'lena.g4.tif',
'RowsPerStrip': (128,),
'ResolutionUnit': (1,),
'PhotometricInterpretation': (0,),
'PageNumber': (0, 1),
'XResolution': ((1207959552, 16777216),),
'ImageWidth': (128,),
'Orientation': (1,),
'StripByteCounts': (1796,),
'SamplesPerPixel': (1,),
'StripOffsets': (8,),
'Software': 'ImageMagick 6.5.7-8 2012-08-17 Q16 http://www.imagemagick.org'}
# assert_equal is equivalent, but less helpful in telling what's wrong.
named = img.tag.named()
for tag, value in named.items():
assert_equal(known[tag], value)
for tag, value in known.items():
assert_equal(value, named[tag])
def test_write_metadata():
""" Test metadata writing through the python code """
img = Image.open('Tests/images/lena.tif')
f = tempfile('temp.tiff')
img.save(f, tiffinfo = img.tag)
loaded = Image.open(f)
original = img.tag.named()
reloaded = loaded.tag.named()
ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets']
for tag, value in reloaded.items():
if tag not in ignored:
assert_equal(original[tag], value, "%s didn't roundtrip" % tag)
for tag, value in original.items():
if tag not in ignored:
assert_equal(value, reloaded[tag], "%s didn't roundtrip" % tag)

View File

@ -71,7 +71,7 @@
* See the README file for information on usage and redistribution. * See the README file for information on usage and redistribution.
*/ */
#define PILLOW_VERSION "2.2.1" #define PILLOW_VERSION "2.3.0"
#include "Python.h" #include "Python.h"
@ -1234,7 +1234,8 @@ static PyObject*
_putdata(ImagingObject* self, PyObject* args) _putdata(ImagingObject* self, PyObject* args)
{ {
Imaging image; Imaging image;
int n, i, x, y; // i & n are # pixels, require py_ssize_t. x can be as large as n. y, just because.
Py_ssize_t n, i, x, y;
PyObject* data; PyObject* data;
double scale = 1.0; double scale = 1.0;
@ -1244,16 +1245,16 @@ _putdata(ImagingObject* self, PyObject* args)
return NULL; return NULL;
if (!PySequence_Check(data)) { if (!PySequence_Check(data)) {
PyErr_SetString(PyExc_TypeError, must_be_sequence); PyErr_SetString(PyExc_TypeError, must_be_sequence);
return NULL; return NULL;
} }
image = self->image; image = self->image;
n = PyObject_Length(data); n = PyObject_Length(data);
if (n > (int) (image->xsize * image->ysize)) { if (n > (Py_ssize_t) (image->xsize * image->ysize)) {
PyErr_SetString(PyExc_TypeError, "too many data entries"); PyErr_SetString(PyExc_TypeError, "too many data entries");
return NULL; return NULL;
} }
if (image->image8) { if (image->image8) {
@ -1648,7 +1649,7 @@ _stretch(ImagingObject* self, PyObject* args)
imIn = self->image; imIn = self->image;
/* two-pass resize: minimize size of intermediate image */ /* two-pass resize: minimize size of intermediate image */
if (imIn->xsize * ysize < xsize * imIn->ysize) if ((Py_ssize_t) imIn->xsize * ysize < (Py_ssize_t) xsize * imIn->ysize)
imTemp = ImagingNew(imIn->mode, imIn->xsize, ysize); imTemp = ImagingNew(imIn->mode, imIn->xsize, ysize);
else else
imTemp = ImagingNew(imIn->mode, xsize, imIn->ysize); imTemp = ImagingNew(imIn->mode, xsize, imIn->ysize);
@ -3073,7 +3074,7 @@ image_length(ImagingObject *self)
{ {
Imaging im = self->image; Imaging im = self->image;
return im->xsize * im->ysize; return (Py_ssize_t) im->xsize * im->ysize;
} }
static PyObject * static PyObject *

View File

@ -59,7 +59,11 @@ struct {
const char* message; const char* message;
} ft_errors[] = } ft_errors[] =
#if defined(USE_FREETYPE_2_1)
#include FT_ERRORS_H
#else
#include <freetype/fterrors.h> #include <freetype/fterrors.h>
#endif
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/* font objects */ /* font objects */

View File

@ -113,11 +113,11 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
WebPMuxAssemble(mux, &output_data); WebPMuxAssemble(mux, &output_data);
WebPMuxDelete(mux); WebPMuxDelete(mux);
free(output);
output = (uint8_t*)output_data.bytes;
ret_size = output_data.size; ret_size = output_data.size;
if (ret_size > 0) { if (ret_size > 0) {
PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size); PyObject *ret = PyBytes_FromStringAndSize((char*)output_data.bytes, ret_size);
WebPDataClear(&output_data); WebPDataClear(&output_data);
return ret; return ret;
} }

View File

@ -36,6 +36,20 @@ PIL identifies EPS files containing image data, and can read files that contain
embedded raster images (ImageData descriptors). If Ghostscript is available, embedded raster images (ImageData descriptors). If Ghostscript is available,
other EPS files can be read as well. The EPS driver can also write EPS images. other EPS files can be read as well. The EPS driver can also write EPS images.
If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load`
method with the following parameter to affect how Ghostscript renders the EPS
**scale**
Affects the scale of the resultant rasterized image. If the EPS suggests
that the image be rendered at 100px x 100px, setting this parameter to
2 will make the Ghostscript render a 200px x 200px image instead. The
relative position of the bounding box is maintained::
im = Image.open(...)
im.size #(100,100)
im.load(scale=2)
im.size #(200,200)
GIF GIF
^^^ ^^^
@ -265,6 +279,57 @@ dictionary of decoded TIFF fields. Values are stored as either strings or
tuples. Note that only short, long and ASCII tags are correctly unpacked by tuples. Note that only short, long and ASCII tags are correctly unpacked by
this release. this release.
Saving Tiff Images
~~~~~~~~~~~~~~~~~~
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
**tiffinfo**
A :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory` object or dict
object containing tiff tags and values. The TIFF field type is
autodetected for Numeric and string values, any other types
require using an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory`
object and setting the type in
:py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory.tagtype` with
the appropriate numerical value from
``TiffTags.TYPES``.
.. versionadded:: 2.3.0
**compression**
A string containing the desired compression method for the
file. (valid only with libtiff installed) Valid compression
methods are: ``[None, "tiff_ccitt", "group3", "group4",
"tiff_jpeg", "tiff_adobe_deflate", "tiff_thunderscan",
"tiff_deflate", "tiff_sgilog", "tiff_sgilog24", "tiff_raw_16"]``
These arguments to set the tiff header fields are an alternative to using the general tags available through tiffinfo.
**description**
**software**
**date time**
**artist**
**copyright**
Strings
**resolution unit**
A string of "inch", "centimeter" or "cm"
**resolution**
**x resolution**
**y resolution**
**dpi**
Either a Float, Integer, or 2 tuple of (numerator,
denominator). Resolution implies an equal x and y resolution, dpi
also implies a unit of inches.
WebP WebP
^^^^ ^^^^
@ -273,19 +338,19 @@ format are currently undocumented.
The :py:meth:`~PIL.Image.Image.save` method supports the following options: The :py:meth:`~PIL.Image.Image.save` method supports the following options:
**lossless** **lossless**
If present, instructs the WEBP writer to use lossless If present, instructs the WEBP writer to use lossless
compression. compression.
**quality** **quality**
Integer, 1-100, Defaults to 80. Sets the quality level for Integer, 1-100, Defaults to 80. Sets the quality level for
lossy compression. lossy compression.
**icc_procfile** **icc_procfile**
The ICC Profile to include in the saved file. Only supported if The ICC Profile to include in the saved file. Only supported if
the system webp library was built with webpmux support. the system webp library was built with webpmux support.
**exif** **exif**
The exif data to include in the saved file. Only supported if The exif data to include in the saved file. Only supported if
the system webp library was built with webpmux support. the system webp library was built with webpmux support.

View File

@ -1,10 +1,13 @@
Writing your own file decoder Writing your own file decoder
============================= =============================
The Python Imaging Library uses a plug-in model which allows you to add your The Python Imaging Library uses a plug-in model which allows you to
own decoders to the library, without any changes to the library itself. Such add your own decoders to the library, without any changes to the
plug-ins have names like :file:`XxxImagePlugin.py`, where ``Xxx`` is a unique library itself. Such plug-ins usually have names like
format name (usually an abbreviation). :file:`XxxImagePlugin.py`, where ``Xxx`` is a unique format name
(usually an abbreviation).
.. warning:: Pillow >= 2.1.0 no longer automatically imports any file in the Python path with a name ending in :file:`ImagePlugin.py`. You will need to import your decoder manually.
A decoder plug-in should contain a decoder class, based on the A decoder plug-in should contain a decoder class, based on the
:py:class:`PIL.ImageFile.ImageFile` base class. This class should provide an :py:class:`PIL.ImageFile.ImageFile` base class. This class should provide an

View File

@ -60,9 +60,13 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management * **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.2**.
* **libwebp** provides the Webp format. * **libwebp** provides the Webp format.
* Pillow has been tested with version **0.1.3**, which does not read transparent webp files. Version **0.3.0** supports transparency. * Pillow has been tested with version **0.1.3**, which does not read
transparent webp files. Version **0.3.0** supports transparency.
* **tcl/tk** provides support for tkinter bitmap and photo images. * **tcl/tk** provides support for tkinter bitmap and photo images.
@ -101,13 +105,13 @@ Or for Python 3::
Prerequisites are installed on **Ubuntu 10.04 LTS** with:: Prerequisites are installed on **Ubuntu 10.04 LTS** with::
$ sudo apt-get install libtiff4-dev libjpeg62-dev zlib1g-dev \ $ sudo apt-get install libtiff4-dev libjpeg62-dev zlib1g-dev \
libfreetype6-dev liblcms1-dev tcl8.5-dev tk8.5-dev libfreetype6-dev tcl8.5-dev tk8.5-dev
Prerequisites are installed with on **Ubuntu 12.04 LTS** or **Raspian Wheezy Prerequisites are installed with on **Ubuntu 12.04 LTS** or **Raspian Wheezy
7.0** with:: 7.0** with::
$ sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \ $ sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \
libfreetype6-dev liblcms1-dev libwebp-dev tcl8.5-dev tk8.5-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev
Mac OS X installation Mac OS X installation
--------------------- ---------------------

View File

@ -15,3 +15,9 @@ to this::
The :py:mod:`_imaging` module has been moved. You can now import it like this:: The :py:mod:`_imaging` module has been moved. You can now import it like this::
from PIL.Image import core as _imaging from PIL.Image import core as _imaging
The image plugin loading mechanisim has changed. Pillow no longer
automatically imports any file in the Python path with a name ending
in :file:`ImagePlugin.py`. You will need to import your image plugin
manually.

View File

@ -82,7 +82,7 @@ except ImportError:
NAME = 'Pillow' NAME = 'Pillow'
VERSION = '2.2.1' VERSION = '2.3.0'
TCL_ROOT = None TCL_ROOT = None
JPEG_ROOT = None JPEG_ROOT = None
ZLIB_ROOT = None ZLIB_ROOT = None
@ -211,6 +211,10 @@ class pil_build_ext(build_ext):
# work ;-) # work ;-)
self.add_multiarch_paths() self.add_multiarch_paths()
elif sys.platform.startswith("netbsd"):
_add_directory(library_dirs, "/usr/pkg/lib")
_add_directory(include_dirs, "/usr/pkg/include")
_add_directory(library_dirs, "/usr/local/lib") _add_directory(library_dirs, "/usr/local/lib")
# FIXME: check /opt/stuff directories here? # FIXME: check /opt/stuff directories here?
@ -583,8 +587,7 @@ setup(
description='Python Imaging Library (Fork)', description='Python Imaging Library (Fork)',
long_description=( long_description=(
_read('README.rst') + b'\n' + _read('README.rst') + b'\n' +
_read('CHANGES.rst') + b'\n' + _read('CHANGES.rst')),
_read('CONTRIBUTORS.rst')).decode('utf-8'),
author='Alex Clark (fork author)', author='Alex Clark (fork author)',
author_email='aclark@aclark.net', author_email='aclark@aclark.net',
url='http://python-imaging.github.io/', url='http://python-imaging.github.io/',