Updated merge from master

This commit is contained in:
wiredfool 2014-09-26 16:07:44 -07:00
commit 460160a1e3
103 changed files with 1321 additions and 680 deletions

View File

@ -60,6 +60,7 @@ after_success:
- travis_retry pip install pep8 pyflakes - travis_retry pip install pep8 pyflakes
- pep8 --statistics --count PIL/*.py - pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py - pep8 --statistics --count Tests/*.py
- pyflakes *.py | tee >(wc -l)
- pyflakes PIL/*.py | tee >(wc -l) - pyflakes PIL/*.py | tee >(wc -l)
- pyflakes Tests/*.py | tee >(wc -l) - pyflakes Tests/*.py | tee >(wc -l)

View File

@ -26,12 +26,12 @@ from PIL import FontFile
# -------------------------------------------------------------------- # --------------------------------------------------------------------
bdf_slant = { bdf_slant = {
"R": "Roman", "R": "Roman",
"I": "Italic", "I": "Italic",
"O": "Oblique", "O": "Oblique",
"RI": "Reverse Italic", "RI": "Reverse Italic",
"RO": "Reverse Oblique", "RO": "Reverse Oblique",
"OT": "Other" "OT": "Other"
} }
bdf_spacing = { bdf_spacing = {
@ -40,8 +40,8 @@ bdf_spacing = {
"C": "Cell" "C": "Cell"
} }
def bdf_char(f):
def bdf_char(f):
# skip to STARTCHAR # skip to STARTCHAR
while True: while True:
s = f.readline() s = f.readline()
@ -82,6 +82,7 @@ def bdf_char(f):
return id, int(props["ENCODING"]), bbox, im return id, int(props["ENCODING"]), bbox, im
## ##
# Font file plugin for the X11 BDF format. # Font file plugin for the X11 BDF format.
@ -113,10 +114,10 @@ class BdfFontFile(FontFile.FontFile):
font[4] = bdf_slant[font[4].upper()] font[4] = bdf_slant[font[4].upper()]
font[11] = bdf_spacing[font[11].upper()] font[11] = bdf_spacing[font[11].upper()]
ascent = int(props["FONT_ASCENT"]) # ascent = int(props["FONT_ASCENT"])
descent = int(props["FONT_DESCENT"]) # descent = int(props["FONT_DESCENT"])
fontname = ";".join(font[1:]) # fontname = ";".join(font[1:])
# print "#", fontname # print "#", fontname
# for i in comments: # for i in comments:

View File

@ -51,9 +51,11 @@ BIT2MODE = {
32: ("RGB", "BGRX") 32: ("RGB", "BGRX")
} }
def _accept(prefix): def _accept(prefix):
return prefix[:2] == b"BM" return prefix[:2] == b"BM"
## ##
# Image plugin for the Windows BMP format. # Image plugin for the Windows BMP format.
@ -62,8 +64,7 @@ class BmpImageFile(ImageFile.ImageFile):
format = "BMP" format = "BMP"
format_description = "Windows Bitmap" format_description = "Windows Bitmap"
def _bitmap(self, header = 0, offset = 0): def _bitmap(self, header=0, offset=0):
if header: if header:
self.fp.seek(header) self.fp.seek(header)
@ -98,7 +99,8 @@ class BmpImageFile(ImageFile.ImageFile):
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)) 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))
@ -137,7 +139,7 @@ class BmpImageFile(ImageFile.ImageFile):
greyscale = 1 greyscale = 1
if colors == 2: if colors == 2:
indices = (0, 255) indices = (0, 255)
elif colors > 2**16 or colors <=0: #We're reading a i32. elif colors > 2**16 or colors <= 0: # We're reading a i32.
raise IOError("Unsupported BMP Palette size (%d)" % colors) raise IOError("Unsupported BMP Palette size (%d)" % colors)
else: else:
indices = list(range(colors)) indices = list(range(colors))
@ -163,7 +165,8 @@ class BmpImageFile(ImageFile.ImageFile):
self.tile = [("raw", self.tile = [("raw",
(0, 0) + self.size, (0, 0) + self.size,
offset, offset,
(rawmode, ((self.size[0]*bits+31)>>3)&(~3), direction))] (rawmode, ((self.size[0]*bits+31) >> 3) & (~3),
direction))]
self.info["compression"] = compression self.info["compression"] = compression
@ -197,8 +200,8 @@ SAVE = {
"RGB": ("BGR", 24, 0), "RGB": ("BGR", 24, 0),
} }
def _save(im, fp, filename, check=0):
def _save(im, fp, filename, check=0):
try: try:
rawmode, bits, colors = SAVE[im.mode] rawmode, bits, colors = SAVE[im.mode]
except KeyError: except KeyError:
@ -214,10 +217,10 @@ def _save(im, fp, filename, check=0):
# 1 meter == 39.3701 inches # 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701), dpi)) 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)
@ -248,7 +251,8 @@ def _save(im, fp, filename, check=0):
elif im.mode == "P": elif im.mode == "P":
fp.write(im.im.getpalette("RGB", "BGRX")) fp.write(im.im.getpalette("RGB", "BGRX"))
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, stride, -1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0,
(rawmode, stride, -1))])
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -13,6 +13,7 @@ from PIL import Image, ImageFile
_handler = None _handler = None
## ##
# Install application-specific BUFR image handler. # Install application-specific BUFR image handler.
# #
@ -22,12 +23,14 @@ def register_handler(handler):
global _handler global _handler
_handler = handler _handler = handler
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC" return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
class BufrStubImageFile(ImageFile.StubImageFile): class BufrStubImageFile(ImageFile.StubImageFile):
format = "BUFR" format = "BUFR"
@ -53,6 +56,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
def _load(self): def _load(self):
return _handler return _handler
def _save(im, fp, filename): def _save(im, fp, filename):
if _handler is None or not hasattr("_handler", "save"): if _handler is None or not hasattr("_handler", "save"):
raise IOError("BUFR save handler not installed") raise IOError("BUFR save handler not installed")

View File

@ -18,6 +18,7 @@
# A file object that provides read access to a part of an existing # A file object that provides read access to a part of an existing
# file (for example a TAR file). # file (for example a TAR file).
class ContainerIO: class ContainerIO:
## ##
@ -48,7 +49,7 @@ class ContainerIO:
# for current offset, and 2 for end of region. You cannot move # for current offset, and 2 for end of region. You cannot move
# the pointer outside the defined region. # the pointer outside the defined region.
def seek(self, offset, mode = 0): def seek(self, offset, mode=0):
if mode == 1: if mode == 1:
self.pos = self.pos + offset self.pos = self.pos + offset
elif mode == 2: elif mode == 2:
@ -75,12 +76,12 @@ class ContainerIO:
# read until end of region. # read until end of region.
# @return An 8-bit string. # @return An 8-bit string.
def read(self, n = 0): def read(self, n=0):
if n: if n:
n = min(n, self.length - self.pos) n = min(n, self.length - self.pos)
else: else:
n = self.length - self.pos n = self.length - self.pos
if not n: # EOF if not n: # EOF
return "" return ""
self.pos = self.pos + n self.pos = self.pos + n
return self.fh.read(n) return self.fh.read(n)

View File

@ -11,7 +11,8 @@
# 1996-08-23 fl Handle files from Macintosh (0.3) # 1996-08-23 fl Handle files from Macintosh (0.3)
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4) # 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5) # 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution resizing # 2014-05-07 e Handling of EPS with binary preview and fixed resolution
# resizing
# #
# Copyright (c) 1997-2003 by Secret Labs AB. # Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2003 by Fredrik Lundh # Copyright (c) 1995-2003 by Fredrik Lundh
@ -51,13 +52,14 @@ if sys.platform.startswith('win'):
else: else:
gs_windows_binary = False gs_windows_binary = False
def has_ghostscript(): def has_ghostscript():
if gs_windows_binary: if gs_windows_binary:
return True return True
if not sys.platform.startswith('win'): if not sys.platform.startswith('win'):
import subprocess import subprocess
try: try:
gs = subprocess.Popen(['gs','--version'], stdout=subprocess.PIPE) gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
gs.stdout.read() gs.stdout.read()
return True return True
except OSError: except OSError:
@ -73,16 +75,19 @@ def Ghostscript(tile, size, fp, scale=1):
decoder, tile, offset, data = tile[0] decoder, tile, offset, data = tile[0]
length, bbox = data length, bbox = data
#Hack to support hi-res rendering # Hack to support hi-res rendering
scale = int(scale) or 1 scale = int(scale) or 1
orig_size = size # orig_size = size
orig_bbox = bbox # orig_bbox = bbox
size = (size[0] * scale, size[1] * scale) size = (size[0] * scale, size[1] * scale)
# resolution is dependend on bbox and size # resolution is dependent on bbox and size
res = ( float((72.0 * size[0]) / (bbox[2]-bbox[0])), float((72.0 * size[1]) / (bbox[3]-bbox[1])) ) res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
#print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res) float((72.0 * size[1]) / (bbox[3]-bbox[1])))
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
import tempfile, os, subprocess import os
import subprocess
import tempfile
out_fd, outfile = tempfile.mkstemp() out_fd, outfile = tempfile.mkstemp()
os.close(out_fd) os.close(out_fd)
@ -115,16 +120,17 @@ def Ghostscript(tile, size, fp, scale=1):
# Build ghostscript command # Build ghostscript command
command = ["gs", command = ["gs",
"-q", # quiet mode "-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels) "-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch) "-r%fx%f" % res, # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode "-dNOPAUSE -dSAFER", # don't pause between pages,
"-sDEVICE=ppmraw", # ppm driver # safe mode
"-sOutputFile=%s" % outfile, # output file "-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]), "-c", "%d %d translate" % (-bbox[0], -bbox[1]),
# adjust for image origin # adjust for image origin
"-f", infile, # input file "-f", infile, # input file
] ]
if gs_windows_binary is not None: if gs_windows_binary is not None:
if not gs_windows_binary: if not gs_windows_binary:
@ -133,7 +139,8 @@ def Ghostscript(tile, size, fp, scale=1):
# push data through ghostscript # push data through ghostscript
try: try:
gs = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) gs = subprocess.Popen(command, stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
gs.stdin.close() gs.stdin.close()
status = gs.wait() status = gs.wait()
if status: if status:
@ -142,21 +149,26 @@ def Ghostscript(tile, size, fp, scale=1):
finally: finally:
try: try:
os.unlink(outfile) os.unlink(outfile)
if infile_temo: if infile_temp:
os.unlink(infile_temp) os.unlink(infile_temp)
except: pass except:
pass
return im return im
class PSFile: class PSFile:
"""Wrapper for bytesio object that treats either CR or LF as end of line.""" """
Wrapper for bytesio object that treats either CR or LF as end of line.
"""
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
self.char = None self.char = None
def seek(self, offset, whence=0): def seek(self, offset, whence=0):
self.char = None self.char = None
self.fp.seek(offset, whence) self.fp.seek(offset, whence)
def readline(self): def readline(self):
s = self.char or b"" s = self.char or b""
self.char = None self.char = None
@ -173,6 +185,7 @@ class PSFile:
return s.decode('latin-1') return s.decode('latin-1')
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5 return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5
@ -180,13 +193,14 @@ def _accept(prefix):
# Image plugin for Encapsulated Postscript. This plugin supports only # Image plugin for Encapsulated Postscript. This plugin supports only
# a few variants of this format. # a few variants of this format.
class EpsImageFile(ImageFile.ImageFile): class EpsImageFile(ImageFile.ImageFile):
"""EPS File Parser for the Python Imaging Library""" """EPS File Parser for the Python Imaging Library"""
format = "EPS" format = "EPS"
format_description = "Encapsulated Postscript" format_description = "Encapsulated Postscript"
mode_map = { 1:"L", 2:"LAB", 3:"RGB" } mode_map = {1: "L", 2: "LAB", 3: "RGB"}
def _open(self): def _open(self):
(length, offset) = self._find_offset(self.fp) (length, offset) = self._find_offset(self.fp)
@ -200,7 +214,7 @@ class EpsImageFile(ImageFile.ImageFile):
else: else:
# Python3, can use bare open command. # Python3, can use bare open command.
fp = open(self.fp.name, "Ur", encoding='latin-1') fp = open(self.fp.name, "Ur", encoding='latin-1')
except Exception as msg: except:
# Expect this for bytesio/stringio # Expect this for bytesio/stringio
fp = PSFile(self.fp) fp = PSFile(self.fp)
@ -210,7 +224,7 @@ class EpsImageFile(ImageFile.ImageFile):
box = None box = None
self.mode = "RGB" self.mode = "RGB"
self.size = 1, 1 # FIXME: huh? self.size = 1, 1 # FIXME: huh?
# #
# Load EPS header # Load EPS header
@ -236,7 +250,7 @@ class EpsImageFile(ImageFile.ImageFile):
# put floating point values there anyway. # put floating point values there anyway.
box = [int(float(s)) for s in v.split()] box = [int(float(s)) for s in v.split()]
self.size = box[2] - box[0], box[3] - box[1] self.size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0,0) + self.size, offset, self.tile = [("eps", (0, 0) + self.size, offset,
(length, box))] (length, box))]
except: except:
pass pass
@ -264,7 +278,6 @@ class EpsImageFile(ImageFile.ImageFile):
if s[0] != "%": if s[0] != "%":
break break
# #
# Scan for an "ImageData" descriptor # Scan for an "ImageData" descriptor
@ -307,7 +320,8 @@ class EpsImageFile(ImageFile.ImageFile):
# FIX for: Some EPS file not handled correctly / issue #302 # FIX for: Some EPS file not handled correctly / issue #302
# EPS can contain binary data # EPS can contain binary data
# or start directly with latin coding # or start directly with latin coding
# more info see http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf # more info see:
# http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
offset = i32(s[4:8]) offset = i32(s[4:8])
length = i32(s[8:12]) length = i32(s[8:12])
else: else:
@ -324,11 +338,12 @@ class EpsImageFile(ImageFile.ImageFile):
self.size = self.im.size self.size = self.im.size
self.tile = [] self.tile = []
def load_seek(self,*args,**kwargs): def load_seek(self, *args, **kwargs):
# we can't incrementally load, so force ImageFile.parser to # we can't incrementally load, so force ImageFile.parser to
# use our custom load method by defining this method. # use our custom load method by defining this method.
pass pass
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -353,8 +368,10 @@ def _save(im, fp, filename, eps=1):
class NoCloseStream: class NoCloseStream:
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.fp, name) return getattr(self.fp, name)
def close(self): def close(self):
pass pass
@ -368,7 +385,7 @@ def _save(im, fp, filename, eps=1):
# write EPS header # write EPS header
fp.write("%!PS-Adobe-3.0 EPSF-3.0\n") fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
fp.write("%%Creator: PIL 0.1 EpsEncode\n") fp.write("%%Creator: PIL 0.1 EpsEncode\n")
#fp.write("%%CreationDate: %s"...) # fp.write("%%CreationDate: %s"...)
fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size) fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
fp.write("%%Pages: 1\n") fp.write("%%Pages: 1\n")
fp.write("%%EndComments\n") fp.write("%%EndComments\n")
@ -382,13 +399,13 @@ def _save(im, fp, filename, eps=1):
fp.write("10 dict begin\n") fp.write("10 dict begin\n")
fp.write("/buf %d string def\n" % (im.size[0] * operator[1])) fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
fp.write("%d %d scale\n" % im.size) fp.write("%d %d scale\n" % im.size)
fp.write("%d %d 8\n" % im.size) # <= bits fp.write("%d %d 8\n" % im.size) # <= bits
fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
fp.write("{ currentfile buf readhexstring pop } bind\n") fp.write("{ currentfile buf readhexstring pop } bind\n")
fp.write(operator[2] + "\n") fp.write(operator[2] + "\n")
fp.flush() fp.flush()
ImageFile._save(im, base_fp, [("eps", (0,0)+im.size, 0, None)]) ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
fp.write("\n%%%%EndBinary\n") fp.write("\n%%%%EndBinary\n")
fp.write("grestore end\n") fp.write("grestore end\n")

View File

@ -25,12 +25,14 @@ i16 = _binary.i16le
i32 = _binary.i32le i32 = _binary.i32le
o8 = _binary.o8 o8 = _binary.o8
# #
# decoder # decoder
def _accept(prefix): def _accept(prefix):
return i16(prefix[4:6]) in [0xAF11, 0xAF12] return i16(prefix[4:6]) in [0xAF11, 0xAF12]
## ##
# Image plugin for the FLI/FLC animation format. Use the <b>seek</b> # Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
# method to load individual frames. # method to load individual frames.
@ -47,7 +49,7 @@ class FliImageFile(ImageFile.ImageFile):
magic = i16(s[4:6]) magic = i16(s[4:6])
if not (magic in [0xAF11, 0xAF12] and if not (magic in [0xAF11, 0xAF12] and
i16(s[14:16]) in [0, 3] and # flags i16(s[14:16]) in [0, 3] and # flags
s[20:22] == b"\x00\x00"): # reserved s[20:22] == b"\x00\x00"): # reserved
raise SyntaxError("not an FLI/FLC file") raise SyntaxError("not an FLI/FLC file")
# image characteristics # image characteristics
@ -61,7 +63,7 @@ class FliImageFile(ImageFile.ImageFile):
self.info["duration"] = duration self.info["duration"] = duration
# look for palette # look for palette
palette = [(a,a,a) for a in range(256)] palette = [(a, a, a) for a in range(256)]
s = self.fp.read(16) s = self.fp.read(16)
@ -80,7 +82,7 @@ class FliImageFile(ImageFile.ImageFile):
elif i16(s[4:6]) == 4: elif i16(s[4:6]) == 4:
self._palette(palette, 0) self._palette(palette, 0)
palette = [o8(r)+o8(g)+o8(b) for (r,g,b) in palette] palette = [o8(r)+o8(g)+o8(b) for (r, g, b) in palette]
self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.palette = ImagePalette.raw("RGB", b"".join(palette))
# set things up to decode first frame # set things up to decode first frame
@ -124,7 +126,7 @@ class FliImageFile(ImageFile.ImageFile):
framesize = i32(s) framesize = i32(s)
self.decodermaxblock = framesize self.decodermaxblock = framesize
self.tile = [("fli", (0,0)+self.size, self.__offset, None)] self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
self.__offset = self.__offset + framesize self.__offset = self.__offset + framesize

View File

@ -34,16 +34,18 @@ MODES = {
(0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"), (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
(0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"), (0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"),
# standard RGB (NIFRGB) # standard RGB (NIFRGB)
(0x00030000, 0x00030001, 0x00030002): ("RGB","RGB"), (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
(0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA","RGBA"), (0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA", "RGBA"),
} }
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _accept(prefix): def _accept(prefix):
return prefix[:8] == MAGIC return prefix[:8] == MAGIC
## ##
# Image plugin for the FlashPix images. # Image plugin for the FlashPix images.
@ -67,7 +69,7 @@ class FpxImageFile(ImageFile.ImageFile):
self._open_index(1) self._open_index(1)
def _open_index(self, index = 1): def _open_index(self, index=1):
# #
# get the Image Contents Property Set # get the Image Contents Property Set
@ -95,7 +97,7 @@ class FpxImageFile(ImageFile.ImageFile):
id = self.maxid << 16 id = self.maxid << 16
s = prop[0x2000002|id] s = prop[0x2000002 | id]
colors = [] colors = []
for i in range(i32(s, 4)): for i in range(i32(s, 4)):
@ -107,7 +109,7 @@ class FpxImageFile(ImageFile.ImageFile):
# load JPEG tables, if any # load JPEG tables, if any
self.jpeg = {} self.jpeg = {}
for i in range(256): for i in range(256):
id = 0x3000001|(i << 16) id = 0x3000001 | (i << 16)
if id in prop: if id in prop:
self.jpeg[i] = prop[id] self.jpeg[i] = prop[id]
@ -115,7 +117,7 @@ class FpxImageFile(ImageFile.ImageFile):
self._open_subimage(1, self.maxid) self._open_subimage(1, self.maxid)
def _open_subimage(self, index = 1, subimage = 0): def _open_subimage(self, index=1, subimage=0):
# #
# setup tile descriptors for a given subimage # setup tile descriptors for a given subimage
@ -159,14 +161,14 @@ class FpxImageFile(ImageFile.ImageFile):
compression = i32(s, i+8) compression = i32(s, i+8)
if compression == 0: if compression == 0:
self.tile.append(("raw", (x,y,x+xtile,y+ytile), self.tile.append(("raw", (x, y, x+xtile, y+ytile),
i32(s, i) + 28, (self.rawmode))) i32(s, i) + 28, (self.rawmode)))
elif compression == 1: elif compression == 1:
# FIXME: the fill decoder is not implemented # FIXME: the fill decoder is not implemented
self.tile.append(("fill", (x,y,x+xtile,y+ytile), self.tile.append(("fill", (x, y, x+xtile, y+ytile),
i32(s, i) + 28, (self.rawmode, s[12:16]))) i32(s, i) + 28, (self.rawmode, s[12:16])))
elif compression == 2: elif compression == 2:
@ -182,14 +184,14 @@ class FpxImageFile(ImageFile.ImageFile):
# this problem : # this problem :
jpegmode, rawmode = "YCbCrK", "CMYK" jpegmode, rawmode = "YCbCrK", "CMYK"
else: else:
jpegmode = None # let the decoder decide jpegmode = None # let the decoder decide
else: else:
# The image is stored as defined by rawmode # The image is stored as defined by rawmode
jpegmode = rawmode jpegmode = rawmode
self.tile.append(("jpeg", (x,y,x+xtile,y+ytile), self.tile.append(("jpeg", (x, y, x+xtile, y+ytile),
i32(s, i) + 28, (rawmode, jpegmode))) i32(s, i) + 28, (rawmode, jpegmode)))
# FIXME: jpeg tables are tile dependent; the prefix # FIXME: jpeg tables are tile dependent; the prefix
# data must be placed in the tile descriptor itself! # data must be placed in the tile descriptor itself!
@ -204,7 +206,7 @@ class FpxImageFile(ImageFile.ImageFile):
if x >= xsize: if x >= xsize:
x, y = 0, y + ytile x, y = 0, y + ytile
if y >= ysize: if y >= ysize:
break # isn't really required break # isn't really required
self.stream = stream self.stream = stream
self.fp = None self.fp = None
@ -212,7 +214,8 @@ class FpxImageFile(ImageFile.ImageFile):
def load(self): def load(self):
if not self.fp: if not self.fp:
self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"]) self.fp = self.ole.openstream(self.stream[:2] +
["Subimage 0000 Data"])
ImageFile.ImageFile.load(self) ImageFile.ImageFile.load(self)

View File

@ -17,9 +17,11 @@ from PIL import Image, ImageFile, _binary
i32 = _binary.i32be i32 = _binary.i32be
def _accept(prefix): def _accept(prefix):
return i32(prefix) >= 20 and i32(prefix[4:8]) == 1 return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
## ##
# Image plugin for the GIMP brush format. # Image plugin for the GIMP brush format.

View File

@ -36,6 +36,7 @@ except ImportError:
i16 = _binary.i16be i16 = _binary.i16be
## ##
# Image plugin for the GD uncompressed format. Note that this format # Image plugin for the GD uncompressed format. Note that this format
# is not supported by the standard <b>Image.open</b> function. To use # is not supported by the standard <b>Image.open</b> function. To use
@ -52,7 +53,7 @@ class GdImageFile(ImageFile.ImageFile):
# Header # Header
s = self.fp.read(775) s = self.fp.read(775)
self.mode = "L" # FIXME: "P" self.mode = "L" # FIXME: "P"
self.size = i16(s[0:2]), i16(s[2:4]) self.size = i16(s[0:2]), i16(s[2:4])
# transparency index # transparency index
@ -62,7 +63,8 @@ class GdImageFile(ImageFile.ImageFile):
self.palette = ImagePalette.raw("RGB", s[7:]) self.palette = ImagePalette.raw("RGB", s[7:])
self.tile = [("raw", (0,0)+self.size, 775, ("L", 0, -1))] self.tile = [("raw", (0, 0)+self.size, 775, ("L", 0, -1))]
## ##
# Load texture from a GD image file. # Load texture from a GD image file.
@ -73,7 +75,7 @@ class GdImageFile(ImageFile.ImageFile):
# @return An image instance. # @return An image instance.
# @exception IOError If the image could not be read. # @exception IOError If the image could not be read.
def open(fp, mode = "r"): def open(fp, mode="r"):
if mode != "r": if mode != "r":
raise ValueError("bad mode") raise ValueError("bad mode")

View File

@ -46,6 +46,7 @@ o16 = _binary.o16le
def _accept(prefix): def _accept(prefix):
return prefix[:6] in [b"GIF87a", b"GIF89a"] return prefix[:6] in [b"GIF87a", b"GIF89a"]
## ##
# Image plugin for GIF images. This plugin supports both GIF87 and # Image plugin for GIF images. This plugin supports both GIF87 and
# GIF89 images. # GIF89 images.
@ -79,16 +80,16 @@ class GifImageFile(ImageFile.ImageFile):
# get global palette # get global palette
self.info["background"] = i8(s[11]) self.info["background"] = i8(s[11])
# check if palette contains colour indices # check if palette contains colour indices
p = self.fp.read(3<<bits) p = self.fp.read(3 << bits)
for i in range(0, len(p), 3): for i in range(0, len(p), 3):
if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])): if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])):
p = ImagePalette.raw("RGB", p) p = ImagePalette.raw("RGB", p)
self.global_palette = self.palette = p self.global_palette = self.palette = p
break break
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell() self.__rewind = self.fp.tell()
self.seek(0) # get ready to read first frame self.seek(0) # get ready to read first frame
def seek(self, frame): def seek(self, frame):
@ -96,7 +97,7 @@ class GifImageFile(ImageFile.ImageFile):
# rewind # rewind
self.__offset = 0 self.__offset = 0
self.dispose = None self.dispose = None
self.dispose_extent = [0, 0, 0, 0] #x0, y0, x1, y1 self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1
self.__frame = -1 self.__frame = -1
self.__fp.seek(self.__rewind) self.__fp.seek(self.__rewind)
self._prev_im = None self._prev_im = None
@ -185,7 +186,7 @@ class GifImageFile(ImageFile.ImageFile):
if flags & 128: if flags & 128:
bits = (flags & 7) + 1 bits = (flags & 7) + 1
self.palette =\ self.palette =\
ImagePalette.raw("RGB", self.fp.read(3<<bits)) ImagePalette.raw("RGB", self.fp.read(3 << bits))
# image data # image data
bits = i8(self.fp.read(1)) bits = i8(self.fp.read(1))
@ -219,7 +220,6 @@ class GifImageFile(ImageFile.ImageFile):
except (AttributeError, KeyError): except (AttributeError, KeyError):
pass pass
if not self.tile: if not self.tile:
# self.__fp = None # self.__fp = None
raise EOFError("no more images in GIF file") raise EOFError("no more images in GIF file")
@ -240,7 +240,8 @@ class GifImageFile(ImageFile.ImageFile):
# we do this by pasting the updated area onto the previous # we do this by pasting the updated area onto the previous
# frame which we then use as the current image content # frame which we then use as the current image content
updated = self.im.crop(self.dispose_extent) updated = self.im.crop(self.dispose_extent)
self._prev_im.paste(updated, self.dispose_extent, updated.convert('RGBA')) self._prev_im.paste(updated, self.dispose_extent,
updated.convert('RGBA'))
self.im = self._prev_im self.im = self._prev_im
self._prev_im = self.im.copy() self._prev_im = self.im.copy()
@ -258,6 +259,7 @@ RAWMODE = {
"P": "P", "P": "P",
} }
def _save(im, fp, filename): def _save(im, fp, filename):
if _imaging_gif: if _imaging_gif:
@ -266,7 +268,7 @@ def _save(im, fp, filename):
_imaging_gif.save(im, fp, filename) _imaging_gif.save(im, fp, filename)
return return
except IOError: except IOError:
pass # write uncompressed file pass # write uncompressed file
if im.mode in RAWMODE: if im.mode in RAWMODE:
imOut = im imOut = im
@ -343,15 +345,17 @@ def _save(im, fp, filename):
o8(8)) # bits o8(8)) # bits
imOut.encoderconfig = (8, interlace) imOut.encoderconfig = (8, interlace)
ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, RAWMODE[imOut.mode])]) ImageFile._save(imOut, fp, [("gif", (0, 0)+im.size, 0,
RAWMODE[imOut.mode])])
fp.write(b"\0") # end of image data fp.write(b"\0") # end of image data
fp.write(b";") # end of file fp.write(b";") # end of file
try: try:
fp.flush() fp.flush()
except: pass except:
pass
def _save_netpbm(im, fp, filename): def _save_netpbm(im, fp, filename):
@ -380,7 +384,8 @@ def _save_netpbm(im, fp, filename):
stderr = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile()
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr) quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
stderr = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile()
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=stderr) togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
stderr=stderr)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits # Allow ppmquant to receive SIGPIPE if ppmtogif exits
quant_proc.stdout.close() quant_proc.stdout.close()
@ -420,7 +425,7 @@ def getheader(im, palette=None, info=None):
sourcePalette = palette[:768] sourcePalette = palette[:768]
else: else:
sourcePalette = im.im.getpalette("RGB")[:768] sourcePalette = im.im.getpalette("RGB")[:768]
else: # L-mode else: # L-mode
if palette and isinstance(palette, bytes): if palette and isinstance(palette, bytes):
sourcePalette = palette[:768] sourcePalette = palette[:768]
else: else:
@ -455,9 +460,11 @@ def getheader(im, palette=None, info=None):
for i in range(len(imageBytes)): for i in range(len(imageBytes)):
imageBytes[i] = newPositions[imageBytes[i]] imageBytes[i] = newPositions[imageBytes[i]]
im.frombytes(bytes(imageBytes)) im.frombytes(bytes(imageBytes))
newPaletteBytes = paletteBytes + (768 - len(paletteBytes)) * b'\x00' newPaletteBytes = (paletteBytes +
(768 - len(paletteBytes)) * b'\x00')
im.putpalette(newPaletteBytes) im.putpalette(newPaletteBytes)
im.palette = ImagePalette.ImagePalette("RGB", palette = paletteBytes, size = len(paletteBytes)) im.palette = ImagePalette.ImagePalette("RGB", palette=paletteBytes,
size=len(paletteBytes))
if not paletteBytes: if not paletteBytes:
paletteBytes = sourcePalette paletteBytes = sourcePalette
@ -466,7 +473,8 @@ def getheader(im, palette=None, info=None):
# calculate the palette size for the header # calculate the palette size for the header
import math import math
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1 colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1
if colorTableSize < 0: colorTableSize = 0 if colorTableSize < 0:
colorTableSize = 0
# size of global color table + global color table flag # size of global color table + global color table flag
header.append(o8(colorTableSize + 128)) header.append(o8(colorTableSize + 128))
# background + reserved/aspect # background + reserved/aspect
@ -475,7 +483,7 @@ def getheader(im, palette=None, info=None):
# add the missing amount of bytes # add the missing amount of bytes
# the palette has to be 2<<n in size # the palette has to be 2<<n in size
actualTargetSizeDiff = (2<<colorTableSize) - len(paletteBytes)//3 actualTargetSizeDiff = (2 << colorTableSize) - len(paletteBytes)//3
if actualTargetSizeDiff > 0: if actualTargetSizeDiff > 0:
paletteBytes += o8(0) * 3 * actualTargetSizeDiff paletteBytes += o8(0) * 3 * actualTargetSizeDiff
@ -484,17 +492,18 @@ def getheader(im, palette=None, info=None):
return header, usedPaletteColors return header, usedPaletteColors
def getdata(im, offset = (0, 0), **params): def getdata(im, offset=(0, 0), **params):
"""Return a list of strings representing this image. """Return a list of strings representing this image.
The first string is a local image header, the rest contains The first string is a local image header, the rest contains
encoded image data.""" encoded image data."""
class collector: class collector:
data = [] data = []
def write(self, data): def write(self, data):
self.data.append(data) self.data.append(data)
im.load() # make sure raster data is available im.load() # make sure raster data is available
fp = collector() fp = collector()
@ -510,9 +519,9 @@ def getdata(im, offset = (0, 0), **params):
o8(0) + # flags o8(0) + # flags
o8(8)) # bits o8(8)) # bits
ImageFile._save(im, fp, [("gif", (0,0)+im.size, 0, RAWMODE[im.mode])]) ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
fp.write(b"\0") # end of image data fp.write(b"\0") # end of image data
finally: finally:
del im.encoderinfo del im.encoderinfo

View File

@ -17,6 +17,7 @@
import re import re
from PIL._binary import o8 from PIL._binary import o8
## ##
# File handler for GIMP's palette format. # File handler for GIMP's palette format.
@ -56,7 +57,6 @@ class GimpPaletteFile:
self.palette = b"".join(self.palette) self.palette = b"".join(self.palette)
def getpalette(self): def getpalette(self):
return self.palette, self.rawmode return self.palette, self.rawmode

View File

@ -13,6 +13,7 @@ from PIL import Image, ImageFile
_handler = None _handler = None
## ##
# Install application-specific GRIB image handler. # Install application-specific GRIB image handler.
# #
@ -22,12 +23,14 @@ def register_handler(handler):
global _handler global _handler
_handler = handler _handler = handler
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01' return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01'
class GribStubImageFile(ImageFile.StubImageFile): class GribStubImageFile(ImageFile.StubImageFile):
format = "GRIB" format = "GRIB"
@ -53,6 +56,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
def _load(self): def _load(self):
return _handler return _handler
def _save(im, fp, filename): def _save(im, fp, filename):
if _handler is None or not hasattr("_handler", "save"): if _handler is None or not hasattr("_handler", "save"):
raise IOError("GRIB save handler not installed") raise IOError("GRIB save handler not installed")

View File

@ -13,6 +13,7 @@ from PIL import Image, ImageFile
_handler = None _handler = None
## ##
# Install application-specific HDF5 image handler. # Install application-specific HDF5 image handler.
# #
@ -22,12 +23,14 @@ def register_handler(handler):
global _handler global _handler
_handler = handler _handler = handler
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image adapter # Image adapter
def _accept(prefix): def _accept(prefix):
return prefix[:8] == b"\x89HDF\r\n\x1a\n" return prefix[:8] == b"\x89HDF\r\n\x1a\n"
class HDF5StubImageFile(ImageFile.StubImageFile): class HDF5StubImageFile(ImageFile.StubImageFile):
format = "HDF5" format = "HDF5"

View File

@ -16,7 +16,8 @@
# #
from PIL import Image, ImageFile, PngImagePlugin, _binary from PIL import Image, ImageFile, PngImagePlugin, _binary
import struct, io import io
import struct
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
if enable_jpeg2k: if enable_jpeg2k:
@ -26,9 +27,11 @@ i8 = _binary.i8
HEADERSIZE = 8 HEADERSIZE = 8
def nextheader(fobj): def nextheader(fobj):
return struct.unpack('>4sI', fobj.read(HEADERSIZE)) return struct.unpack('>4sI', fobj.read(HEADERSIZE))
def read_32t(fobj, start_length, size): def read_32t(fobj, start_length, size):
# The 128x128 icon seems to have an extra header for some reason. # The 128x128 icon seems to have an extra header for some reason.
(start, length) = start_length (start, length) = start_length
@ -38,6 +41,7 @@ def read_32t(fobj, start_length, size):
raise SyntaxError('Unknown signature, expecting 0x00000000') raise SyntaxError('Unknown signature, expecting 0x00000000')
return read_32(fobj, (start + 4, length - 4), size) return read_32(fobj, (start + 4, length - 4), size)
def read_32(fobj, start_length, size): def read_32(fobj, start_length, size):
""" """
Read a 32bit RGB icon resource. Seems to be either uncompressed or Read a 32bit RGB icon resource. Seems to be either uncompressed or
@ -83,6 +87,7 @@ def read_32(fobj, start_length, size):
im.im.putband(band.im, band_ix) im.im.putband(band.im, band_ix)
return {"RGB": im} return {"RGB": im}
def read_mk(fobj, start_length, size): def read_mk(fobj, start_length, size):
# Alpha masks seem to be uncompressed # Alpha masks seem to be uncompressed
(start, length) = start_length (start, length) = start_length
@ -94,6 +99,7 @@ def read_mk(fobj, start_length, size):
) )
return {"A": band} return {"A": band}
def read_png_or_jpeg2000(fobj, start_length, size): def read_png_or_jpeg2000(fobj, start_length, size):
(start, length) = start_length (start, length) = start_length
fobj.seek(start) fobj.seek(start)
@ -103,10 +109,11 @@ def read_png_or_jpeg2000(fobj, start_length, size):
im = PngImagePlugin.PngImageFile(fobj) im = PngImagePlugin.PngImageFile(fobj)
return {"RGBA": im} return {"RGBA": im}
elif sig[:4] == b'\xff\x4f\xff\x51' \ elif sig[:4] == b'\xff\x4f\xff\x51' \
or sig[:4] == b'\x0d\x0a\x87\x0a' \ or sig[:4] == b'\x0d\x0a\x87\x0a' \
or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
if not enable_jpeg2k: if not enable_jpeg2k:
raise ValueError('Unsupported icon subimage format (rebuild PIL with JPEG 2000 support to fix this)') raise ValueError('Unsupported icon subimage format (rebuild PIL '
'with JPEG 2000 support to fix this)')
# j2k, jpc or j2c # j2k, jpc or j2c
fobj.seek(start) fobj.seek(start)
jp2kstream = fobj.read(length) jp2kstream = fobj.read(length)
@ -118,6 +125,7 @@ def read_png_or_jpeg2000(fobj, start_length, size):
else: else:
raise ValueError('Unsupported icon subimage format') raise ValueError('Unsupported icon subimage format')
class IcnsFile: class IcnsFile:
SIZES = { SIZES = {
@ -233,6 +241,7 @@ class IcnsFile:
pass pass
return im return im
## ##
# Image plugin for Mac OS icons. # Image plugin for Mac OS icons.
@ -288,7 +297,8 @@ Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
Image.register_extension("ICNS", '.icns') Image.register_extension("ICNS", '.icns')
if __name__ == '__main__': if __name__ == '__main__':
import os, sys import os
import sys
imf = IcnsImageFile(open(sys.argv[1], 'rb')) imf = IcnsImageFile(open(sys.argv[1], 'rb'))
for size in imf.info['sizes']: for size in imf.info['sizes']:
imf.size = size imf.size = size

View File

@ -13,7 +13,8 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis <casadebender@gmail.com>. # This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
# <casadebender@gmail.com>.
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin # https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
# #
# Icon format references: # Icon format references:
@ -35,6 +36,7 @@ i32 = _binary.i32le
_MAGIC = b"\0\0\1\0" _MAGIC = b"\0\0\1\0"
def _accept(prefix): def _accept(prefix):
return prefix[:4] == _MAGIC return prefix[:4] == _MAGIC
@ -63,7 +65,7 @@ class IcoFile:
icon_header = { icon_header = {
'width': i8(s[0]), 'width': i8(s[0]),
'height': i8(s[1]), 'height': i8(s[1]),
'nb_color': i8(s[2]), # Number of colors in image (0 if >=8bpp) 'nb_color': i8(s[2]), # No. of colors in image (0 if >=8bpp)
'reserved': i8(s[3]), 'reserved': i8(s[3]),
'planes': i16(s[4:]), 'planes': i16(s[4:]),
'bpp': i16(s[6:]), 'bpp': i16(s[6:]),
@ -78,10 +80,14 @@ class IcoFile:
# See Wikipedia notes about color depth. # See Wikipedia notes about color depth.
# We need this just to differ images with equal sizes # We need this just to differ images with equal sizes
icon_header['color_depth'] = (icon_header['bpp'] or (icon_header['nb_color'] != 0 and ceil(log(icon_header['nb_color'],2))) or 256) icon_header['color_depth'] = (icon_header['bpp'] or
(icon_header['nb_color'] != 0 and
ceil(log(icon_header['nb_color'],
2))) or 256)
icon_header['dim'] = (icon_header['width'], icon_header['height']) icon_header['dim'] = (icon_header['width'], icon_header['height'])
icon_header['square'] = icon_header['width'] * icon_header['height'] icon_header['square'] = (icon_header['width'] *
icon_header['height'])
self.entry.append(icon_header) self.entry.append(icon_header)
@ -102,7 +108,7 @@ class IcoFile:
Get an image from the icon Get an image from the icon
""" """
for (i, h) in enumerate(self.entry): for (i, h) in enumerate(self.entry):
if size == h['dim'] and (bpp == False or bpp == h['color_depth']): if size == h['dim'] and (bpp is False or bpp == h['color_depth']):
return self.frame(i) return self.frame(i)
return self.frame(0) return self.frame(0)
@ -127,7 +133,7 @@ class IcoFile:
# change tile dimension to only encompass XOR image # change tile dimension to only encompass XOR image
im.size = (im.size[0], int(im.size[1] / 2)) im.size = (im.size[0], int(im.size[1] / 2))
d, e, o, a = im.tile[0] d, e, o, a = im.tile[0]
im.tile[0] = d, (0,0) + im.size, o, a im.tile[0] = d, (0, 0) + im.size, o, a
# figure out where AND mask image starts # figure out where AND mask image starts
mode = a[0] mode = a[0]
@ -139,8 +145,9 @@ class IcoFile:
if 32 == bpp: if 32 == bpp:
# 32-bit color depth icon image allows semitransparent areas # 32-bit color depth icon image allows semitransparent areas
# PIL's DIB format ignores transparency bits, recover them # PIL's DIB format ignores transparency bits, recover them.
# The DIB is packed in BGRX byte order where X is the alpha channel # The DIB is packed in BGRX byte order where X is the alpha
# channel.
# Back up to start of bmp data # Back up to start of bmp data
self.buf.seek(o) self.buf.seek(o)
@ -162,9 +169,11 @@ class IcoFile:
# bitmap row data is aligned to word boundaries # bitmap row data is aligned to word boundaries
w += 32 - (im.size[0] % 32) w += 32 - (im.size[0] % 32)
# the total mask data is padded row size * height / bits per char # the total mask data is
# padded row size * height / bits per char
and_mask_offset = o + int(im.size[0] * im.size[1] * (bpp / 8.0)) and_mask_offset = o + int(im.size[0] * im.size[1] *
(bpp / 8.0))
total_bytes = int((w * im.size[1]) / 8) total_bytes = int((w * im.size[1]) / 8)
self.buf.seek(and_mask_offset) self.buf.seek(and_mask_offset)
@ -187,6 +196,7 @@ class IcoFile:
return im return im
## ##
# Image plugin for Windows Icon files. # Image plugin for Windows Icon files.
@ -194,15 +204,16 @@ class IcoImageFile(ImageFile.ImageFile):
""" """
PIL read-only image support for Microsoft Windows .ico files. PIL read-only image support for Microsoft Windows .ico files.
By default the largest resolution image in the file will be loaded. This can By default the largest resolution image in the file will be loaded. This
be changed by altering the 'size' attribute before calling 'load'. can be changed by altering the 'size' attribute before calling 'load'.
The info dictionary has a key 'sizes' that is a list of the sizes available The info dictionary has a key 'sizes' that is a list of the sizes available
in the icon file. in the icon file.
Handles classic, XP and Vista icon formats. Handles classic, XP and Vista icon formats.
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis <casadebender@gmail.com>. This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
<casadebender@gmail.com>.
https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
""" """
format = "ICO" format = "ICO"
@ -222,9 +233,9 @@ class IcoImageFile(ImageFile.ImageFile):
self.mode = im.mode self.mode = im.mode
self.size = im.size self.size = im.size
def load_seek(self): def load_seek(self):
# Flage the ImageFile.Parser so that it just does all the decode at the end. # Flage the ImageFile.Parser so that it
# just does all the decode at the end.
pass pass
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -30,7 +30,7 @@ __version__ = "0.7"
import re import re
from PIL import Image, ImageFile, ImagePalette from PIL import Image, ImageFile, ImagePalette
from PIL._binary import i8, o8 from PIL._binary import i8
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -46,8 +46,8 @@ SCALE = "Scale (x,y)"
SIZE = "Image size (x*y)" SIZE = "Image size (x*y)"
MODE = "Image type" MODE = "Image type"
TAGS = { COMMENT:0, DATE:0, EQUIPMENT:0, FRAMES:0, LUT:0, NAME:0, TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0,
SCALE:0, SIZE:0, MODE:0 } SCALE: 0, SIZE: 0, MODE: 0}
OPEN = { OPEN = {
# ifunc93/p3cfunc formats # ifunc93/p3cfunc formats
@ -94,12 +94,14 @@ for i in range(2, 33):
split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$") split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
def number(s): def number(s):
try: try:
return int(s) return int(s)
except ValueError: except ValueError:
return float(s) return float(s)
## ##
# Image plugin for the IFUNC IM file format. # Image plugin for the IFUNC IM file format.
@ -113,7 +115,7 @@ class ImImageFile(ImageFile.ImageFile):
# Quick rejection: if there's not an LF among the first # Quick rejection: if there's not an LF among the first
# 100 bytes, this is (probably) not a text header. # 100 bytes, this is (probably) not a text header.
if not b"\n" in self.fp.read(100): if b"\n" not in self.fp.read(100):
raise SyntaxError("not an IM file") raise SyntaxError("not an IM file")
self.fp.seek(0) self.fp.seek(0)
@ -155,10 +157,10 @@ class ImImageFile(ImageFile.ImageFile):
if m: if m:
k, v = m.group(1,2) k, v = m.group(1, 2)
# Don't know if this is the correct encoding, but a decent guess # Don't know if this is the correct encoding,
# (I guess) # but a decent guess (I guess)
k = k.decode('latin-1', 'replace') k = k.decode('latin-1', 'replace')
v = v.decode('latin-1', 'replace') v = v.decode('latin-1', 'replace')
@ -186,7 +188,8 @@ class ImImageFile(ImageFile.ImageFile):
else: else:
raise SyntaxError("Syntax error in IM header: " + s.decode('ascii', 'replace')) raise SyntaxError("Syntax error in IM header: " +
s.decode('ascii', 'replace'))
if not n: if not n:
raise SyntaxError("Not an IM file") raise SyntaxError("Not an IM file")
@ -204,8 +207,8 @@ class ImImageFile(ImageFile.ImageFile):
if LUT in self.info: if LUT in self.info:
# convert lookup table to palette or lut attribute # convert lookup table to palette or lut attribute
palette = self.fp.read(768) palette = self.fp.read(768)
greyscale = 1 # greyscale palette greyscale = 1 # greyscale palette
linear = 1 # linear greyscale palette linear = 1 # linear greyscale palette
for i in range(256): for i in range(256):
if palette[i] == palette[i+256] == palette[i+512]: if palette[i] == palette[i+256] == palette[i+512]:
if i8(palette[i]) != i: if i8(palette[i]) != i:
@ -230,7 +233,7 @@ class ImImageFile(ImageFile.ImageFile):
self.__offset = offs = self.fp.tell() self.__offset = offs = self.fp.tell()
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
if self.rawmode[:2] == "F;": if self.rawmode[:2] == "F;":
@ -239,7 +242,7 @@ class ImImageFile(ImageFile.ImageFile):
# use bit decoder (if necessary) # use bit decoder (if necessary)
bits = int(self.rawmode[2:]) bits = int(self.rawmode[2:])
if bits not in [8, 16, 32]: if bits not in [8, 16, 32]:
self.tile = [("bit", (0,0)+self.size, offs, self.tile = [("bit", (0, 0)+self.size, offs,
(bits, 8, 3, 0, -1))] (bits, 8, 3, 0, -1))]
return return
except ValueError: except ValueError:
@ -249,12 +252,13 @@ class ImImageFile(ImageFile.ImageFile):
# Old LabEye/3PC files. Would be very surprised if anyone # Old LabEye/3PC files. Would be very surprised if anyone
# ever stumbled upon such a file ;-) # ever stumbled upon such a file ;-)
size = self.size[0] * self.size[1] size = self.size[0] * self.size[1]
self.tile = [("raw", (0,0)+self.size, offs, ("G", 0, -1)), self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)),
("raw", (0,0)+self.size, offs+size, ("R", 0, -1)), ("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)),
("raw", (0,0)+self.size, offs+2*size, ("B", 0, -1))] ("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))]
else: else:
# LabEye/IFUNC files # LabEye/IFUNC files
self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))] self.tile = [("raw", (0, 0)+self.size, offs,
(self.rawmode, 0, -1))]
def seek(self, frame): def seek(self, frame):
@ -276,7 +280,7 @@ class ImImageFile(ImageFile.ImageFile):
self.fp = self.__fp self.fp = self.__fp
self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))] self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]
def tell(self): def tell(self):
@ -305,6 +309,7 @@ SAVE = {
"YCbCr": ("YCC", "YCbCr;L") "YCbCr": ("YCC", "YCbCr;L")
} }
def _save(im, fp, filename, check=0): def _save(im, fp, filename, check=0):
try: try:
@ -329,8 +334,8 @@ def _save(im, fp, filename, check=0):
fp.write(b"Lut: 1\r\n") fp.write(b"Lut: 1\r\n")
fp.write(b"\000" * (511-fp.tell()) + b"\032") fp.write(b"\000" * (511-fp.tell()) + b"\032")
if im.mode == "P": if im.mode == "P":
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, -1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))])
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -34,6 +34,7 @@ import warnings
class DecompressionBombWarning(RuntimeWarning): class DecompressionBombWarning(RuntimeWarning):
pass pass
class _imaging_not_installed: class _imaging_not_installed:
# module placeholder # module placeholder
def __getattr__(self, id): def __getattr__(self, id):
@ -851,8 +852,9 @@ class Image:
t = self.info['transparency'] t = self.info['transparency']
if isinstance(t, bytes): if isinstance(t, bytes):
# Dragons. This can't be represented by a single color # Dragons. This can't be represented by a single color
warnings.warn('Palette images with Transparency expressed ' + warnings.warn('Palette images with Transparency ' +
' in bytes should be converted to RGBA images') ' expressed in bytes should be converted ' +
'to RGBA images')
delete_trns = True delete_trns = True
else: else:
# get the new transparency color. # get the new transparency color.
@ -868,7 +870,7 @@ class Image:
# can't just retrieve the palette number, got to do it # can't just retrieve the palette number, got to do it
# after quantization. # after quantization.
trns_im = trns_im.convert('RGB') trns_im = trns_im.convert('RGB')
trns = trns_im.getpixel((0,0)) trns = trns_im.getpixel((0, 0))
elif self.mode == 'P' and mode == 'RGBA': elif self.mode == 'P' and mode == 'RGBA':
t = self.info['transparency'] t = self.info['transparency']

View File

@ -1,19 +1,19 @@
## The Python Imaging Library. # The Python Imaging Library.
## $Id$ # $Id$
## Optional color managment support, based on Kevin Cazabon's PyCMS # Optional color managment support, based on Kevin Cazabon's PyCMS
## library. # library.
## History: # History:
## 2009-03-08 fl Added to PIL. # 2009-03-08 fl Added to PIL.
## Copyright (C) 2002-2003 Kevin Cazabon # Copyright (C) 2002-2003 Kevin Cazabon
## Copyright (c) 2009 by Fredrik Lundh # Copyright (c) 2009 by Fredrik Lundh
## Copyright (c) 2013 by Eric Soroos # Copyright (c) 2013 by Eric Soroos
## See the README file for information on usage and redistribution. See # See the README file for information on usage and redistribution. See
## below for the original description. # below for the original description.
from __future__ import print_function from __future__ import print_function
@ -184,6 +184,7 @@ class ImageCmsProfile:
return core.profile_tobytes(self.profile) return core.profile_tobytes(self.profile)
class ImageCmsTransform(Image.ImagePointHandler): class ImageCmsTransform(Image.ImagePointHandler):
# Transform. This can be used with the procedural API, or with the # Transform. This can be used with the procedural API, or with the
@ -191,7 +192,6 @@ class ImageCmsTransform(Image.ImagePointHandler):
# #
# Will return the output profile in the output.info['icc_profile']. # Will return the output profile in the output.info['icc_profile'].
def __init__(self, input, output, input_mode, output_mode, def __init__(self, input, output, input_mode, output_mode,
intent=INTENT_PERCEPTUAL, proof=None, intent=INTENT_PERCEPTUAL, proof=None,
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0): proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):

View File

@ -20,6 +20,7 @@
from PIL import Image from PIL import Image
import re import re
def getrgb(color): def getrgb(color):
""" """
Convert a color string to an RGB tuple. If the string cannot be parsed, Convert a color string to an RGB tuple. If the string cannot be parsed,
@ -86,7 +87,8 @@ def getrgb(color):
int(rgb[1] * 255 + 0.5), int(rgb[1] * 255 + 0.5),
int(rgb[2] * 255 + 0.5) int(rgb[2] * 255 + 0.5)
) )
m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$",
color)
if m: if m:
return ( return (
int(m.group(1)), int(m.group(1)),
@ -96,6 +98,7 @@ def getrgb(color):
) )
raise ValueError("unknown color specifier: %r" % color) raise ValueError("unknown color specifier: %r" % color)
def getcolor(color, mode): def getcolor(color, mode):
""" """
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a

View File

@ -40,6 +40,7 @@ try:
except ImportError: except ImportError:
warnings = None warnings = None
## ##
# A simple 2D drawing interface for PIL images. # A simple 2D drawing interface for PIL images.
# <p> # <p>
@ -61,7 +62,7 @@ class ImageDraw:
def __init__(self, im, mode=None): def __init__(self, im, mode=None):
im.load() im.load()
if im.readonly: if im.readonly:
im._copy() # make it writable im._copy() # make it writeable
blend = 0 blend = 0
if mode is None: if mode is None:
mode = im.mode mode = im.mode
@ -85,7 +86,7 @@ class ImageDraw:
# FIXME: fix Fill2 to properly support matte for I+F images # FIXME: fix Fill2 to properly support matte for I+F images
self.fontmode = "1" self.fontmode = "1"
else: else:
self.fontmode = "L" # aliasing is okay for other modes self.fontmode = "L" # aliasing is okay for other modes
self.fill = 0 self.fill = 0
self.font = None self.font = None
@ -280,6 +281,7 @@ class ImageDraw:
font = self.getfont() font = self.getfont()
return font.getsize(text) return font.getsize(text)
## ##
# A simple 2D drawing interface for PIL images. # A simple 2D drawing interface for PIL images.
# #
@ -302,6 +304,7 @@ try:
except: except:
Outline = None Outline = None
## ##
# (Experimental) A more advanced 2D drawing interface for PIL images, # (Experimental) A more advanced 2D drawing interface for PIL images,
# based on the WCK interface. # based on the WCK interface.
@ -325,6 +328,7 @@ def getdraw(im=None, hints=None):
im = handler.Draw(im) im = handler.Draw(im)
return im, handler return im, handler
## ##
# (experimental) Fills a bounded region with a given color. # (experimental) Fills a bounded region with a given color.
# #
@ -344,10 +348,10 @@ def floodfill(image, xy, value, border=None):
try: try:
background = pixel[x, y] background = pixel[x, y]
if background == value: if background == value:
return # seed point already has fill color return # seed point already has fill color
pixel[x, y] = value pixel[x, y] = value
except IndexError: except IndexError:
return # seed point outside image return # seed point outside image
edge = [(x, y)] edge = [(x, y)]
if border is None: if border is None:
while edge: while edge:

View File

@ -18,21 +18,25 @@
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
class Pen: class Pen:
def __init__(self, color, width=1, opacity=255): def __init__(self, color, width=1, opacity=255):
self.color = ImageColor.getrgb(color) self.color = ImageColor.getrgb(color)
self.width = width self.width = width
class Brush: class Brush:
def __init__(self, color, opacity=255): def __init__(self, color, opacity=255):
self.color = ImageColor.getrgb(color) self.color = ImageColor.getrgb(color)
class Font: class Font:
def __init__(self, color, file, size=12): def __init__(self, color, file, size=12):
# FIXME: add support for bitmap fonts # FIXME: add support for bitmap fonts
self.color = ImageColor.getrgb(color) self.color = ImageColor.getrgb(color)
self.font = ImageFont.truetype(file, size) self.font = ImageFont.truetype(file, size)
class Draw: class Draw:
def __init__(self, image, size=None, color=None): def __init__(self, image, size=None, color=None):
@ -47,7 +51,8 @@ class Draw:
def render(self, op, xy, pen, brush=None): def render(self, op, xy, pen, brush=None):
# handle color arguments # handle color arguments
outline = fill = None; width = 1 outline = fill = None
width = 1
if isinstance(pen, Pen): if isinstance(pen, Pen):
outline = pen.color outline = pen.color
width = pen.width width = pen.width

View File

@ -29,8 +29,10 @@
from PIL import Image from PIL import Image
from PIL._util import isPath from PIL._util import isPath
import traceback, os, sys
import io import io
import os
import sys
import traceback
MAXBLOCK = 65536 MAXBLOCK = 65536
@ -46,6 +48,7 @@ ERRORS = {
-9: "out of memory error" -9: "out of memory error"
} }
def raise_ioerror(error): def raise_ioerror(error):
try: try:
message = Image.core.getcodecstatus(error) message = Image.core.getcodecstatus(error)
@ -55,6 +58,7 @@ def raise_ioerror(error):
message = "decoder error %d" % error message = "decoder error %d" % error
raise IOError(message + " when reading image file") raise IOError(message + " when reading image file")
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Helpers # Helpers
@ -63,6 +67,7 @@ def _tilesort(t):
# sort on offset # sort on offset
return t[2] return t[2]
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# ImageFile base class # ImageFile base class
@ -74,7 +79,7 @@ class ImageFile(Image.Image):
Image.Image.__init__(self) Image.Image.__init__(self)
self.tile = None self.tile = None
self.readonly = 1 # until we know better self.readonly = 1 # until we know better
self.decoderconfig = () self.decoderconfig = ()
self.decodermaxblock = MAXBLOCK self.decodermaxblock = MAXBLOCK
@ -90,19 +95,19 @@ class ImageFile(Image.Image):
try: try:
self._open() self._open()
except IndexError as v: # end of data except IndexError as v: # end of data
if Image.DEBUG > 1: if Image.DEBUG > 1:
traceback.print_exc() traceback.print_exc()
raise SyntaxError(v) raise SyntaxError(v)
except TypeError as v: # end of data (ord) except TypeError as v: # end of data (ord)
if Image.DEBUG > 1: if Image.DEBUG > 1:
traceback.print_exc() traceback.print_exc()
raise SyntaxError(v) raise SyntaxError(v)
except KeyError as v: # unsupported mode except KeyError as v: # unsupported mode
if Image.DEBUG > 1: if Image.DEBUG > 1:
traceback.print_exc() traceback.print_exc()
raise SyntaxError(v) raise SyntaxError(v)
except EOFError as v: # got header but not the first frame except EOFError as v: # got header but not the first frame
if Image.DEBUG > 1: if Image.DEBUG > 1:
traceback.print_exc() traceback.print_exc()
raise SyntaxError(v) raise SyntaxError(v)
@ -203,23 +208,25 @@ class ImageFile(Image.Image):
while True: while True:
try: try:
s = read(self.decodermaxblock) s = read(self.decodermaxblock)
except IndexError as ie: # truncated png/gif except IndexError as ie: # truncated png/gif
if LOAD_TRUNCATED_IMAGES: if LOAD_TRUNCATED_IMAGES:
break break
else: else:
raise IndexError(ie) raise IndexError(ie)
if not s and not d.handles_eof: # truncated jpeg if not s and not d.handles_eof: # truncated jpeg
self.tile = [] self.tile = []
# JpegDecode needs to clean things up here either way # JpegDecode needs to clean things up here either way
# If we don't destroy the decompressor, we have a memory leak. # If we don't destroy the decompressor,
# we have a memory leak.
d.cleanup() d.cleanup()
if LOAD_TRUNCATED_IMAGES: if LOAD_TRUNCATED_IMAGES:
break break
else: else:
raise IOError("image file is truncated (%d bytes not processed)" % len(b)) raise IOError("image file is truncated "
"(%d bytes not processed)" % len(b))
b = b + s b = b + s
n, e = d.decode(b) n, e = d.decode(b)
@ -233,7 +240,7 @@ class ImageFile(Image.Image):
self.tile = [] self.tile = []
self.readonly = readonly self.readonly = readonly
self.fp = None # might be shared self.fp = None # might be shared
if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0: if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0:
# still raised if decoder fails to return anything # still raised if decoder fails to return anything
@ -380,10 +387,10 @@ class Parser:
fp = io.BytesIO(self.data) fp = io.BytesIO(self.data)
im = Image.open(fp) im = Image.open(fp)
finally: finally:
fp.close() # explicitly close the virtual file fp.close() # explicitly close the virtual file
except IOError: except IOError:
# traceback.print_exc() # traceback.print_exc()
pass # not enough data pass # not enough data
else: else:
flag = hasattr(im, "load_seek") or hasattr(im, "load_read") flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
if flag or len(im.tile) != 1: if flag or len(im.tile) != 1:
@ -433,9 +440,10 @@ class Parser:
self.image = Image.open(fp) self.image = Image.open(fp)
finally: finally:
self.image.load() self.image.load()
fp.close() # explicitly close the virtual file fp.close() # explicitly close the virtual file
return self.image return self.image
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _save(im, fp, tile, bufsize=0): def _save(im, fp, tile, bufsize=0):
@ -452,10 +460,10 @@ def _save(im, fp, tile, bufsize=0):
im.encoderconfig = () im.encoderconfig = ()
tile.sort(key=_tilesort) tile.sort(key=_tilesort)
# FIXME: make MAXBLOCK a configuration parameter # FIXME: make MAXBLOCK a configuration parameter
# It would be great if we could have the encoder specifiy what it needs # It would be great if we could have the encoder specify what it needs
# But, it would need at least the image size in most cases. RawEncode is # But, it would need at least the image size in most cases. RawEncode is
# a tricky case. # a tricky case.
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
try: try:
fh = fp.fileno() fh = fp.fileno()
fp.flush() fp.flush()
@ -487,7 +495,8 @@ def _save(im, fp, tile, bufsize=0):
e.cleanup() e.cleanup()
try: try:
fp.flush() fp.flush()
except: pass except:
pass
def _safe_read(fp, size): def _safe_read(fp, size):

View File

@ -43,7 +43,7 @@ class Kernel(Filter):
def __init__(self, size, kernel, scale=None, offset=0): def __init__(self, size, kernel, scale=None, offset=0):
if scale is None: if scale is None:
# default scale is sum of kernel # default scale is sum of kernel
scale = reduce(lambda a,b: a+b, kernel) scale = reduce(lambda a, b: a+b, kernel)
if size[0] * size[1] != len(kernel): if size[0] * size[1] != len(kernel):
raise ValueError("not enough coefficients in kernel") raise ValueError("not enough coefficients in kernel")
self.filterargs = size, scale, offset, kernel self.filterargs = size, scale, offset, kernel
@ -162,7 +162,8 @@ class UnsharpMask(Filter):
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
the parameters. the parameters.
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking .. _digital unsharp masking:
https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
""" """
name = "UnsharpMask" name = "UnsharpMask"

View File

@ -16,6 +16,7 @@
# mode descriptor cache # mode descriptor cache
_modes = {} _modes = {}
## ##
# Wrapper for mode strings. # Wrapper for mode strings.
@ -30,6 +31,7 @@ class ModeDescriptor:
def __str__(self): def __str__(self):
return self.mode return self.mode
## ##
# Gets a mode descriptor for the given mode. # Gets a mode descriptor for the given mode.

View File

@ -22,6 +22,7 @@ from PIL._util import isStringType
import operator import operator
from functools import reduce from functools import reduce
# #
# helpers # helpers
@ -35,12 +36,14 @@ def _border(border):
left = top = right = bottom = border left = top = right = bottom = border
return left, top, right, bottom return left, top, right, bottom
def _color(color, mode): def _color(color, mode):
if isStringType(color): if isStringType(color):
from PIL import ImageColor from PIL import ImageColor
color = ImageColor.getcolor(color, mode) color = ImageColor.getcolor(color, mode)
return color return color
def _lut(image, lut): def _lut(image, lut):
if image.mode == "P": if image.mode == "P":
# FIXME: apply to lookup table, not image data # FIXME: apply to lookup table, not image data
@ -147,7 +150,9 @@ def colorize(image, black, white):
assert image.mode == "L" assert image.mode == "L"
black = _color(black, "RGB") black = _color(black, "RGB")
white = _color(white, "RGB") white = _color(white, "RGB")
red = []; green = []; blue = [] red = []
green = []
blue = []
for i in range(256): for i in range(256):
red.append(black[0]+i*(white[0]-black[0])//255) red.append(black[0]+i*(white[0]-black[0])//255)
green.append(black[1]+i*(white[1]-black[1])//255) green.append(black[1]+i*(white[1]-black[1])//255)
@ -273,7 +278,7 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
centering = [centering[0], centering[1]] centering = [centering[0], centering[1]]
if centering[0] > 1.0 or centering[0] < 0.0: if centering[0] > 1.0 or centering[0] < 0.0:
centering [0] = 0.50 centering[0] = 0.50
if centering[1] > 1.0 or centering[1] < 0.0: if centering[1] > 1.0 or centering[1] < 0.0:
centering[1] = 0.50 centering[1] = 0.50
@ -404,6 +409,7 @@ def solarize(image, threshold=128):
lut.append(255-i) lut.append(255-i)
return _lut(image, lut) return _lut(image, lut)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PIL USM components, from Kevin Cazabon. # PIL USM components, from Kevin Cazabon.
@ -419,6 +425,7 @@ def gaussian_blur(im, radius=None):
gblur = gaussian_blur gblur = gaussian_blur
def unsharp_mask(im, radius=None, percent=None, threshold=None): def unsharp_mask(im, radius=None, percent=None, threshold=None):
""" PIL_usm.usm(im, [radius, percent, threshold])""" """ PIL_usm.usm(im, [radius, percent, threshold])"""

View File

@ -24,6 +24,7 @@ try:
except: except:
from PyQt4.QtGui import QImage, qRgba from PyQt4.QtGui import QImage, qRgba
## ##
# (Internal) Turns an RGB color into a Qt compatible color integer. # (Internal) Turns an RGB color into a Qt compatible color integer.
@ -32,6 +33,7 @@ def rgb(r, g, b, a=255):
# into a negative integer with the same bitpattern. # into a negative integer with the same bitpattern.
return (qRgba(r, g, b, a) & 0xffffffff) return (qRgba(r, g, b, a) & 0xffffffff)
## ##
# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage # An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage
# class. # class.

View File

@ -15,6 +15,7 @@
## ##
class Iterator: class Iterator:
""" """
This class implements an iterator object that can be used to loop This class implements an iterator object that can be used to loop
@ -38,4 +39,4 @@ class Iterator:
self.im.seek(ix) self.im.seek(ix)
return self.im return self.im
except EOFError: except EOFError:
raise IndexError # end of sequence raise IndexError # end of sequence

View File

@ -15,7 +15,8 @@
from __future__ import print_function from __future__ import print_function
from PIL import Image from PIL import Image
import os, sys import os
import sys
if sys.version_info >= (3, 3): if sys.version_info >= (3, 3):
from shlex import quote from shlex import quote
@ -24,17 +25,19 @@ else:
_viewers = [] _viewers = []
def register(viewer, order=1): def register(viewer, order=1):
try: try:
if issubclass(viewer, Viewer): if issubclass(viewer, Viewer):
viewer = viewer() viewer = viewer()
except TypeError: except TypeError:
pass # raised if viewer wasn't a class pass # raised if viewer wasn't a class
if order > 0: if order > 0:
_viewers.append(viewer) _viewers.append(viewer)
elif order < 0: elif order < 0:
_viewers.insert(0, viewer) _viewers.insert(0, viewer)
## ##
# Displays a given image. # Displays a given image.
# #
@ -49,6 +52,7 @@ def show(image, title=None, **options):
return 1 return 1
return 0 return 0
## ##
# Base class for viewers. # Base class for viewers.
@ -102,6 +106,7 @@ if sys.platform == "win32":
class WindowsViewer(Viewer): class WindowsViewer(Viewer):
format = "BMP" format = "BMP"
def get_command(self, file, **options): def get_command(self, file, **options):
return ('start "Pillow" /WAIT "%s" ' return ('start "Pillow" /WAIT "%s" '
'&& ping -n 2 127.0.0.1 >NUL ' '&& ping -n 2 127.0.0.1 >NUL '
@ -113,11 +118,13 @@ elif sys.platform == "darwin":
class MacViewer(Viewer): class MacViewer(Viewer):
format = "BMP" format = "BMP"
def get_command(self, file, **options): def get_command(self, file, **options):
# on darwin open returns immediately resulting in the temp # on darwin open returns immediately resulting in the temp
# file removal while app is opening # file removal while app is opening
command = "open -a /Applications/Preview.app" command = "open -a /Applications/Preview.app"
command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file), quote(file)) command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file),
quote(file))
return command return command
register(MacViewer) register(MacViewer)
@ -140,7 +147,8 @@ else:
class UnixViewer(Viewer): class UnixViewer(Viewer):
def show_file(self, file, **options): def show_file(self, file, **options):
command, executable = self.get_command_ex(file, **options) command, executable = self.get_command_ex(file, **options)
command = "(%s %s; rm -f %s)&" % (command, quote(file), quote(file)) command = "(%s %s; rm -f %s)&" % (command, quote(file),
quote(file))
os.system(command) os.system(command)
return 1 return 1

View File

@ -21,20 +21,21 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import operator, math import math
import operator
from functools import reduce from functools import reduce
class Stat: class Stat:
def __init__(self, image_or_list, mask = None): def __init__(self, image_or_list, mask=None):
try: try:
if mask: if mask:
self.h = image_or_list.histogram(mask) self.h = image_or_list.histogram(mask)
else: else:
self.h = image_or_list.histogram() self.h = image_or_list.histogram()
except AttributeError: except AttributeError:
self.h = image_or_list # assume it to be a histogram list self.h = image_or_list # assume it to be a histogram list
if not isinstance(self.h, list): if not isinstance(self.h, list):
raise TypeError("first argument must be image or list") raise TypeError("first argument must be image or list")
self.bands = list(range(len(self.h) // 256)) self.bands = list(range(len(self.h) // 256))
@ -58,7 +59,7 @@ class Stat:
if histogram[i]: if histogram[i]:
n = min(n, i) n = min(n, i)
x = max(x, i) x = max(x, i)
return n, x # returns (255, 0) if there's no data in the histogram return n, x # returns (255, 0) if there's no data in the histogram
v = [] v = []
for i in range(0, len(self.h), 256): for i in range(0, len(self.h), 256):
@ -126,7 +127,6 @@ class Stat:
v.append(math.sqrt(self.sum2[i] / self.count[i])) v.append(math.sqrt(self.sum2[i] / self.count[i]))
return v return v
def _getvar(self): def _getvar(self):
"Get variance for each layer" "Get variance for each layer"
@ -144,4 +144,4 @@ class Stat:
v.append(math.sqrt(self.var[i])) v.append(math.sqrt(self.var[i]))
return v return v
Global = Stat # compatibility Global = Stat # compatibility

View File

@ -40,17 +40,19 @@ from PIL import Image
_pilbitmap_ok = None _pilbitmap_ok = None
def _pilbitmap_check(): def _pilbitmap_check():
global _pilbitmap_ok global _pilbitmap_ok
if _pilbitmap_ok is None: if _pilbitmap_ok is None:
try: try:
im = Image.new("1", (1,1)) im = Image.new("1", (1, 1))
tkinter.BitmapImage(data="PIL:%d" % im.im.id) tkinter.BitmapImage(data="PIL:%d" % im.im.id)
_pilbitmap_ok = 1 _pilbitmap_ok = 1
except tkinter.TclError: except tkinter.TclError:
_pilbitmap_ok = 0 _pilbitmap_ok = 0
return _pilbitmap_ok return _pilbitmap_ok
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PhotoImage # PhotoImage
@ -95,7 +97,7 @@ class PhotoImage:
try: try:
mode = image.palette.mode mode = image.palette.mode
except AttributeError: except AttributeError:
mode = "RGB" # default mode = "RGB" # default
size = image.size size = image.size
kw["width"], kw["height"] = size kw["width"], kw["height"] = size
else: else:
@ -118,8 +120,7 @@ class PhotoImage:
try: try:
self.__photo.tk.call("image", "delete", name) self.__photo.tk.call("image", "delete", name)
except: except:
pass # ignore internal errors pass # ignore internal errors
def __str__(self): def __str__(self):
""" """
@ -131,7 +132,6 @@ class PhotoImage:
""" """
return str(self.__photo) return str(self.__photo)
def width(self): def width(self):
""" """
Get the width of the image. Get the width of the image.
@ -140,7 +140,6 @@ class PhotoImage:
""" """
return self.__size[0] return self.__size[0]
def height(self): def height(self):
""" """
Get the height of the image. Get the height of the image.
@ -149,7 +148,6 @@ class PhotoImage:
""" """
return self.__size[1] return self.__size[1]
def paste(self, im, box=None): def paste(self, im, box=None):
""" """
Paste a PIL image into the photo image. Note that this can Paste a PIL image into the photo image. Note that this can
@ -170,13 +168,13 @@ class PhotoImage:
block = image block = image
else: else:
block = image.new_block(self.__mode, im.size) block = image.new_block(self.__mode, im.size)
image.convert2(block, image) # convert directly between buffers image.convert2(block, image) # convert directly between buffers
tk = self.__photo.tk tk = self.__photo.tk
try: try:
tk.call("PyImagingPhoto", self.__photo, block.id) tk.call("PyImagingPhoto", self.__photo, block.id)
except tkinter.TclError as v: except tkinter.TclError:
# activate Tkinter hook # activate Tkinter hook
try: try:
from PIL import _imagingtk from PIL import _imagingtk
@ -186,7 +184,7 @@ class PhotoImage:
_imagingtk.tkinit(id(tk), 0) _imagingtk.tkinit(id(tk), 0)
tk.call("PyImagingPhoto", self.__photo, block.id) tk.call("PyImagingPhoto", self.__photo, block.id)
except (ImportError, AttributeError, tkinter.TclError): except (ImportError, AttributeError, tkinter.TclError):
raise # configuration problem; cannot attach to Tkinter raise # configuration problem; cannot attach to Tkinter
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# BitmapImage # BitmapImage
@ -226,7 +224,7 @@ class BitmapImage:
# fast way (requires the pilbitmap booster patch) # fast way (requires the pilbitmap booster patch)
image.load() image.load()
kw["data"] = "PIL:%d" % image.im.id kw["data"] = "PIL:%d" % image.im.id
self.__im = image # must keep a reference self.__im = image # must keep a reference
else: else:
# slow but safe way # slow but safe way
kw["data"] = image.tobitmap() kw["data"] = image.tobitmap()
@ -238,8 +236,7 @@ class BitmapImage:
try: try:
self.__photo.tk.call("image", "delete", name) self.__photo.tk.call("image", "delete", name)
except: except:
pass # ignore internal errors pass # ignore internal errors
def width(self): def width(self):
""" """
@ -249,7 +246,6 @@ class BitmapImage:
""" """
return self.__size[0] return self.__size[0]
def height(self): def height(self):
""" """
Get the height of the image. Get the height of the image.
@ -258,7 +254,6 @@ class BitmapImage:
""" """
return self.__size[1] return self.__size[1]
def __str__(self): def __str__(self):
""" """
Get the Tkinter bitmap image identifier. This method is automatically Get the Tkinter bitmap image identifier. This method is automatically
@ -274,6 +269,7 @@ def getimage(photo):
"""Copies the contents of a PhotoImage to a PIL image memory.""" """Copies the contents of a PhotoImage to a PIL image memory."""
photo.tk.call("PyImagingPhotoGet", photo) photo.tk.call("PyImagingPhotoGet", photo)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Helper for the Image.show method. # Helper for the Image.show method.
@ -286,7 +282,7 @@ def _show(image, title):
else: else:
self.image = PhotoImage(im, master=master) self.image = PhotoImage(im, master=master)
tkinter.Label.__init__(self, master, image=self.image, tkinter.Label.__init__(self, master, image=self.image,
bg="black", bd=0) bg="black", bd=0)
if not tkinter._default_root: if not tkinter._default_root:
raise IOError("tkinter not initialized") raise IOError("tkinter not initialized")

View File

@ -15,16 +15,20 @@
from PIL import Image from PIL import Image
class Transform(Image.ImageTransformHandler): class Transform(Image.ImageTransformHandler):
def __init__(self, data): def __init__(self, data):
self.data = data self.data = data
def getdata(self): def getdata(self):
return self.method, self.data return self.method, self.data
def transform(self, size, image, **options): def transform(self, size, image, **options):
# can be overridden # can be overridden
method, data = self.getdata() method, data = self.getdata()
return image.transform(size, method, data, **options) return image.transform(size, method, data, **options)
## ##
# Define an affine image transform. # Define an affine image transform.
# <p> # <p>
@ -43,9 +47,11 @@ class Transform(Image.ImageTransformHandler):
# the first two rows from an affine transform matrix. # the first two rows from an affine transform matrix.
# @see Image#Image.transform # @see Image#Image.transform
class AffineTransform(Transform): class AffineTransform(Transform):
method = Image.AFFINE method = Image.AFFINE
## ##
# Define a transform to extract a subregion from an image. # Define a transform to extract a subregion from an image.
# <p> # <p>
@ -68,6 +74,7 @@ class AffineTransform(Transform):
class ExtentTransform(Transform): class ExtentTransform(Transform):
method = Image.EXTENT method = Image.EXTENT
## ##
# Define an quad image transform. # Define an quad image transform.
# <p> # <p>
@ -83,6 +90,7 @@ class ExtentTransform(Transform):
class QuadTransform(Transform): class QuadTransform(Transform):
method = Image.QUAD method = Image.QUAD
## ##
# Define an mesh image transform. A mesh transform consists of one # Define an mesh image transform. A mesh transform consists of one
# or more individual quad transforms. # or more individual quad transforms.

View File

@ -26,6 +26,7 @@ from PIL import Image, ImageFile
field = re.compile(br"([a-z]*) ([^ \r\n]*)") field = re.compile(br"([a-z]*) ([^ \r\n]*)")
## ##
# Image plugin for IM Tools images. # Image plugin for IM Tools images.
@ -39,7 +40,7 @@ class ImtImageFile(ImageFile.ImageFile):
# Quick rejection: if there's not a LF among the first # Quick rejection: if there's not a LF among the first
# 100 bytes, this is (probably) not a text header. # 100 bytes, this is (probably) not a text header.
if not b"\n" in self.fp.read(100): if b"\n" not in self.fp.read(100):
raise SyntaxError("not an IM file") raise SyntaxError("not an IM file")
self.fp.seek(0) self.fp.seek(0)
@ -54,7 +55,7 @@ class ImtImageFile(ImageFile.ImageFile):
if s == b'\x0C': if s == b'\x0C':
# image data begins # image data begins
self.tile = [("raw", (0,0)+self.size, self.tile = [("raw", (0, 0)+self.size,
self.fp.tell(), self.fp.tell(),
(self.mode, 0, 1))] (self.mode, 0, 1))]
@ -68,12 +69,12 @@ class ImtImageFile(ImageFile.ImageFile):
if len(s) == 1 or len(s) > 100: if len(s) == 1 or len(s) > 100:
break break
if s[0] == b"*": if s[0] == b"*":
continue # comment continue # comment
m = field.match(s) m = field.match(s)
if not m: if not m:
break break
k, v = m.group(1,2) k, v = m.group(1, 2)
if k == "width": if k == "width":
xsize = int(v) xsize = int(v)
self.size = xsize, ysize self.size = xsize, ysize

View File

@ -115,7 +115,8 @@ def APP(self, marker):
elif marker == 0xFFE2 and s[:4] == b"MPF\0": elif marker == 0xFFE2 and s[:4] == b"MPF\0":
# extract MPO information # extract MPO information
self.info["mp"] = s[4:] self.info["mp"] = s[4:]
# offset is current location minus buffer size plus constant header size # offset is current location minus buffer size
# plus constant header size
self.info["mpoffset"] = self.fp.tell() - n + 4 self.info["mpoffset"] = self.fp.tell() - n + 4
@ -321,7 +322,8 @@ class JpegImageFile(ImageFile.ImageFile):
rawmode = self.mode rawmode = self.mode
if self.mode == "CMYK": if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))] self.tile = [("jpeg", (0, 0) + self.size, 0,
(rawmode, ""))]
# self.__offset = self.fp.tell() # self.__offset = self.fp.tell()
break break
s = self.fp.read(1) s = self.fp.read(1)
@ -472,14 +474,18 @@ def _getmp(self):
for entrynum in range(0, quant): for entrynum in range(0, quant):
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16] rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry) unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2') labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
'EntryNo2')
mpentry = dict(zip(labels, unpackedentry)) mpentry = dict(zip(labels, unpackedentry))
mpentryattr = { mpentryattr = {
'DependentParentImageFlag': bool(mpentry['Attribute'] & (1<<31)), 'DependentParentImageFlag': bool(mpentry['Attribute'] &
'DependentChildImageFlag': bool(mpentry['Attribute'] & (1<<30)), (1 << 31)),
'RepresentativeImageFlag': bool(mpentry['Attribute'] & (1<<29)), 'DependentChildImageFlag': bool(mpentry['Attribute'] &
'Reserved': (mpentry['Attribute'] & (3<<27)) >> 27, (1 << 30)),
'ImageDataFormat': (mpentry['Attribute'] & (7<<24)) >> 24, 'RepresentativeImageFlag': bool(mpentry['Attribute'] &
(1 << 29)),
'Reserved': (mpentry['Attribute'] & (3 << 27)) >> 27,
'ImageDataFormat': (mpentry['Attribute'] & (7 << 24)) >> 24,
'MPType': mpentry['Attribute'] & 0x00FFFFFF 'MPType': mpentry['Attribute'] & 0x00FFFFFF
} }
if mpentryattr['ImageDataFormat'] == 0: if mpentryattr['ImageDataFormat'] == 0:
@ -496,7 +502,7 @@ def _getmp(self):
0x030000: 'Baseline MP Primary Image' 0x030000: 'Baseline MP Primary Image'
} }
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'], mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
'Unknown') 'Unknown')
mpentry['Attribute'] = mpentryattr mpentry['Attribute'] = mpentryattr
mpentries.append(mpentry) mpentries.append(mpentry)
mp[0xB002] = mpentries mp[0xB002] = mpentries
@ -530,11 +536,10 @@ zigzag_index = ( 0, 1, 5, 6, 14, 15, 27, 28,
21, 34, 37, 47, 50, 56, 59, 61, 21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63) 35, 36, 48, 49, 57, 58, 62, 63)
samplings = { samplings = {(1, 1, 1, 1, 1, 1): 0,
(1, 1, 1, 1, 1, 1): 0,
(2, 1, 1, 1, 1, 1): 1, (2, 1, 1, 1, 1, 1): 1,
(2, 2, 1, 1, 1, 1): 2, (2, 2, 1, 1, 1, 1): 2,
} }
def convert_dict_qtables(qtables): def convert_dict_qtables(qtables):
@ -598,7 +603,8 @@ def _save(im, fp, filename):
subsampling = 2 subsampling = 2
elif subsampling == "keep": elif subsampling == "keep":
if im.format != "JPEG": if im.format != "JPEG":
raise ValueError("Cannot use 'keep' when original image is not a JPEG") raise ValueError(
"Cannot use 'keep' when original image is not a JPEG")
subsampling = get_sampling(im) subsampling = get_sampling(im)
def validate_qtables(qtables): def validate_qtables(qtables):
@ -632,7 +638,8 @@ def _save(im, fp, filename):
if qtables == "keep": if qtables == "keep":
if im.format != "JPEG": if im.format != "JPEG":
raise ValueError("Cannot use 'keep' when original image is not a JPEG") raise ValueError(
"Cannot use 'keep' when original image is not a JPEG")
qtables = getattr(im, "quantization", None) qtables = getattr(im, "quantization", None)
qtables = validate_qtables(qtables) qtables = validate_qtables(qtables)
@ -650,7 +657,8 @@ def _save(im, fp, filename):
i = 1 i = 1
for marker in markers: for marker in markers:
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker)) size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
extra += b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker extra += (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) +
o8(len(markers)) + marker)
i += 1 i += 1
# get keyword arguments # get keyword arguments

View File

@ -48,8 +48,8 @@ You can get the quantization tables of a JPEG with::
im.quantization im.quantization
This will return a dict with a number of arrays. You can pass this dict directly This will return a dict with a number of arrays. You can pass this dict
as the qtables argument when saving a JPEG. directly as the qtables argument when saving a JPEG.
The tables format between im.quantization and quantization in presets differ in The tables format between im.quantization and quantization in presets differ in
3 ways: 3 ways:

View File

@ -21,9 +21,11 @@ __version__ = "0.2"
import struct import struct
from PIL import Image, ImageFile from PIL import Image, ImageFile
def _accept(s): def _accept(s):
return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04" return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04"
## ##
# Image plugin for McIdas area images. # Image plugin for McIdas area images.
@ -47,10 +49,12 @@ class McIdasImageFile(ImageFile.ImageFile):
mode = rawmode = "L" mode = rawmode = "L"
elif w[11] == 2: elif w[11] == 2:
# FIXME: add memory map support # FIXME: add memory map support
mode = "I"; rawmode = "I;16B" mode = "I"
rawmode = "I;16B"
elif w[11] == 4: elif w[11] == 4:
# FIXME: add memory map support # FIXME: add memory map support
mode = "I"; rawmode = "I;32B" mode = "I"
rawmode = "I;32B"
else: else:
raise SyntaxError("unsupported McIdas format") raise SyntaxError("unsupported McIdas format")

View File

@ -31,6 +31,7 @@ from PIL.OleFileIO import *
def _accept(prefix): def _accept(prefix):
return prefix[:8] == MAGIC return prefix[:8] == MAGIC
## ##
# Image plugin for Microsoft's Image Composer file format. # Image plugin for Microsoft's Image Composer file format.

View File

@ -18,6 +18,7 @@ __version__ = "0.1"
from PIL import Image, ImageFile from PIL import Image, ImageFile
from PIL._binary import i8 from PIL._binary import i8
# #
# Bitstream parser # Bitstream parser
@ -52,6 +53,7 @@ class BitStream:
self.bits = self.bits - bits self.bits = self.bits - bits
return v return v
## ##
# Image plugin for MPEG streams. This plugin can identify a stream, # Image plugin for MPEG streams. This plugin can identify a stream,
# but it cannot read it. # but it cannot read it.

View File

@ -22,13 +22,16 @@ __version__ = "0.1"
from PIL import Image, JpegImagePlugin from PIL import Image, JpegImagePlugin
def _accept(prefix): def _accept(prefix):
return JpegImagePlugin._accept(prefix) return JpegImagePlugin._accept(prefix)
def _save(im, fp, filename): def _save(im, fp, filename):
# Note that we can only save the current frame at present # Note that we can only save the current frame at present
return JpegImagePlugin._save(im, fp, filename) return JpegImagePlugin._save(im, fp, filename)
## ##
# Image plugin for MPO images. # Image plugin for MPO images.
@ -38,19 +41,19 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
format_description = "MPO (CIPA DC-007)" format_description = "MPO (CIPA DC-007)"
def _open(self): def _open(self):
self.fp.seek(0) # prep the fp in order to pass the JPEG test self.fp.seek(0) # prep the fp in order to pass the JPEG test
JpegImagePlugin.JpegImageFile._open(self) JpegImagePlugin.JpegImageFile._open(self)
self.mpinfo = self._getmp() self.mpinfo = self._getmp()
self.__framecount = self.mpinfo[0xB001] self.__framecount = self.mpinfo[0xB001]
self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] \ self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset']
for mpent in self.mpinfo[0xB002]] for mpent in self.mpinfo[0xB002]]
self.__mpoffsets[0] = 0 self.__mpoffsets[0] = 0
# Note that the following assertion will only be invalid if something # Note that the following assertion will only be invalid if something
# gets broken within JpegImagePlugin. # gets broken within JpegImagePlugin.
assert self.__framecount == len(self.__mpoffsets) assert self.__framecount == len(self.__mpoffsets)
del self.info['mpoffset'] # no longer needed del self.info['mpoffset'] # no longer needed
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self.__frame = 0 self.__frame = 0
self.offset = 0 self.offset = 0
# for now we can only handle reading and individual frame extraction # for now we can only handle reading and individual frame extraction
@ -79,7 +82,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
# Note that since MPO shares a factory with JPEG, we do not need to do a # Note that since MPO shares a factory with JPEG, we do not need to do a
# separate registration for it here. # separate registration for it here.
#Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept) # Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
Image.register_save("MPO", _save) Image.register_save("MPO", _save)
Image.register_extension("MPO", ".mpo") Image.register_extension("MPO", ".mpo")

View File

@ -27,9 +27,11 @@ from PIL import Image, ImageFile, _binary
i16 = _binary.i16le i16 = _binary.i16le
def _accept(prefix): def _accept(prefix):
return prefix[:4] in [b"DanM", b"LinS"] return prefix[:4] in [b"DanM", b"LinS"]
## ##
# Image plugin for Windows MSP images. This plugin supports both # Image plugin for Windows MSP images. This plugin supports both
# uncompressed (Windows 1.0). # uncompressed (Windows 1.0).
@ -57,15 +59,16 @@ class MspImageFile(ImageFile.ImageFile):
self.size = i16(s[4:]), i16(s[6:]) self.size = i16(s[4:]), i16(s[6:])
if s[:4] == b"DanM": if s[:4] == b"DanM":
self.tile = [("raw", (0,0)+self.size, 32, ("1", 0, 1))] self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
else: else:
self.tile = [("msp", (0,0)+self.size, 32+2*self.size[1], None)] self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)]
# #
# write MSP files (uncompressed only) # write MSP files (uncompressed only)
o16 = _binary.o16le o16 = _binary.o16le
def _save(im, fp, filename): def _save(im, fp, filename):
if im.mode != "1": if im.mode != "1":
@ -74,7 +77,7 @@ def _save(im, fp, filename):
# create MSP header # create MSP header
header = [0] * 16 header = [0] * 16
header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1 header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1
header[2], header[3] = im.size header[2], header[3] = im.size
header[4], header[5] = 1, 1 header[4], header[5] = 1, 1
header[6], header[7] = 1, 1 header[6], header[7] = 1, 1
@ -83,14 +86,14 @@ def _save(im, fp, filename):
sum = 0 sum = 0
for h in header: for h in header:
sum = sum ^ h sum = sum ^ h
header[12] = sum # FIXME: is this the right field? header[12] = sum # FIXME: is this the right field?
# header # header
for h in header: for h in header:
fp.write(o16(h)) fp.write(o16(h))
# image body # image body
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 32, ("1", 0, 1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
# #
# registry # registry

View File

@ -19,6 +19,7 @@ from __future__ import print_function
from PIL import EpsImagePlugin from PIL import EpsImagePlugin
## ##
# Simple Postscript graphics interface. # Simple Postscript graphics interface.
@ -34,7 +35,7 @@ class PSDraw:
fp = sys.stdout fp = sys.stdout
self.fp = fp self.fp = fp
def begin_document(self, id = None): def begin_document(self, id=None):
"""Set up printing of a document. (Write Postscript DSC header.)""" """Set up printing of a document. (Write Postscript DSC header.)"""
# FIXME: incomplete # FIXME: incomplete
self.fp.write("%!PS-Adobe-3.0\n" self.fp.write("%!PS-Adobe-3.0\n"
@ -42,7 +43,7 @@ class PSDraw:
"/showpage { } def\n" "/showpage { } def\n"
"%%EndComments\n" "%%EndComments\n"
"%%BeginDocument\n") "%%BeginDocument\n")
#self.fp.write(ERROR_PS) # debugging! # self.fp.write(ERROR_PS) # debugging!
self.fp.write(EDROFF_PS) self.fp.write(EDROFF_PS)
self.fp.write(VDI_PS) self.fp.write(VDI_PS)
self.fp.write("%%EndProlog\n") self.fp.write("%%EndProlog\n")
@ -65,7 +66,7 @@ class PSDraw:
""" """
if font not in self.isofont: if font not in self.isofont:
# reencode font # reencode font
self.fp.write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %\ self.fp.write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %
(font, font)) (font, font))
self.isofont[font] = 1 self.isofont[font] = 1
# rough # rough
@ -112,14 +113,14 @@ class PSDraw:
xy = xy + (text,) xy = xy + (text,)
self.fp.write("%d %d M (%s) S\n" % xy) self.fp.write("%d %d M (%s) S\n" % xy)
def image(self, box, im, dpi = None): def image(self, box, im, dpi=None):
"""Draw a PIL image, centered in the given box.""" """Draw a PIL image, centered in the given box."""
# default resolution depends on mode # default resolution depends on mode
if not dpi: if not dpi:
if im.mode == "1": if im.mode == "1":
dpi = 200 # fax dpi = 200 # fax
else: else:
dpi = 100 # greyscale dpi = 100 # greyscale
# image size (on paper) # image size (on paper)
x = float(im.size[0] * 72) / dpi x = float(im.size[0] * 72) / dpi
y = float(im.size[1] * 72) / dpi y = float(im.size[1] * 72) / dpi
@ -127,9 +128,11 @@ class PSDraw:
xmax = float(box[2] - box[0]) xmax = float(box[2] - box[0])
ymax = float(box[3] - box[1]) ymax = float(box[3] - box[1])
if x > xmax: if x > xmax:
y = y * xmax / x; x = xmax y = y * xmax / x
x = xmax
if y > ymax: if y > ymax:
x = x * ymax / y; y = ymax x = x * ymax / y
y = ymax
dx = (xmax - x) / 2 + box[0] dx = (xmax - x) / 2 + box[0]
dy = (ymax - y) / 2 + box[1] dy = (ymax - y) / 2 + box[1]
self.fp.write("gsave\n%f %f translate\n" % (dx, dy)) self.fp.write("gsave\n%f %f translate\n" % (dx, dy))

View File

@ -15,6 +15,7 @@
from PIL._binary import o8 from PIL._binary import o8
## ##
# File handler for Teragon-style palette files. # File handler for Teragon-style palette files.
@ -49,7 +50,6 @@ class PaletteFile:
self.palette = b"".join(self.palette) self.palette = b"".join(self.palette)
def getpalette(self): def getpalette(self):
return self.palette, self.rawmode return self.palette, self.rawmode

View File

@ -22,6 +22,7 @@ from PIL import Image, ImageFile, _binary
i8 = _binary.i8 i8 = _binary.i8
## ##
# Image plugin for PhotoCD images. This plugin only reads the 768x512 # Image plugin for PhotoCD images. This plugin only reads the 768x512
# image from the file; higher resolutions are encoded in a proprietary # image from the file; higher resolutions are encoded in a proprietary
@ -43,13 +44,13 @@ class PcdImageFile(ImageFile.ImageFile):
orientation = i8(s[1538]) & 3 orientation = i8(s[1538]) & 3
if orientation == 1: if orientation == 1:
self.tile_post_rotate = 90 # hack self.tile_post_rotate = 90 # hack
elif orientation == 3: elif orientation == 3:
self.tile_post_rotate = -90 self.tile_post_rotate = -90
self.mode = "RGB" self.mode = "RGB"
self.size = 768, 512 # FIXME: not correct for rotated images! self.size = 768, 512 # FIXME: not correct for rotated images!
self.tile = [("pcd", (0,0)+self.size, 96*2048, None)] self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)]
def draft(self, mode, size): def draft(self, mode, size):
@ -60,7 +61,7 @@ class PcdImageFile(ImageFile.ImageFile):
if size: if size:
scale = max(self.size[0] / size[0], self.size[1] / size[1]) scale = max(self.size[0] / size[0], self.size[1] / size[1])
for s, o in [(4,0*2048), (2,0*2048), (1,96*2048)]: for s, o in [(4, 0*2048), (2, 0*2048), (1, 96*2048)]:
if scale >= s: if scale >= s:
break break
# e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1] # e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1]

View File

@ -23,20 +23,20 @@ from PIL import _binary
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# declarations # declarations
PCF_MAGIC = 0x70636601 # "\x01fcp" PCF_MAGIC = 0x70636601 # "\x01fcp"
PCF_PROPERTIES = (1<<0) PCF_PROPERTIES = (1 << 0)
PCF_ACCELERATORS = (1<<1) PCF_ACCELERATORS = (1 << 1)
PCF_METRICS = (1<<2) PCF_METRICS = (1 << 2)
PCF_BITMAPS = (1<<3) PCF_BITMAPS = (1 << 3)
PCF_INK_METRICS = (1<<4) PCF_INK_METRICS = (1 << 4)
PCF_BDF_ENCODINGS = (1<<5) PCF_BDF_ENCODINGS = (1 << 5)
PCF_SWIDTHS = (1<<6) PCF_SWIDTHS = (1 << 6)
PCF_GLYPH_NAMES = (1<<7) PCF_GLYPH_NAMES = (1 << 7)
PCF_BDF_ACCELERATORS = (1<<8) PCF_BDF_ACCELERATORS = (1 << 8)
BYTES_PER_ROW = [ BYTES_PER_ROW = [
lambda bits: ((bits+7) >> 3), lambda bits: ((bits+7) >> 3),
lambda bits: ((bits+15) >> 3) & ~1, lambda bits: ((bits+15) >> 3) & ~1,
lambda bits: ((bits+31) >> 3) & ~3, lambda bits: ((bits+31) >> 3) & ~3,
lambda bits: ((bits+63) >> 3) & ~7, lambda bits: ((bits+63) >> 3) & ~7,
@ -48,9 +48,11 @@ l32 = _binary.i32le
b16 = _binary.i16be b16 = _binary.i16be
b32 = _binary.i32be b32 = _binary.i32be
def sz(s, o): def sz(s, o):
return s[o:s.index(b"\0", o)] return s[o:s.index(b"\0", o)]
## ##
# Font file plugin for the X11 PCF format. # Font file plugin for the X11 PCF format.
@ -122,7 +124,7 @@ class PcfFontFile(FontFile.FontFile):
for i in range(nprops): for i in range(nprops):
p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4)))) p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
if nprops & 3: if nprops & 3:
fp.seek(4 - (nprops & 3), 1) # pad fp.seek(4 - (nprops & 3), 1) # pad
data = fp.read(i32(fp.read(4))) data = fp.read(i32(fp.read(4)))
@ -202,16 +204,16 @@ class PcfFontFile(FontFile.FontFile):
for i in range(4): for i in range(4):
bitmapSizes.append(i32(fp.read(4))) bitmapSizes.append(i32(fp.read(4)))
byteorder = format & 4 # non-zero => MSB byteorder = format & 4 # non-zero => MSB
bitorder = format & 8 # non-zero => MSB bitorder = format & 8 # non-zero => MSB
padindex = format & 3 padindex = format & 3
bitmapsize = bitmapSizes[padindex] bitmapsize = bitmapSizes[padindex]
offsets.append(bitmapsize) offsets.append(bitmapsize)
data = fp.read(bitmapsize) data = fp.read(bitmapsize)
pad = BYTES_PER_ROW[padindex] pad = BYTES_PER_ROW[padindex]
mode = "1;R" mode = "1;R"
if bitorder: if bitorder:
mode = "1" mode = "1"
@ -245,6 +247,6 @@ class PcfFontFile(FontFile.FontFile):
try: try:
encoding[i+firstCol] = encodingOffset encoding[i+firstCol] = encodingOffset
except IndexError: except IndexError:
break # only load ISO-8859-1 glyphs break # only load ISO-8859-1 glyphs
return encoding return encoding

View File

@ -33,9 +33,11 @@ i8 = _binary.i8
i16 = _binary.i16le i16 = _binary.i16le
o8 = _binary.o8 o8 = _binary.o8
def _accept(prefix): def _accept(prefix):
return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5] return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
## ##
# Image plugin for Paintbrush images. # Image plugin for Paintbrush images.
@ -52,23 +54,22 @@ class PcxImageFile(ImageFile.ImageFile):
raise SyntaxError("not a PCX file") raise SyntaxError("not a PCX file")
# image # image
bbox = i16(s,4), i16(s,6), i16(s,8)+1, i16(s,10)+1 bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]: if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
raise SyntaxError("bad PCX image size") raise SyntaxError("bad PCX image size")
if Image.DEBUG: if Image.DEBUG:
print ("BBox: %s %s %s %s" % bbox) print ("BBox: %s %s %s %s" % bbox)
# format # format
version = i8(s[1]) version = i8(s[1])
bits = i8(s[3]) bits = i8(s[3])
planes = i8(s[65]) planes = i8(s[65])
stride = i16(s,66) stride = i16(s, 66)
if Image.DEBUG: if Image.DEBUG:
print ("PCX version %s, bits %s, planes %s, stride %s" % print ("PCX version %s, bits %s, planes %s, stride %s" %
(version, bits, planes, stride)) (version, bits, planes, stride))
self.info["dpi"] = i16(s,12), i16(s,14) self.info["dpi"] = i16(s, 12), i16(s, 14)
if bits == 1 and planes == 1: if bits == 1 and planes == 1:
mode = rawmode = "1" mode = rawmode = "1"
@ -122,6 +123,7 @@ SAVE = {
o16 = _binary.o16le o16 = _binary.o16le
def _save(im, fp, filename, check=0): def _save(im, fp, filename, check=0):
try: try:
@ -140,7 +142,6 @@ def _save(im, fp, filename, check=0):
# Ideally it should be passed in in the state, but the bytes value # Ideally it should be passed in in the state, but the bytes value
# gets overwritten. # gets overwritten.
if Image.DEBUG: if Image.DEBUG:
print ("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % ( print ("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
im.size[0], bits, stride)) im.size[0], bits, stride))
@ -163,13 +164,13 @@ def _save(im, fp, filename, check=0):
assert fp.tell() == 128 assert fp.tell() == 128
ImageFile._save(im, fp, [("pcx", (0,0)+im.size, 0, ImageFile._save(im, fp, [("pcx", (0, 0)+im.size, 0,
(rawmode, bits*planes))]) (rawmode, bits*planes))])
if im.mode == "P": if im.mode == "P":
# colour palette # colour palette
fp.write(o8(12)) fp.write(o8(12))
fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
elif im.mode == "L": elif im.mode == "L":
# greyscale palette # greyscale palette
fp.write(o8(12)) fp.write(o8(12))

View File

@ -29,6 +29,7 @@ from PIL import Image, ImageFile, _binary
i16 = _binary.i16le i16 = _binary.i16le
i32 = _binary.i32le i32 = _binary.i32le
## ##
# Image plugin for PIXAR raster images. # Image plugin for PIXAR raster images.
@ -57,7 +58,7 @@ class PixarImageFile(ImageFile.ImageFile):
# FIXME: to be continued... # FIXME: to be continued...
# create tile descriptor (assuming "dumped") # create tile descriptor (assuming "dumped")
self.tile = [("raw", (0,0)+self.size, 1024, (self.mode, 0, 1))] self.tile = [("raw", (0, 0)+self.size, 1024, (self.mode, 0, 1))]
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -52,26 +52,27 @@ _MAGIC = b"\211PNG\r\n\032\n"
_MODES = { _MODES = {
# supported bits/color combinations, and corresponding modes/rawmodes # supported bits/color combinations, and corresponding modes/rawmodes
(1, 0): ("1", "1"), (1, 0): ("1", "1"),
(2, 0): ("L", "L;2"), (2, 0): ("L", "L;2"),
(4, 0): ("L", "L;4"), (4, 0): ("L", "L;4"),
(8, 0): ("L", "L"), (8, 0): ("L", "L"),
(16,0): ("I", "I;16B"), (16, 0): ("I", "I;16B"),
(8, 2): ("RGB", "RGB"), (8, 2): ("RGB", "RGB"),
(16,2): ("RGB", "RGB;16B"), (16, 2): ("RGB", "RGB;16B"),
(1, 3): ("P", "P;1"), (1, 3): ("P", "P;1"),
(2, 3): ("P", "P;2"), (2, 3): ("P", "P;2"),
(4, 3): ("P", "P;4"), (4, 3): ("P", "P;4"),
(8, 3): ("P", "P"), (8, 3): ("P", "P"),
(8, 4): ("LA", "LA"), (8, 4): ("LA", "LA"),
(16,4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
(8, 6): ("RGBA", "RGBA"), (8, 6): ("RGBA", "RGBA"),
(16,6): ("RGBA", "RGBA;16B"), (16, 6): ("RGBA", "RGBA;16B"),
} }
_simple_palette = re.compile(b'^\xff+\x00\xff*$') _simple_palette = re.compile(b'^\xff+\x00\xff*$')
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Support classes. Suitable for PNG and related formats like MNG etc. # Support classes. Suitable for PNG and related formats like MNG etc.
@ -123,15 +124,15 @@ class ChunkStream:
crc1 = Image.core.crc32(data, Image.core.crc32(cid)) crc1 = Image.core.crc32(data, Image.core.crc32(cid))
crc2 = i16(self.fp.read(2)), i16(self.fp.read(2)) crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
if crc1 != crc2: if crc1 != crc2:
raise SyntaxError("broken PNG file"\ raise SyntaxError("broken PNG file"
"(bad header checksum in %s)" % cid) "(bad header checksum in %s)" % cid)
def crc_skip(self, cid, data): def crc_skip(self, cid, data):
"Read checksum. Used if the C module is not present" "Read checksum. Used if the C module is not present"
self.fp.read(4) self.fp.read(4)
def verify(self, endchunk = b"IEND"): def verify(self, endchunk=b"IEND"):
# Simple approach; just calculate checksum for all remaining # Simple approach; just calculate checksum for all remaining
# blocks. Must be called directly after open. # blocks. Must be called directly after open.
@ -147,6 +148,7 @@ class ChunkStream:
return cids return cids
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Subclass of string to allow iTXt chunks to look like strings while # Subclass of string to allow iTXt chunks to look like strings while
# keeping their extra information # keeping their extra information
@ -159,6 +161,7 @@ class iTXt(str):
self.tkey = tkey self.tkey = tkey
return self return self
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG chunk container (for use with save(pnginfo=)) # PNG chunk container (for use with save(pnginfo=))
@ -182,9 +185,11 @@ class PngInfo:
if zip: if zip:
import zlib import zlib
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value)) self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" +
zlib.compress(value))
else: else:
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value) self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" +
value)
def add_text(self, key, value, zip=0): def add_text(self, key, value, zip=0):
if isinstance(value, iTXt): if isinstance(value, iTXt):
@ -206,6 +211,7 @@ class PngInfo:
else: else:
self.add(b"tEXt", key + b"\0" + value) self.add(b"tEXt", key + b"\0" + value)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG image stream (IHDR/IEND) # PNG image stream (IHDR/IEND)
@ -218,7 +224,7 @@ class PngStream(ChunkStream):
# local copies of Image attributes # local copies of Image attributes
self.im_info = {} self.im_info = {}
self.im_text = {} self.im_text = {}
self.im_size = (0,0) self.im_size = (0, 0)
self.im_mode = None self.im_mode = None
self.im_tile = None self.im_tile = None
self.im_palette = None self.im_palette = None
@ -238,11 +244,12 @@ class PngStream(ChunkStream):
print("Compression method", i8(s[i])) print("Compression method", i8(s[i]))
comp_method = i8(s[i]) comp_method = i8(s[i])
if comp_method != 0: if comp_method != 0:
raise SyntaxError("Unknown compression method %s in iCCP chunk" % comp_method) raise SyntaxError("Unknown compression method %s in iCCP chunk" %
comp_method)
try: try:
icc_profile = zlib.decompress(s[i+2:]) icc_profile = zlib.decompress(s[i+2:])
except zlib.error: except zlib.error:
icc_profile = None # FIXME icc_profile = None # FIXME
self.im_info["icc_profile"] = icc_profile self.im_info["icc_profile"] = icc_profile
return s return s
@ -264,7 +271,7 @@ class PngStream(ChunkStream):
def chunk_IDAT(self, pos, length): def chunk_IDAT(self, pos, length):
# image data # image data
self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)] self.im_tile = [("zip", (0, 0)+self.im_size, pos, self.im_rawmode)]
self.im_idat = length self.im_idat = length
raise EOFError raise EOFError
@ -311,7 +318,7 @@ class PngStream(ChunkStream):
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
px, py = i32(s), i32(s[4:]) px, py = i32(s), i32(s[4:])
unit = i8(s[8]) unit = i8(s[8])
if unit == 1: # meter if unit == 1: # meter
dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5) dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5)
self.im_info["dpi"] = dpi self.im_info["dpi"] = dpi
elif unit == 0: elif unit == 0:
@ -325,7 +332,9 @@ class PngStream(ChunkStream):
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
k = s; v = b"" # fallback for broken tEXt tags # fallback for broken tEXt tags
k = s
v = b""
if k: if k:
if bytes is not str: if bytes is not str:
k = k.decode('latin-1', 'strict') k = k.decode('latin-1', 'strict')
@ -341,13 +350,15 @@ class PngStream(ChunkStream):
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
k = s; v = b"" k = s
v = b""
if v: if v:
comp_method = i8(v[0]) comp_method = i8(v[0])
else: else:
comp_method = 0 comp_method = 0
if comp_method != 0: if comp_method != 0:
raise SyntaxError("Unknown compression method %s in zTXt chunk" % comp_method) raise SyntaxError("Unknown compression method %s in zTXt chunk" %
comp_method)
import zlib import zlib
try: try:
v = zlib.decompress(v[1:]) v = zlib.decompress(v[1:])
@ -399,12 +410,14 @@ class PngStream(ChunkStream):
return s return s
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# PNG reader # PNG reader
def _accept(prefix): def _accept(prefix):
return prefix[:8] == _MAGIC return prefix[:8] == _MAGIC
## ##
# Image plugin for PNG images. # Image plugin for PNG images.
@ -451,7 +464,7 @@ class PngImageFile(ImageFile.ImageFile):
self.mode = self.png.im_mode self.mode = self.png.im_mode
self.size = self.png.im_size self.size = self.png.im_size
self.info = self.png.im_info self.info = self.png.im_info
self.text = self.png.im_text # experimental self.text = self.png.im_text # experimental
self.tile = self.png.im_tile self.tile = self.png.im_tile
if self.png.im_palette: if self.png.im_palette:
@ -460,7 +473,6 @@ class PngImageFile(ImageFile.ImageFile):
self.__idat = length # used by load_read() self.__idat = length # used by load_read()
def verify(self): def verify(self):
"Verify PNG file" "Verify PNG file"
@ -489,7 +501,7 @@ class PngImageFile(ImageFile.ImageFile):
while self.__idat == 0: while self.__idat == 0:
# end of chunk, skip forward to next one # end of chunk, skip forward to next one
self.fp.read(4) # CRC self.fp.read(4) # CRC
cid, pos, length = self.png.read() cid, pos, length = self.png.read()
@ -509,7 +521,6 @@ class PngImageFile(ImageFile.ImageFile):
return self.fp.read(read_bytes) return self.fp.read(read_bytes)
def load_end(self): def load_end(self):
"internal: finished reading image data" "internal: finished reading image data"
@ -526,21 +537,22 @@ o32 = _binary.o32be
_OUTMODES = { _OUTMODES = {
# supported PIL modes, and corresponding rawmodes/bits/color combinations # supported PIL modes, and corresponding rawmodes/bits/color combinations
"1": ("1", b'\x01\x00'), "1": ("1", b'\x01\x00'),
"L;1": ("L;1", b'\x01\x00'), "L;1": ("L;1", b'\x01\x00'),
"L;2": ("L;2", b'\x02\x00'), "L;2": ("L;2", b'\x02\x00'),
"L;4": ("L;4", b'\x04\x00'), "L;4": ("L;4", b'\x04\x00'),
"L": ("L", b'\x08\x00'), "L": ("L", b'\x08\x00'),
"LA": ("LA", b'\x08\x04'), "LA": ("LA", b'\x08\x04'),
"I": ("I;16B", b'\x10\x00'), "I": ("I;16B", b'\x10\x00'),
"P;1": ("P;1", b'\x01\x03'), "P;1": ("P;1", b'\x01\x03'),
"P;2": ("P;2", b'\x02\x03'), "P;2": ("P;2", b'\x02\x03'),
"P;4": ("P;4", b'\x04\x03'), "P;4": ("P;4", b'\x04\x03'),
"P": ("P", b'\x08\x03'), "P": ("P", b'\x08\x03'),
"RGB": ("RGB", b'\x08\x02'), "RGB": ("RGB", b'\x08\x02'),
"RGBA":("RGBA", b'\x08\x06'), "RGBA": ("RGBA", b'\x08\x06'),
} }
def putchunk(fp, cid, *data): def putchunk(fp, cid, *data):
"Write a PNG chunk (including CRC field)" "Write a PNG chunk (including CRC field)"
@ -551,15 +563,18 @@ def putchunk(fp, cid, *data):
hi, lo = Image.core.crc32(data, Image.core.crc32(cid)) hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
fp.write(o16(hi) + o16(lo)) fp.write(o16(hi) + o16(lo))
class _idat: class _idat:
# wrap output from the encoder in IDAT chunks # wrap output from the encoder in IDAT chunks
def __init__(self, fp, chunk): def __init__(self, fp, chunk):
self.fp = fp self.fp = fp
self.chunk = chunk self.chunk = chunk
def write(self, data): def write(self, data):
self.chunk(self.fp, b"IDAT", data) self.chunk(self.fp, b"IDAT", data)
def _save(im, fp, filename, chunk=putchunk, check=0): def _save(im, fp, filename, chunk=putchunk, check=0):
# save an image to disk (called by the save method) # save an image to disk (called by the save method)
@ -597,9 +612,9 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
dictionary = b"" dictionary = b""
im.encoderconfig = ("optimize" in im.encoderinfo, im.encoderconfig = ("optimize" in im.encoderinfo,
im.encoderinfo.get("compress_level", -1), im.encoderinfo.get("compress_level", -1),
im.encoderinfo.get("compress_type", -1), im.encoderinfo.get("compress_type", -1),
dictionary) dictionary)
# get the corresponding PNG mode # get the corresponding PNG mode
try: try:
@ -616,8 +631,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
fp.write(_MAGIC) fp.write(_MAGIC)
chunk(fp, b"IHDR", chunk(fp, b"IHDR",
o32(im.size[0]), o32(im.size[1]), # 0: size o32(im.size[0]), o32(im.size[1]), # 0: size
mode, # 8: depth/type mode, # 8: depth/type
b'\0', # 10: compression b'\0', # 10: compression
b'\0', # 11: filter category b'\0', # 11: filter category
b'\0') # 12: interlace flag b'\0') # 12: interlace flag
@ -629,7 +644,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
palette_bytes += b'\0' palette_bytes += b'\0'
chunk(fp, b"PLTE", palette_bytes) chunk(fp, b"PLTE", palette_bytes)
transparency = im.encoderinfo.get('transparency',im.info.get('transparency', None)) transparency = im.encoderinfo.get('transparency',
im.info.get('transparency', None))
if transparency or transparency == 0: if transparency or transparency == 0:
if im.mode == "P": if im.mode == "P":
@ -686,7 +702,8 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
data = name + b"\0\0" + zlib.compress(im.info["icc_profile"]) data = name + b"\0\0" + zlib.compress(im.info["icc_profile"])
chunk(fp, b"iCCP", data) chunk(fp, b"iCCP", data)
ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)]) ImageFile._save(im, _idat(fp, chunk),
[("zip", (0, 0)+im.size, 0, rawmode)])
chunk(fp, b"IEND", b"") chunk(fp, b"IEND", b"")
@ -704,8 +721,10 @@ def getchunks(im, **params):
class collector: class collector:
data = [] data = []
def write(self, data): def write(self, data):
pass pass
def append(self, chunk): def append(self, chunk):
self.data.append(chunk) self.data.append(chunk)

View File

@ -27,12 +27,13 @@ from PIL import Image, ImageFile
b_whitespace = string.whitespace b_whitespace = string.whitespace
try: try:
import locale import locale
locale_lang,locale_enc = locale.getlocale() locale_lang, locale_enc = locale.getlocale()
if locale_enc is None: if locale_enc is None:
locale_lang,locale_enc = locale.getdefaultlocale() locale_lang, locale_enc = locale.getdefaultlocale()
b_whitespace = b_whitespace.decode(locale_enc) b_whitespace = b_whitespace.decode(locale_enc)
except: pass except:
b_whitespace = b_whitespace.encode('ascii','ignore') pass
b_whitespace = b_whitespace.encode('ascii', 'ignore')
MODES = { MODES = {
# standard # standard
@ -47,9 +48,11 @@ MODES = {
b"PyCMYK": "CMYK" b"PyCMYK": "CMYK"
} }
def _accept(prefix): def _accept(prefix):
return prefix[0:1] == b"P" and prefix[1] in b"0456y" return prefix[0:1] == b"P" and prefix[1] in b"0456y"
## ##
# Image plugin for PBM, PGM, and PPM images. # Image plugin for PBM, PGM, and PPM images.
@ -58,8 +61,8 @@ class PpmImageFile(ImageFile.ImageFile):
format = "PPM" format = "PPM"
format_description = "Pbmplus image" format_description = "Pbmplus image"
def _token(self, s = b""): def _token(self, s=b""):
while True: # read until next whitespace while True: # read until next whitespace
c = self.fp.read(1) c = self.fp.read(1)
if not c or c in b_whitespace: if not c or c in b_whitespace:
break break
@ -104,12 +107,12 @@ class PpmImageFile(ImageFile.ImageFile):
# maxgrey # maxgrey
if s > 255: if s > 255:
if not mode == 'L': if not mode == 'L':
raise ValueError("Too many colors for band: %s" %s) raise ValueError("Too many colors for band: %s" % s)
if s < 2**16: if s < 2**16:
self.mode = 'I' self.mode = 'I'
rawmode = 'I;16B' rawmode = 'I;16B'
else: else:
self.mode = 'I'; self.mode = 'I'
rawmode = 'I;32B' rawmode = 'I;32B'
self.size = xsize, ysize self.size = xsize, ysize
@ -123,6 +126,7 @@ class PpmImageFile(ImageFile.ImageFile):
# self.mode = self.im.mode # self.mode = self.im.mode
# self.size = self.im.size # self.size = self.im.size
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -152,7 +156,7 @@ def _save(im, fp, filename):
fp.write(b"65535\n") fp.write(b"65535\n")
elif rawmode == "I;32B": elif rawmode == "I;32B":
fp.write(b"2147483648\n") fp.write(b"2147483648\n")
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
# ALTERNATIVE: save via builtin debug function # ALTERNATIVE: save via builtin debug function
# im._dump(filename) # im._dump(filename)

View File

@ -28,8 +28,8 @@ MODES = {
(2, 8): ("P", 1), (2, 8): ("P", 1),
(3, 8): ("RGB", 3), (3, 8): ("RGB", 3),
(4, 8): ("CMYK", 4), (4, 8): ("CMYK", 4),
(7, 8): ("L", 1), # FIXME: multilayer (7, 8): ("L", 1), # FIXME: multilayer
(8, 8): ("L", 1), # duotone (8, 8): ("L", 1), # duotone
(9, 8): ("LAB", 3) (9, 8): ("LAB", 3)
} }
@ -40,12 +40,14 @@ i8 = _binary.i8
i16 = _binary.i16be i16 = _binary.i16be
i32 = _binary.i32be i32 = _binary.i32be
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
# read PSD images # read PSD images
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"8BPS" return prefix[:4] == b"8BPS"
## ##
# Image plugin for Photoshop images. # Image plugin for Photoshop images.
@ -100,12 +102,12 @@ class PsdImageFile(ImageFile.ImageFile):
id = i16(read(2)) id = i16(read(2))
name = read(i8(read(1))) name = read(i8(read(1)))
if not (len(name) & 1): if not (len(name) & 1):
read(1) # padding read(1) # padding
data = read(i32(read(4))) data = read(i32(read(4)))
if (len(data) & 1): if (len(data) & 1):
read(1) # padding read(1) # padding
self.resources.append((id, name, data)) self.resources.append((id, name, data))
if id == 1039: # ICC profile if id == 1039: # ICC profile
self.info["icc_profile"] = data self.info["icc_profile"] = data
# #
@ -159,6 +161,7 @@ class PsdImageFile(ImageFile.ImageFile):
if self.mode == "P": if self.mode == "P":
Image.Image.load(self) Image.Image.load(self)
def _layerinfo(file): def _layerinfo(file):
# read layerinfo block # read layerinfo block
layers = [] layers = []
@ -166,8 +169,10 @@ def _layerinfo(file):
for i in range(abs(i16(read(2)))): for i in range(abs(i16(read(2)))):
# bounding box # bounding box
y0 = i32(read(4)); x0 = i32(read(4)) y0 = i32(read(4))
y1 = i32(read(4)); x1 = i32(read(4)) x0 = i32(read(4))
y1 = i32(read(4))
x1 = i32(read(4))
# image info # image info
info = [] info = []
@ -197,7 +202,7 @@ def _layerinfo(file):
elif mode == ["A", "B", "G", "R"]: elif mode == ["A", "B", "G", "R"]:
mode = "RGBA" mode = "RGBA"
else: else:
mode = None # unknown mode = None # unknown
# skip over blend flags and extra information # skip over blend flags and extra information
filler = read(12) filler = read(12)
@ -207,8 +212,10 @@ def _layerinfo(file):
if size: if size:
length = i32(read(4)) length = i32(read(4))
if length: if length:
mask_y = i32(read(4)); mask_x = i32(read(4)) mask_y = i32(read(4))
mask_h = i32(read(4)) - mask_y; mask_w = i32(read(4)) - mask_x mask_x = i32(read(4))
mask_h = i32(read(4)) - mask_y
mask_w = i32(read(4)) - mask_x
file.seek(length - 16, 1) file.seek(length - 16, 1)
combined += length + 4 combined += length + 4
@ -219,7 +226,8 @@ def _layerinfo(file):
length = i8(read(1)) length = i8(read(1))
if length: if length:
# Don't know the proper encoding, Latin-1 should be a good guess # Don't know the proper encoding,
# Latin-1 should be a good guess
name = read(length).decode('latin-1', 'replace') name = read(length).decode('latin-1', 'replace')
combined += length + 1 combined += length + 1
@ -239,6 +247,7 @@ def _layerinfo(file):
return layers return layers
def _maketile(file, mode, bbox, channels): def _maketile(file, mode, bbox, channels):
tile = None tile = None
@ -283,7 +292,7 @@ def _maketile(file, mode, bbox, channels):
file.seek(offset) file.seek(offset)
if offset & 1: if offset & 1:
read(1) # padding read(1) # padding
return tile return tile

View File

@ -16,7 +16,8 @@
# * Implements the pixel access object following Access. # * Implements the pixel access object following Access.
# * Does not implement the line functions, as they don't appear to be used # * Does not implement the line functions, as they don't appear to be used
# * Taking only the tuple form, which is used from python. # * Taking only the tuple form, which is used from python.
# * Fill.c uses the integer form, but it's still going to use the old Access.c implementation. # * Fill.c uses the integer form, but it's still going to use the old
# Access.c implementation.
# #
from __future__ import print_function from __future__ import print_function
@ -40,7 +41,7 @@ ffi.cdef(defs)
class PyAccess(object): class PyAccess(object):
def __init__(self, img, readonly = False): def __init__(self, img, readonly=False):
vals = dict(img.im.unsafe_ptrs) vals = dict(img.im.unsafe_ptrs)
self.readonly = readonly self.readonly = readonly
self.image8 = ffi.cast('unsigned char **', vals['image8']) self.image8 = ffi.cast('unsigned char **', vals['image8'])
@ -53,7 +54,8 @@ class PyAccess(object):
print (vals) print (vals)
self._post_init() self._post_init()
def _post_init(): pass def _post_init():
pass
def __setitem__(self, xy, color): def __setitem__(self, xy, color):
""" """
@ -64,9 +66,10 @@ class PyAccess(object):
:param xy: The pixel coordinate, given as (x, y). :param xy: The pixel coordinate, given as (x, y).
:param value: The pixel value. :param value: The pixel value.
""" """
if self.readonly: raise ValueError('Attempt to putpixel a read only image') if self.readonly:
(x,y) = self.check_xy(xy) raise ValueError('Attempt to putpixel a read only image')
return self.set_pixel(x,y,color) (x, y) = self.check_xy(xy)
return self.set_pixel(x, y, color)
def __getitem__(self, xy): def __getitem__(self, xy):
""" """
@ -77,32 +80,34 @@ class PyAccess(object):
:param xy: The pixel coordinate, given as (x, y). :param xy: The pixel coordinate, given as (x, y).
""" """
(x,y) = self.check_xy(xy) (x, y) = self.check_xy(xy)
return self.get_pixel(x,y) return self.get_pixel(x, y)
putpixel = __setitem__ putpixel = __setitem__
getpixel = __getitem__ getpixel = __getitem__
def check_xy(self, xy): def check_xy(self, xy):
(x,y) = xy (x, y) = xy
if not (0 <= x < self.xsize and 0 <= y < self.ysize): if not (0 <= x < self.xsize and 0 <= y < self.ysize):
raise ValueError('pixel location out of range') raise ValueError('pixel location out of range')
return xy return xy
class _PyAccess32_2(PyAccess): class _PyAccess32_2(PyAccess):
""" PA, LA, stored in first and last bytes of a 32 bit word """ """ PA, LA, stored in first and last bytes of a 32 bit word """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return (pixel.r, pixel.a) return (pixel.r, pixel.a)
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
# tuple # tuple
pixel.r = min(color[0],255) pixel.r = min(color[0], 255)
pixel.a = min(color[1],255) pixel.a = min(color[1], 255)
class _PyAccess32_3(PyAccess): class _PyAccess32_3(PyAccess):
""" RGB and friends, stored in the first three bytes of a 32 bit word """ """ RGB and friends, stored in the first three bytes of a 32 bit word """
@ -110,33 +115,34 @@ class _PyAccess32_3(PyAccess):
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return (pixel.r, pixel.g, pixel.b) return (pixel.r, pixel.g, pixel.b)
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
# tuple # tuple
pixel.r = min(color[0],255) pixel.r = min(color[0], 255)
pixel.g = min(color[1],255) pixel.g = min(color[1], 255)
pixel.b = min(color[2],255) pixel.b = min(color[2], 255)
class _PyAccess32_4(PyAccess): class _PyAccess32_4(PyAccess):
""" RGBA etc, all 4 bytes of a 32 bit word """ """ RGBA etc, all 4 bytes of a 32 bit word """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return (pixel.r, pixel.g, pixel.b, pixel.a) return (pixel.r, pixel.g, pixel.b, pixel.a)
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
# tuple # tuple
pixel.r = min(color[0],255) pixel.r = min(color[0], 255)
pixel.g = min(color[1],255) pixel.g = min(color[1], 255)
pixel.b = min(color[2],255) pixel.b = min(color[2], 255)
pixel.a = min(color[3],255) pixel.a = min(color[3], 255)
class _PyAccess8(PyAccess): class _PyAccess8(PyAccess):
@ -144,26 +150,27 @@ class _PyAccess8(PyAccess):
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = self.image8 self.pixels = self.image8
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
try: try:
# integer # integer
self.pixels[y][x] = min(color,255) self.pixels[y][x] = min(color, 255)
except: except:
# tuple # tuple
self.pixels[y][x] = min(color[0],255) self.pixels[y][x] = min(color[0], 255)
class _PyAccessI16_N(PyAccess): class _PyAccessI16_N(PyAccess):
""" I;16 access, native bitendian without conversion """ """ I;16 access, native bitendian without conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('unsigned short **', self.image) self.pixels = ffi.cast('unsigned short **', self.image)
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
try: try:
# integer # integer
self.pixels[y][x] = min(color, 65535) self.pixels[y][x] = min(color, 65535)
@ -171,16 +178,17 @@ class _PyAccessI16_N(PyAccess):
# tuple # tuple
self.pixels[y][x] = min(color[0], 65535) self.pixels[y][x] = min(color[0], 65535)
class _PyAccessI16_L(PyAccess): class _PyAccessI16_L(PyAccess):
""" I;16L access, with conversion """ """ I;16L access, with conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('struct Pixel_I16 **', self.image) self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return pixel.l + pixel.r * 256 return pixel.l + pixel.r * 256
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
try: try:
color = min(color, 65535) color = min(color, 65535)
@ -190,16 +198,17 @@ class _PyAccessI16_L(PyAccess):
pixel.l = color & 0xFF pixel.l = color & 0xFF
pixel.r = color >> 8 pixel.r = color >> 8
class _PyAccessI16_B(PyAccess): class _PyAccessI16_B(PyAccess):
""" I;16B access, with conversion """ """ I;16B access, with conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('struct Pixel_I16 **', self.image) self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
def get_pixel(self, x,y): def get_pixel(self, x, y):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
return pixel.l *256 + pixel.r return pixel.l * 256 + pixel.r
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
pixel = self.pixels[y][x] pixel = self.pixels[y][x]
try: try:
color = min(color, 65535) color = min(color, 65535)
@ -209,17 +218,19 @@ class _PyAccessI16_B(PyAccess):
pixel.l = color >> 8 pixel.l = color >> 8
pixel.r = color & 0xFF pixel.r = color & 0xFF
class _PyAccessI32_N(PyAccess): class _PyAccessI32_N(PyAccess):
""" Signed Int32 access, native endian """ """ Signed Int32 access, native endian """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = self.image32 self.pixels = self.image32
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
self.pixels[y][x] = color self.pixels[y][x] = color
class _PyAccessI32_Swap(PyAccess): class _PyAccessI32_Swap(PyAccess):
""" I;32L/B access, with byteswapping conversion """ """ I;32L/B access, with byteswapping conversion """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
@ -228,24 +239,26 @@ class _PyAccessI32_Swap(PyAccess):
def reverse(self, i): def reverse(self, i):
orig = ffi.new('int *', i) orig = ffi.new('int *', i)
chars = ffi.cast('unsigned char *', orig) chars = ffi.cast('unsigned char *', orig)
chars[0],chars[1],chars[2],chars[3] = chars[3], chars[2],chars[1],chars[0] chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], \
chars[1], chars[0]
return ffi.cast('int *', chars)[0] return ffi.cast('int *', chars)[0]
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.reverse(self.pixels[y][x]) return self.reverse(self.pixels[y][x])
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
self.pixels[y][x] = self.reverse(color) self.pixels[y][x] = self.reverse(color)
class _PyAccessF(PyAccess): class _PyAccessF(PyAccess):
""" 32 bit float access """ """ 32 bit float access """
def _post_init(self, *args, **kwargs): def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast('float **', self.image32) self.pixels = ffi.cast('float **', self.image32)
def get_pixel(self, x,y): def get_pixel(self, x, y):
return self.pixels[y][x] return self.pixels[y][x]
def set_pixel(self, x,y, color): def set_pixel(self, x, y, color):
try: try:
# not a tuple # not a tuple
self.pixels[y][x] = color self.pixels[y][x] = color
@ -286,13 +299,15 @@ else:
mode_map['I;32L'] = _PyAccessI32_Swap mode_map['I;32L'] = _PyAccessI32_Swap
mode_map['I;32B'] = _PyAccessI32_N mode_map['I;32B'] = _PyAccessI32_N
def new(img, readonly=False):
def new(img, readonly=False):
access_type = mode_map.get(img.mode, None) access_type = mode_map.get(img.mode, None)
if not access_type: if not access_type:
if DEBUG: print ("PyAccess Not Implemented: %s" % img.mode) if DEBUG:
print("PyAccess Not Implemented: %s" % img.mode)
return None return None
if DEBUG: print ("New PyAccess: %s" % img.mode) if DEBUG:
print("New PyAccess: %s" % img.mode)
return access_type(img, readonly) return access_type(img, readonly)
# End of file

View File

@ -16,6 +16,7 @@
from PIL import ContainerIO from PIL import ContainerIO
## ##
# A file object that provides read access to a given member of a TAR # A file object that provides read access to a given member of a TAR
# file. # file.

View File

@ -33,6 +33,7 @@ except ImportError:
i32 = _binary.i32le i32 = _binary.i32le
## ##
# Load texture from a Quake2 WAL texture file. # Load texture from a Quake2 WAL texture file.
# <p> # <p>

View File

@ -12,7 +12,7 @@ _VALID_WEBP_MODES = {
_VP8_MODES_BY_IDENTIFIER = { _VP8_MODES_BY_IDENTIFIER = {
b"VP8 ": "RGB", b"VP8 ": "RGB",
b"VP8X": "RGBA", b"VP8X": "RGBA",
b"VP8L": "RGBA", # lossless b"VP8L": "RGBA", # lossless
} }
@ -30,7 +30,8 @@ class WebPImageFile(ImageFile.ImageFile):
format_description = "WebP image" format_description = "WebP image"
def _open(self): def _open(self):
data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(self.fp.read()) data, width, height, self.mode, icc_profile, exif = \
_webp.WebPDecode(self.fp.read())
if icc_profile: if icc_profile:
self.info["icc_profile"] = icc_profile self.info["icc_profile"] = icc_profile

View File

@ -24,6 +24,7 @@ _handler = None
if str != bytes: if str != bytes:
long = int long = int
## ##
# Install application-specific WMF image handler. # Install application-specific WMF image handler.
# #
@ -43,7 +44,7 @@ if hasattr(Image.core, "drawwmf"):
self.bbox = im.info["wmf_bbox"] self.bbox = im.info["wmf_bbox"]
def load(self, im): def load(self, im):
im.fp.seek(0) # rewind im.fp.seek(0) # rewind
return Image.frombytes( return Image.frombytes(
"RGB", im.size, "RGB", im.size,
Image.core.drawwmf(im.fp.read(), im.size, self.bbox), Image.core.drawwmf(im.fp.read(), im.size, self.bbox),
@ -56,6 +57,7 @@ if hasattr(Image.core, "drawwmf"):
word = _binary.i16le word = _binary.i16le
def short(c, o=0): def short(c, o=0):
v = word(c, o) v = word(c, o)
if v >= 32768: if v >= 32768:
@ -64,6 +66,7 @@ def short(c, o=0):
dword = _binary.i32le dword = _binary.i32le
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Read WMF file # Read WMF file
@ -74,6 +77,7 @@ def _accept(prefix):
prefix[:4] == b"\x01\x00\x00\x00" prefix[:4] == b"\x01\x00\x00\x00"
) )
## ##
# Image plugin for Windows metafiles. # Image plugin for Windows metafiles.
@ -95,8 +99,10 @@ class WmfStubImageFile(ImageFile.StubImageFile):
inch = word(s, 14) inch = word(s, 14)
# get bounding box # get bounding box
x0 = short(s, 6); y0 = short(s, 8) x0 = short(s, 6)
x1 = short(s, 10); y1 = short(s, 12) y0 = short(s, 8)
x1 = short(s, 10)
y1 = short(s, 12)
# normalize size to 72 dots per inch # normalize size to 72 dots per inch
size = (x1 - x0) * 72 // inch, (y1 - y0) * 72 // inch size = (x1 - x0) * 72 // inch, (y1 - y0) * 72 // inch
@ -115,8 +121,10 @@ class WmfStubImageFile(ImageFile.StubImageFile):
# enhanced metafile # enhanced metafile
# get bounding box # get bounding box
x0 = dword(s, 8); y0 = dword(s, 12) x0 = dword(s, 8)
x1 = dword(s, 16); y1 = dword(s, 20) y0 = dword(s, 12)
x1 = dword(s, 16)
y1 = dword(s, 20)
# get frame (in 0.01 millimeter units) # get frame (in 0.01 millimeter units)
frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36) frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36)

View File

@ -30,6 +30,7 @@ for r in range(8):
for b in range(4): for b in range(4):
PALETTE = PALETTE + (o8((r*255)//7)+o8((g*255)//7)+o8((b*255)//3)) PALETTE = PALETTE + (o8((r*255)//7)+o8((g*255)//7)+o8((b*255)//3))
## ##
# Image plugin for XV thumbnail images. # Image plugin for XV thumbnail images.

View File

@ -35,9 +35,11 @@ xbm_head = re.compile(
b"[\\000-\\377]*_bits\\[\\]" b"[\\000-\\377]*_bits\\[\\]"
) )
def _accept(prefix): def _accept(prefix):
return prefix.lstrip()[:7] == b"#define" return prefix.lstrip()[:7] == b"#define"
## ##
# Image plugin for X11 bitmaps. # Image plugin for X11 bitmaps.
@ -81,7 +83,7 @@ def _save(im, fp, filename):
fp.write(b"static char im_bits[] = {\n") fp.write(b"static char im_bits[] = {\n")
ImageFile._save(im, fp, [("xbm", (0,0)+im.size, 0, None)]) ImageFile._save(im, fp, [("xbm", (0, 0)+im.size, 0, None)])
fp.write(b"};\n") fp.write(b"};\n")

View File

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

View File

@ -16,16 +16,17 @@ if bytes is str:
return ord(c) return ord(c)
def o8(i): def o8(i):
return chr(i&255) return chr(i & 255)
else: else:
def i8(c): def i8(c):
return c if c.__class__ is int else c[0] return c if c.__class__ is int else c[0]
def o8(i): def o8(i):
return bytes((i&255,)) return bytes((i & 255,))
# Input, le = little endian, be = big endian # Input, le = little endian, be = big endian
#TODO: replace with more readable struct.unpack equivalent # TODO: replace with more readable struct.unpack equivalent
def i16le(c, o=0): def i16le(c, o=0):
""" """
Converts a 2-bytes (16 bits) string to an integer. Converts a 2-bytes (16 bits) string to an integer.
@ -33,7 +34,8 @@ def i16le(c, o=0):
c: string containing bytes to convert c: string containing bytes to convert
o: offset of bytes to convert in string o: offset of bytes to convert in string
""" """
return i8(c[o]) | (i8(c[o+1])<<8) return i8(c[o]) | (i8(c[o+1]) << 8)
def i32le(c, o=0): def i32le(c, o=0):
""" """
@ -42,24 +44,33 @@ def i32le(c, o=0):
c: string containing bytes to convert c: string containing bytes to convert
o: offset of bytes to convert in string o: offset of bytes to convert in string
""" """
return i8(c[o]) | (i8(c[o+1])<<8) | (i8(c[o+2])<<16) | (i8(c[o+3])<<24) return (i8(c[o]) | (i8(c[o+1]) << 8) | (i8(c[o+2]) << 16) |
(i8(c[o+3]) << 24))
def i16be(c, o=0): def i16be(c, o=0):
return (i8(c[o])<<8) | i8(c[o+1]) return (i8(c[o]) << 8) | i8(c[o+1])
def i32be(c, o=0): def i32be(c, o=0):
return (i8(c[o])<<24) | (i8(c[o+1])<<16) | (i8(c[o+2])<<8) | i8(c[o+3]) return ((i8(c[o]) << 24) | (i8(c[o+1]) << 16) |
(i8(c[o+2]) << 8) | i8(c[o+3]))
# Output, le = little endian, be = big endian # Output, le = little endian, be = big endian
def o16le(i): def o16le(i):
return o8(i) + o8(i>>8) return o8(i) + o8(i >> 8)
def o32le(i): def o32le(i):
return o8(i) + o8(i>>8) + o8(i>>16) + o8(i>>24) return o8(i) + o8(i >> 8) + o8(i >> 16) + o8(i >> 24)
def o16be(i): def o16be(i):
return o8(i>>8) + o8(i) return o8(i >> 8) + o8(i)
def o32be(i): def o32be(i):
return o8(i>>24) + o8(i>>16) + o8(i>>8) + o8(i) return o8(i >> 24) + o8(i >> 16) + o8(i >> 8) + o8(i)
# End of file

55
RELEASING.md Normal file
View File

@ -0,0 +1,55 @@
# Release Checklist
## Main Release
Released quarterly.
* [ ] Get master to the appropriate code release state. [Travis CI](https://travis-ci.org/python-pillow/Pillow) should be running cleanly for all merges to master.
* [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c`, Update date in `CHANGES.rst`.
* [ ] Tag and push to release branch in python-pillow repo.
* [ ] Upload binaries.
## Point Release
Released as required for security or installation fixes.
* [ ] Make necessary changes in master.
* [ ] Cherry pick individual commits. Touch up `CHANGES.rst` to reflect reality.
* [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c`
* [ ] Push to release branch in personal repo. Let Travis run cleanly.
* [ ] Tag and push to release branch in python-pillow repo.
* [ ] Upload binaries.
## Embargoed Release
Security fixes that need to be pushed to the distros prior to public release.
* [ ] Prepare patch for all versions that will get a fix. Test against local installations.
* [ ] Commit against master, cherry pick to affected release branches.
* [ ] Run local test matrix on each release & Python version.
* [ ] Privately send to distros.
* [ ] Amend any commits with the CVE #
* [ ] On release date, tag and push to GitHub.
```
git checkout 2.5.x
git tag 2.5.3
git push origin 2.5.x
git push origin --tags
```
* [ ] Upload binaries
## Binary Upload Process
* [ ] Ping cgohlke for Windows binaries
* [ ] From a clean source directory with no extra temp files:
```
python setup.py register
python setup.py sdist --format=zip upload
python setup.py sdist upload
```
(Debian requests a tarball, everyone else would just prefer that we choose one and stick to it. So both it is)
* [ ] Push a commit to https://github.com/python-pillow/pillow-wheels to build OSX versions (UNDONE latest tag or specific release???)
* [ ] Retrieve the OS X Wheels from Rackspace files, upload to PyPi (Twine?)
* [ ] Grab Windows binaries, `twine upload dist/*.[whl|egg]`. Manually upload .exe installers.
* [ ] Announce release availability. [Twitter](https://twitter.com/pythonpillow), web.

View File

@ -6,5 +6,3 @@ import sys
if sys.maxsize < 2**32: if sys.maxsize < 2**32:
im = Image.new('L', (999999, 999999), 0) im = Image.new('L', (999999, 999999), 0)

View File

@ -7,4 +7,5 @@ from io import BytesIO
if bytes is str: if bytes is str:
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00'))) Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00')))
else: else:
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00', 'latin-1'))) Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00',
'latin-1')))

View File

@ -5,7 +5,9 @@ from PIL import Image
from io import BytesIO from io import BytesIO
if bytes is str: if bytes is str:
Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang'))) Image.open(BytesIO(bytes(
'\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang')))
else: else:
Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang', 'latin-1'))) Image.open(BytesIO(bytes(
'\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang',
'latin-1')))

212
Tests/check_jpeg_leaks.py Normal file
View File

@ -0,0 +1,212 @@
from helper import unittest, PillowTestCase, hopper
from PIL import Image
from io import BytesIO
import sys
iterations = 5000
"""
When run on a system without the jpeg leak fixes, the valgrind runs look like this.
NOSE_PROCESSES=0 NOSE_TIMEOUT=600 valgrind --tool=massif \
python test-installed.py -s -v Tests/check_jpeg_leaks.py
"""
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
class TestJpegLeaks(PillowTestCase):
"""
pre patch:
MB
31.62^ :
| @:@:@:@#::
| @:@:@@:@:@:@:@:@#::
| ::::::::@:@:@@:@:@:@:@:@#::
| :::::@::::::: ::::@:@:@@:@:@:@:@:@#::
| @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| ::::::@::::@:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| ::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| :::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| ::::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| : ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| @: ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| @@: ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| :@@: ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| :@@: ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| :@:@@: ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| :@:@@: ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
| :@:@@: ::::::::: : :@: : @:::::::::::::@: : ::: ::::@:@:@@:@:@:@:@:@#::
0 +----------------------------------------------------------------------->Gi
0 8.535
post-patch:
MB
21.03^ :::@@:::@::::@@:::::::@@::::::::@::::::::::::@:::@:::::::@::::
| #:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| #:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :::#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| : :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| : :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| @: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| @@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| @@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| @@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :@@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :@@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :@:@@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :@:@@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :@:@@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :@:@@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
| :@:@@: :#:::@ :::@::::@ : :: : @ :::::: :@:: ::: :::: @:: @:::::::@::::
0 +----------------------------------------------------------------------->Gi
0 8.421
"""
def test_qtables_leak(self):
im = hopper('RGB')
standard_l_qtable = [int(s) for s in """
16 11 10 16 24 40 51 61
12 12 14 19 26 58 60 55
14 13 16 24 40 57 69 56
14 17 22 29 51 87 80 62
18 22 37 56 68 109 103 77
24 35 55 64 81 104 113 92
49 64 78 87 103 121 120 101
72 92 95 98 112 100 103 99
""".split(None)]
standard_chrominance_qtable = [int(s) for s in """
17 18 24 47 99 99 99 99
18 21 26 66 99 99 99 99
24 26 56 99 99 99 99 99
47 66 99 99 99 99 99 99
99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99
""".split(None)]
qtables = [standard_l_qtable,
standard_chrominance_qtable]
for count in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
"""
pre patch:
MB
177.1^ #
| @@@#
| :@@@@@@#
| ::::@@@@@@#
| ::::::::@@@@@@#
| @@::::: ::::@@@@@@#
| @@@@ ::::: ::::@@@@@@#
| @@@@@@@ ::::: ::::@@@@@@#
| @@::@@@@@@@ ::::: ::::@@@@@@#
| @@@@ : @@@@@@@ ::::: ::::@@@@@@#
| @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
0 +----------------------------------------------------------------------->Gi
0 11.37
post patch:
MB
21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
0 +----------------------------------------------------------------------->Gi
0 11.33
"""
def test_exif_leak(self):
im = hopper('RGB')
exif = b'12345678'*4096
for count in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", exif=exif)
"""
base case:
MB
20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@:::
| ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@:::
| # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
0 +----------------------------------------------------------------------->Gi
0 7.882
"""
def test_base_save(self):
im = hopper('RGB')
for count in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG")
if __name__ == '__main__':
unittest.main()

View File

@ -9,6 +9,6 @@ i = Image.new("RGB", (500, h), "white")
d = ImageDraw.Draw(i) d = ImageDraw.Draw(i)
# this line causes a MemoryError # this line causes a MemoryError
d.text((0,0), s, font=f, fill=0) d.text((0, 0), s, font=f, fill=0)
i.show() i.show()

View File

@ -100,7 +100,8 @@ class PillowTestCase(unittest.TestCase):
ave_diff = float(diff)/(a.size[0]*a.size[1]) ave_diff = float(diff)/(a.size[0]*a.size[1])
self.assertGreaterEqual( self.assertGreaterEqual(
epsilon, ave_diff, epsilon, ave_diff,
(msg or '') + " average pixel value difference %.4f > epsilon %.4f" % ( (msg or '') +
" average pixel value difference %.4f > epsilon %.4f" % (
ave_diff, epsilon)) ave_diff, epsilon))
def assert_warning(self, warn_class, func): def assert_warning(self, warn_class, func):
@ -138,7 +139,8 @@ class PillowTestCase(unittest.TestCase):
if travis is not None: if travis is not None:
skip = skip and (travis == bool(os.environ.get('TRAVIS', False))) skip = skip and (travis == bool(os.environ.get('TRAVIS', False)))
if interpreter is not None: if interpreter is not None:
skip = skip and (interpreter == 'pypy' and hasattr(sys, 'pypy_version_info')) skip = skip and (interpreter == 'pypy' and
hasattr(sys, 'pypy_version_info'))
if skip: if skip:
self.skipTest(msg or "Known Bad Test") self.skipTest(msg or "Known Bad Test")

Binary file not shown.

25
Tests/icc/LICENSE.txt Normal file
View File

@ -0,0 +1,25 @@
from http://www.color.org/srgbprofiles.xalter
Terms of use
To anyone who acknowledges that the file "sRGB_v4_ICC_preference.icc"
is provided "AS IS" WITH NO EXPRESS OR IMPLIED WARRANTY, permission
to use, copy and distribute this file for any purpose is hereby
granted without fee, provided that the file is not changed including
the ICC copyright notice tag, and that the name of ICC shall not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission. ICC makes no
representations about the suitability of this software for any
purpose.
To anyone who acknowledges that the file
"sRGB_IEC61966-2-1_blackscaled.icc" is provided "AS IS" WITH NO
EXPRESS OR IMPLIED WARRANTY, permission to use, copy and distribute
these file for any purpose is hereby granted without fee, provided
that the file is not changed including the ICC copyright notice tag,
and that the name of ICC shall not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission. ICC makes no representations about the suitability
of this software for any purpose.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,8 @@
import sys import sys
sys.path.insert(0, ".") sys.path.insert(0, ".")
import glob, os import glob
import os
import traceback import traceback
for file in glob.glob("PIL/*.py"): for file in glob.glob("PIL/*.py"):

View File

@ -1,6 +1,6 @@
import sys import sys
from helper import * from helper import unittest, PillowTestCase
# This test is not run automatically. # This test is not run automatically.
# #

View File

@ -1,6 +1,6 @@
import sys import sys
from helper import * from helper import unittest, PillowTestCase
# This test is not run automatically. # This test is not run automatically.
# #

View File

@ -1,3 +1,4 @@
from __future__ import print_function
import sys import sys
sys.path.insert(0, ".") sys.path.insert(0, ".")

View File

@ -121,7 +121,7 @@ class TestFileGif(PillowTestCase):
try: try:
while True: while True:
framecount += 1 framecount += 1
img.seek(img.tell() +1) img.seek(img.tell() + 1)
except EOFError: except EOFError:
self.assertEqual(framecount, 5) self.assertEqual(framecount, 5)
@ -129,7 +129,7 @@ class TestFileGif(PillowTestCase):
img = Image.open("Tests/images/dispose_none.gif") img = Image.open("Tests/images/dispose_none.gif")
try: try:
while True: while True:
img.seek(img.tell() +1) img.seek(img.tell() + 1)
self.assertEqual(img.disposal_method, 1) self.assertEqual(img.disposal_method, 1)
except EOFError: except EOFError:
pass pass
@ -138,7 +138,7 @@ class TestFileGif(PillowTestCase):
img = Image.open("Tests/images/dispose_bgnd.gif") img = Image.open("Tests/images/dispose_bgnd.gif")
try: try:
while True: while True:
img.seek(img.tell() +1) img.seek(img.tell() + 1)
self.assertEqual(img.disposal_method, 2) self.assertEqual(img.disposal_method, 2)
except EOFError: except EOFError:
pass pass
@ -147,7 +147,7 @@ class TestFileGif(PillowTestCase):
img = Image.open("Tests/images/dispose_prev.gif") img = Image.open("Tests/images/dispose_prev.gif")
try: try:
while True: while True:
img.seek(img.tell() +1) img.seek(img.tell() + 1)
self.assertEqual(img.disposal_method, 3) self.assertEqual(img.disposal_method, 3)
except EOFError: except EOFError:
pass pass
@ -155,8 +155,9 @@ class TestFileGif(PillowTestCase):
def test_iss634(self): def test_iss634(self):
img = Image.open("Tests/images/iss634.gif") img = Image.open("Tests/images/iss634.gif")
# seek to the second frame # seek to the second frame
img.seek(img.tell() +1) img.seek(img.tell() + 1)
# all transparent pixels should be replaced with the color from the first frame # all transparent pixels should be replaced with the color from the
# first frame
self.assertEqual(img.histogram()[img.info['transparency']], 0) self.assertEqual(img.histogram()[img.info['transparency']], 0)

View File

@ -277,17 +277,17 @@ class TestFileJpeg(PillowTestCase):
99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
""".split(None)] """.split(None)]
# list of qtable lists # list of qtable lists
self.assert_image_similar(im, self.assert_image_similar(
self.roundtrip(im, im, self.roundtrip(
qtables=[standard_l_qtable, im, qtables=[standard_l_qtable, standard_chrominance_qtable]),
standard_chrominance_qtable]), 30)
30)
# tuple of qtable lists # tuple of qtable lists
self.assert_image_similar(im, self.assert_image_similar(
self.roundtrip(im, im, self.roundtrip(
qtables=(standard_l_qtable, im, qtables=(standard_l_qtable, standard_chrominance_qtable)),
standard_chrominance_qtable)), 30)
30)
# dict of qtable lists # dict of qtable lists
self.assert_image_similar(im, self.assert_image_similar(im,
self.roundtrip(im, self.roundtrip(im,
@ -295,6 +295,21 @@ class TestFileJpeg(PillowTestCase):
1: standard_chrominance_qtable}), 1: standard_chrominance_qtable}),
30) 30)
# not a sequence
self.assertRaises(Exception, lambda: self.roundtrip(im, qtables='a'))
# sequence wrong length
self.assertRaises(Exception, lambda: self.roundtrip(im, qtables=[]))
# sequence wrong length
self.assertRaises(Exception, lambda: self.roundtrip(im, qtables=[1,2,3,4,5]))
# qtable entry not a sequence
self.assertRaises(Exception, lambda: self.roundtrip(im, qtables=[1]))
# qtable entry has wrong number of items
self.assertRaises(Exception, lambda: self.roundtrip(im, qtables=[[1,2,3,4]]))
@unittest.skipUnless(djpeg_available(), "djpeg not available") @unittest.skipUnless(djpeg_available(), "djpeg not available")
def test_load_djpeg(self): def test_load_djpeg(self):
img = Image.open(TEST_FILE) img = Image.open(TEST_FILE)

View File

@ -38,7 +38,7 @@ class TestFileMpo(PillowTestCase):
self.assertEqual(im.applist[0][0], 'APP1') self.assertEqual(im.applist[0][0], 'APP1')
self.assertEqual(im.applist[1][0], 'APP2') self.assertEqual(im.applist[1][0], 'APP2')
self.assertEqual(im.applist[1][1][:16], self.assertEqual(im.applist[1][1][:16],
b'MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00') b'MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00')
self.assertEqual(len(im.applist), 2) self.assertEqual(len(im.applist), 2)
def test_exif(self): def test_exif(self):
@ -71,7 +71,7 @@ class TestFileMpo(PillowTestCase):
self.assertFalse(mpattr['DependentChildImageFlag']) self.assertFalse(mpattr['DependentChildImageFlag'])
self.assertEqual(mpattr['ImageDataFormat'], 'JPEG') self.assertEqual(mpattr['ImageDataFormat'], 'JPEG')
self.assertEqual(mpattr['MPType'], self.assertEqual(mpattr['MPType'],
'Multi-Frame Image: (Disparity)') 'Multi-Frame Image: (Disparity)')
self.assertEqual(mpattr['Reserved'], 0) self.assertEqual(mpattr['Reserved'], 0)
frameNumber += 1 frameNumber += 1
@ -82,7 +82,8 @@ class TestFileMpo(PillowTestCase):
# prior to first image raises an error, both blatant and borderline # prior to first image raises an error, both blatant and borderline
self.assertRaises(EOFError, im.seek, -1) self.assertRaises(EOFError, im.seek, -1)
self.assertRaises(EOFError, im.seek, -523) self.assertRaises(EOFError, im.seek, -523)
# after the final image raises an error, both blatant and borderline # after the final image raises an error,
# both blatant and borderline
self.assertRaises(EOFError, im.seek, 2) self.assertRaises(EOFError, im.seek, 2)
self.assertRaises(EOFError, im.seek, 523) self.assertRaises(EOFError, im.seek, 523)
# bad calls shouldn't change the frame # bad calls shouldn't change the frame

View File

@ -29,7 +29,6 @@ class TestFilePalm(PillowTestCase):
converted = self.open_withImagemagick(outfile) converted = self.open_withImagemagick(outfile)
self.assert_image_equal(converted, im) self.assert_image_equal(converted, im)
def test_monochrome(self): def test_monochrome(self):
# Arrange # Arrange
mode = "1" mode = "1"

View File

@ -151,13 +151,16 @@ class TestFilePng(PillowTestCase):
self.assertEqual(im.info["spam"].lang, "en") self.assertEqual(im.info["spam"].lang, "en")
self.assertEqual(im.info["spam"].tkey, "Spam") self.assertEqual(im.info["spam"].tkey, "Spam")
im = load(HEAD + chunk(b'iTXt', b'spam\0\1\0en\0Spam\0' + zlib.compress(b"egg")[:1]) + TAIL) im = load(HEAD + chunk(b'iTXt', b'spam\0\1\0en\0Spam\0' +
zlib.compress(b"egg")[:1]) + TAIL)
self.assertEqual(im.info, {}) self.assertEqual(im.info, {})
im = load(HEAD + chunk(b'iTXt', b'spam\0\1\1en\0Spam\0' + zlib.compress(b"egg")) + TAIL) im = load(HEAD + chunk(b'iTXt', b'spam\0\1\1en\0Spam\0' +
zlib.compress(b"egg")) + TAIL)
self.assertEqual(im.info, {}) self.assertEqual(im.info, {})
im = load(HEAD + chunk(b'iTXt', b'spam\0\1\0en\0Spam\0' + zlib.compress(b"egg")) + TAIL) im = load(HEAD + chunk(b'iTXt', b'spam\0\1\0en\0Spam\0' +
zlib.compress(b"egg")) + TAIL)
self.assertEqual(im.info, {"spam": "egg"}) self.assertEqual(im.info, {"spam": "egg"})
self.assertEqual(im.info["spam"].lang, "en") self.assertEqual(im.info["spam"].lang, "en")
self.assertEqual(im.info["spam"].tkey, "Spam") self.assertEqual(im.info["spam"].tkey, "Spam")
@ -271,7 +274,8 @@ class TestFilePng(PillowTestCase):
im = Image.new("RGB", (32, 32)) im = Image.new("RGB", (32, 32))
info = PngImagePlugin.PngInfo() info = PngImagePlugin.PngInfo()
info.add_itxt("spam", "Eggs", "en", "Spam") info.add_itxt("spam", "Eggs", "en", "Spam")
info.add_text("eggs", PngImagePlugin.iTXt("Spam", "en", "Eggs"), zip=True) info.add_text("eggs", PngImagePlugin.iTXt("Spam", "en", "Eggs"),
zip=True)
im = roundtrip(im, pnginfo=info) im = roundtrip(im, pnginfo=info)
self.assertEqual(im.info, {"spam": "Eggs", "eggs": "Spam"}) self.assertEqual(im.info, {"spam": "Eggs", "eggs": "Spam"})
@ -303,11 +307,11 @@ class TestFilePng(PillowTestCase):
self.assertEqual(im.info, {"Text": value}) self.assertEqual(im.info, {"Text": value})
if str is not bytes: if str is not bytes:
rt_text(" Aa" + chr(0xa0) + chr(0xc4) + chr(0xff)) # Latin1 rt_text(" Aa" + chr(0xa0) + chr(0xc4) + chr(0xff)) # Latin1
rt_text(chr(0x400) + chr(0x472) + chr(0x4ff)) # Cyrillic rt_text(chr(0x400) + chr(0x472) + chr(0x4ff)) # Cyrillic
rt_text(chr(0x4e00) + chr(0x66f0) + # CJK rt_text(chr(0x4e00) + chr(0x66f0) + # CJK
chr(0x9fba) + chr(0x3042) + chr(0xac00)) chr(0x9fba) + chr(0x3042) + chr(0xac00))
rt_text("A" + chr(0xc4) + chr(0x472) + chr(0x3042)) # Combined rt_text("A" + chr(0xc4) + chr(0x472) + chr(0x3042)) # Combined
def test_scary(self): def test_scary(self):
# Check reading of evil PNG file. For information, see: # Check reading of evil PNG file. For information, see:

View File

@ -18,7 +18,8 @@ class TestFileWebpAlpha(PillowTestCase):
self.skipTest('WebP support not installed') self.skipTest('WebP support not installed')
if _webp.WebPDecoderBuggyAlpha(self): if _webp.WebPDecoderBuggyAlpha(self):
self.skipTest("Buggy early version of WebP installed, not testing transparency") self.skipTest("Buggy early version of WebP installed, "
"not testing transparency")
def test_read_rgba(self): def test_read_rgba(self):
# Generated with `cwebp transparent.png -o transparent.webp` # Generated with `cwebp transparent.png -o transparent.webp`

View File

@ -2,54 +2,60 @@ from helper import unittest, PillowTestCase, hopper
from PIL import Image from PIL import Image
import colorsys, itertools import colorsys
import itertools
class TestFormatHSV(PillowTestCase): class TestFormatHSV(PillowTestCase):
def int_to_float(self, i): def int_to_float(self, i):
return float(i)/255.0 return float(i)/255.0
def str_to_float(self, i): def str_to_float(self, i):
return float(ord(i))/255.0 return float(ord(i))/255.0
def to_int(self, f): def to_int(self, f):
return int(f*255.0) return int(f*255.0)
def tuple_to_ints(self, tp): def tuple_to_ints(self, tp):
x,y,z = tp x, y, z = tp
return (int(x*255.0), int(y*255.0), int(z*255.0)) return (int(x*255.0), int(y*255.0), int(z*255.0))
def test_sanity(self): def test_sanity(self):
im = Image.new('HSV', (100,100)) Image.new('HSV', (100, 100))
def wedge(self): def wedge(self):
w =Image._wedge() w = Image._wedge()
w90 = w.rotate(90) w90 = w.rotate(90)
(px, h) = w.size (px, h) = w.size
r = Image.new('L', (px*3,h)) r = Image.new('L', (px*3, h))
g = r.copy() g = r.copy()
b = r.copy() b = r.copy()
r.paste(w, (0,0)) r.paste(w, (0, 0))
r.paste(w90, (px,0)) r.paste(w90, (px, 0))
g.paste(w90, (0,0)) g.paste(w90, (0, 0))
g.paste(w, (2*px,0)) g.paste(w, (2*px, 0))
b.paste(w, (px,0)) b.paste(w, (px, 0))
b.paste(w90, (2*px,0)) b.paste(w90, (2*px, 0))
img = Image.merge('RGB',(r,g,b)) img = Image.merge('RGB', (r, g, b))
#print (("%d, %d -> "% (int(1.75*px),int(.25*px))) + \ # print (("%d, %d -> "% (int(1.75*px),int(.25*px))) + \
# "(%s, %s, %s)"%img.getpixel((1.75*px, .25*px))) # "(%s, %s, %s)"%img.getpixel((1.75*px, .25*px)))
#print (("%d, %d -> "% (int(.75*px),int(.25*px))) + \ # print (("%d, %d -> "% (int(.75*px),int(.25*px))) + \
# "(%s, %s, %s)"%img.getpixel((.75*px, .25*px))) # "(%s, %s, %s)"%img.getpixel((.75*px, .25*px)))
return img return img
def to_xxx_colorsys(self, im, func, mode): def to_xxx_colorsys(self, im, func, mode):
# convert the hard way using the library colorsys routines. # convert the hard way using the library colorsys routines.
(r,g,b) = im.split() (r, g, b) = im.split()
if bytes is str: if bytes is str:
conv_func = self.str_to_float conv_func = self.str_to_float
@ -61,16 +67,19 @@ class TestFormatHSV(PillowTestCase):
else: else:
iter_helper = itertools.zip_longest iter_helper = itertools.zip_longest
converted = [self.tuple_to_ints(func(conv_func(_r), conv_func(_g),
converted = [self.tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b))) conv_func(_b)))
for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(), b.tobytes())] for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(),
b.tobytes())]
if str is bytes: if str is bytes:
new_bytes = b''.join(chr(h)+chr(s)+chr(v) for (h,s,v) in converted) new_bytes = b''.join(chr(h)+chr(s)+chr(v) for (
h, s, v) in converted)
else: else:
new_bytes = b''.join(bytes(chr(h)+chr(s)+chr(v), 'latin-1') for (h,s,v) in converted) new_bytes = b''.join(bytes(chr(h)+chr(s)+chr(v), 'latin-1') for (
h, s, v) in converted)
hsv = Image.frombytes(mode,r.size, new_bytes) hsv = Image.frombytes(mode, r.size, new_bytes)
return hsv return hsv
@ -81,18 +90,18 @@ class TestFormatHSV(PillowTestCase):
return self.to_xxx_colorsys(im, colorsys.hsv_to_rgb, 'RGB') return self.to_xxx_colorsys(im, colorsys.hsv_to_rgb, 'RGB')
def test_wedge(self): def test_wedge(self):
src = self.wedge().resize((3*32,32),Image.BILINEAR) src = self.wedge().resize((3*32, 32), Image.BILINEAR)
im = src.convert('HSV') im = src.convert('HSV')
comparable = self.to_hsv_colorsys(src) comparable = self.to_hsv_colorsys(src)
#print (im.getpixel((448, 64))) # print (im.getpixel((448, 64)))
#print (comparable.getpixel((448, 64))) # print (comparable.getpixel((448, 64)))
#print(im.split()[0].histogram()) # print(im.split()[0].histogram())
#print(comparable.split()[0].histogram()) # print(comparable.split()[0].histogram())
#im.split()[0].show() # im.split()[0].show()
#comparable.split()[0].show() # comparable.split()[0].show()
self.assert_image_similar(im.split()[0], comparable.split()[0], self.assert_image_similar(im.split()[0], comparable.split()[0],
1, "Hue conversion is wrong") 1, "Hue conversion is wrong")
@ -101,15 +110,15 @@ class TestFormatHSV(PillowTestCase):
self.assert_image_similar(im.split()[2], comparable.split()[2], self.assert_image_similar(im.split()[2], comparable.split()[2],
1, "Value conversion is wrong") 1, "Value conversion is wrong")
#print (im.getpixel((192, 64))) # print (im.getpixel((192, 64)))
comparable = src comparable = src
im = im.convert('RGB') im = im.convert('RGB')
#im.split()[0].show() # im.split()[0].show()
#comparable.split()[0].show() # comparable.split()[0].show()
#print (im.getpixel((192, 64))) # print (im.getpixel((192, 64)))
#print (comparable.getpixel((192, 64))) # print (comparable.getpixel((192, 64)))
self.assert_image_similar(im.split()[0], comparable.split()[0], self.assert_image_similar(im.split()[0], comparable.split()[0],
3, "R conversion is wrong") 3, "R conversion is wrong")
@ -118,7 +127,6 @@ class TestFormatHSV(PillowTestCase):
self.assert_image_similar(im.split()[2], comparable.split()[2], self.assert_image_similar(im.split()[2], comparable.split()[2],
3, "B conversion is wrong") 3, "B conversion is wrong")
def test_convert(self): def test_convert(self):
im = hopper('RGB').convert('HSV') im = hopper('RGB').convert('HSV')
comparable = self.to_hsv_colorsys(hopper('RGB')) comparable = self.to_hsv_colorsys(hopper('RGB'))
@ -136,18 +144,16 @@ class TestFormatHSV(PillowTestCase):
self.assert_image_similar(im.split()[2], comparable.split()[2], self.assert_image_similar(im.split()[2], comparable.split()[2],
1, "Value conversion is wrong") 1, "Value conversion is wrong")
def test_hsv_to_rgb(self): def test_hsv_to_rgb(self):
comparable = self.to_hsv_colorsys(hopper('RGB')) comparable = self.to_hsv_colorsys(hopper('RGB'))
converted = comparable.convert('RGB') converted = comparable.convert('RGB')
comparable = self.to_rgb_colorsys(comparable) comparable = self.to_rgb_colorsys(comparable)
# print(converted.split()[1].histogram()) # print(converted.split()[1].histogram())
# print(target.split()[1].histogram()) # print(target.split()[1].histogram())
# print ([ord(x) for x in target.split()[1].tobytes()[:80]])
# print ([ord(x) for x in converted.split()[1].tobytes()[:80]])
# print ([ord(x) for x in target.split()[1].tobytes()[:80]])
# print ([ord(x) for x in converted.split()[1].tobytes()[:80]])
self.assert_image_similar(converted.split()[0], comparable.split()[0], self.assert_image_similar(converted.split()[0], comparable.split()[0],
3, "R conversion is wrong") 3, "R conversion is wrong")
@ -157,12 +163,6 @@ class TestFormatHSV(PillowTestCase):
3, "B conversion is wrong") 3, "B conversion is wrong")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,7 +1,5 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper
from PIL import Image
class TestImageCopy(PillowTestCase): class TestImageCopy(PillowTestCase):

View File

@ -1,7 +1,5 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper
import sys
class TestImagePoint(PillowTestCase): class TestImagePoint(PillowTestCase):
@ -26,7 +24,7 @@ class TestImagePoint(PillowTestCase):
""" """
# This takes _forever_ on PyPy. Open Bug, # This takes _forever_ on PyPy. Open Bug,
# see https://github.com/python-pillow/Pillow/issues/484 # see https://github.com/python-pillow/Pillow/issues/484
#self.skipKnownBadTest(msg="Too Slow on pypy", interpreter='pypy') # self.skipKnownBadTest(msg="Too Slow on pypy", interpreter='pypy')
im = hopper("I") im = hopper("I")
im.point(list(range(256))*256, 'L') im.point(list(range(256))*256, 'L')

View File

@ -41,9 +41,8 @@ class TestImagePutData(PillowTestCase):
else: else:
self.assertEqual(put(sys.maxsize), (255, 255, 255, 127)) self.assertEqual(put(sys.maxsize), (255, 255, 255, 127))
def test_pypy_performance(self): def test_pypy_performance(self):
im = Image.new('L', (256,256)) im = Image.new('L', (256, 256))
im.putdata(list(range(256))*256) im.putdata(list(range(256))*256)
def test_mode_i(self): def test_mode_i(self):
@ -52,7 +51,7 @@ class TestImagePutData(PillowTestCase):
im = Image.new('I', src.size, 0) im = Image.new('I', src.size, 0)
im.putdata(data, 2, 256) im.putdata(data, 2, 256)
target = [2* elt + 256 for elt in data] target = [2 * elt + 256 for elt in data]
self.assertEqual(list(im.getdata()), target) self.assertEqual(list(im.getdata()), target)
def test_mode_F(self): def test_mode_F(self):
@ -61,11 +60,10 @@ class TestImagePutData(PillowTestCase):
im = Image.new('F', src.size, 0) im = Image.new('F', src.size, 0)
im.putdata(data, 2.0, 256.0) im.putdata(data, 2.0, 256.0)
target = [2.0* float(elt) + 256.0 for elt in data] target = [2.0 * float(elt) + 256.0 for elt in data]
self.assertEqual(list(im.getdata()), target) self.assertEqual(list(im.getdata()), target)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -3,6 +3,7 @@ from helper import unittest, PillowTestCase, hopper
from PIL import Image from PIL import Image
from io import BytesIO from io import BytesIO
import os
try: try:
from PIL import ImageCms from PIL import ImageCms
@ -13,8 +14,8 @@ except ImportError as v:
pass pass
SRGB = "Tests/icc/sRGB.icm" SRGB = "Tests/icc/sRGB_IEC61966-2-1_black_scaled.icc"
HAVE_PROFILE = os.path.exists(SRGB)
class TestImageCms(PillowTestCase): class TestImageCms(PillowTestCase):
@ -26,6 +27,10 @@ class TestImageCms(PillowTestCase):
except ImportError as v: except ImportError as v:
self.skipTest(v) self.skipTest(v)
def skip_missing(self):
if not HAVE_PROFILE:
self.skipTest("SRGB profile not available")
def test_sanity(self): def test_sanity(self):
# basic smoke test. # basic smoke test.
@ -38,6 +43,7 @@ class TestImageCms(PillowTestCase):
# internal version number # internal version number
self.assertRegexpMatches(ImageCms.core.littlecms_version, "\d+\.\d+$") self.assertRegexpMatches(ImageCms.core.littlecms_version, "\d+\.\d+$")
self.skip_missing()
i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) i = ImageCms.profileToProfile(hopper(), SRGB, SRGB)
self.assert_image(i, "RGB", (128, 128)) self.assert_image(i, "RGB", (128, 128))
@ -70,38 +76,45 @@ class TestImageCms(PillowTestCase):
hopper().point(t) hopper().point(t)
def test_name(self): def test_name(self):
self.skip_missing()
# get profile information for file # get profile information for file
self.assertEqual( self.assertEqual(
ImageCms.getProfileName(SRGB).strip(), ImageCms.getProfileName(SRGB).strip(),
'IEC 61966-2.1 Default RGB colour space - sRGB') 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
def test_info(self): def test_info(self):
self.skip_missing()
self.assertEqual( self.assertEqual(
ImageCms.getProfileInfo(SRGB).splitlines(), [ ImageCms.getProfileInfo(SRGB).splitlines(), [
'sRGB IEC61966-2.1', '', 'sRGB IEC61966-2-1 black scaled', '',
'Copyright (c) 1998 Hewlett-Packard Company', '']) 'Copyright International Color Consortium, 2009', ''])
def test_copyright(self): def test_copyright(self):
self.skip_missing()
self.assertEqual( self.assertEqual(
ImageCms.getProfileCopyright(SRGB).strip(), ImageCms.getProfileCopyright(SRGB).strip(),
'Copyright (c) 1998 Hewlett-Packard Company') 'Copyright International Color Consortium, 2009')
def test_manufacturer(self): def test_manufacturer(self):
self.skip_missing()
self.assertEqual( self.assertEqual(
ImageCms.getProfileManufacturer(SRGB).strip(), ImageCms.getProfileManufacturer(SRGB).strip(),
'IEC http://www.iec.ch') '')
def test_model(self): def test_model(self):
self.skip_missing()
self.assertEqual( self.assertEqual(
ImageCms.getProfileModel(SRGB).strip(), ImageCms.getProfileModel(SRGB).strip(),
'IEC 61966-2.1 Default RGB colour space - sRGB') 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
def test_description(self): def test_description(self):
self.skip_missing()
self.assertEqual( self.assertEqual(
ImageCms.getProfileDescription(SRGB).strip(), ImageCms.getProfileDescription(SRGB).strip(),
'sRGB IEC61966-2.1') 'sRGB IEC61966-2-1 black scaled')
def test_intent(self): def test_intent(self):
self.skip_missing()
self.assertEqual(ImageCms.getDefaultIntent(SRGB), 0) self.assertEqual(ImageCms.getDefaultIntent(SRGB), 0)
self.assertEqual(ImageCms.isIntentSupported( self.assertEqual(ImageCms.isIntentSupported(
SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC,
@ -139,6 +152,7 @@ class TestImageCms(PillowTestCase):
self.assertRaises( self.assertRaises(
ImageCms.PyCMSError, ImageCms.PyCMSError,
lambda: ImageCms.getProfileName(None)) lambda: ImageCms.getProfileName(None))
self.skip_missing()
self.assertRaises( self.assertRaises(
ImageCms.PyCMSError, ImageCms.PyCMSError,
lambda: ImageCms.isIntentSupported(SRGB, None, None)) lambda: ImageCms.isIntentSupported(SRGB, None, None))
@ -154,8 +168,9 @@ class TestImageCms(PillowTestCase):
def test_simple_lab(self): def test_simple_lab(self):
i = Image.new('RGB', (10, 10), (128, 128, 128)) i = Image.new('RGB', (10, 10), (128, 128, 128))
p_lab = ImageCms.createProfile("LAB") psRGB = ImageCms.createProfile("sRGB")
t = ImageCms.buildTransform(SRGB, p_lab, "RGB", "LAB") pLab = ImageCms.createProfile("LAB")
t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB")
i_lab = ImageCms.applyTransform(i, t) i_lab = ImageCms.applyTransform(i, t)
@ -174,8 +189,10 @@ class TestImageCms(PillowTestCase):
self.assertEqual(list(b), [128] * 100) self.assertEqual(list(b), [128] * 100)
def test_lab_color(self): def test_lab_color(self):
p_lab = ImageCms.createProfile("LAB") psRGB = ImageCms.createProfile("sRGB")
t = ImageCms.buildTransform(SRGB, p_lab, "RGB", "LAB") pLab = ImageCms.createProfile("LAB")
t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB")
# Need to add a type mapping for some PIL type to TYPE_Lab_8 in # Need to add a type mapping for some PIL type to TYPE_Lab_8 in
# findLCMSType, and have that mapping work back to a PIL mode # findLCMSType, and have that mapping work back to a PIL mode
# (likely RGB). # (likely RGB).
@ -189,8 +206,9 @@ class TestImageCms(PillowTestCase):
self.assert_image_similar(i, target, 30) self.assert_image_similar(i, target, 30)
def test_lab_srgb(self): def test_lab_srgb(self):
p_lab = ImageCms.createProfile("LAB") psRGB = ImageCms.createProfile("sRGB")
t = ImageCms.buildTransform(p_lab, SRGB, "LAB", "RGB") pLab = ImageCms.createProfile("LAB")
t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB")
img = Image.open('Tests/images/hopper.Lab.tif') img = Image.open('Tests/images/hopper.Lab.tif')
@ -206,15 +224,16 @@ class TestImageCms(PillowTestCase):
def test_lab_roundtrip(self): def test_lab_roundtrip(self):
# check to see if we're at least internally consistent. # check to see if we're at least internally consistent.
p_lab = ImageCms.createProfile("LAB") psRGB = ImageCms.createProfile("sRGB")
t = ImageCms.buildTransform(SRGB, p_lab, "RGB", "LAB") pLab = ImageCms.createProfile("LAB")
t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB")
t2 = ImageCms.buildTransform(p_lab, SRGB, "LAB", "RGB") t2 = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB")
i = ImageCms.applyTransform(hopper(), t) i = ImageCms.applyTransform(hopper(), t)
self.assertEqual(i.info['icc_profile'], self.assertEqual(i.info['icc_profile'],
ImageCmsProfile(p_lab).tobytes()) ImageCmsProfile(pLab).tobytes())
out = ImageCms.applyTransform(i, t2) out = ImageCms.applyTransform(i, t2)

View File

@ -255,7 +255,6 @@ class TestImageDraw(PillowTestCase):
self.assert_image_equal( self.assert_image_equal(
im, Image.open("Tests/images/imagedraw_floodfill2.png")) im, Image.open("Tests/images/imagedraw_floodfill2.png"))
def create_base_image_draw(self, size, def create_base_image_draw(self, size,
mode=DEFAULT_MODE, mode=DEFAULT_MODE,
background1=WHITE, background1=WHITE,
@ -267,23 +266,25 @@ class TestImageDraw(PillowTestCase):
img.putpixel((x, y), background2) img.putpixel((x, y), background2)
return (img, ImageDraw.Draw(img)) return (img, ImageDraw.Draw(img))
def test_square(self): def test_square(self):
expected = Image.open(os.path.join(IMAGES_PATH, 'square.png')) expected = Image.open(os.path.join(IMAGES_PATH, 'square.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((10, 10)) img, draw = self.create_base_image_draw((10, 10))
draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK)
self.assert_image_equal(img, expected, 'square as normal polygon failed') self.assert_image_equal(img, expected,
'square as normal polygon failed')
img, draw = self.create_base_image_draw((10, 10)) img, draw = self.create_base_image_draw((10, 10))
draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK)
self.assert_image_equal(img, expected, 'square as inverted polygon failed') self.assert_image_equal(img, expected,
'square as inverted polygon failed')
img, draw = self.create_base_image_draw((10, 10)) img, draw = self.create_base_image_draw((10, 10))
draw.rectangle((2, 2, 7, 7), BLACK) draw.rectangle((2, 2, 7, 7), BLACK)
self.assert_image_equal(img, expected, 'square as normal rectangle failed') self.assert_image_equal(img, expected,
'square as normal rectangle failed')
img, draw = self.create_base_image_draw((10, 10)) img, draw = self.create_base_image_draw((10, 10))
draw.rectangle((7, 7, 2, 2), BLACK) draw.rectangle((7, 7, 2, 2), BLACK)
self.assert_image_equal(img, expected, 'square as inverted rectangle failed') self.assert_image_equal(
img, expected, 'square as inverted rectangle failed')
def test_triangle_right(self): def test_triangle_right(self):
expected = Image.open(os.path.join(IMAGES_PATH, 'triangle_right.png')) expected = Image.open(os.path.join(IMAGES_PATH, 'triangle_right.png'))
@ -292,93 +293,117 @@ class TestImageDraw(PillowTestCase):
draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK)
self.assert_image_equal(img, expected, 'triangle right failed') self.assert_image_equal(img, expected, 'triangle right failed')
def test_line_horizontal(self): def test_line_horizontal(self):
expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w2px_normal.png')) expected = Image.open(os.path.join(IMAGES_PATH,
'line_horizontal_w2px_normal.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 5, 14, 5), BLACK, 2) draw.line((5, 5, 14, 5), BLACK, 2)
self.assert_image_equal(img, expected, 'line straigth horizontal normal 2px wide failed') self.assert_image_equal(
expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w2px_inverted.png')) img, expected, 'line straigth horizontal normal 2px wide failed')
expected = Image.open(os.path.join(IMAGES_PATH,
'line_horizontal_w2px_inverted.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((14, 5, 5, 5), BLACK, 2) draw.line((14, 5, 5, 5), BLACK, 2)
self.assert_image_equal(img, expected, 'line straigth horizontal inverted 2px wide failed') self.assert_image_equal(
expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w3px.png')) img, expected, 'line straigth horizontal inverted 2px wide failed')
expected = Image.open(os.path.join(IMAGES_PATH,
'line_horizontal_w3px.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 5, 14, 5), BLACK, 3) draw.line((5, 5, 14, 5), BLACK, 3)
self.assert_image_equal(img, expected, 'line straigth horizontal normal 3px wide failed') self.assert_image_equal(
img, expected, 'line straigth horizontal normal 3px wide failed')
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((14, 5, 5, 5), BLACK, 3) draw.line((14, 5, 5, 5), BLACK, 3)
self.assert_image_equal(img, expected, 'line straigth horizontal inverted 3px wide failed') self.assert_image_equal(
expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w101px.png')) img, expected, 'line straigth horizontal inverted 3px wide failed')
expected = Image.open(os.path.join(IMAGES_PATH,
'line_horizontal_w101px.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((200, 110)) img, draw = self.create_base_image_draw((200, 110))
draw.line((5, 55, 195, 55), BLACK, 101) draw.line((5, 55, 195, 55), BLACK, 101)
self.assert_image_equal(img, expected, 'line straigth horizontal 101px wide failed') self.assert_image_equal(
img, expected, 'line straigth horizontal 101px wide failed')
def test_line_h_s1_w2(self): def test_line_h_s1_w2(self):
self.skipTest('failing') self.skipTest('failing')
expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_slope1px_w2px.png')) expected = Image.open(os.path.join(IMAGES_PATH,
'line_horizontal_slope1px_w2px.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 5, 14, 6), BLACK, 2) draw.line((5, 5, 14, 6), BLACK, 2)
self.assert_image_equal(img, expected, 'line horizontal 1px slope 2px wide failed') self.assert_image_equal(
img, expected, 'line horizontal 1px slope 2px wide failed')
def test_line_vertical(self): def test_line_vertical(self):
expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w2px_normal.png')) expected = Image.open(os.path.join(IMAGES_PATH,
'line_vertical_w2px_normal.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 5, 5, 14), BLACK, 2) draw.line((5, 5, 5, 14), BLACK, 2)
self.assert_image_equal(img, expected, 'line straigth vertical normal 2px wide failed') self.assert_image_equal(
expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w2px_inverted.png')) img, expected, 'line straigth vertical normal 2px wide failed')
expected = Image.open(os.path.join(IMAGES_PATH,
'line_vertical_w2px_inverted.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 14, 5, 5), BLACK, 2) draw.line((5, 14, 5, 5), BLACK, 2)
self.assert_image_equal(img, expected, 'line straigth vertical inverted 2px wide failed') self.assert_image_equal(
expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w3px.png')) img, expected, 'line straigth vertical inverted 2px wide failed')
expected = Image.open(os.path.join(IMAGES_PATH,
'line_vertical_w3px.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 5, 5, 14), BLACK, 3) draw.line((5, 5, 5, 14), BLACK, 3)
self.assert_image_equal(img, expected, 'line straigth vertical normal 3px wide failed') self.assert_image_equal(
img, expected, 'line straigth vertical normal 3px wide failed')
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 14, 5, 5), BLACK, 3) draw.line((5, 14, 5, 5), BLACK, 3)
self.assert_image_equal(img, expected, 'line straigth vertical inverted 3px wide failed') self.assert_image_equal(
expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w101px.png')) img, expected, 'line straigth vertical inverted 3px wide failed')
expected = Image.open(os.path.join(IMAGES_PATH,
'line_vertical_w101px.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((110, 200)) img, draw = self.create_base_image_draw((110, 200))
draw.line((55, 5, 55, 195), BLACK, 101) draw.line((55, 5, 55, 195), BLACK, 101)
self.assert_image_equal(img, expected, 'line straigth vertical 101px wide failed') self.assert_image_equal(img, expected,
expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_slope1px_w2px.png')) 'line straigth vertical 101px wide failed')
expected = Image.open(os.path.join(IMAGES_PATH,
'line_vertical_slope1px_w2px.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 5, 6, 14), BLACK, 2) draw.line((5, 5, 6, 14), BLACK, 2)
self.assert_image_equal(img, expected, 'line vertical 1px slope 2px wide failed') self.assert_image_equal(img, expected,
'line vertical 1px slope 2px wide failed')
def test_line_oblique_45(self): def test_line_oblique_45(self):
expected = Image.open(os.path.join(IMAGES_PATH, 'line_oblique_45_w3px_a.png')) expected = Image.open(os.path.join(IMAGES_PATH,
'line_oblique_45_w3px_a.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 5, 14, 14), BLACK, 3) draw.line((5, 5, 14, 14), BLACK, 3)
self.assert_image_equal(img, expected, 'line oblique 45 normal 3px wide A failed') self.assert_image_equal(img, expected,
'line oblique 45 normal 3px wide A failed')
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((14, 14, 5, 5), BLACK, 3) draw.line((14, 14, 5, 5), BLACK, 3)
self.assert_image_equal(img, expected, 'line oblique 45 inverted 3px wide A failed') self.assert_image_equal(img, expected,
expected = Image.open(os.path.join(IMAGES_PATH, 'line_oblique_45_w3px_b.png')) 'line oblique 45 inverted 3px wide A failed')
expected = Image.open(os.path.join(IMAGES_PATH,
'line_oblique_45_w3px_b.png'))
expected.load() expected.load()
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((14, 5, 5, 14), BLACK, 3) draw.line((14, 5, 5, 14), BLACK, 3)
self.assert_image_equal(img, expected, 'line oblique 45 normal 3px wide B failed') self.assert_image_equal(img, expected,
'line oblique 45 normal 3px wide B failed')
img, draw = self.create_base_image_draw((20, 20)) img, draw = self.create_base_image_draw((20, 20))
draw.line((5, 14, 14, 5), BLACK, 3) draw.line((5, 14, 14, 5), BLACK, 3)
self.assert_image_equal(img, expected, 'line oblique 45 inverted 3px wide B failed') self.assert_image_equal(img, expected,
'line oblique 45 inverted 3px wide B failed')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
# End of file # End of file

View File

@ -1,5 +1,5 @@
# Test the ImageMorphology functionality # Test the ImageMorphology functionality
from helper import * from helper import unittest, PillowTestCase
from PIL import Image from PIL import Image
from PIL import ImageMorph from PIL import ImageMorph

View File

@ -1,4 +1,4 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase
from PIL import Image from PIL import Image

View File

@ -5,7 +5,7 @@ from PIL import Image
class TestModeI16(PillowTestCase): class TestModeI16(PillowTestCase):
original = hopper().resize((32,32)).convert('I') original = hopper().resize((32, 32)).convert('I')
def verify(self, im1): def verify(self, im1):
im2 = self.original.copy() im2 = self.original.copy()

View File

@ -1,4 +1,4 @@
from helper import * from helper import unittest, PillowTestCase
try: try:
import pyroma import pyroma

View File

@ -17,6 +17,7 @@ test_filenames = (
"temp_'\"&&", "temp_'\"&&",
) )
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS") @unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
class TestShellInjection(PillowTestCase): class TestShellInjection(PillowTestCase):

View File

@ -1,8 +1,10 @@
from PIL import Image from PIL import Image
import sys, time
import io import io
import threading, queue import queue
import sys
import threading
import time
try: try:
format = sys.argv[1] format = sys.argv[1]
@ -16,6 +18,7 @@ queue = queue.Queue()
result = [] result = []
class Worker(threading.Thread): class Worker(threading.Thread):
def run(self): def run(self):
while True: while True:

View File

@ -1,5 +1,6 @@
from PIL import Image from PIL import Image
def version(module, version): def version(module, version):
v = getattr(module.core, version + "_version", None) v = getattr(module.core, version + "_version", None)
if v: if v:

View File

@ -117,20 +117,48 @@ converting ``RGB`` images to ``L``, and resize images to 1/2, 1/4 or 1/8 of
their original size while loading them. The :py:meth:`~PIL.Image.Image.draft` their original size while loading them. The :py:meth:`~PIL.Image.Image.draft`
method also configures the JPEG decoder to trade some quality for speed. method also configures the JPEG decoder to trade some quality for speed.
The :py:meth:`~PIL.Image.Image.open` method sets the following The :py:meth:`~PIL.Image.Image.open` method may set the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties if available:
**jfif** **jfif**
JFIF application marker found. If the file is not a JFIF file, this key is JFIF application marker found. If the file is not a JFIF file, this key is
not present. not present.
**jfif_version**
A tuple representing the jfif version, (major version, minor version).
**jfif_density**
A tuple representing the pixel density of the image, in units specified
by jfif_unit.
**jfif_unit**
Units for the jfif_density:
* 0 - No Units
* 1 - Pixels per Inch
* 2 - Pixels per Centimeter
**dpi**
A tuple representing the reported pixel density in pixels per inch, if
the file is a jfif file and the units are in inches.
**adobe** **adobe**
Adobe application marker found. If the file is not an Adobe JPEG file, this Adobe application marker found. If the file is not an Adobe JPEG file, this
key is not present. key is not present.
**adobe_transform**
Vendor Specific Tag.
**progression** **progression**
Indicates that this is a progressive JPEG file. Indicates that this is a progressive JPEG file.
**icc-profile**
The ICC color profile for the image.
**exif**
Raw EXIF data from the image.
The :py:meth:`~PIL.Image.Image.save` method supports the following options: The :py:meth:`~PIL.Image.Image.save` method supports the following options:
**quality** **quality**
@ -147,6 +175,19 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
If present, indicates that this image should be stored as a progressive If present, indicates that this image should be stored as a progressive
JPEG file. JPEG file.
**dpi**
A tuple of integers representing the pixel density, ``(x,y)``.
**icc-profile**
If present, the image is stored with the provided ICC profile. If
this parameter is not provided, the image will be saved with no
profile attached. To preserve the existing profile::
im.save(filename, 'jpeg', icc_profile=im.info.get('icc_profile'))
**exif**
If present, the image will be stored with the provided raw EXIF data.
**subsampling** **subsampling**
If present, sets the subsampling for the encoder. If present, sets the subsampling for the encoder.
@ -670,7 +711,7 @@ files, using either JPEG or HEX encoding depending on the image mode (and
whether JPEG support is available or not). whether JPEG support is available or not).
PIXAR (read only) PIXAR (read only)
^^^^ ^^^^^^^^^^^^^^^^^
PIL provides limited support for PIXAR raster files. The library can identify PIL provides limited support for PIXAR raster files. The library can identify
and read “dumped” RGB files. and read “dumped” RGB files.

View File

@ -535,12 +535,12 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args)
#include "Jpeg.h" #include "Jpeg.h"
static unsigned int** get_qtables_arrays(PyObject* qtables, int* qtablesLen) { static unsigned int* get_qtables_arrays(PyObject* qtables, int* qtablesLen) {
PyObject* tables; PyObject* tables;
PyObject* table; PyObject* table;
PyObject* table_data; PyObject* table_data;
int i, j, num_tables; int i, j, num_tables;
unsigned int **qarrays; unsigned int *qarrays;
if ((qtables == NULL) || (qtables == Py_None)) { if ((qtables == NULL) || (qtables == Py_None)) {
return NULL; return NULL;
@ -554,10 +554,12 @@ static unsigned int** get_qtables_arrays(PyObject* qtables, int* qtablesLen) {
tables = PySequence_Fast(qtables, "expected a sequence"); tables = PySequence_Fast(qtables, "expected a sequence");
num_tables = PySequence_Size(qtables); num_tables = PySequence_Size(qtables);
if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) { if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) {
PyErr_SetString(PyExc_ValueError, "Not a valid numbers of quantization tables. Should be between 1 and 4."); PyErr_SetString(PyExc_ValueError,
"Not a valid number of quantization tables. Should be between 1 and 4.");
Py_DECREF(tables);
return NULL; return NULL;
} }
qarrays = (unsigned int**) PyMem_Malloc(num_tables * sizeof(unsigned int*)); qarrays = (unsigned int*) malloc(num_tables * DCTSIZE2 * sizeof(unsigned int));
if (!qarrays) { if (!qarrays) {
Py_DECREF(tables); Py_DECREF(tables);
PyErr_NoMemory(); PyErr_NoMemory();
@ -566,39 +568,33 @@ static unsigned int** get_qtables_arrays(PyObject* qtables, int* qtablesLen) {
for (i = 0; i < num_tables; i++) { for (i = 0; i < num_tables; i++) {
table = PySequence_Fast_GET_ITEM(tables, i); table = PySequence_Fast_GET_ITEM(tables, i);
if (!PySequence_Check(table)) { if (!PySequence_Check(table)) {
Py_DECREF(tables);
PyErr_SetString(PyExc_ValueError, "Invalid quantization tables"); PyErr_SetString(PyExc_ValueError, "Invalid quantization tables");
return NULL; goto JPEG_QTABLES_ERR;
} }
if (PySequence_Size(table) != DCTSIZE2) { if (PySequence_Size(table) != DCTSIZE2) {
Py_DECREF(tables); PyErr_SetString(PyExc_ValueError, "Invalid quantization table size");
PyErr_SetString(PyExc_ValueError, "Invalid quantization tables"); goto JPEG_QTABLES_ERR;
return NULL;
} }
table_data = PySequence_Fast(table, "expected a sequence"); table_data = PySequence_Fast(table, "expected a sequence");
qarrays[i] = (unsigned int*) PyMem_Malloc(DCTSIZE2 * sizeof(unsigned int));
if (!qarrays[i]) {
Py_DECREF(tables);
PyErr_NoMemory();
return NULL;
}
for (j = 0; j < DCTSIZE2; j++) { for (j = 0; j < DCTSIZE2; j++) {
qarrays[i][j] = PyInt_AS_LONG(PySequence_Fast_GET_ITEM(table_data, j)); qarrays[i * DCTSIZE2 + j] = PyInt_AS_LONG(PySequence_Fast_GET_ITEM(table_data, j));
} }
Py_DECREF(table_data);
} }
Py_DECREF(tables);
*qtablesLen = num_tables; *qtablesLen = num_tables;
JPEG_QTABLES_ERR:
Py_DECREF(tables); // Run on both error and not error
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
PyMem_Free(qarrays); free(qarrays);
qarrays = NULL; qarrays = NULL;
return NULL;
} }
return qarrays; return qarrays;
} }
PyObject* PyObject*
PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
{ {
@ -614,7 +610,7 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
int xdpi = 0, ydpi = 0; int xdpi = 0, ydpi = 0;
int subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */ int subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */
PyObject* qtables=NULL; PyObject* qtables=NULL;
unsigned int **qarrays = NULL; unsigned int *qarrays = NULL;
int qtablesLen = 0; int qtablesLen = 0;
char* extra = NULL; char* extra = NULL;
int extra_size; int extra_size;
@ -638,7 +634,7 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
qarrays = get_qtables_arrays(qtables, &qtablesLen); qarrays = get_qtables_arrays(qtables, &qtablesLen);
if (extra && extra_size > 0) { if (extra && extra_size > 0) {
char* p = malloc(extra_size); char* p = malloc(extra_size); // Freed in JpegEncode, Case 5
if (!p) if (!p)
return PyErr_NoMemory(); return PyErr_NoMemory();
memcpy(p, extra, extra_size); memcpy(p, extra, extra_size);
@ -647,7 +643,7 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
extra = NULL; extra = NULL;
if (rawExif && rawExifLen > 0) { if (rawExif && rawExifLen > 0) {
char* pp = malloc(rawExifLen); char* pp = malloc(rawExifLen); // Freed in JpegEncode, Case 5
if (!pp) if (!pp)
return PyErr_NoMemory(); return PyErr_NoMemory();
memcpy(pp, rawExif, rawExifLen); memcpy(pp, rawExif, rawExifLen);

View File

@ -89,7 +89,9 @@ typedef struct {
int subsampling; int subsampling;
/* Custom quantization tables () */ /* Custom quantization tables () */
unsigned int **qtables; unsigned int *qtables;
/* in factors of DCTSIZE2 */
int qtablesLen; int qtablesLen;
/* Extra data (to be injected after header) */ /* Extra data (to be injected after header) */

View File

@ -153,7 +153,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
} }
for (i = 0; i < context->qtablesLen; i++) { for (i = 0; i < context->qtablesLen; i++) {
// TODO: Should add support for none baseline // TODO: Should add support for none baseline
jpeg_add_quant_table(&context->cinfo, i, context->qtables[i], jpeg_add_quant_table(&context->cinfo, i, &context->qtables[i * DCTSIZE2],
quality, TRUE); quality, TRUE);
} }
} else if (context->quality > 0) { } else if (context->quality > 0) {
@ -289,8 +289,19 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
jpeg_finish_compress(&context->cinfo); jpeg_finish_compress(&context->cinfo);
/* Clean up */ /* Clean up */
if (context->extra) if (context->extra) {
free(context->extra); free(context->extra);
context->extra = NULL;
}
if (context->rawExif) {
free(context->rawExif);
context->rawExif = NULL;
}
if (context->qtables) {
free(context->qtables);
context->qtables = NULL;
}
jpeg_destroy_compress(&context->cinfo); jpeg_destroy_compress(&context->cinfo);
/* if (jerr.pub.num_warnings) return BROKEN; */ /* if (jerr.pub.num_warnings) return BROKEN; */
state->errcode = IMAGING_CODEC_END; state->errcode = IMAGING_CODEC_END;

View File

@ -57,6 +57,7 @@ if MAX_PROCS != 1 and not sys.platform.startswith('win'):
pool = Pool(2) pool = Pool(2)
CCompiler.compile = _mp_compile CCompiler.compile = _mp_compile
except Exception as msg: except Exception as msg:
print("Exception installing mp_compile, proceeding without: %s" %msg) print("Exception installing mp_compile, proceeding without: %s" % msg)
else: else:
print("Single threaded build, not installing mp_compile: %s processes" %MAX_PROCS) print("Single threaded build, not installing mp_compile: %s processes" %
MAX_PROCS)

View File

@ -24,8 +24,8 @@ if 'NOSE_PROCESSES' not in os.environ:
for arg in sys.argv: for arg in sys.argv:
if '--processes' in arg: if '--processes' in arg:
break break
else: # for else: # for
sys.argv.insert(1, '--processes=-1') # -1 == number of cores sys.argv.insert(1, '--processes=-1') # -1 == number of cores
sys.argv.insert(1, '--process-timeout=30') sys.argv.insert(1, '--process-timeout=30')
if __name__ == '__main__': if __name__ == '__main__':