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:
irc: "chat.freenode.net#pil"
env: MAX_CONCURRENCY=4
python:
- "pypy"
- 2.6
@ -12,9 +14,9 @@ python:
- 3.4
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 coveralls nose"
- "pip install coveralls nose pyroma"
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi
# webp

View File

@ -4,6 +4,39 @@ Changelog (Pillow)
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
[brightpisces]

View File

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

View File

@ -1,7 +1,11 @@
pre:
virtualenv .
bin/pip install -r requirements.txt
bin/python setup.py develop
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
pyroma .
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
import math
i8 = _binary.i8
i16 = _binary.i16le
@ -88,6 +89,7 @@ class BmpImageFile(ImageFile.ImageFile):
bits = i16(s[14:])
self.size = i32(s[4:]), i32(s[8:])
compression = i32(s[16:])
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
lutsize = 4
colors = i32(s[32:])
direction = -1
@ -96,6 +98,8 @@ class BmpImageFile(ImageFile.ImageFile):
self.size = self.size[0], 2**32 - self.size[1]
direction = 0
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm))
else:
raise IOError("Unsupported BMP header type (%d)" % len(s))
@ -203,6 +207,13 @@ def _save(im, fp, filename, check=0):
if 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)
header = 40 # or 64 for OS/2 version 2
offset = 14 + header + colors * 4
@ -222,7 +233,7 @@ def _save(im, fp, filename, check=0):
o16(bits) + # depth
o32(0) + # compression (0=uncompressed)
o32(image) + # size of bitmap
o32(1) + o32(1) + # resolution
o32(ppm[0]) + o32(ppm[1]) + # resolution
o32(colors) + # colors used
o32(colors)) # colors important

View File

@ -333,13 +333,41 @@ def _save_netpbm(im, fp, filename):
# below for information on how to enable this.
import os
from subprocess import Popen, check_call, PIPE, CalledProcessError
import tempfile
file = im._dump()
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:
os.system("ppmquant 256 %s | ppmtogif >%s" % (file, filename))
try: os.unlink(file)
except: pass
with open(filename, 'wb') as f:
# 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:
self.fp.close()
except Exception as msg:
if Image.DEBUG:
if DEBUG:
print ("Error closing: %s" % msg)
# Instead of simply setting to None, we're setting up a

View File

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

View File

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

View File

@ -14,8 +14,7 @@
VERSION = '1.1.7' # PIL version
PILLOW_VERSION = '2.4.0' # Pillow
_plugins = ['ArgImagePlugin',
'BmpImagePlugin',
_plugins = ['BmpImagePlugin',
'BufrStubImagePlugin',
'CurImagePlugin',
'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
# --------------------------------------------------------------------
# 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
@ -56,10 +37,6 @@ class UI(Label):
else:
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)
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 Image
import time
def iterate_get(size, access):
(w,h) = size
(w, h) = size
for x in range(w):
for y in range(h):
access[(x,y)]
access[(x, y)]
def iterate_set(size, access):
(w,h) = size
(w, h) = size
for x in range(w):
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):
iterations = 5000
@ -25,20 +27,25 @@ def timer(func, label, *args):
for x in range(iterations):
func(*args)
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
if x == iterations-1:
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():
class BenchCffiAccess(PillowTestCase):
def test_direct(self):
im = lena()
im.load()
#im = Image.new( "RGB", (2000,2000), (1,3,2))
# 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)])
self.assertEqual(caccess[(0, 0)], access[(0, 0)])
print ("Size: %sx%s" % im.size)
timer(iterate_get, 'PyAccess - get', im.size, access)
@ -47,5 +54,7 @@ def test_direct():
timer(iterate_set, 'C-api - set', im.size, caccess)
if __name__ == '__main__':
unittest.main()
# End of file

View File

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

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

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.
#
@ -6,32 +6,37 @@ from tester import *
# 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).
# Raspberry Pis).
from PIL import Image
try:
import numpy as np
except:
skip()
sys.exit("Skipping: Numpy not installed")
ydim = 32769
xdim = 48000
f = tempfile('temp.png')
YDIM = 32769
XDIM = 48000
def _write_png(xdim,ydim):
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)
success()
def test_large():
def test_large(self):
""" succeeded prepatch"""
_write_png(xdim,ydim)
def test_2gpx():
self._write_png(XDIM, YDIM)
def test_2gpx(self):
"""failed prepatch"""
_write_png(xdim,xdim)
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.
#
@ -6,22 +6,31 @@ from tester import *
# 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
# Raspberry Pis). 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')
YDIM = 32769
XDIM = 48000
def _write_png(xdim,ydim):
im = Image.new('L',(xdim,ydim),(0))
class LargeMemoryTest(PillowTestCase):
def _write_png(self, xdim, ydim):
f = self.tempfile('temp.png')
im = Image.new('L', (xdim, ydim), (0))
im.save(f)
success()
def test_large():
def test_large(self):
""" succeeded prepatch"""
_write_png(xdim,ydim)
def test_2gpx():
self._write_png(XDIM, YDIM)
def test_2gpx(self):
"""failed prepatch"""
_write_png(xdim,xdim)
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
import random
modes = [
"1",
"L", "LA",
@ -13,12 +11,14 @@ modes = [
"YCbCr",
]
def hash(s, i):
# djb2 hash: multiply by 33 and xor character
for c in s:
i = (((i<<5) + i) ^ ord(c)) & 0xffffffff
i = (((i << 5) + i) ^ ord(c)) & 0xffffffff
return i
def check(size, i0):
h = [None] * size
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(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__':
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 GifImagePlugin
codecs = dir(Image.core)
@ -89,6 +90,22 @@ class TestFileGif(PillowTestCase):
reloaded = roundtrip(im)[1].convert('RGB')
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__':
unittest.main()

View File

@ -1,10 +1,12 @@
from helper import unittest, PillowTestCase, tearDownModule, lena, py3
from helper import djpeg_available, cjpeg_available
import random
from io import BytesIO
from PIL import Image
from PIL import ImageFile
from PIL import JpegImagePlugin
codecs = dir(Image.core)
@ -274,6 +276,21 @@ class TestFileJpeg(PillowTestCase):
1:standard_chrominance_qtable}),
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__':
unittest.main()

View File

@ -120,6 +120,42 @@ class TestFileJpeg2k(PillowTestCase):
self.assertEqual(j2k.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__':
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[] = {
/* Functions */
{"apply", (PyCFunction)apply, 1},
{"get_on_pixels", (PyCFunction)get_on_pixels, 1},
{"match", (PyCFunction)match, 1},
{"apply", (PyCFunction)apply, METH_VARARGS, NULL},
{"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL},
{"match", (PyCFunction)match, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
};
#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
================
:mod:`ArgImagePlugin` Module
----------------------------
.. automodule:: PIL.ArgImagePlugin
:members:
:undoc-members:
:show-inheritance:
:mod:`BmpImagePlugin` Module
----------------------------

View File

@ -49,6 +49,8 @@ Functions
.. 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
^^^^^^^^^^^^^^^^

View File

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

View File

@ -20,7 +20,7 @@ extern "C" {
#ifndef M_PI
#define M_PI 3.14159265359
#define M_PI 3.1415926535897932384626433832795
#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
j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
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[] = {
{ "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 },
{ "RGB", OPJ_CLRSPC_GRAY, 1, 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
j2k_pack_la(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0, unsigned w, unsigned h)
@ -247,6 +263,9 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
j2k_pack_tile_t pack;
int ret = -1;
unsigned prec = 8;
unsigned bpp = 8;
stream = opj_stream_default_create(OPJ_FALSE);
if (!stream) {
@ -271,6 +290,18 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
components = 1;
color_space = OPJ_CLRSPC_GRAY;
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) {
components = 2;
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].h = im->ysize;
image_params[n].x0 = image_params[n].y0 = 0;
image_params[n].prec = 8;
image_params[n].bpp = 8;
image_params[n].prec = prec;
image_params[n].bpp = bpp;
image_params[n].sgnd = 0;
}
@ -442,7 +473,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
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;
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);
data_size = pixw * pixh * components;
data_size = pixw * pixh * components * prec / 8;
if (!opj_write_tile(codec, tile_ndx++, state->buffer,
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
import subprocess
try:
prefix = subprocess.check_output(['brew', '--prefix']).strip()
prefix = subprocess.check_output(
['brew', '--prefix']
).strip().decode('latin1')
except:
# Homebrew not installed
prefix = None
@ -403,7 +405,12 @@ class pil_build_ext(build_ext):
# Find the best version
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 \
os.path.isfile(os.path.join(directory, name,
'openjpeg.h')):
@ -705,6 +712,7 @@ class pil_build_ext(build_ext):
finally:
os.unlink(tmpfile)
setup(
name=NAME,
version=VERSION,
@ -728,7 +736,9 @@ setup(
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"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},
ext_modules=[Extension("PIL._imaging", ["_imaging.c"])],
include_package_data=True,
@ -738,6 +748,5 @@ setup(
keywords=["Imaging", ],
license='Standard PIL License',
zip_safe=True,
)
)
# End of file

View File

@ -4,8 +4,8 @@ import os
import sys
import glob
# monkey with the path, removing the local directory but adding the Tests/ directory
# for helper.py and the other local imports there.
# monkey with the path, removing the local directory but adding the Tests/
# directory for helper.py and the other local imports there.
del(sys.path[0])
sys.path.insert(0, os.path.abspath('./Tests'))