Merge branch 'master' into terseus_imagedraw

This commit is contained in:
wiredfool 2014-06-30 14:24:59 -07:00
commit 3bf375c5f7
44 changed files with 641 additions and 1582 deletions

View File

@ -3,6 +3,8 @@ language: python
notifications: notifications:
irc: "chat.freenode.net#pil" irc: "chat.freenode.net#pil"
env: MAX_CONCURRENCY=4
python: python:
- "pypy" - "pypy"
- 2.6 - 2.6
@ -12,9 +14,9 @@ python:
- 3.4 - 3.4
install: install:
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake"
- "pip install cffi" - "pip install cffi"
- "pip install coveralls nose" - "pip install coveralls nose pyroma"
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi
# webp # webp

View File

@ -4,6 +4,39 @@ Changelog (Pillow)
2.5.0 (unreleased) 2.5.0 (unreleased)
------------------ ------------------
- Prevent shell injection #748
[mbrown1413, wiredfool]
- Support for Resolution in BMP files #734
[gcq]
- Fix error in setup.py for Python 3
[matthew-brett]
- Pyroma fix and add Python 3.4 to setup metadata #742
[wirefool]
- Top level flake8 fixes #741
[aclark]
- Remove obsolete Animated Raster Graphics (ARG) support
[hugovk]
- Fix test_imagedraw failures #727
[cgohlke]
- Fix AttributeError: class Image has no attribute 'DEBUG' #726
[cgohlke]
- Fix msvc warning: 'inline' : macro redefinition #725
[cgohlke]
- Cleanup #654
[dvska, hugovk, wiredfool]
- 16-bit monochrome support for JPEG2000
[videan42]
- Fixed ImagePalette.save - Fixed ImagePalette.save
[brightpisces] [brightpisces]

View File

@ -2,6 +2,8 @@ include *.c
include *.h include *.h
include *.py include *.py
include *.rst include *.rst
include *.txt
include .coveragerc
include .gitattributes include .gitattributes
include .travis.yml include .travis.yml
include Makefile include Makefile
@ -28,29 +30,45 @@ recursive-include Sane CHANGES
recursive-include Sane README recursive-include Sane README
recursive-include Scripts *.py recursive-include Scripts *.py
recursive-include Scripts README recursive-include Scripts README
recursive-include Tests *.bdf
recursive-include Tests *.bin recursive-include Tests *.bin
recursive-include Tests *.bmp recursive-include Tests *.bmp
recursive-include Tests *.doc
recursive-include Tests *.eps recursive-include Tests *.eps
recursive-include Tests *.fli
recursive-include Tests *.gif recursive-include Tests *.gif
recursive-include Tests *.gnuplot recursive-include Tests *.gnuplot
recursive-include Tests *.html recursive-include Tests *.html
recursive-include Tests *.icm recursive-include Tests *.icm
recursive-include Tests *.icns recursive-include Tests *.icns
recursive-include Tests *.ico recursive-include Tests *.ico
recursive-include Tests *.j2k
recursive-include Tests *.jp2 recursive-include Tests *.jp2
recursive-include Tests *.jpg recursive-include Tests *.jpg
recursive-include Tests *.lut
recursive-include Tests *.pbm
recursive-include Tests *.pcf recursive-include Tests *.pcf
recursive-include Tests *.pcx recursive-include Tests *.pcx
recursive-include Tests *.pgm
recursive-include Tests *.pil
recursive-include Tests *.png recursive-include Tests *.png
recursive-include Tests *.ppm recursive-include Tests *.ppm
recursive-include Tests *.psd
recursive-include Tests *.py recursive-include Tests *.py
recursive-include Tests *.rst
recursive-include Tests *.spider
recursive-include Tests *.tar
recursive-include Tests *.tif recursive-include Tests *.tif
recursive-include Tests *.tiff recursive-include Tests *.tiff
recursive-include Tests *.ttf recursive-include Tests *.ttf
recursive-include Tests *.txt recursive-include Tests *.txt
recursive-include Tests *.webp
recursive-include Tests *.xpm
recursive-include Tk *.c recursive-include Tk *.c
recursive-include Tk *.txt recursive-include Tk *.txt
recursive-include Tk *.rst
recursive-include depends *.sh recursive-include depends *.sh
recursive-include depends *.rst
recursive-include docs *.bat recursive-include docs *.bat
recursive-include docs *.gitignore recursive-include docs *.gitignore
recursive-include docs *.html recursive-include docs *.html
@ -64,3 +82,5 @@ recursive-include docs COPYING
recursive-include docs LICENSE recursive-include docs LICENSE
recursive-include libImaging *.c recursive-include libImaging *.c
recursive-include libImaging *.h recursive-include libImaging *.h
recursive-include Sane *.rst
recursive-include Scripts *.rst

View File

@ -1,7 +1,11 @@
pre: pre:
virtualenv .
bin/pip install -r requirements.txt
bin/python setup.py develop bin/python setup.py develop
bin/python selftest.py bin/python selftest.py
bin/python Tests/run.py bin/nosetests Tests/test_*.py
bin/python setup.py install
bin/python test-installed.py
check-manifest check-manifest
pyroma . pyroma .
viewdoc viewdoc

View File

@ -1,506 +0,0 @@
#
# THIS IS WORK IN PROGRESS
#
# The Python Imaging Library.
# $Id$
#
# ARG animation support code
#
# history:
# 1996-12-30 fl Created
# 1996-01-06 fl Added safe scripting environment
# 1996-01-10 fl Added JHDR, UHDR and sYNC support
# 2005-03-02 fl Removed AAPP and ARUN support
#
# Copyright (c) Secret Labs AB 1997.
# Copyright (c) Fredrik Lundh 1996-97.
#
# See the README file for information on usage and redistribution.
#
from __future__ import print_function
__version__ = "0.4"
from PIL import Image, ImageFile, ImagePalette
from PIL.PngImagePlugin import i8, i16, i32, ChunkStream, _MODES
MAGIC = b"\212ARG\r\n\032\n"
# --------------------------------------------------------------------
# ARG parser
class ArgStream(ChunkStream):
"Parser callbacks for ARG data"
def __init__(self, fp):
ChunkStream.__init__(self, fp)
self.eof = 0
self.im = None
self.palette = None
self.__reset()
def __reset(self):
# reset decoder state (called on init and sync)
self.count = 0
self.id = None
self.action = ("NONE",)
self.images = {}
self.names = {}
def chunk_AHDR(self, offset, bytes):
"AHDR -- animation header"
# assertions
if self.count != 0:
raise SyntaxError("misplaced AHDR chunk")
s = self.fp.read(bytes)
self.size = i32(s), i32(s[4:])
try:
self.mode, self.rawmode = _MODES[(i8(s[8]), i8(s[9]))]
except:
raise SyntaxError("unknown ARG mode")
if Image.DEBUG:
print("AHDR size", self.size)
print("AHDR mode", self.mode, self.rawmode)
return s
def chunk_AFRM(self, offset, bytes):
"AFRM -- next frame follows"
# assertions
if self.count != 0:
raise SyntaxError("misplaced AFRM chunk")
self.show = 1
self.id = 0
self.count = 1
self.repair = None
s = self.fp.read(bytes)
if len(s) >= 2:
self.id = i16(s)
if len(s) >= 4:
self.count = i16(s[2:4])
if len(s) >= 6:
self.repair = i16(s[4:6])
else:
self.repair = None
if Image.DEBUG:
print("AFRM", self.id, self.count)
return s
def chunk_ADEF(self, offset, bytes):
"ADEF -- store image"
# assertions
if self.count != 0:
raise SyntaxError("misplaced ADEF chunk")
self.show = 0
self.id = 0
self.count = 1
self.repair = None
s = self.fp.read(bytes)
if len(s) >= 2:
self.id = i16(s)
if len(s) >= 4:
self.count = i16(s[2:4])
if Image.DEBUG:
print("ADEF", self.id, self.count)
return s
def chunk_NAME(self, offset, bytes):
"NAME -- name the current image"
# assertions
if self.count == 0:
raise SyntaxError("misplaced NAME chunk")
name = self.fp.read(bytes)
self.names[self.id] = name
return name
def chunk_AEND(self, offset, bytes):
"AEND -- end of animation"
if Image.DEBUG:
print("AEND")
self.eof = 1
raise EOFError("end of ARG file")
def __getmodesize(self, s, full=1):
size = i32(s), i32(s[4:])
try:
mode, rawmode = _MODES[(i8(s[8]), i8(s[9]))]
except:
raise SyntaxError("unknown image mode")
if full:
if i8(s[12]):
pass # interlace not yet supported
if i8(s[11]):
raise SyntaxError("unknown filter category")
return size, mode, rawmode
def chunk_PAST(self, offset, bytes):
"PAST -- paste one image into another"
# assertions
if self.count == 0:
raise SyntaxError("misplaced PAST chunk")
if self.repair is not None:
# we must repair the target image before we
# start pasting
# brute force; a better solution would be to
# update only the dirty rectangles in images[id].
# note that if images[id] doesn't exist, it must
# be created
self.images[self.id] = self.images[self.repair].copy()
self.repair = None
s = self.fp.read(bytes)
im = self.images[i16(s)]
x, y = i32(s[2:6]), i32(s[6:10])
bbox = x, y, im.size[0]+x, im.size[1]+y
if im.mode in ["RGBA"]:
# paste with transparency
# FIXME: should handle P+transparency as well
self.images[self.id].paste(im, bbox, im)
else:
# paste without transparency
self.images[self.id].paste(im, bbox)
self.action = ("PAST",)
self.__store()
return s
def chunk_BLNK(self, offset, bytes):
"BLNK -- create blank image"
# assertions
if self.count == 0:
raise SyntaxError("misplaced BLNK chunk")
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s, 0)
# store image (FIXME: handle colour)
self.action = ("BLNK",)
self.im = Image.core.fill(mode, size, 0)
self.__store()
return s
def chunk_IHDR(self, offset, bytes):
"IHDR -- full image follows"
# assertions
if self.count == 0:
raise SyntaxError("misplaced IHDR chunk")
# image header
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s)
# decode and store image
self.action = ("IHDR",)
self.im = Image.core.new(mode, size)
self.decoder = Image.core.zip_decoder(rawmode)
self.decoder.setimage(self.im, (0,0) + size)
self.data = b""
return s
def chunk_DHDR(self, offset, bytes):
"DHDR -- delta image follows"
# assertions
if self.count == 0:
raise SyntaxError("misplaced DHDR chunk")
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s)
# delta header
diff = i8(s[13])
offs = i32(s[14:18]), i32(s[18:22])
bbox = offs + (offs[0]+size[0], offs[1]+size[1])
if Image.DEBUG:
print("DHDR", diff, bbox)
# FIXME: decode and apply image
self.action = ("DHDR", diff, bbox)
# setup decoder
self.im = Image.core.new(mode, size)
self.decoder = Image.core.zip_decoder(rawmode)
self.decoder.setimage(self.im, (0,0) + size)
self.data = b""
return s
def chunk_JHDR(self, offset, bytes):
"JHDR -- JPEG image follows"
# assertions
if self.count == 0:
raise SyntaxError("misplaced JHDR chunk")
# image header
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s, 0)
# decode and store image
self.action = ("JHDR",)
self.im = Image.core.new(mode, size)
self.decoder = Image.core.jpeg_decoder(rawmode)
self.decoder.setimage(self.im, (0,0) + size)
self.data = b""
return s
def chunk_UHDR(self, offset, bytes):
"UHDR -- uncompressed image data follows (EXPERIMENTAL)"
# assertions
if self.count == 0:
raise SyntaxError("misplaced UHDR chunk")
# image header
s = self.fp.read(bytes)
size, mode, rawmode = self.__getmodesize(s, 0)
# decode and store image
self.action = ("UHDR",)
self.im = Image.core.new(mode, size)
self.decoder = Image.core.raw_decoder(rawmode)
self.decoder.setimage(self.im, (0,0) + size)
self.data = b""
return s
def chunk_IDAT(self, offset, bytes):
"IDAT -- image data block"
# pass compressed chunks through the decoder
s = self.fp.read(bytes)
self.data = self.data + s
n, e = self.decoder.decode(self.data)
if n < 0:
# end of image
if e < 0:
raise IOError("decoder error %d" % e)
else:
self.data = self.data[n:]
return s
def chunk_DEND(self, offset, bytes):
return self.chunk_IEND(offset, bytes)
def chunk_JEND(self, offset, bytes):
return self.chunk_IEND(offset, bytes)
def chunk_UEND(self, offset, bytes):
return self.chunk_IEND(offset, bytes)
def chunk_IEND(self, offset, bytes):
"IEND -- end of image"
# we now have a new image. carry out the operation
# defined by the image header.
# won't need these anymore
del self.decoder
del self.data
self.__store()
return self.fp.read(bytes)
def __store(self):
# apply operation
cid = self.action[0]
if cid in ["BLNK", "IHDR", "JHDR", "UHDR"]:
# store
self.images[self.id] = self.im
elif cid == "DHDR":
# paste
cid, mode, bbox = self.action
im0 = self.images[self.id]
im1 = self.im
if mode == 0:
im1 = im1.chop_add_modulo(im0.crop(bbox))
im0.paste(im1, bbox)
self.count -= 1
if self.count == 0 and self.show:
self.im = self.images[self.id]
raise EOFError # end of this frame
def chunk_PLTE(self, offset, bytes):
"PLTE -- palette data"
s = self.fp.read(bytes)
if self.mode == "P":
self.palette = ImagePalette.raw("RGB", s)
return s
def chunk_sYNC(self, offset, bytes):
"SYNC -- reset decoder"
if self.count != 0:
raise SyntaxError("misplaced sYNC chunk")
s = self.fp.read(bytes)
self.__reset()
return s
# --------------------------------------------------------------------
# ARG reader
def _accept(prefix):
return prefix[:8] == MAGIC
##
# Image plugin for the experimental Animated Raster Graphics format.
class ArgImageFile(ImageFile.ImageFile):
format = "ARG"
format_description = "Animated raster graphics"
def _open(self):
if Image.warnings:
Image.warnings.warn(
"The ArgImagePlugin driver is obsolete, and will be removed "
"from a future release of PIL. If you rely on this module, "
"please contact the PIL authors.",
RuntimeWarning
)
if self.fp.read(8) != MAGIC:
raise SyntaxError("not an ARG file")
self.arg = ArgStream(self.fp)
# read and process the first chunk (AHDR)
cid, offset, bytes = self.arg.read()
if cid != "AHDR":
raise SyntaxError("expected an AHDR chunk")
s = self.arg.call(cid, offset, bytes)
self.arg.crc(cid, s)
# image characteristics
self.mode = self.arg.mode
self.size = self.arg.size
def load(self):
if self.arg.im is None:
self.seek(0)
# image data
self.im = self.arg.im
self.palette = self.arg.palette
# set things up for further processing
Image.Image.load(self)
def seek(self, frame):
if self.arg.eof:
raise EOFError("end of animation")
self.fp = self.arg.fp
while True:
#
# process chunks
cid, offset, bytes = self.arg.read()
if self.arg.eof:
raise EOFError("end of animation")
try:
s = self.arg.call(cid, offset, bytes)
except EOFError:
break
except "glurk": # AttributeError
if Image.DEBUG:
print(cid, bytes, "(unknown)")
s = self.fp.read(bytes)
self.arg.crc(cid, s)
self.fp.read(4) # ship extra CRC
def tell(self):
return 0
def verify(self):
"Verify ARG file"
# back up to first chunk
self.fp.seek(8)
self.arg.verify(self)
self.arg.close()
self.fp = None
#
# --------------------------------------------------------------------
Image.register_open("ARG", ArgImageFile, _accept)
Image.register_extension("ARG", ".arg")
Image.register_mime("ARG", "video/x-arg")

View File

@ -28,6 +28,7 @@ __version__ = "0.7"
from PIL import Image, ImageFile, ImagePalette, _binary from PIL import Image, ImageFile, ImagePalette, _binary
import math
i8 = _binary.i8 i8 = _binary.i8
i16 = _binary.i16le i16 = _binary.i16le
@ -88,6 +89,7 @@ class BmpImageFile(ImageFile.ImageFile):
bits = i16(s[14:]) bits = i16(s[14:])
self.size = i32(s[4:]), i32(s[8:]) self.size = i32(s[4:]), i32(s[8:])
compression = i32(s[16:]) compression = i32(s[16:])
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
lutsize = 4 lutsize = 4
colors = i32(s[32:]) colors = i32(s[32:])
direction = -1 direction = -1
@ -95,6 +97,8 @@ class BmpImageFile(ImageFile.ImageFile):
# upside-down storage # upside-down storage
self.size = self.size[0], 2**32 - self.size[1] self.size = self.size[0], 2**32 - self.size[1]
direction = 0 direction = 0
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm))
else: else:
raise IOError("Unsupported BMP header type (%d)" % len(s)) raise IOError("Unsupported BMP header type (%d)" % len(s))
@ -203,30 +207,37 @@ def _save(im, fp, filename, check=0):
if check: if check:
return check return check
info = im.encoderinfo
dpi = info.get("dpi", (96, 96))
# 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
stride = ((im.size[0]*bits+7)//8+3)&(~3) stride = ((im.size[0]*bits+7)//8+3)&(~3)
header = 40 # or 64 for OS/2 version 2 header = 40 # or 64 for OS/2 version 2
offset = 14 + header + colors * 4 offset = 14 + header + colors * 4
image = stride * im.size[1] image = stride * im.size[1]
# bitmap header # bitmap header
fp.write(b"BM" + # file type (magic) fp.write(b"BM" + # file type (magic)
o32(offset+image) + # file size o32(offset+image) + # file size
o32(0) + # reserved o32(0) + # reserved
o32(offset)) # image data offset o32(offset)) # image data offset
# bitmap info header # bitmap info header
fp.write(o32(header) + # info header size fp.write(o32(header) + # info header size
o32(im.size[0]) + # width o32(im.size[0]) + # width
o32(im.size[1]) + # height o32(im.size[1]) + # height
o16(1) + # planes o16(1) + # planes
o16(bits) + # depth o16(bits) + # depth
o32(0) + # compression (0=uncompressed) o32(0) + # compression (0=uncompressed)
o32(image) + # size of bitmap o32(image) + # size of bitmap
o32(1) + o32(1) + # resolution o32(ppm[0]) + o32(ppm[1]) + # resolution
o32(colors) + # colors used o32(colors) + # colors used
o32(colors)) # colors important o32(colors)) # colors important
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
if im.mode == "1": if im.mode == "1":
for i in (0, 255): for i in (0, 255):

View File

@ -333,13 +333,41 @@ def _save_netpbm(im, fp, filename):
# below for information on how to enable this. # below for information on how to enable this.
import os import os
from subprocess import Popen, check_call, PIPE, CalledProcessError
import tempfile
file = im._dump() file = im._dump()
if im.mode != "RGB": if im.mode != "RGB":
os.system("ppmtogif %s >%s" % (file, filename)) with open(filename, 'wb') as f:
stderr = tempfile.TemporaryFile()
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
else: else:
os.system("ppmquant 256 %s | ppmtogif >%s" % (file, filename)) with open(filename, 'wb') as f:
try: os.unlink(file)
except: pass # Pipe ppmquant output into ppmtogif
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
quant_cmd = ["ppmquant", "256", file]
togif_cmd = ["ppmtogif"]
stderr = tempfile.TemporaryFile()
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
stderr = tempfile.TemporaryFile()
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=stderr)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
quant_proc.stdout.close()
retcode = quant_proc.wait()
if retcode:
raise CalledProcessError(retcode, quant_cmd)
retcode = togif_proc.wait()
if retcode:
raise CalledProcessError(retcode, togif_cmd)
try:
os.unlink(file)
except:
pass
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -539,7 +539,7 @@ class Image:
try: try:
self.fp.close() self.fp.close()
except Exception as msg: except Exception as msg:
if Image.DEBUG: if DEBUG:
print ("Error closing: %s" % msg) print ("Error closing: %s" % msg)
# Instead of simply setting to None, we're setting up a # Instead of simply setting to None, we're setting up a

View File

@ -40,7 +40,10 @@ def _parse_codestream(fp):
size = (xsiz - xosiz, ysiz - yosiz) size = (xsiz - xosiz, ysiz - yosiz)
if csiz == 1: if csiz == 1:
mode = 'L' if (yrsiz[0] & 0x7f) > 8:
mode = 'I;16'
else:
mode = 'L'
elif csiz == 2: elif csiz == 2:
mode = 'LA' mode = 'LA'
elif csiz == 3: elif csiz == 3:
@ -78,6 +81,7 @@ def _parse_jp2_header(fp):
size = None size = None
mode = None mode = None
bpc = None
hio = io.BytesIO(header) hio = io.BytesIO(header)
while True: while True:
@ -95,7 +99,9 @@ def _parse_jp2_header(fp):
= struct.unpack('>IIHBBBB', content) = struct.unpack('>IIHBBBB', content)
size = (width, height) size = (width, height)
if unkc: if unkc:
if nc == 1: if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L' mode = 'L'
elif nc == 2: elif nc == 2:
mode = 'LA' mode = 'LA'
@ -109,13 +115,19 @@ def _parse_jp2_header(fp):
if meth == 1: if meth == 1:
cs = struct.unpack('>I', content[3:7])[0] cs = struct.unpack('>I', content[3:7])[0]
if cs == 16: # sRGB if cs == 16: # sRGB
if nc == 3: if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L'
elif nc == 3:
mode = 'RGB' mode = 'RGB'
elif nc == 4: elif nc == 4:
mode = 'RGBA' mode = 'RGBA'
break break
elif cs == 17: # grayscale elif cs == 17: # grayscale
if nc == 1: if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L' mode = 'L'
elif nc == 2: elif nc == 2:
mode = 'LA' mode = 'LA'
@ -129,10 +141,10 @@ def _parse_jp2_header(fp):
return (size, mode) return (size, mode)
## ##
# Image plugin for JPEG2000 images. # Image plugin for JPEG2000 images.
class Jpeg2KImageFile(ImageFile.ImageFile): class Jpeg2KImageFile(ImageFile.ImageFile):
format = "JPEG2000" format = "JPEG2000"
format_description = "JPEG 2000 (ISO 15444)" format_description = "JPEG 2000 (ISO 15444)"
@ -174,7 +186,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
f.seek(pos, 0) f.seek(pos, 0)
except: except:
length = -1 length = -1
self.tile = [('jpeg2k', (0, 0) + self.size, 0, self.tile = [('jpeg2k', (0, 0) + self.size, 0,
(self.codec, self.reduce, self.layers, fd, length))] (self.codec, self.reduce, self.layers, fd, length))]

View File

@ -354,12 +354,14 @@ class JpegImageFile(ImageFile.ImageFile):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities # ALTERNATIVE: handle JPEGs via the IJG command line utilities
import subprocess
import tempfile import tempfile
import os import os
f, path = tempfile.mkstemp() f, path = tempfile.mkstemp()
os.close(f) os.close(f)
if os.path.exists(self.filename): if os.path.exists(self.filename):
os.system("djpeg '%s' >'%s'" % (self.filename, path)) with open(path, 'wb') as f:
subprocess.check_call(["djpeg", self.filename], stdout=f)
else: else:
raise ValueError("Invalid Filename") raise ValueError("Invalid Filename")
@ -602,8 +604,10 @@ def _save(im, fp, filename):
def _save_cjpeg(im, fp, filename): def _save_cjpeg(im, fp, filename):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities. # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
import os import os
file = im._dump() import subprocess
os.system("cjpeg %s >%s" % (file, filename)) tempfile = im._dump()
with open(filename, 'wb') as f:
subprocess.check_call(["cjpeg", tempfile], stdout=f)
try: try:
os.unlink(file) os.unlink(file)
except: except:

View File

@ -14,8 +14,7 @@
VERSION = '1.1.7' # PIL version VERSION = '1.1.7' # PIL version
PILLOW_VERSION = '2.4.0' # Pillow PILLOW_VERSION = '2.4.0' # Pillow
_plugins = ['ArgImagePlugin', _plugins = ['BmpImagePlugin',
'BmpImagePlugin',
'BufrStubImagePlugin', 'BufrStubImagePlugin',
'CurImagePlugin', 'CurImagePlugin',
'DcxImagePlugin', 'DcxImagePlugin',

View File

@ -1,17 +0,0 @@
import unittest
class PillowTests(unittest.TestCase):
"""
Can we start moving the test suite here?
"""
def test_suite_should_move_here(self):
"""
Great idea!
"""
assert True is True
if __name__ == '__main__':
unittest.main()

View File

@ -18,25 +18,6 @@ import sys
Image.DEBUG = 0 Image.DEBUG = 0
# --------------------------------------------------------------------
# experimental: support ARG animation scripts
import ArgImagePlugin
def applet_hook(animation, images):
app = animation(animation_display, images)
app.run()
ArgImagePlugin.APPLET_HOOK = applet_hook
class AppletDisplay:
def __init__(self, ui):
self.__ui = ui
def paste(self, im, bbox):
self.__ui.image.paste(im, bbox)
def update(self):
self.__ui.update_idletasks()
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# an image animation player # an image animation player
@ -56,10 +37,6 @@ class UI(Label):
else: else:
self.image = ImageTk.PhotoImage(im) self.image = ImageTk.PhotoImage(im)
# APPLET SUPPORT (very crude, and not 100% safe)
global animation_display
animation_display = AppletDisplay(self)
Label.__init__(self, master, image=self.image, bg="black", bd=0) Label.__init__(self, master, image=self.image, bg="black", bd=0)
self.update() self.update()

24
Tests/README.rst Normal file
View File

@ -0,0 +1,24 @@
Pillow Tests
============
Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``.
Execution
---------
Run the tests from the root of the Pillow source distribution::
python selftest.py
nosetests Tests/test_*.py
Or with coverage::
coverage run --append --include=PIL/* selftest.py
coverage run --append --include=PIL/* -m nose Tests/test_*.py
coverage report
coverage html
open htmlcov/index.html
To run an individual test::
python Tests/test_image.py

View File

@ -1,14 +0,0 @@
Minimalistic PIL test framework.
Test scripts are named "test_xxx" and are supposed to output "ok". That's it. To run the tests::
python setup.py develop
Run the tests from the root of the Pillow source distribution:
python selftest.py
python Tests/run.py --installed
To run an individual test:
python Tests/test_image.py

View File

@ -1,23 +1,25 @@
from tester import * from helper import *
# not running this test by default. No DOS against travis. # Not running this test by default. No DOS against Travis CI.
from PIL import PyAccess from PIL import PyAccess
from PIL import Image
import time import time
def iterate_get(size, access): def iterate_get(size, access):
(w,h) = size (w, h) = size
for x in range(w): for x in range(w):
for y in range(h): for y in range(h):
access[(x,y)] access[(x, y)]
def iterate_set(size, access): def iterate_set(size, access):
(w,h) = size (w, h) = size
for x in range(w): for x in range(w):
for y in range(h): for y in range(h):
access[(x,y)] = (x %256,y%256,0) access[(x, y)] = (x % 256, y % 256, 0)
def timer(func, label, *args): def timer(func, label, *args):
iterations = 5000 iterations = 5000
@ -25,27 +27,34 @@ def timer(func, label, *args):
for x in range(iterations): for x in range(iterations):
func(*args) func(*args)
if time.time()-starttime > 10: if time.time()-starttime > 10:
print ("%s: breaking at %s iterations, %.6f per iteration"%(label, x+1, (time.time()-starttime)/(x+1.0))) print("%s: breaking at %s iterations, %.6f per iteration" % (
label, x+1, (time.time()-starttime)/(x+1.0)))
break break
if x == iterations-1: if x == iterations-1:
endtime = time.time() endtime = time.time()
print ("%s: %.4f s %.6f per iteration" %(label, endtime-starttime, (endtime-starttime)/(x+1.0))) print("%s: %.4f s %.6f per iteration" % (
label, endtime-starttime, (endtime-starttime)/(x+1.0)))
def test_direct():
im = lena()
im.load()
#im = Image.new( "RGB", (2000,2000), (1,3,2))
caccess = im.im.pixel_access(False)
access = PyAccess.new(im, False)
assert_equal(caccess[(0,0)], access[(0,0)]) class BenchCffiAccess(PillowTestCase):
print ("Size: %sx%s" % im.size) def test_direct(self):
timer(iterate_get, 'PyAccess - get', im.size, access) im = lena()
timer(iterate_set, 'PyAccess - set', im.size, access) im.load()
timer(iterate_get, 'C-api - get', im.size, caccess) # im = Image.new( "RGB", (2000, 2000), (1, 3, 2))
timer(iterate_set, 'C-api - set', im.size, caccess) caccess = im.im.pixel_access(False)
access = PyAccess.new(im, False)
self.assertEqual(caccess[(0, 0)], access[(0, 0)])
print ("Size: %sx%s" % im.size)
timer(iterate_get, 'PyAccess - get', im.size, access)
timer(iterate_set, 'PyAccess - set', im.size, access)
timer(iterate_get, 'C-api - get', im.size, caccess)
timer(iterate_set, 'C-api - set', im.size, caccess)
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -1,13 +1,14 @@
import sys import sys
sys.path.insert(0, ".") sys.path.insert(0, ".")
import tester import helper
import timeit import timeit
def bench(mode): def bench(mode):
im = tester.lena(mode) im = helper.lena(mode)
get = im.im.getpixel get = im.im.getpixel
xy = 50, 50 # position shouldn't really matter xy = 50, 50 # position shouldn't really matter
t0 = timeit.default_timer() t0 = timeit.default_timer()
for i in range(1000000): for i in range(1000000):
get(xy) get(xy)

View File

@ -1,222 +0,0 @@
# PyCMSTests.py
# Examples of how to use pyCMS, as well as tests to verify it works properly
# By Kevin Cazabon (kevin@cazabon.com)
# Imports
import os
from PIL import Image
from PIL import ImageCms
# import PyCMSError separately so we can catch it
PyCMSError = ImageCms.PyCMSError
#######################################################################
# Configuration:
#######################################################################
# set this to the image you want to test with
IMAGE = "c:\\temp\\test.tif"
# set this to where you want to save the output images
OUTPUTDIR = "c:\\temp\\"
# set these to two different ICC profiles, one for input, one for output
# set the corresponding mode to the proper PIL mode for that profile
INPUT_PROFILE = "c:\\temp\\profiles\\sRGB.icm"
INMODE = "RGB"
OUTPUT_PROFILE = "c:\\temp\\profiles\\genericRGB.icm"
OUTMODE = "RGB"
PROOF_PROFILE = "c:\\temp\\profiles\\monitor.icm"
# set to True to show() images, False to save them into OUTPUT_DIRECTORY
SHOW = False
# Tests you can enable/disable
TEST_error_catching = True
TEST_profileToProfile = True
TEST_profileToProfile_inPlace = True
TEST_buildTransform = True
TEST_buildTransformFromOpenProfiles = True
TEST_buildProofTransform = True
TEST_getProfileInfo = True
TEST_misc = False
#######################################################################
# helper functions
#######################################################################
def outputImage(im, funcName = None):
# save or display the image, depending on value of SHOW_IMAGES
if SHOW:
im.show()
else:
im.save(os.path.join(OUTPUTDIR, "%s.tif" %funcName))
#######################################################################
# The tests themselves
#######################################################################
if TEST_error_catching:
im = Image.open(IMAGE)
try:
#neither of these proifles exists (unless you make them), so we should
# get an error
imOut = ImageCms.profileToProfile(im, "missingProfile.icm", "cmyk.icm")
except PyCMSError as reason:
print("We caught a PyCMSError: %s\n\n" %reason)
print("error catching test completed successfully (if you see the message \
above that we caught the error).")
if TEST_profileToProfile:
# open the image file using the standard PIL function Image.open()
im = Image.open(IMAGE)
# send the image, input/output profiles, and rendering intent to
# ImageCms.profileToProfile()
imOut = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \
outputMode = OUTMODE)
# now that the image is converted, save or display it
outputImage(imOut, "profileToProfile")
print("profileToProfile test completed successfully.")
if TEST_profileToProfile_inPlace:
# we'll do the same test as profileToProfile, but modify im in place
# instead of getting a new image returned to us
im = Image.open(IMAGE)
# send the image to ImageCms.profileToProfile(), specifying inPlace = True
result = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \
outputMode = OUTMODE, inPlace = True)
# now that the image is converted, save or display it
if result is None:
# this is the normal result when modifying in-place
outputImage(im, "profileToProfile_inPlace")
else:
# something failed...
print("profileToProfile in-place failed: %s" %result)
print("profileToProfile in-place test completed successfully.")
if TEST_buildTransform:
# make a transform using the input and output profile path strings
transform = ImageCms.buildTransform(INPUT_PROFILE, OUTPUT_PROFILE, INMODE, \
OUTMODE)
# now, use the trnsform to convert a couple images
im = Image.open(IMAGE)
# transform im normally
im2 = ImageCms.applyTransform(im, transform)
outputImage(im2, "buildTransform")
# then transform it again using the same transform, this time in-place.
result = ImageCms.applyTransform(im, transform, inPlace = True)
outputImage(im, "buildTransform_inPlace")
print("buildTransform test completed successfully.")
# and, to clean up a bit, delete the transform
# this should call the C destructor for the transform structure.
# Python should also do this automatically when it goes out of scope.
del(transform)
if TEST_buildTransformFromOpenProfiles:
# we'll actually test a couple profile open/creation functions here too
# first, get a handle to an input profile, in this case we'll create
# an sRGB profile on the fly:
inputProfile = ImageCms.createProfile("sRGB")
# then, get a handle to the output profile
outputProfile = ImageCms.getOpenProfile(OUTPUT_PROFILE)
# make a transform from these
transform = ImageCms.buildTransformFromOpenProfiles(inputProfile, \
outputProfile, INMODE, OUTMODE)
# now, use the trnsform to convert a couple images
im = Image.open(IMAGE)
# transform im normally
im2 = ImageCms.applyTransform(im, transform)
outputImage(im2, "buildTransformFromOpenProfiles")
# then do it again using the same transform, this time in-place.
result = ImageCms.applyTransform(im, transform, inPlace = True)
outputImage(im, "buildTransformFromOpenProfiles_inPlace")
print("buildTransformFromOpenProfiles test completed successfully.")
# and, to clean up a bit, delete the transform
# this should call the C destructor for the each item.
# Python should also do this automatically when it goes out of scope.
del(inputProfile)
del(outputProfile)
del(transform)
if TEST_buildProofTransform:
# make a transform using the input and output and proof profile path
# strings
# images converted with this transform will simulate the appearance
# of the output device while actually being displayed/proofed on the
# proof device. This usually means a monitor, but can also mean
# other proof-printers like dye-sub, etc.
transform = ImageCms.buildProofTransform(INPUT_PROFILE, OUTPUT_PROFILE, \
PROOF_PROFILE, INMODE, OUTMODE)
# now, use the trnsform to convert a couple images
im = Image.open(IMAGE)
# transform im normally
im2 = ImageCms.applyTransform(im, transform)
outputImage(im2, "buildProofTransform")
# then transform it again using the same transform, this time in-place.
result = ImageCms.applyTransform(im, transform, inPlace = True)
outputImage(im, "buildProofTransform_inPlace")
print("buildProofTransform test completed successfully.")
# and, to clean up a bit, delete the transform
# this should call the C destructor for the transform structure.
# Python should also do this automatically when it goes out of scope.
del(transform)
if TEST_getProfileInfo:
# get a profile handle
profile = ImageCms.getOpenProfile(INPUT_PROFILE)
# lets print some info about our input profile:
print("Profile name (retrieved from profile string path name): %s" %ImageCms.getProfileName(INPUT_PROFILE))
# or, you could do the same thing using a profile handle as the arg
print("Profile name (retrieved from profile handle): %s" %ImageCms.getProfileName(profile))
# now lets get the embedded "info" tag contents
# once again, you can use a path to a profile, or a profile handle
print("Profile info (retrieved from profile handle): %s" %ImageCms.getProfileInfo(profile))
# and what's the default intent of this profile?
print("The default intent is (this will be an integer): %d" %(ImageCms.getDefaultIntent(profile)))
# Hmmmm... but does this profile support INTENT_ABSOLUTE_COLORIMETRIC?
print("Does it support INTENT_ABSOLUTE_COLORIMETRIC?: (1 is yes, -1 is no): %s" \
%ImageCms.isIntentSupported(profile, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, \
ImageCms.DIRECTION_INPUT))
print("getProfileInfo test completed successfully.")
if TEST_misc:
# test the versions, about, and copyright functions
print("Versions: %s" %str(ImageCms.versions()))
print("About:\n\n%s" %ImageCms.about())
print("Copyright:\n\n%s" %ImageCms.copyright())
print("misc test completed successfully.")

View File

@ -10,6 +10,8 @@ else:
import unittest import unittest
# This should be imported into every test_XXX.py file to report
# any remaining temp files at the end of the run.
def tearDownModule(): def tearDownModule():
import glob import glob
import os import os
@ -90,9 +92,8 @@ class PillowTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
a.size, b.size, a.size, b.size,
msg or "got size %r, expected %r" % (a.size, b.size)) msg or "got size %r, expected %r" % (a.size, b.size))
self.assertEqual( if a.tobytes() != b.tobytes():
a.tobytes(), b.tobytes(), self.fail(msg or "got different content")
msg or "got different content")
def assert_image_similar(self, a, b, epsilon, msg=None): def assert_image_similar(self, a, b, epsilon, msg=None):
epsilon = float(epsilon) epsilon = float(epsilon)
@ -160,59 +161,11 @@ class PillowTestCase(unittest.TestCase):
return files[0] return files[0]
# # require that deprecation warnings are triggered # helpers
# import warnings
# warnings.simplefilter('default')
# # temporarily turn off resource warnings that warn about unclosed
# # files in the test scripts.
# try:
# warnings.filterwarnings("ignore", category=ResourceWarning)
# except NameError:
# # we expect a NameError on py2.x, since it doesn't have ResourceWarnings.
# pass
import sys import sys
py3 = (sys.version_info >= (3, 0)) py3 = (sys.version_info >= (3, 0))
# # some test helpers
#
# _target = None
# _tempfiles = []
# _logfile = None
#
#
# def success():
# import sys
# success.count += 1
# if _logfile:
# print(sys.argv[0], success.count, failure.count, file=_logfile)
# return True
#
#
# def failure(msg=None, frame=None):
# import sys
# import linecache
# failure.count += 1
# if _target:
# if frame is None:
# frame = sys._getframe()
# while frame.f_globals.get("__name__") != _target.__name__:
# frame = frame.f_back
# location = (frame.f_code.co_filename, frame.f_lineno)
# prefix = "%s:%d: " % location
# line = linecache.getline(*location)
# print(prefix + line.strip() + " failed:")
# if msg:
# print("- " + msg)
# if _logfile:
# print(sys.argv[0], success.count, failure.count, file=_logfile)
# return False
#
# success.count = failure.count = 0
#
# helpers
def fromstring(data): def fromstring(data):
from io import BytesIO from io import BytesIO
@ -230,6 +183,9 @@ def tostring(im, format, **options):
def lena(mode="RGB", cache={}): def lena(mode="RGB", cache={}):
from PIL import Image from PIL import Image
im = None im = None
# FIXME: Implement caching to reduce reading from disk but so an original
# copy is returned each time and the cached image isn't modified by tests
# (for fast, isolated, repeatable tests).
# im = cache.get(mode) # im = cache.get(mode)
if im is None: if im is None:
if mode == "RGB": if mode == "RGB":
@ -244,98 +200,28 @@ def lena(mode="RGB", cache={}):
return im return im
# def assert_image_completely_equal(a, b, msg=None): def command_succeeds(cmd):
# if a != b: """
# failure(msg or "images different") Runs the command, which must be a list of strings. Returns True if the
# else: command succeeds, or False if an OSError was raised by subprocess.Popen.
# success() """
# import os
# import subprocess
# # test runner with open(os.devnull, 'w') as f:
# try:
# def run(): subprocess.Popen(cmd, stdout=f, stderr=subprocess.STDOUT).wait()
# global _target, _tests, run except OSError:
# import sys return False
# import traceback return True
# _target = sys.modules["__main__"]
# run = None # no need to run twice def djpeg_available():
# tests = [] return command_succeeds(['djpeg', '--help'])
# for name, value in list(vars(_target).items()):
# if name[:5] == "test_" and type(value) is type(success): def cjpeg_available():
# tests.append((value.__code__.co_firstlineno, name, value)) return command_succeeds(['cjpeg', '--help'])
# tests.sort() # sort by line
# for lineno, name, func in tests: def netpbm_available():
# try: return command_succeeds(["ppmquant", "--help"]) and \
# _tests = [] command_succeeds(["ppmtogif", "--help"])
# func()
# for func, args in _tests: # End of file
# func(*args)
# except:
# t, v, tb = sys.exc_info()
# tb = tb.tb_next
# if tb:
# failure(frame=tb.tb_frame)
# traceback.print_exception(t, v, tb)
# else:
# print("%s:%d: cannot call test function: %s" % (
# sys.argv[0], lineno, v))
# failure.count += 1
#
#
# def yield_test(function, *args):
# # collect delayed/generated tests
# _tests.append((function, args))
#
#
# def skip(msg=None):
# import os
# print("skip")
# os._exit(0) # don't run exit handlers
#
#
# def ignore(pattern):
# """Tells the driver to ignore messages matching the pattern, for the
# duration of the current test."""
# print('ignore: %s' % pattern)
#
#
# def _setup():
# global _logfile
#
# import sys
# if "--coverage" in sys.argv:
# # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4)
# with warnings.catch_warnings():
# warnings.simplefilter("ignore")
# import coverage
# cov = coverage.coverage(auto_data=True, include="PIL/*")
# cov.start()
#
# def report():
# if run:
# run()
# if success.count and not failure.count:
# print("ok")
# # only clean out tempfiles if test passed
# import os
# import os.path
# import tempfile
# for file in _tempfiles:
# try:
# os.remove(file)
# except OSError:
# pass # report?
# temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
# try:
# os.rmdir(temp_root)
# except OSError:
# pass
#
# import atexit
# atexit.register(report)
#
# if "--log" in sys.argv:
# _logfile = open("test.log", "a")
#
#
# _setup()

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,4 @@
from tester import * from helper import *
# This test is not run automatically. # This test is not run automatically.
# #
@ -6,32 +6,37 @@ from tester import *
# second test. Running this automatically would amount to a denial of # second test. Running this automatically would amount to a denial of
# service on our testing infrastructure. I expect this test to fail # service on our testing infrastructure. I expect this test to fail
# on any 32 bit machine, as well as any smallish things (like # on any 32 bit machine, as well as any smallish things (like
# raspberrypis). # Raspberry Pis).
from PIL import Image from PIL import Image
try: try:
import numpy as np import numpy as np
except: except:
skip() sys.exit("Skipping: Numpy not installed")
ydim = 32769
xdim = 48000
f = tempfile('temp.png')
def _write_png(xdim,ydim): YDIM = 32769
dtype = np.uint8 XDIM = 48000
a = np.zeros((xdim, ydim), dtype=dtype)
im = Image.fromarray(a, 'L')
im.save(f)
success()
def test_large():
""" succeeded prepatch"""
_write_png(xdim,ydim)
def test_2gpx():
"""failed prepatch"""
_write_png(xdim,xdim)
class LargeMemoryNumpyTest(PillowTestCase):
def _write_png(self, xdim, ydim):
dtype = np.uint8
a = np.zeros((xdim, ydim), dtype=dtype)
f = self.tempfile('temp.png')
im = Image.fromarray(a, 'L')
im.save(f)
def test_large(self):
""" succeeded prepatch"""
self._write_png(XDIM, YDIM)
def test_2gpx(self):
"""failed prepatch"""
self._write_png(XDIM, XDIM)
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -1,4 +1,4 @@
from tester import * from helper import *
# This test is not run automatically. # This test is not run automatically.
# #
@ -6,22 +6,31 @@ from tester import *
# second test. Running this automatically would amount to a denial of # second test. Running this automatically would amount to a denial of
# service on our testing infrastructure. I expect this test to fail # service on our testing infrastructure. I expect this test to fail
# on any 32 bit machine, as well as any smallish things (like # 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 # Raspberry Pis). It does succeed on a 3gb Ubuntu 12.04x64 VM on Python
# 2.7 an 3.2 # 2.7 an 3.2.
from PIL import Image from PIL import Image
ydim = 32769 YDIM = 32769
xdim = 48000 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(): class LargeMemoryTest(PillowTestCase):
""" succeeded prepatch"""
_write_png(xdim,ydim) def _write_png(self, xdim, ydim):
def test_2gpx(): f = self.tempfile('temp.png')
"""failed prepatch""" im = Image.new('L', (xdim, ydim), (0))
_write_png(xdim,xdim) im.save(f)
def test_large(self):
""" succeeded prepatch"""
self._write_png(XDIM, YDIM)
def test_2gpx(self):
"""failed prepatch"""
self._write_png(XDIM, XDIM)
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -1,7 +1,5 @@
# brute-force search for access descriptor hash table # brute-force search for access descriptor hash table
import random
modes = [ modes = [
"1", "1",
"L", "LA", "L", "LA",
@ -13,12 +11,14 @@ modes = [
"YCbCr", "YCbCr",
] ]
def hash(s, i): def hash(s, i):
# djb2 hash: multiply by 33 and xor character # djb2 hash: multiply by 33 and xor character
for c in s: for c in s:
i = (((i<<5) + i) ^ ord(c)) & 0xffffffff i = (((i << 5) + i) ^ ord(c)) & 0xffffffff
return i return i
def check(size, i0): def check(size, i0):
h = [None] * size h = [None] * size
for m in modes: for m in modes:

View File

@ -1,135 +0,0 @@
from __future__ import print_function
# minimal test runner
import glob
import os
import os.path
import re
import sys
import tempfile
try:
root = os.path.dirname(__file__)
except NameError:
root = os.path.dirname(sys.argv[0])
if not os.path.isfile("PIL/Image.py"):
print("***", "please run this script from the PIL development directory as")
print("***", "$ python Tests/run.py")
sys.exit(1)
print("-"*68)
python_options = []
tester_options = []
if "--installed" not in sys.argv:
os.environ["PYTHONPATH"] = "."
if "--coverage" in sys.argv:
tester_options.append("--coverage")
if "--log" in sys.argv:
tester_options.append("--log")
files = glob.glob(os.path.join(root, "test_*.py"))
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)
ignore_re = re.compile('^ignore: (.*)$', re.MULTILINE)
for file in files:
test, ext = os.path.splitext(os.path.basename(file))
if include and test not in include:
continue
print("running", test, "...")
# 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
))
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)
try:
def fix_re(p):
if not p.startswith('^'):
p = '^' + p
if not p.endswith('$'):
p += '$'
return p
ignore_res = [re.compile(fix_re(p), re.MULTILINE) for p in ignore_pats]
except:
print('(bad ignore patterns %r)' % ignore_pats)
ignore_res = []
for r in ignore_res:
result = r.sub('', result)
result = result.strip()
if result == "ok":
result = None
elif result == "skip":
print("---", "skipped") # FIXME: driver should include a reason
skipped.append(test)
continue
elif not result:
result = "(no output)"
status = out.close()
if status or result:
if status:
print("=== error", status)
if result:
if result[-3:] == "\nok":
# if there's an ok at the end, it's not really ok
result = result[:-3]
print(result)
failed.append(test)
else:
success += 1
print("-"*68)
temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
tempfiles = glob.glob(os.path.join(temp_root, "temp_*"))
if tempfiles:
print("===", "remaining temporary files")
for file in tempfiles:
print(file)
print("-"*68)
def tests(n):
if n == 1:
return "1 test"
else:
return "%d tests" % n
if skipped:
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.")

View File

@ -37,6 +37,18 @@ class TestFileBmp(PillowTestCase):
self.assertEqual(im.size, reloaded.size) self.assertEqual(im.size, reloaded.size)
self.assertEqual(reloaded.format, "BMP") self.assertEqual(reloaded.format, "BMP")
def test_dpi(self):
dpi = (72, 72)
output = io.BytesIO()
im = lena()
im.save(output, "BMP", dpi=dpi)
output.seek(0)
reloaded = Image.open(output)
self.assertEqual(reloaded.info["dpi"], dpi)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,6 +1,7 @@
from helper import unittest, PillowTestCase, tearDownModule, lena from helper import unittest, PillowTestCase, tearDownModule, lena, netpbm_available
from PIL import Image from PIL import Image
from PIL import GifImagePlugin
codecs = dir(Image.core) codecs = dir(Image.core)
@ -89,6 +90,22 @@ class TestFileGif(PillowTestCase):
reloaded = roundtrip(im)[1].convert('RGB') reloaded = roundtrip(im)[1].convert('RGB')
self.assert_image_equal(im, reloaded) self.assert_image_equal(im, reloaded)
@unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_bmp_mode(self):
img = Image.open(file).convert("RGB")
tempfile = self.tempfile("temp.gif")
GifImagePlugin._save_netpbm(img, 0, tempfile)
self.assert_image_similar(img, Image.open(tempfile).convert("RGB"), 0)
@unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_l_mode(self):
img = Image.open(file).convert("L")
tempfile = self.tempfile("temp.gif")
GifImagePlugin._save_netpbm(img, 0, tempfile)
self.assert_image_similar(img, Image.open(tempfile).convert("L"), 0)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,10 +1,12 @@
from helper import unittest, PillowTestCase, tearDownModule, lena, py3 from helper import unittest, PillowTestCase, tearDownModule, lena, py3
from helper import djpeg_available, cjpeg_available
import random import random
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
from PIL import ImageFile from PIL import ImageFile
from PIL import JpegImagePlugin
codecs = dir(Image.core) codecs = dir(Image.core)
@ -273,8 +275,23 @@ class TestFileJpeg(PillowTestCase):
qtables={0:standard_l_qtable, qtables={0:standard_l_qtable,
1:standard_chrominance_qtable}), 1:standard_chrominance_qtable}),
30) 30)
@unittest.skipUnless(djpeg_available(), "djpeg not available")
def test_load_djpeg(self):
img = Image.open(test_file)
img.load_djpeg()
self.assert_image_similar(img, Image.open(test_file), 0)
@unittest.skipUnless(cjpeg_available(), "cjpeg not available")
def test_save_cjpeg(self):
img = Image.open(test_file)
tempfile = self.tempfile("temp.jpg")
JpegImagePlugin._save_cjpeg(img, 0, tempfile)
# Default save quality is 75%, so a tiny bit of difference is alright
self.assert_image_similar(img, Image.open(tempfile), 1)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -120,6 +120,42 @@ class TestFileJpeg2k(PillowTestCase):
self.assertEqual(j2k.mode, 'RGBA') self.assertEqual(j2k.mode, 'RGBA')
self.assertEqual(jp2.mode, 'RGBA') self.assertEqual(jp2.mode, 'RGBA')
def test_16bit_monochrome_has_correct_mode(self):
j2k = Image.open('Tests/images/16bit.cropped.j2k')
jp2 = Image.open('Tests/images/16bit.cropped.jp2')
j2k.load()
jp2.load()
self.assertEqual(j2k.mode, 'I;16')
self.assertEqual(jp2.mode, 'I;16')
def test_16bit_monchrome_jp2_like_tiff(self):
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
jp2 = Image.open('Tests/images/16bit.cropped.jp2')
self.assert_image_similar(jp2, tiff_16bit, 1e-3)
def test_16bit_monchrome_j2k_like_tiff(self):
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
j2k = Image.open('Tests/images/16bit.cropped.j2k')
self.assert_image_similar(j2k, tiff_16bit, 1e-3)
def test_16bit_j2k_roundtrips(self):
j2k = Image.open('Tests/images/16bit.cropped.j2k')
im = self.roundtrip(j2k)
self.assert_image_equal(im, j2k)
def test_16bit_jp2_roundtrips(self):
jp2 = Image.open('Tests/images/16bit.cropped.jp2')
im = self.roundtrip(jp2)
self.assert_image_equal(im, jp2)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

33
Tests/test_pyroma.py Normal file
View File

@ -0,0 +1,33 @@
import unittest
try:
import pyroma
except ImportError:
# Skip via setUp()
pass
class TestPyroma(unittest.TestCase):
def setUp(self):
try:
import pyroma
except ImportError:
self.skipTest("ImportError")
def test_pyroma(self):
# Arrange
data = pyroma.projectdata.get_data(".")
# Act
rating = pyroma.ratings.rate(data)
# Assert
# Should have a perfect score
self.assertEqual(rating, (10, []))
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -0,0 +1,56 @@
from helper import unittest, PillowTestCase, tearDownModule
from helper import djpeg_available, cjpeg_available, netpbm_available
import shutil
from PIL import Image, JpegImagePlugin, GifImagePlugin
test_jpg = "Tests/images/lena.jpg"
test_gif = "Tests/images/lena.gif"
test_filenames = (
"temp_';",
"temp_\";",
"temp_'\"|",
"temp_'\"||",
"temp_'\"&&",
)
class TestShellInjection(PillowTestCase):
def assert_save_filename_check(self, src_img, save_func):
for filename in test_filenames:
dest_file = self.tempfile(filename)
save_func(src_img, 0, dest_file)
# If file can't be opened, shell injection probably occurred
Image.open(dest_file).load()
@unittest.skipUnless(djpeg_available(), "djpeg not available")
def test_load_djpeg_filename(self):
for filename in test_filenames:
src_file = self.tempfile(filename)
shutil.copy(test_jpg, src_file)
im = Image.open(src_file)
im.load_djpeg()
@unittest.skipUnless(cjpeg_available(), "cjpeg not available")
def test_save_cjpeg_filename(self):
im = Image.open(test_jpg)
self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg)
@unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_filename_bmp_mode(self):
im = Image.open(test_gif).convert("RGB")
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
@unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_filename_l_mode(self):
im = Image.open(test_gif).convert("L")
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -1,388 +0,0 @@
from __future__ import print_function
# require that deprecation warnings are triggered
import warnings
warnings.simplefilter('default')
# temporarily turn off resource warnings that warn about unclosed
# files in the test scripts.
try:
warnings.filterwarnings("ignore", category=ResourceWarning)
except NameError:
# we expect a NameError on py2.x, since it doesn't have ResourceWarnings.
pass
import sys
py3 = (sys.version_info >= (3, 0))
# some test helpers
_target = None
_tempfiles = []
_logfile = None
def success():
import sys
success.count += 1
if _logfile:
print(sys.argv[0], success.count, failure.count, file=_logfile)
return True
def failure(msg=None, frame=None):
import sys
import linecache
failure.count += 1
if _target:
if frame is None:
frame = sys._getframe()
while frame.f_globals.get("__name__") != _target.__name__:
frame = frame.f_back
location = (frame.f_code.co_filename, frame.f_lineno)
prefix = "%s:%d: " % location
line = linecache.getline(*location)
print(prefix + line.strip() + " failed:")
if msg:
print("- " + msg)
if _logfile:
print(sys.argv[0], success.count, failure.count, file=_logfile)
return False
success.count = failure.count = 0
# predicates
def assert_true(v, msg=None):
if v:
success()
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)]):
success()
else:
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)
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):
import re
if re.match(pattern, v):
success()
else:
failure(msg or "got %r, doesn't match pattern %r" % (v, pattern))
def assert_exception(exc_class, func):
import sys
import traceback
try:
func()
except exc_class:
success()
except:
failure("expected %r exception, got %r" % (
exc_class.__name__, sys.exc_info()[0].__name__))
traceback.print_exc()
else:
failure("expected %r exception, got no exception" % exc_class.__name__)
def assert_no_exception(func):
import sys
import traceback
try:
func()
except:
failure("expected no exception, got %r" % sys.exc_info()[0].__name__)
traceback.print_exc()
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
result = None
try:
warnings.warn = warn_ignore
assert_no_exception(func)
result = func()
warnings.warn = warn_error
assert_exception(warn_class, func)
finally:
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)
if im is None:
if mode == "RGB":
im = Image.open("Tests/images/lena.ppm")
elif mode == "F":
im = lena("L").convert(mode)
elif mode[:4] == "I;16":
im = lena("I").convert(mode)
else:
im = lena("RGB").convert(mode)
cache[mode] = im
return im
def assert_image(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))
elif size is not None and im.size != size:
failure(msg or "got size %r, expected %r" % (im.size, size))
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))
elif a.size != b.size:
failure(msg or "got size %r, expected %r" % (a.size, b.size))
elif a.tobytes() != b.tobytes():
failure(msg or "got different content")
else:
success()
def assert_image_completely_equal(a, b, msg=None):
if a != b:
failure(msg or "images different")
else:
success()
def assert_image_similar(a, b, epsilon, msg=None):
epsilon = float(epsilon)
if a.mode != b.mode:
return failure(msg or "got mode %r, expected %r" % (a.mode, b.mode))
elif a.size != b.size:
return failure(msg or "got size %r, expected %r" % (a.size, b.size))
diff = 0
try:
ord(b'0')
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
diff += abs(ord(abyte)-ord(bbyte))
except:
for abyte, bbyte in zip(a.tobytes(), b.tobytes()):
diff += abs(abyte-bbyte)
ave_diff = float(diff)/(a.size[0]*a.size[1])
if epsilon < ave_diff:
return failure(
msg or "average pixel value difference %.4f > epsilon %.4f" % (
ave_diff, epsilon))
else:
return success()
def tempfile(template, *extra):
import os
import os.path
import sys
import tempfile
files = []
root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
try:
os.mkdir(root)
except OSError:
pass
for temp in (template,) + extra:
assert temp[:5] in ("temp.", "temp_")
name = os.path.basename(sys.argv[0])
name = temp[:4] + os.path.splitext(name)[0][4:]
name = name + "_%d" % len(_tempfiles) + temp[4:]
name = os.path.join(root, name)
files.append(name)
_tempfiles.extend(files)
return files[0]
# test runner
def run():
global _target, _tests, run
import sys
import traceback
_target = sys.modules["__main__"]
run = None # no need to run twice
tests = []
for name, value in list(vars(_target).items()):
if name[:5] == "test_" and type(value) is type(success):
tests.append((value.__code__.co_firstlineno, name, value))
tests.sort() # sort by line
for lineno, name, func in tests:
try:
_tests = []
func()
for func, args in _tests:
func(*args)
except:
t, v, tb = sys.exc_info()
tb = tb.tb_next
if tb:
failure(frame=tb.tb_frame)
traceback.print_exception(t, v, tb)
else:
print("%s:%d: cannot call test function: %s" % (
sys.argv[0], lineno, v))
failure.count += 1
def yield_test(function, *args):
# collect delayed/generated tests
_tests.append((function, args))
def skip(msg=None):
import os
print("skip")
os._exit(0) # don't run exit handlers
def ignore(pattern):
"""Tells the driver to ignore messages matching the pattern, for the
duration of the current test."""
print('ignore: %s' % pattern)
def _setup():
global _logfile
import sys
if "--coverage" in sys.argv:
# Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
import coverage
cov = coverage.coverage(auto_data=True, include="PIL/*")
cov.start()
def report():
if run:
run()
if success.count and not failure.count:
print("ok")
# only clean out tempfiles if test passed
import os
import os.path
import tempfile
for file in _tempfiles:
try:
os.remove(file)
except OSError:
pass # report?
temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests')
try:
os.rmdir(temp_root)
except OSError:
pass
import atexit
atexit.register(report)
if "--log" in sys.argv:
_logfile = open("test.log", "a")
_setup()

View File

@ -267,9 +267,10 @@ setup_module(PyObject* m)
static PyMethodDef functions[] = { static PyMethodDef functions[] = {
/* Functions */ /* Functions */
{"apply", (PyCFunction)apply, 1}, {"apply", (PyCFunction)apply, METH_VARARGS, NULL},
{"get_on_pixels", (PyCFunction)get_on_pixels, 1}, {"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL},
{"match", (PyCFunction)match, 1}, {"match", (PyCFunction)match, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
}; };
#if PY_VERSION_HEX >= 0x03000000 #if PY_VERSION_HEX >= 0x03000000

4
depends/README.rst Normal file
View File

@ -0,0 +1,4 @@
Depends
=======
Scripts in this directory can be used to download, build & install non-packaged dependencies; useful for testing with Travis CI.

View File

@ -1,14 +1,6 @@
Plugin reference Plugin reference
================ ================
:mod:`ArgImagePlugin` Module
----------------------------
.. automodule:: PIL.ArgImagePlugin
:members:
:undoc-members:
:show-inheritance:
:mod:`BmpImagePlugin` Module :mod:`BmpImagePlugin` Module
---------------------------- ----------------------------

View File

@ -49,6 +49,8 @@ Functions
.. autofunction:: open .. autofunction:: open
.. warning:: > To protect against potential DOS attacks caused by "[decompression bombs](https://en.wikipedia.org/wiki/Zip_bomb)" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also [the logging documentation](https://docs.python.org/2/library/logging.html?highlight=logging#integration-with-the-warnings-module) to have warnings output to the logging facility instead of stderr.
Image processing Image processing
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^

View File

@ -17,12 +17,12 @@
#error Sorry, this library requires ANSI header files. #error Sorry, this library requires ANSI header files.
#endif #endif
#if !defined(PIL_USE_INLINE)
#define inline
#else
#if defined(_MSC_VER) && !defined(__GNUC__) #if defined(_MSC_VER) && !defined(__GNUC__)
#define inline __inline #define inline __inline
#endif #endif
#if !defined(PIL_USE_INLINE)
#define inline
#endif #endif
#ifdef _WIN32 #ifdef _WIN32

View File

@ -20,7 +20,7 @@ extern "C" {
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265359 #define M_PI 3.1415926535897932384626433832795
#endif #endif

View File

@ -135,6 +135,56 @@ j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
} }
} }
static void
j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
{
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
int shift = 16 - in->comps[0].prec;
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
int csiz = (in->comps[0].prec + 7) >> 3;
unsigned x, y;
if (csiz == 3)
csiz = 4;
if (shift < 0)
offset += 1 << (-shift - 1);
switch (csiz) {
case 1:
for (y = 0; y < h; ++y) {
const UINT8 *data = &tiledata[y * w];
UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x)
*row++ = j2ku_shift(offset + *data++, shift);
}
break;
case 2:
for (y = 0; y < h; ++y) {
const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w];
UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x)
*row++ = j2ku_shift(offset + *data++, shift);
}
break;
case 4:
for (y = 0; y < h; ++y) {
const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w];
UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x)
*row++ = j2ku_shift(offset + *data++, shift);
}
break;
}
}
static void static void
j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im) const UINT8 *tiledata, Imaging im)
@ -466,6 +516,8 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
static const struct j2k_decode_unpacker j2k_unpackers[] = { static const struct j2k_decode_unpacker j2k_unpackers[] = {
{ "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l }, { "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l },
{ "I;16", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i },
{ "I;16B", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i },
{ "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la }, { "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la },
{ "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb }, { "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb },
{ "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb }, { "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb },

View File

@ -88,6 +88,22 @@ j2k_pack_l(Imaging im, UINT8 *buf,
} }
} }
static void
j2k_pack_i16(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0, unsigned w, unsigned h)
{
UINT8 *ptr = buf;
unsigned x,y;
for (y = 0; y < h; ++y) {
UINT8 *data = (UINT8 *)(im->image[y + y0] + x0);
for (x = 0; x < w; ++x) {
*ptr++ = *data++;
*ptr++ = *data++;
}
}
}
static void static void
j2k_pack_la(Imaging im, UINT8 *buf, j2k_pack_la(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0, unsigned w, unsigned h) unsigned x0, unsigned y0, unsigned w, unsigned h)
@ -247,6 +263,9 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
j2k_pack_tile_t pack; j2k_pack_tile_t pack;
int ret = -1; int ret = -1;
unsigned prec = 8;
unsigned bpp = 8;
stream = opj_stream_default_create(OPJ_FALSE); stream = opj_stream_default_create(OPJ_FALSE);
if (!stream) { if (!stream) {
@ -271,6 +290,18 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
components = 1; components = 1;
color_space = OPJ_CLRSPC_GRAY; color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_l; pack = j2k_pack_l;
} else if (strcmp (im->mode, "I;16") == 0){
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_i16;
prec = 16;
bpp = 12;
} else if (strcmp (im->mode, "I;16B") == 0){
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_i16;
prec = 16;
bpp = 12;
} else if (strcmp (im->mode, "LA") == 0) { } else if (strcmp (im->mode, "LA") == 0) {
components = 2; components = 2;
color_space = OPJ_CLRSPC_GRAY; color_space = OPJ_CLRSPC_GRAY;
@ -298,8 +329,8 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
image_params[n].w = im->xsize; image_params[n].w = im->xsize;
image_params[n].h = im->ysize; image_params[n].h = im->ysize;
image_params[n].x0 = image_params[n].y0 = 0; image_params[n].x0 = image_params[n].y0 = 0;
image_params[n].prec = 8; image_params[n].prec = prec;
image_params[n].bpp = 8; image_params[n].bpp = bpp;
image_params[n].sgnd = 0; image_params[n].sgnd = 0;
} }
@ -442,7 +473,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
num_tiles = tiles_x * tiles_y; num_tiles = tiles_x * tiles_y;
state->buffer = malloc (tile_width * tile_height * components); state->buffer = malloc (tile_width * tile_height * components * prec / 8);
tile_ndx = 0; tile_ndx = 0;
for (y = 0; y < tiles_y; ++y) { for (y = 0; y < tiles_y; ++y) {
@ -474,7 +505,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
pack(im, state->buffer, pixx, pixy, pixw, pixh); pack(im, state->buffer, pixx, pixy, pixw, pixh);
data_size = pixw * pixh * components; data_size = pixw * pixh * components * prec / 8;
if (!opj_write_tile(codec, tile_ndx++, state->buffer, if (!opj_write_tile(codec, tile_ndx++, state->buffer,
data_size, stream)) { data_size, stream)) {

52
mp_compile.py Normal file
View File

@ -0,0 +1,52 @@
# A monkey patch of the base distutils.ccompiler to use parallel builds
# Tested on 2.7, looks to be identical to 3.3.
from multiprocessing import Pool, cpu_count
from distutils.ccompiler import CCompiler
import os
# hideous monkeypatching. but. but. but.
def _mp_compile_one(tp):
(self, obj, build, cc_args, extra_postargs, pp_opts) = tp
try:
src, ext = build[obj]
except KeyError:
return
self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
return
def _mp_compile(self, sources, output_dir=None, macros=None,
include_dirs=None, debug=0, extra_preargs=None,
extra_postargs=None, depends=None):
"""Compile one or more source files.
see distutils.ccompiler.CCompiler.compile for comments.
"""
# A concrete compiler class can either override this method
# entirely or implement _compile().
macros, objects, extra_postargs, pp_opts, build = self._setup_compile(
output_dir, macros, include_dirs, sources, depends, extra_postargs)
cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)
try:
max_procs = int(os.environ.get('MAX_CONCURRENCY', cpu_count()))
except:
max_procs = None
pool = Pool(max_procs)
try:
print ("Building using %d processes" % pool._processes)
except:
pass
arr = [
(self, obj, build, cc_args, extra_postargs, pp_opts) for obj in objects
]
pool.map_async(_mp_compile_one, arr)
pool.close()
pool.join()
# Return *all* object filenames, not just the ones we just built.
return objects
CCompiler.compile = _mp_compile

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
# Testing reqs
-e .
nose

View File

@ -210,7 +210,9 @@ class pil_build_ext(build_ext):
# if Homebrew is installed, use its lib and include directories # if Homebrew is installed, use its lib and include directories
import subprocess import subprocess
try: try:
prefix = subprocess.check_output(['brew', '--prefix']).strip() prefix = subprocess.check_output(
['brew', '--prefix']
).strip().decode('latin1')
except: except:
# Homebrew not installed # Homebrew not installed
prefix = None prefix = None
@ -403,7 +405,12 @@ class pil_build_ext(build_ext):
# Find the best version # Find the best version
for directory in self.compiler.include_dirs: for directory in self.compiler.include_dirs:
for name in os.listdir(directory): try:
listdir = os.listdir(directory)
except Exception:
# WindowsError, FileNotFoundError
continue
for name in listdir:
if name.startswith('openjpeg-') and \ if name.startswith('openjpeg-') and \
os.path.isfile(os.path.join(directory, name, os.path.isfile(os.path.join(directory, name,
'openjpeg.h')): 'openjpeg.h')):
@ -705,6 +712,7 @@ class pil_build_ext(build_ext):
finally: finally:
os.unlink(tmpfile) os.unlink(tmpfile)
setup( setup(
name=NAME, name=NAME,
version=VERSION, version=VERSION,
@ -728,7 +736,9 @@ setup(
"Programming Language :: Python :: 2.7", "Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3", ], "Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
],
cmdclass={"build_ext": pil_build_ext}, cmdclass={"build_ext": pil_build_ext},
ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], ext_modules=[Extension("PIL._imaging", ["_imaging.c"])],
include_package_data=True, include_package_data=True,
@ -738,6 +748,5 @@ setup(
keywords=["Imaging", ], keywords=["Imaging", ],
license='Standard PIL License', license='Standard PIL License',
zip_safe=True, zip_safe=True,
) )
# End of file # End of file

View File

@ -4,8 +4,8 @@ import os
import sys import sys
import glob import glob
# monkey with the path, removing the local directory but adding the Tests/ directory # monkey with the path, removing the local directory but adding the Tests/
# for helper.py and the other local imports there. # directory for helper.py and the other local imports there.
del(sys.path[0]) del(sys.path[0])
sys.path.insert(0, os.path.abspath('./Tests')) sys.path.insert(0, os.path.abspath('./Tests'))
@ -16,7 +16,7 @@ sys.path.insert(0, os.path.abspath('./Tests'))
if len(sys.argv) == 1: if len(sys.argv) == 1:
sys.argv.extend(glob.glob('Tests/test*.py')) sys.argv.extend(glob.glob('Tests/test*.py'))
# Make sure that nose doesn't muck with our paths. # Make sure that nose doesn't muck with our paths.
if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv): if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv):
sys.argv.insert(1, '--no-path-adjustment') sys.argv.insert(1, '--no-path-adjustment')