Merge branch 'master' into flake8
17
.travis.yml
|
@ -16,15 +16,14 @@ python:
|
||||||
- 3.4
|
- 3.4
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick lcov"
|
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
|
||||||
- "pip install cffi"
|
- "travis_retry pip install cffi"
|
||||||
- "pip install coveralls nose coveralls-merge"
|
- "travis_retry pip install coverage nose"
|
||||||
- "gem install coveralls-lcov"
|
|
||||||
|
|
||||||
# Pyroma installation is slow on Py3, so just do it for Py2.
|
# Pyroma installation is slow on Py3, so just do it for Py2.
|
||||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then travis_retry pip install pyroma; fi
|
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then travis_retry pip install pyroma; fi
|
||||||
|
|
||||||
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi
|
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
|
||||||
|
|
||||||
# webp
|
# webp
|
||||||
- pushd depends && ./install_webp.sh && popd
|
- pushd depends && ./install_webp.sh && popd
|
||||||
|
@ -42,18 +41,20 @@ script:
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
# gather the coverage data
|
# gather the coverage data
|
||||||
|
- travis_retry sudo apt-get -qq install lcov
|
||||||
- lcov --capture --directory . -b . --output-file coverage.info
|
- lcov --capture --directory . -b . --output-file coverage.info
|
||||||
# filter to remove system headers
|
# filter to remove system headers
|
||||||
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
|
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
|
||||||
# convert to json
|
# convert to json
|
||||||
|
- travis_retry gem install coveralls-lcov
|
||||||
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
|
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
|
||||||
|
|
||||||
- coverage report
|
- coverage report
|
||||||
|
- travis_retry pip install coveralls-merge
|
||||||
- coveralls-merge coverage.c.json
|
- coveralls-merge coverage.c.json
|
||||||
|
|
||||||
|
|
||||||
- pip install pep8 pyflakes
|
- travis_retry pip install pep8 pyflakes
|
||||||
- pep8 --statistics --count *.py
|
|
||||||
- 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 *.py | tee >(wc -l)
|
||||||
|
@ -65,3 +66,5 @@ after_success:
|
||||||
# (Installation is very slow on Py3, so just do it for Py2.)
|
# (Installation is very slow on Py3, so just do it for Py2.)
|
||||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
|
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
|
||||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi
|
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
|
23
CHANGES.rst
|
@ -4,6 +4,27 @@ Changelog (Pillow)
|
||||||
2.6.0 (unreleased)
|
2.6.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Jpeg2k Decode/Encode Memory Leak Fix #898
|
||||||
|
[joshware, wiredfool]
|
||||||
|
|
||||||
|
- EpsFilePlugin Speed improvements #886
|
||||||
|
[wiredfool, karstenw]
|
||||||
|
|
||||||
|
- Don't resize if already the right size.
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix for reading multipage TIFFs #885
|
||||||
|
[kostrom, wiredfool]
|
||||||
|
|
||||||
|
- Correctly handle saving gray and CMYK JPEGs with quality=keep #857
|
||||||
|
[etienned]
|
||||||
|
|
||||||
|
- Correct duplicate Tiff Metadata and Exif tag values
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Windows fixes #871
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
- Fix TGA files with image ID field #856
|
- Fix TGA files with image ID field #856
|
||||||
[megabuz]
|
[megabuz]
|
||||||
|
|
||||||
|
@ -55,7 +76,7 @@ Changelog (Pillow)
|
||||||
- Added docs for ExifTags
|
- Added docs for ExifTags
|
||||||
[Wintermute3]
|
[Wintermute3]
|
||||||
|
|
||||||
- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
|
- More tests for CurImagePlugin, DcxImagePlugin, Effects.c, GimpGradientFile, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- Fix return value of FreeTypeFont.textsize() does not include font offsets
|
- Fix return value of FreeTypeFont.textsize() does not include font offsets
|
||||||
|
|
|
@ -91,26 +91,32 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
|
|
||||||
out_fd, outfile = tempfile.mkstemp()
|
out_fd, outfile = tempfile.mkstemp()
|
||||||
os.close(out_fd)
|
os.close(out_fd)
|
||||||
in_fd, infile = tempfile.mkstemp()
|
|
||||||
os.close(in_fd)
|
|
||||||
|
|
||||||
# ignore length and offset!
|
infile_temp = None
|
||||||
# ghostscript can read it
|
if hasattr(fp, 'name') and os.path.exists(fp.name):
|
||||||
# copy whole file to read in ghostscript
|
infile = fp.name
|
||||||
with open(infile, 'wb') as f:
|
else:
|
||||||
# fetch length of fp
|
in_fd, infile_temp = tempfile.mkstemp()
|
||||||
fp.seek(0, 2)
|
os.close(in_fd)
|
||||||
fsize = fp.tell()
|
infile = infile_temp
|
||||||
# ensure start position
|
|
||||||
# go back
|
# ignore length and offset!
|
||||||
fp.seek(0)
|
# ghostscript can read it
|
||||||
lengthfile = fsize
|
# copy whole file to read in ghostscript
|
||||||
while lengthfile > 0:
|
with open(infile_temp, 'wb') as f:
|
||||||
s = fp.read(min(lengthfile, 100*1024))
|
# fetch length of fp
|
||||||
if not s:
|
fp.seek(0, 2)
|
||||||
break
|
fsize = fp.tell()
|
||||||
length -= len(s)
|
# ensure start position
|
||||||
f.write(s)
|
# go back
|
||||||
|
fp.seek(0)
|
||||||
|
lengthfile = fsize
|
||||||
|
while lengthfile > 0:
|
||||||
|
s = fp.read(min(lengthfile, 100*1024))
|
||||||
|
if not s:
|
||||||
|
break
|
||||||
|
lengthfile -= len(s)
|
||||||
|
f.write(s)
|
||||||
|
|
||||||
# Build ghostscript command
|
# Build ghostscript command
|
||||||
command = ["gs",
|
command = ["gs",
|
||||||
|
@ -143,56 +149,36 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(outfile)
|
os.unlink(outfile)
|
||||||
os.unlink(infile)
|
if infile_temo:
|
||||||
except:
|
os.unlink(infile_temp)
|
||||||
pass
|
except: pass
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
class PSFile:
|
class PSFile:
|
||||||
"""Wrapper 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 __getattr__(self, id):
|
|
||||||
v = getattr(self.fp, id)
|
|
||||||
setattr(self, id, v)
|
|
||||||
return v
|
|
||||||
|
|
||||||
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 read(self, count):
|
|
||||||
return self.fp.read(count).decode('latin-1')
|
|
||||||
|
|
||||||
def readbinary(self, count):
|
|
||||||
return self.fp.read(count)
|
|
||||||
|
|
||||||
def tell(self):
|
|
||||||
pos = self.fp.tell()
|
|
||||||
if self.char:
|
|
||||||
pos -= 1
|
|
||||||
return pos
|
|
||||||
|
|
||||||
def readline(self):
|
def readline(self):
|
||||||
s = b""
|
s = self.char or b""
|
||||||
if self.char:
|
self.char = None
|
||||||
c = self.char
|
|
||||||
self.char = None
|
c = self.fp.read(1)
|
||||||
else:
|
|
||||||
c = self.fp.read(1)
|
|
||||||
while c not in b"\r\n":
|
while c not in b"\r\n":
|
||||||
s = s + c
|
s = s + c
|
||||||
c = self.fp.read(1)
|
c = self.fp.read(1)
|
||||||
if c == b"\r":
|
|
||||||
self.char = self.fp.read(1)
|
|
||||||
if self.char == b"\n":
|
|
||||||
self.char = None
|
|
||||||
return s.decode('latin-1') + "\n"
|
|
||||||
|
|
||||||
|
self.char = self.fp.read(1)
|
||||||
|
# line endings can be 1 or 2 of \r \n, in either order
|
||||||
|
if self.char in b"\r\n":
|
||||||
|
self.char = None
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -208,33 +194,23 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
format = "EPS"
|
format = "EPS"
|
||||||
format_description = "Encapsulated Postscript"
|
format_description = "Encapsulated Postscript"
|
||||||
|
|
||||||
|
mode_map = { 1:"L", 2:"LAB", 3:"RGB" }
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
(length, offset) = self._find_offset(self.fp)
|
||||||
|
|
||||||
fp = PSFile(self.fp)
|
# Rewrap the open file pointer in something that will
|
||||||
|
# convert line endings and decode to latin-1.
|
||||||
# FIX for: Some EPS file not handled correctly / issue #302
|
try:
|
||||||
# EPS can contain binary data
|
if bytes is str:
|
||||||
# or start directly with latin coding
|
# Python2, no encoding conversion necessary
|
||||||
# read header in both ways to handle both
|
fp = open(self.fp.name, "Ur")
|
||||||
# file types
|
else:
|
||||||
# more info see:
|
# Python3, can use bare open command.
|
||||||
# http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
fp = open(self.fp.name, "Ur", encoding='latin-1')
|
||||||
|
except Exception as msg:
|
||||||
# for HEAD without binary preview
|
# Expect this for bytesio/stringio
|
||||||
s = fp.read(4)
|
fp = PSFile(self.fp)
|
||||||
# for HEAD with binary preview
|
|
||||||
fp.seek(0)
|
|
||||||
sb = fp.readbinary(160)
|
|
||||||
|
|
||||||
if s[:4] == "%!PS":
|
|
||||||
fp.seek(0, 2)
|
|
||||||
length = fp.tell()
|
|
||||||
offset = 0
|
|
||||||
elif i32(sb[0:4]) == 0xC6D3D0C5:
|
|
||||||
offset = i32(sb[4:8])
|
|
||||||
length = i32(sb[8:12])
|
|
||||||
else:
|
|
||||||
raise SyntaxError("not an EPS file")
|
|
||||||
|
|
||||||
# go to offset - start of "%!PS"
|
# go to offset - start of "%!PS"
|
||||||
fp.seek(offset)
|
fp.seek(offset)
|
||||||
|
@ -247,18 +223,12 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# Load EPS header
|
# Load EPS header
|
||||||
|
|
||||||
s = fp.readline()
|
s = fp.readline().strip('\r\n')
|
||||||
|
|
||||||
while s:
|
while s:
|
||||||
|
|
||||||
if len(s) > 255:
|
if len(s) > 255:
|
||||||
raise SyntaxError("not an EPS file")
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
if s[-2:] == '\r\n':
|
|
||||||
s = s[:-2]
|
|
||||||
elif s[-1:] == '\n':
|
|
||||||
s = s[:-1]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
m = split.match(s)
|
m = split.match(s)
|
||||||
except re.error as v:
|
except re.error as v:
|
||||||
|
@ -280,9 +250,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
m = field.match(s)
|
m = field.match(s)
|
||||||
|
|
||||||
if m:
|
if m:
|
||||||
k = m.group(1)
|
k = m.group(1)
|
||||||
|
|
||||||
|
@ -292,16 +260,16 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
self.info[k[:8]] = k[9:]
|
self.info[k[:8]] = k[9:]
|
||||||
else:
|
else:
|
||||||
self.info[k] = ""
|
self.info[k] = ""
|
||||||
elif s[0:1] == '%':
|
elif s[0] == '%':
|
||||||
# handle non-DSC Postscript comments that some
|
# handle non-DSC Postscript comments that some
|
||||||
# tools mistakenly put in the Comments section
|
# tools mistakenly put in the Comments section
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise IOError("bad EPS header")
|
raise IOError("bad EPS header")
|
||||||
|
|
||||||
s = fp.readline()
|
s = fp.readline().strip('\r\n')
|
||||||
|
|
||||||
if s[:1] != "%":
|
if s[0] != "%":
|
||||||
break
|
break
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -312,64 +280,48 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
if len(s) > 255:
|
if len(s) > 255:
|
||||||
raise SyntaxError("not an EPS file")
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
if s[-2:] == '\r\n':
|
|
||||||
s = s[:-2]
|
|
||||||
elif s[-1:] == '\n':
|
|
||||||
s = s[:-1]
|
|
||||||
|
|
||||||
if s[:11] == "%ImageData:":
|
if s[:11] == "%ImageData:":
|
||||||
|
# Encoded bitmapped image.
|
||||||
|
[x, y, bi, mo, z3, z4, en, id] = s[11:].split(None, 7)
|
||||||
|
|
||||||
[x, y, bi, mo, z3, z4, en, id] =\
|
if int(bi) != 8:
|
||||||
s[11:].split(None, 7)
|
|
||||||
|
|
||||||
x = int(x)
|
|
||||||
y = int(y)
|
|
||||||
|
|
||||||
bi = int(bi)
|
|
||||||
mo = int(mo)
|
|
||||||
|
|
||||||
en = int(en)
|
|
||||||
|
|
||||||
if en == 1:
|
|
||||||
decoder = "eps_binary"
|
|
||||||
elif en == 2:
|
|
||||||
decoder = "eps_hex"
|
|
||||||
else:
|
|
||||||
break
|
break
|
||||||
if bi != 8:
|
try:
|
||||||
|
self.mode = self.mode_map[int(mo)]
|
||||||
|
except:
|
||||||
break
|
break
|
||||||
if mo == 1:
|
|
||||||
self.mode = "L"
|
self.size = int(x), int(y)
|
||||||
elif mo == 2:
|
return
|
||||||
self.mode = "LAB"
|
|
||||||
elif mo == 3:
|
s = fp.readline().strip('\r\n')
|
||||||
self.mode = "RGB"
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
if id[:1] == id[-1:] == '"':
|
|
||||||
id = id[1:-1]
|
|
||||||
|
|
||||||
# Scan forward to the actual image data
|
|
||||||
while True:
|
|
||||||
s = fp.readline()
|
|
||||||
if not s:
|
|
||||||
break
|
|
||||||
if s[:len(id)] == id:
|
|
||||||
self.size = x, y
|
|
||||||
self.tile2 = [(decoder,
|
|
||||||
(0, 0, x, y),
|
|
||||||
fp.tell(),
|
|
||||||
0)]
|
|
||||||
return
|
|
||||||
|
|
||||||
s = fp.readline()
|
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
|
|
||||||
if not box:
|
if not box:
|
||||||
raise IOError("cannot determine EPS bounding box")
|
raise IOError("cannot determine EPS bounding box")
|
||||||
|
|
||||||
|
def _find_offset(self, fp):
|
||||||
|
|
||||||
|
s = fp.read(160)
|
||||||
|
|
||||||
|
if s[:4] == b"%!PS":
|
||||||
|
# for HEAD without binary preview
|
||||||
|
fp.seek(0, 2)
|
||||||
|
length = fp.tell()
|
||||||
|
offset = 0
|
||||||
|
elif i32(s[0:4]) == 0xC6D3D0C5:
|
||||||
|
# FIX for: Some EPS file not handled correctly / issue #302
|
||||||
|
# EPS can contain binary data
|
||||||
|
# or start directly with latin coding
|
||||||
|
# more info see http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
||||||
|
offset = i32(s[4:8])
|
||||||
|
length = i32(s[8:12])
|
||||||
|
else:
|
||||||
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
|
return (length, offset)
|
||||||
|
|
||||||
def load(self, scale=1):
|
def load(self, scale=1):
|
||||||
# Load EPS via Ghostscript
|
# Load EPS via Ghostscript
|
||||||
if not self.tile:
|
if not self.tile:
|
||||||
|
|
|
@ -67,8 +67,8 @@ TAGS = {
|
||||||
0x0213: "YCbCrPositioning",
|
0x0213: "YCbCrPositioning",
|
||||||
0x0214: "ReferenceBlackWhite",
|
0x0214: "ReferenceBlackWhite",
|
||||||
0x1000: "RelatedImageFileFormat",
|
0x1000: "RelatedImageFileFormat",
|
||||||
0x1001: "RelatedImageLength", # FIXME / Dictionary contains duplicate keys
|
0x1001: "RelatedImageWidth",
|
||||||
0x1001: "RelatedImageWidth", # FIXME \ Dictionary contains duplicate keys
|
0x1002: "RelatedImageLength",
|
||||||
0x828d: "CFARepeatPatternDim",
|
0x828d: "CFARepeatPatternDim",
|
||||||
0x828e: "CFAPattern",
|
0x828e: "CFAPattern",
|
||||||
0x828f: "BatteryLevel",
|
0x828f: "BatteryLevel",
|
||||||
|
|
|
@ -24,6 +24,7 @@ from PIL._binary import o8
|
||||||
|
|
||||||
EPSILON = 1e-10
|
EPSILON = 1e-10
|
||||||
|
|
||||||
|
|
||||||
def linear(middle, pos):
|
def linear(middle, pos):
|
||||||
if pos <= middle:
|
if pos <= middle:
|
||||||
if middle < EPSILON:
|
if middle < EPSILON:
|
||||||
|
@ -38,25 +39,30 @@ def linear(middle, pos):
|
||||||
else:
|
else:
|
||||||
return 0.5 + 0.5 * pos / middle
|
return 0.5 + 0.5 * pos / middle
|
||||||
|
|
||||||
|
|
||||||
def curved(middle, pos):
|
def curved(middle, pos):
|
||||||
return pos ** (log(0.5) / log(max(middle, EPSILON)))
|
return pos ** (log(0.5) / log(max(middle, EPSILON)))
|
||||||
|
|
||||||
|
|
||||||
def sine(middle, pos):
|
def sine(middle, pos):
|
||||||
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
|
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
|
||||||
|
|
||||||
|
|
||||||
def sphere_increasing(middle, pos):
|
def sphere_increasing(middle, pos):
|
||||||
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
|
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
|
||||||
|
|
||||||
|
|
||||||
def sphere_decreasing(middle, pos):
|
def sphere_decreasing(middle, pos):
|
||||||
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
|
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
|
||||||
|
|
||||||
SEGMENTS = [ linear, curved, sine, sphere_increasing, sphere_decreasing ]
|
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
|
||||||
|
|
||||||
|
|
||||||
class GradientFile:
|
class GradientFile:
|
||||||
|
|
||||||
gradient = None
|
gradient = None
|
||||||
|
|
||||||
def getpalette(self, entries = 256):
|
def getpalette(self, entries=256):
|
||||||
|
|
||||||
palette = []
|
palette = []
|
||||||
|
|
||||||
|
@ -89,6 +95,7 @@ class GradientFile:
|
||||||
|
|
||||||
return b"".join(palette), "RGBA"
|
return b"".join(palette), "RGBA"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# File handler for GIMP's gradient format.
|
# File handler for GIMP's gradient format.
|
||||||
|
|
||||||
|
@ -99,7 +106,13 @@ class GimpGradientFile(GradientFile):
|
||||||
if fp.readline()[:13] != b"GIMP Gradient":
|
if fp.readline()[:13] != b"GIMP Gradient":
|
||||||
raise SyntaxError("not a GIMP gradient file")
|
raise SyntaxError("not a GIMP gradient file")
|
||||||
|
|
||||||
count = int(fp.readline())
|
line = fp.readline()
|
||||||
|
|
||||||
|
# GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
|
||||||
|
if line.startswith(b"Name: "):
|
||||||
|
line = fp.readline().strip()
|
||||||
|
|
||||||
|
count = int(line)
|
||||||
|
|
||||||
gradient = []
|
gradient = []
|
||||||
|
|
||||||
|
@ -108,13 +121,13 @@ class GimpGradientFile(GradientFile):
|
||||||
s = fp.readline().split()
|
s = fp.readline().split()
|
||||||
w = [float(x) for x in s[:11]]
|
w = [float(x) for x in s[:11]]
|
||||||
|
|
||||||
x0, x1 = w[0], w[2]
|
x0, x1 = w[0], w[2]
|
||||||
xm = w[1]
|
xm = w[1]
|
||||||
rgb0 = w[3:7]
|
rgb0 = w[3:7]
|
||||||
rgb1 = w[7:11]
|
rgb1 = w[7:11]
|
||||||
|
|
||||||
segment = SEGMENTS[int(s[11])]
|
segment = SEGMENTS[int(s[11])]
|
||||||
cspace = int(s[12])
|
cspace = int(s[12])
|
||||||
|
|
||||||
if cspace != 0:
|
if cspace != 0:
|
||||||
raise IOError("cannot handle HSV colour space")
|
raise IOError("cannot handle HSV colour space")
|
||||||
|
|
42
PIL/Image.py
|
@ -1516,6 +1516,9 @@ class Image:
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
|
if self.size == size:
|
||||||
|
return self._new(self.im)
|
||||||
|
|
||||||
if self.mode in ("1", "P"):
|
if self.mode in ("1", "P"):
|
||||||
resample = NEAREST
|
resample = NEAREST
|
||||||
|
|
||||||
|
@ -1912,6 +1915,16 @@ class Image:
|
||||||
im = self.im.transpose(method)
|
im = self.im.transpose(method)
|
||||||
return self._new(im)
|
return self._new(im)
|
||||||
|
|
||||||
|
def effect_spread(self, distance):
|
||||||
|
"""
|
||||||
|
Randomly spread pixels in an image.
|
||||||
|
|
||||||
|
:param distance: Distance to spread pixels.
|
||||||
|
"""
|
||||||
|
self.load()
|
||||||
|
im = self.im.effect_spread(distance)
|
||||||
|
return self._new(im)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Lazy operations
|
# Lazy operations
|
||||||
|
@ -2421,3 +2434,32 @@ def _show(image, **options):
|
||||||
def _showxv(image, title=None, **options):
|
def _showxv(image, title=None, **options):
|
||||||
from PIL import ImageShow
|
from PIL import ImageShow
|
||||||
ImageShow.show(image, title, **options)
|
ImageShow.show(image, title, **options)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Effects
|
||||||
|
|
||||||
|
def effect_mandelbrot(size, extent, quality):
|
||||||
|
"""
|
||||||
|
Generate a Mandelbrot set covering the given extent.
|
||||||
|
|
||||||
|
:param size: The requested size in pixels, as a 2-tuple:
|
||||||
|
(width, height).
|
||||||
|
:param extent: The extent to cover, as a 4-tuple:
|
||||||
|
(x0, y0, x1, y2).
|
||||||
|
:param quality: Quality.
|
||||||
|
"""
|
||||||
|
return Image()._new(core.effect_mandelbrot(size, extent, quality))
|
||||||
|
|
||||||
|
|
||||||
|
def effect_noise(size, sigma):
|
||||||
|
"""
|
||||||
|
Generate Gaussian noise centered around 128.
|
||||||
|
|
||||||
|
:param size: The requested size in pixels, as a 2-tuple:
|
||||||
|
(width, height).
|
||||||
|
:param sigma: Standard deviation of noise.
|
||||||
|
"""
|
||||||
|
return Image()._new(core.effect_noise(size, sigma))
|
||||||
|
|
||||||
|
# End of file
|
||||||
|
|
|
@ -234,6 +234,8 @@ class ImageFile(Image.Image):
|
||||||
break
|
break
|
||||||
b = b[n:]
|
b = b[n:]
|
||||||
t = t + n
|
t = t + n
|
||||||
|
# Need to cleanup here to prevent leaks in PyPy
|
||||||
|
d.cleanup()
|
||||||
|
|
||||||
self.tile = []
|
self.tile = []
|
||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
|
@ -479,6 +481,7 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
break
|
break
|
||||||
if s < 0:
|
if s < 0:
|
||||||
raise IOError("encoder error %d when writing image file" % s)
|
raise IOError("encoder error %d when writing image file" % s)
|
||||||
|
e.cleanup()
|
||||||
else:
|
else:
|
||||||
# slight speedup: compress to real file object
|
# slight speedup: compress to real file object
|
||||||
for e, b, o, a in tile:
|
for e, b, o, a in tile:
|
||||||
|
@ -489,6 +492,7 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
s = e.encode_to_file(fh, bufsize)
|
s = e.encode_to_file(fh, bufsize)
|
||||||
if s < 0:
|
if s < 0:
|
||||||
raise IOError("encoder error %d when writing image file" % s)
|
raise IOError("encoder error %d when writing image file" % s)
|
||||||
|
e.cleanup()
|
||||||
try:
|
try:
|
||||||
fp.flush()
|
fp.flush()
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -550,6 +550,15 @@ def convert_dict_qtables(qtables):
|
||||||
|
|
||||||
|
|
||||||
def get_sampling(im):
|
def get_sampling(im):
|
||||||
|
# There's no subsampling when image have only 1 layer
|
||||||
|
# (grayscale images) or when they are CMYK (4 layers),
|
||||||
|
# so set subsampling to default value.
|
||||||
|
#
|
||||||
|
# NOTE: currently Pillow can't encode JPEG to YCCK format.
|
||||||
|
# If YCCK support is added in the future, subsampling code will have
|
||||||
|
# to be updated (here and in JpegEncode.c) to deal with 4 layers.
|
||||||
|
if not hasattr(im, 'layers') or im.layers in (1, 4):
|
||||||
|
return -1
|
||||||
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
|
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
|
||||||
return samplings.get(sampling, -1)
|
return samplings.get(sampling, -1)
|
||||||
|
|
||||||
|
|
|
@ -281,6 +281,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
self.tagdata = {}
|
self.tagdata = {}
|
||||||
self.tagtype = {} # added 2008-06-05 by Florian Hoech
|
self.tagtype = {} # added 2008-06-05 by Florian Hoech
|
||||||
self.next = None
|
self.next = None
|
||||||
|
self.offset = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.as_dict())
|
return str(self.as_dict())
|
||||||
|
@ -415,6 +416,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
# load tag dictionary
|
# load tag dictionary
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
self.offset = fp.tell()
|
||||||
|
|
||||||
i16 = self.i16
|
i16 = self.i16
|
||||||
i32 = self.i32
|
i32 = self.i32
|
||||||
|
@ -446,7 +448,11 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
# Get and expand tag value
|
# Get and expand tag value
|
||||||
if size > 4:
|
if size > 4:
|
||||||
here = fp.tell()
|
here = fp.tell()
|
||||||
|
if Image.DEBUG:
|
||||||
|
print ("Tag Location: %s" %here)
|
||||||
fp.seek(i32(ifd, 8))
|
fp.seek(i32(ifd, 8))
|
||||||
|
if Image.DEBUG:
|
||||||
|
print ("Data Location: %s" %fp.tell())
|
||||||
data = ImageFile._safe_read(fp, size)
|
data = ImageFile._safe_read(fp, size)
|
||||||
fp.seek(here)
|
fp.seek(here)
|
||||||
else:
|
else:
|
||||||
|
@ -630,18 +636,20 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
"Select a given frame as current image"
|
"Select a given frame as current image"
|
||||||
|
|
||||||
if frame < 0:
|
if frame < 0:
|
||||||
frame = 0
|
frame = 0
|
||||||
self._seek(frame)
|
self._seek(frame)
|
||||||
|
# Create a new core image object on second and
|
||||||
|
# subsequent frames in the image. Image may be
|
||||||
|
# different size/mode.
|
||||||
|
Image._decompression_bomb_check(self.size)
|
||||||
|
self.im = Image.core.new(self.mode, self.size)
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
"Return the current frame number"
|
"Return the current frame number"
|
||||||
|
|
||||||
return self._tell()
|
return self._tell()
|
||||||
|
|
||||||
def _seek(self, frame):
|
def _seek(self, frame):
|
||||||
|
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
if frame < self.__frame:
|
if frame < self.__frame:
|
||||||
# rewind file
|
# rewind file
|
||||||
|
@ -650,14 +658,21 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
while self.__frame < frame:
|
while self.__frame < frame:
|
||||||
if not self.__next:
|
if not self.__next:
|
||||||
raise EOFError("no more images in TIFF file")
|
raise EOFError("no more images in TIFF file")
|
||||||
|
if Image.DEBUG:
|
||||||
|
print("Seeking to frame %s, on frame %s, __next %s, location: %s"%
|
||||||
|
(frame, self.__frame, self.__next, self.fp.tell()))
|
||||||
|
# reset python3 buffered io handle in case fp
|
||||||
|
# was passed to libtiff, invalidating the buffer
|
||||||
|
self.fp.tell()
|
||||||
self.fp.seek(self.__next)
|
self.fp.seek(self.__next)
|
||||||
|
if Image.DEBUG:
|
||||||
|
print("Loading tags, location: %s"%self.fp.tell())
|
||||||
self.tag.load(self.fp)
|
self.tag.load(self.fp)
|
||||||
self.__next = self.tag.next
|
self.__next = self.tag.next
|
||||||
self.__frame += 1
|
self.__frame += 1
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
def _tell(self):
|
def _tell(self):
|
||||||
|
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
def _decoder(self, rawmode, layer, tile=None):
|
def _decoder(self, rawmode, layer, tile=None):
|
||||||
|
@ -706,6 +721,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# (self._compression, (extents tuple),
|
# (self._compression, (extents tuple),
|
||||||
# 0, (rawmode, self._compression, fp))
|
# 0, (rawmode, self._compression, fp))
|
||||||
ignored, extents, ignored_2, args = self.tile[0]
|
ignored, extents, ignored_2, args = self.tile[0]
|
||||||
|
args = args + (self.ifd.offset,)
|
||||||
decoder = Image._getdecoder(self.mode, 'libtiff', args,
|
decoder = Image._getdecoder(self.mode, 'libtiff', args,
|
||||||
self.decoderconfig)
|
self.decoderconfig)
|
||||||
try:
|
try:
|
||||||
|
@ -744,7 +760,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
# libtiff closed the fp in a, we need to close self.fp, if possible
|
# libtiff closed the fp in a, we need to close self.fp, if possible
|
||||||
if hasattr(self.fp, 'close'):
|
if hasattr(self.fp, 'close'):
|
||||||
self.fp.close()
|
if not self.__next:
|
||||||
|
self.fp.close()
|
||||||
self.fp = None # might be shared
|
self.fp = None # might be shared
|
||||||
|
|
||||||
if err < 0:
|
if err < 0:
|
||||||
|
|
|
@ -46,8 +46,8 @@ TAGS = {
|
||||||
(262, 5): "CMYK",
|
(262, 5): "CMYK",
|
||||||
(262, 6): "YCbCr",
|
(262, 6): "YCbCr",
|
||||||
(262, 8): "CieLAB",
|
(262, 8): "CieLAB",
|
||||||
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
|
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
|
||||||
(262, 32892): "LinearRaw", # Adobe DNG
|
(262, 32892): "LinearRaw", # Adobe DNG
|
||||||
|
|
||||||
263: "Thresholding",
|
263: "Thresholding",
|
||||||
264: "CellWidth",
|
264: "CellWidth",
|
||||||
|
@ -240,7 +240,7 @@ TAGS = {
|
||||||
45579: "YawAngle",
|
45579: "YawAngle",
|
||||||
45580: "PitchAngle",
|
45580: "PitchAngle",
|
||||||
45581: "RollAngle",
|
45581: "RollAngle",
|
||||||
|
|
||||||
# Adobe DNG
|
# Adobe DNG
|
||||||
50706: "DNGVersion",
|
50706: "DNGVersion",
|
||||||
50707: "DNGBackwardVersion",
|
50707: "DNGBackwardVersion",
|
||||||
|
@ -255,7 +255,6 @@ TAGS = {
|
||||||
50716: "BlackLevelDeltaV",
|
50716: "BlackLevelDeltaV",
|
||||||
50717: "WhiteLevel",
|
50717: "WhiteLevel",
|
||||||
50718: "DefaultScale",
|
50718: "DefaultScale",
|
||||||
50741: "BestQualityScale", # FIXME! Dictionary contains duplicate keys 50741
|
|
||||||
50719: "DefaultCropOrigin",
|
50719: "DefaultCropOrigin",
|
||||||
50720: "DefaultCropSize",
|
50720: "DefaultCropSize",
|
||||||
50778: "CalibrationIlluminant1",
|
50778: "CalibrationIlluminant1",
|
||||||
|
@ -279,11 +278,12 @@ TAGS = {
|
||||||
50737: "ChromaBlurRadius",
|
50737: "ChromaBlurRadius",
|
||||||
50738: "AntiAliasStrength",
|
50738: "AntiAliasStrength",
|
||||||
50740: "DNGPrivateData",
|
50740: "DNGPrivateData",
|
||||||
50741: "MakerNoteSafety", # FIXME! Dictionary contains duplicate keys 50741
|
50741: "MakerNoteSafety",
|
||||||
|
50780: "BestQualityScale",
|
||||||
|
|
||||||
#ImageJ
|
# ImageJ
|
||||||
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
|
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
|
||||||
50839: "ImageJMetaData", # private tag registered with Adobe
|
50839: "ImageJMetaData", # private tag registered with Adobe
|
||||||
}
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -20,3 +20,6 @@ Pillow is the "friendly" PIL fork by `Alex Clark and Contributors <https://githu
|
||||||
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
|
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
|
||||||
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
|
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
|
||||||
|
|
||||||
|
.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.png
|
||||||
|
:target: https://landscape.io/github/python-pillow/Pillow/master
|
||||||
|
:alt: Code Health
|
||||||
|
|
|
@ -10,6 +10,9 @@ Install::
|
||||||
|
|
||||||
pip install coverage nose
|
pip install coverage nose
|
||||||
|
|
||||||
|
If you're using Python 2.6, there's one additional dependency::
|
||||||
|
|
||||||
|
pip install unittest2
|
||||||
|
|
||||||
Execution
|
Execution
|
||||||
---------
|
---------
|
||||||
|
|
42
Tests/check_j2k_leaks.py
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
import sys
|
||||||
|
from PIL import Image
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
# Limits for testing the leak
|
||||||
|
mem_limit = 1024*1048576
|
||||||
|
stack_size = 8*1048576
|
||||||
|
iterations = int((mem_limit/stack_size)*2)
|
||||||
|
codecs = dir(Image.core)
|
||||||
|
test_file = "Tests/images/rgb_trns_ycbc.jp2"
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
|
||||||
|
class TestJpegLeaks(PillowTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs:
|
||||||
|
self.skipTest('JPEG 2000 support not available')
|
||||||
|
|
||||||
|
def test_leak_load(self):
|
||||||
|
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
|
||||||
|
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||||
|
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
|
||||||
|
for count in range(iterations):
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
def test_leak_save(self):
|
||||||
|
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
|
||||||
|
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||||
|
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
|
||||||
|
for count in range(iterations):
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
im.load()
|
||||||
|
test_output = BytesIO()
|
||||||
|
im.save(test_output, "JPEG2000")
|
||||||
|
test_output.seek(0)
|
||||||
|
output = test_output.read()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -182,6 +182,26 @@ def tostring(im, format, **options):
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def hopper(mode="RGB", cache={}):
|
||||||
|
from PIL import Image
|
||||||
|
im = None
|
||||||
|
# FIXME: Implement caching to reduce reading from disk but so an original
|
||||||
|
# copy is returned each time and the cached image isn't modified by tests
|
||||||
|
# (for fast, isolated, repeatable tests).
|
||||||
|
# im = cache.get(mode)
|
||||||
|
if im is None:
|
||||||
|
if mode == "RGB":
|
||||||
|
im = Image.open("Tests/images/hopper.ppm")
|
||||||
|
elif mode == "F":
|
||||||
|
im = lena("L").convert(mode)
|
||||||
|
elif mode[:4] == "I;16":
|
||||||
|
im = lena("I").convert(mode)
|
||||||
|
else:
|
||||||
|
im = lena("RGB").convert(mode)
|
||||||
|
# cache[mode] = im
|
||||||
|
return im
|
||||||
|
|
||||||
|
|
||||||
def lena(mode="RGB", cache={}):
|
def lena(mode="RGB", cache={}):
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
im = None
|
im = None
|
||||||
|
|
BIN
Tests/images/effect_mandelbrot.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
Tests/images/effect_spread.png
Normal file
After Width: | Height: | Size: 40 KiB |
6
Tests/images/gimp_gradient.ggr
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
GIMP Gradient
|
||||||
|
4
|
||||||
|
0.000000 0.351923 0.534893 1.000000 1.000000 1.000000 0.910000 0.730303 0.510606 1.000000 0.480000 1 0
|
||||||
|
0.501504 0.611002 0.637730 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 1 0
|
||||||
|
0.931891 0.951264 1.000000 0.531255 0.316078 1.031173 1.000000 0.000000 0.000000 0.000000 1.000000 0 0
|
||||||
|
0.810551 0.881217 0.921883 0.717576 0.441331 0.081217 1.000000 0.751576 0.410331 0.081217 1.000000 0 0
|
7
Tests/images/gimp_gradient_with_name.ggr
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
GIMP Gradient
|
||||||
|
Name: A GIMP 1.3 gradient file
|
||||||
|
4
|
||||||
|
0.000000 0.351923 0.534893 1.000000 1.000000 1.000000 0.910000 0.730303 0.510606 1.000000 0.480000 1 0
|
||||||
|
0.501504 0.611002 0.637730 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 1 0
|
||||||
|
0.931891 0.951264 1.000000 0.531255 0.316078 1.031173 1.000000 0.000000 0.000000 0.000000 1.000000 0 0
|
||||||
|
0.810551 0.881217 0.921883 0.717576 0.441331 0.081217 1.000000 0.751576 0.410331 0.081217 1.000000 0 0
|
BIN
Tests/images/hopper.bw
Normal file
BIN
Tests/images/hopper.dcx
Normal file
BIN
Tests/images/hopper.gif
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Tests/images/hopper.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Tests/images/hopper.jpg
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
Tests/images/hopper.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
Tests/images/hopper.ppm
Normal file
BIN
Tests/images/hopper.ras
Normal file
BIN
Tests/images/hopper.rgb
Normal file
BIN
Tests/images/hopper.spider
Normal file
BIN
Tests/images/hopper.tar
Normal file
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 37 KiB |
BIN
Tests/images/lena_gray.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
Tests/images/multipage-lastframe.tif
Normal file
BIN
Tests/images/multipage.tiff
Normal file
|
@ -1,9 +1,9 @@
|
||||||
from helper import unittest, PillowTestCase, lena
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
from PIL import Image, DcxImagePlugin
|
from PIL import Image, DcxImagePlugin
|
||||||
|
|
||||||
# Created with ImageMagick: convert lena.ppm lena.dcx
|
# Created with ImageMagick: convert hopper.ppm hopper.dcx
|
||||||
TEST_FILE = "Tests/images/lena.dcx"
|
TEST_FILE = "Tests/images/hopper.dcx"
|
||||||
|
|
||||||
|
|
||||||
class TestFileDcx(PillowTestCase):
|
class TestFileDcx(PillowTestCase):
|
||||||
|
@ -17,7 +17,7 @@ class TestFileDcx(PillowTestCase):
|
||||||
# Assert
|
# Assert
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
self.assertIsInstance(im, DcxImagePlugin.DcxImageFile)
|
self.assertIsInstance(im, DcxImagePlugin.DcxImageFile)
|
||||||
orig = lena()
|
orig = hopper()
|
||||||
self.assert_image_equal(im, orig)
|
self.assert_image_equal(im, orig)
|
||||||
|
|
||||||
def test_tell(self):
|
def test_tell(self):
|
||||||
|
|
|
@ -63,6 +63,17 @@ class TestFileEps(PillowTestCase):
|
||||||
with io.open(self.tempfile('temp_iobase.eps'), 'wb') as fh:
|
with io.open(self.tempfile('temp_iobase.eps'), 'wb') as fh:
|
||||||
image1.save(fh, 'EPS')
|
image1.save(fh, 'EPS')
|
||||||
|
|
||||||
|
def test_bytesio_object(self):
|
||||||
|
with open(file1, 'rb') as f:
|
||||||
|
img_bytes = io.BytesIO(f.read())
|
||||||
|
|
||||||
|
img = Image.open(img_bytes)
|
||||||
|
img.load()
|
||||||
|
|
||||||
|
image1_scale1_compare = Image.open(file1_compare).convert("RGB")
|
||||||
|
image1_scale1_compare.load()
|
||||||
|
self.assert_image_similar(img, image1_scale1_compare, 5)
|
||||||
|
|
||||||
def test_render_scale1(self):
|
def test_render_scale1(self):
|
||||||
# We need png support for these render test
|
# We need png support for these render test
|
||||||
codecs = dir(Image.core)
|
codecs = dir(Image.core)
|
||||||
|
@ -137,6 +148,83 @@ class TestFileEps(PillowTestCase):
|
||||||
# open image with binary preview
|
# open image with binary preview
|
||||||
Image.open(file3)
|
Image.open(file3)
|
||||||
|
|
||||||
|
def _test_readline(self,t, ending):
|
||||||
|
ending = "Failure with line ending: %s" %("".join("%s" %ord(s) for s in ending))
|
||||||
|
self.assertEqual(t.readline().strip('\r\n'), 'something', ending)
|
||||||
|
self.assertEqual(t.readline().strip('\r\n'), 'else', ending)
|
||||||
|
self.assertEqual(t.readline().strip('\r\n'), 'baz', ending)
|
||||||
|
self.assertEqual(t.readline().strip('\r\n'), 'bif', ending)
|
||||||
|
|
||||||
|
def _test_readline_stringio(self, test_string, ending):
|
||||||
|
# check all the freaking line endings possible
|
||||||
|
try:
|
||||||
|
import StringIO
|
||||||
|
except:
|
||||||
|
# don't skip, it skips everything in the parent test
|
||||||
|
return
|
||||||
|
t = StringIO.StringIO(test_string)
|
||||||
|
self._test_readline(t, ending)
|
||||||
|
|
||||||
|
def _test_readline_io(self, test_string, ending):
|
||||||
|
import io
|
||||||
|
if str is bytes:
|
||||||
|
t = io.StringIO(unicode(test_string))
|
||||||
|
else:
|
||||||
|
t = io.StringIO(test_string)
|
||||||
|
self._test_readline(t, ending)
|
||||||
|
|
||||||
|
def _test_readline_file_universal(self, test_string, ending):
|
||||||
|
f = self.tempfile('temp.txt')
|
||||||
|
with open(f,'wb') as w:
|
||||||
|
if str is bytes:
|
||||||
|
w.write(test_string)
|
||||||
|
else:
|
||||||
|
w.write(test_string.encode('UTF-8'))
|
||||||
|
|
||||||
|
with open(f,'rU') as t:
|
||||||
|
self._test_readline(t, ending)
|
||||||
|
|
||||||
|
def _test_readline_file_psfile(self, test_string, ending):
|
||||||
|
f = self.tempfile('temp.txt')
|
||||||
|
with open(f,'wb') as w:
|
||||||
|
if str is bytes:
|
||||||
|
w.write(test_string)
|
||||||
|
else:
|
||||||
|
w.write(test_string.encode('UTF-8'))
|
||||||
|
|
||||||
|
with open(f,'rb') as r:
|
||||||
|
t = EpsImagePlugin.PSFile(r)
|
||||||
|
self._test_readline(t, ending)
|
||||||
|
|
||||||
|
def test_readline(self):
|
||||||
|
# check all the freaking line endings possible from the spec
|
||||||
|
#test_string = u'something\r\nelse\n\rbaz\rbif\n'
|
||||||
|
line_endings = ['\r\n', '\n']
|
||||||
|
not_working_endings = ['\n\r', '\r']
|
||||||
|
strings = ['something', 'else', 'baz', 'bif']
|
||||||
|
|
||||||
|
for ending in line_endings:
|
||||||
|
s = ending.join(strings)
|
||||||
|
# Native python versions will pass these endings.
|
||||||
|
#self._test_readline_stringio(s, ending)
|
||||||
|
#self._test_readline_io(s, ending)
|
||||||
|
#self._test_readline_file_universal(s, ending)
|
||||||
|
|
||||||
|
self._test_readline_file_psfile(s, ending)
|
||||||
|
|
||||||
|
for ending in not_working_endings:
|
||||||
|
# these only work with the PSFile, while they're in spec,
|
||||||
|
# they're not likely to be used
|
||||||
|
s = ending.join(strings)
|
||||||
|
|
||||||
|
# Native python versions may fail on these endings.
|
||||||
|
#self._test_readline_stringio(s, ending)
|
||||||
|
#self._test_readline_io(s, ending)
|
||||||
|
#self._test_readline_file_universal(s, ending)
|
||||||
|
|
||||||
|
self._test_readline_file_psfile(s, ending)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from helper import unittest, PillowTestCase, lena, netpbm_available
|
from helper import unittest, PillowTestCase, hopper, netpbm_available
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL import GifImagePlugin
|
from PIL import GifImagePlugin
|
||||||
|
@ -6,8 +6,9 @@ from PIL import GifImagePlugin
|
||||||
codecs = dir(Image.core)
|
codecs = dir(Image.core)
|
||||||
|
|
||||||
# sample gif stream
|
# sample gif stream
|
||||||
file = "Tests/images/lena.gif"
|
TEST_GIF = "Tests/images/hopper.gif"
|
||||||
with open(file, "rb") as f:
|
|
||||||
|
with open(TEST_GIF, "rb") as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ class TestFileGif(PillowTestCase):
|
||||||
self.skipTest("gif support not available") # can this happen?
|
self.skipTest("gif support not available") # can this happen?
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
im = Image.open(file)
|
im = Image.open(TEST_GIF)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertEqual(im.mode, "P")
|
self.assertEqual(im.mode, "P")
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
|
@ -45,7 +46,7 @@ class TestFileGif(PillowTestCase):
|
||||||
|
|
||||||
def test_roundtrip(self):
|
def test_roundtrip(self):
|
||||||
out = self.tempfile('temp.gif')
|
out = self.tempfile('temp.gif')
|
||||||
im = lena()
|
im = hopper()
|
||||||
im.save(out)
|
im.save(out)
|
||||||
reread = Image.open(out)
|
reread = Image.open(out)
|
||||||
|
|
||||||
|
@ -54,17 +55,17 @@ class TestFileGif(PillowTestCase):
|
||||||
def test_roundtrip2(self):
|
def test_roundtrip2(self):
|
||||||
# see https://github.com/python-pillow/Pillow/issues/403
|
# see https://github.com/python-pillow/Pillow/issues/403
|
||||||
out = self.tempfile('temp.gif')
|
out = self.tempfile('temp.gif')
|
||||||
im = Image.open('Tests/images/lena.gif')
|
im = Image.open(TEST_GIF)
|
||||||
im2 = im.copy()
|
im2 = im.copy()
|
||||||
im2.save(out)
|
im2.save(out)
|
||||||
reread = Image.open(out)
|
reread = Image.open(out)
|
||||||
|
|
||||||
self.assert_image_similar(reread.convert('RGB'), lena(), 50)
|
self.assert_image_similar(reread.convert('RGB'), hopper(), 50)
|
||||||
|
|
||||||
def test_palette_handling(self):
|
def test_palette_handling(self):
|
||||||
# see https://github.com/python-pillow/Pillow/issues/513
|
# see https://github.com/python-pillow/Pillow/issues/513
|
||||||
|
|
||||||
im = Image.open('Tests/images/lena.gif')
|
im = Image.open(TEST_GIF)
|
||||||
im = im.convert('RGB')
|
im = im.convert('RGB')
|
||||||
|
|
||||||
im = im.resize((100, 100), Image.ANTIALIAS)
|
im = im.resize((100, 100), Image.ANTIALIAS)
|
||||||
|
@ -100,7 +101,7 @@ class TestFileGif(PillowTestCase):
|
||||||
|
|
||||||
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
||||||
def test_save_netpbm_bmp_mode(self):
|
def test_save_netpbm_bmp_mode(self):
|
||||||
img = Image.open(file).convert("RGB")
|
img = Image.open(TEST_GIF).convert("RGB")
|
||||||
|
|
||||||
tempfile = self.tempfile("temp.gif")
|
tempfile = self.tempfile("temp.gif")
|
||||||
GifImagePlugin._save_netpbm(img, 0, tempfile)
|
GifImagePlugin._save_netpbm(img, 0, tempfile)
|
||||||
|
@ -108,7 +109,7 @@ class TestFileGif(PillowTestCase):
|
||||||
|
|
||||||
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
||||||
def test_save_netpbm_l_mode(self):
|
def test_save_netpbm_l_mode(self):
|
||||||
img = Image.open(file).convert("L")
|
img = Image.open(TEST_GIF).convert("L")
|
||||||
|
|
||||||
tempfile = self.tempfile("temp.gif")
|
tempfile = self.tempfile("temp.gif")
|
||||||
GifImagePlugin._save_netpbm(img, 0, tempfile)
|
GifImagePlugin._save_netpbm(img, 0, tempfile)
|
||||||
|
|
127
Tests/test_file_gimpgradient.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
|
from PIL import GimpGradientFile
|
||||||
|
|
||||||
|
|
||||||
|
class TestImage(PillowTestCase):
|
||||||
|
|
||||||
|
def test_linear_pos_le_middle(self):
|
||||||
|
# Arrange
|
||||||
|
middle = 0.5
|
||||||
|
pos = 0.25
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ret = GimpGradientFile.linear(middle, pos)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(ret, 0.25)
|
||||||
|
|
||||||
|
def test_linear_pos_le_small_middle(self):
|
||||||
|
# Arrange
|
||||||
|
middle = 1e-11
|
||||||
|
pos = 1e-12
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ret = GimpGradientFile.linear(middle, pos)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(ret, 0.0)
|
||||||
|
|
||||||
|
def test_linear_pos_gt_middle(self):
|
||||||
|
# Arrange
|
||||||
|
middle = 0.5
|
||||||
|
pos = 0.75
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ret = GimpGradientFile.linear(middle, pos)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(ret, 0.75)
|
||||||
|
|
||||||
|
def test_linear_pos_gt_small_middle(self):
|
||||||
|
# Arrange
|
||||||
|
middle = 1 - 1e-11
|
||||||
|
pos = 1 - 1e-12
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ret = GimpGradientFile.linear(middle, pos)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(ret, 1.0)
|
||||||
|
|
||||||
|
def test_curved(self):
|
||||||
|
# Arrange
|
||||||
|
middle = 0.5
|
||||||
|
pos = 0.75
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ret = GimpGradientFile.curved(middle, pos)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(ret, 0.75)
|
||||||
|
|
||||||
|
def test_sine(self):
|
||||||
|
# Arrange
|
||||||
|
middle = 0.5
|
||||||
|
pos = 0.75
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ret = GimpGradientFile.sine(middle, pos)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(ret, 0.8535533905932737)
|
||||||
|
|
||||||
|
def test_sphere_increasing(self):
|
||||||
|
# Arrange
|
||||||
|
middle = 0.5
|
||||||
|
pos = 0.75
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ret = GimpGradientFile.sphere_increasing(middle, pos)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(ret, 0.9682458365518543)
|
||||||
|
|
||||||
|
def test_sphere_decreasing(self):
|
||||||
|
# Arrange
|
||||||
|
middle = 0.5
|
||||||
|
pos = 0.75
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ret = GimpGradientFile.sphere_decreasing(middle, pos)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(ret, 0.3385621722338523)
|
||||||
|
|
||||||
|
def test_load_via_imagepalette(self):
|
||||||
|
# Arrange
|
||||||
|
from PIL import ImagePalette
|
||||||
|
test_file = "Tests/images/gimp_gradient.ggr"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
palette = ImagePalette.load(test_file)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
# load returns raw palette information
|
||||||
|
self.assertEqual(len(palette[0]), 1024)
|
||||||
|
self.assertEqual(palette[1], "RGBA")
|
||||||
|
|
||||||
|
def test_load_1_3_via_imagepalette(self):
|
||||||
|
# Arrange
|
||||||
|
from PIL import ImagePalette
|
||||||
|
# GIMP 1.3 gradient files contain a name field
|
||||||
|
test_file = "Tests/images/gimp_gradient_with_name.ggr"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
palette = ImagePalette.load(test_file)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
# load returns raw palette information
|
||||||
|
self.assertEqual(len(palette[0]), 1024)
|
||||||
|
self.assertEqual(palette[1], "RGBA")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
|
@ -3,14 +3,14 @@ from helper import unittest, PillowTestCase
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
# sample ppm stream
|
# sample ppm stream
|
||||||
file = "Tests/images/lena.ico"
|
TEST_ICO_FILE = "Tests/images/hopper.ico"
|
||||||
data = open(file, "rb").read()
|
TEST_DATA = open(TEST_ICO_FILE, "rb").read()
|
||||||
|
|
||||||
|
|
||||||
class TestFileIco(PillowTestCase):
|
class TestFileIco(PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
im = Image.open(file)
|
im = Image.open(TEST_ICO_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertEqual(im.mode, "RGBA")
|
self.assertEqual(im.mode, "RGBA")
|
||||||
self.assertEqual(im.size, (16, 16))
|
self.assertEqual(im.size, (16, 16))
|
||||||
|
|
|
@ -60,8 +60,8 @@ class TestFileJpeg(PillowTestCase):
|
||||||
self.assertGreater(y, 0.8)
|
self.assertGreater(y, 0.8)
|
||||||
self.assertEqual(k, 0.0)
|
self.assertEqual(k, 0.0)
|
||||||
# the opposite corner is black
|
# the opposite corner is black
|
||||||
c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1,
|
c, m, y, k = [x / 255.0 for x in im.getpixel((
|
||||||
im.size[1]-1))]
|
im.size[0]-1, im.size[1]-1))]
|
||||||
self.assertGreater(k, 0.9)
|
self.assertGreater(k, 0.9)
|
||||||
# roundtrip, and check again
|
# roundtrip, and check again
|
||||||
im = self.roundtrip(im)
|
im = self.roundtrip(im)
|
||||||
|
@ -70,8 +70,8 @@ class TestFileJpeg(PillowTestCase):
|
||||||
self.assertGreater(m, 0.8)
|
self.assertGreater(m, 0.8)
|
||||||
self.assertGreater(y, 0.8)
|
self.assertGreater(y, 0.8)
|
||||||
self.assertEqual(k, 0.0)
|
self.assertEqual(k, 0.0)
|
||||||
c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1,
|
c, m, y, k = [x / 255.0 for x in im.getpixel((
|
||||||
im.size[1]-1))]
|
im.size[0]-1, im.size[1]-1))]
|
||||||
self.assertGreater(k, 0.9)
|
self.assertGreater(k, 0.9)
|
||||||
|
|
||||||
def test_dpi(self):
|
def test_dpi(self):
|
||||||
|
@ -152,8 +152,8 @@ class TestFileJpeg(PillowTestCase):
|
||||||
if py3:
|
if py3:
|
||||||
a = bytes(random.randint(0, 255) for _ in range(256 * 256 * 3))
|
a = bytes(random.randint(0, 255) for _ in range(256 * 256 * 3))
|
||||||
else:
|
else:
|
||||||
a = b''.join(chr(random.randint(0, 255)) for _ in
|
a = b''.join(chr(random.randint(0, 255)) for _ in range(
|
||||||
range(256 * 256 * 3))
|
256 * 256 * 3))
|
||||||
im = Image.frombuffer("RGB", (256, 256), a, "raw", "RGB", 0, 1)
|
im = Image.frombuffer("RGB", (256, 256), a, "raw", "RGB", 0, 1)
|
||||||
# this requires more bytes than pixels in the image
|
# this requires more bytes than pixels in the image
|
||||||
im.save(f, format="JPEG", progressive=True, quality=100)
|
im.save(f, format="JPEG", progressive=True, quality=100)
|
||||||
|
@ -224,9 +224,18 @@ class TestFileJpeg(PillowTestCase):
|
||||||
self.assertIsNone(im._getmp())
|
self.assertIsNone(im._getmp())
|
||||||
|
|
||||||
def test_quality_keep(self):
|
def test_quality_keep(self):
|
||||||
|
# RGB
|
||||||
im = Image.open("Tests/images/lena.jpg")
|
im = Image.open("Tests/images/lena.jpg")
|
||||||
f = self.tempfile('temp.jpg')
|
f = self.tempfile('temp.jpg')
|
||||||
im.save(f, quality='keep')
|
im.save(f, quality='keep')
|
||||||
|
# Grayscale
|
||||||
|
im = Image.open("Tests/images/lena_gray.jpg")
|
||||||
|
f = self.tempfile('temp.jpg')
|
||||||
|
im.save(f, quality='keep')
|
||||||
|
# CMYK
|
||||||
|
im = Image.open("Tests/images/pil_sample_cmyk.jpg")
|
||||||
|
f = self.tempfile('temp.jpg')
|
||||||
|
im.save(f, quality='keep')
|
||||||
|
|
||||||
def test_junk_jpeg_header(self):
|
def test_junk_jpeg_header(self):
|
||||||
# https://github.com/python-pillow/Pillow/issues/630
|
# https://github.com/python-pillow/Pillow/issues/630
|
||||||
|
@ -279,11 +288,11 @@ class TestFileJpeg(PillowTestCase):
|
||||||
30)
|
30)
|
||||||
|
|
||||||
# dict of qtable lists
|
# dict of qtable lists
|
||||||
self.assert_image_similar(
|
self.assert_image_similar(im,
|
||||||
im, self.roundtrip(
|
self.roundtrip(im,
|
||||||
im, qtables={
|
qtables={0: standard_l_qtable,
|
||||||
0: standard_l_qtable, 1: standard_chrominance_qtable}),
|
1: standard_chrominance_qtable}),
|
||||||
30)
|
30)
|
||||||
|
|
||||||
@unittest.skipUnless(djpeg_available(), "djpeg not available")
|
@unittest.skipUnless(djpeg_available(), "djpeg not available")
|
||||||
def test_load_djpeg(self):
|
def test_load_djpeg(self):
|
||||||
|
@ -300,6 +309,15 @@ class TestFileJpeg(PillowTestCase):
|
||||||
# Default save quality is 75%, so a tiny bit of difference is alright
|
# Default save quality is 75%, so a tiny bit of difference is alright
|
||||||
self.assert_image_similar(img, Image.open(tempfile), 1)
|
self.assert_image_similar(img, Image.open(tempfile), 1)
|
||||||
|
|
||||||
|
def test_no_duplicate_0x1001_tag(self):
|
||||||
|
# Arrange
|
||||||
|
from PIL import ExifTags
|
||||||
|
tag_ids = dict(zip(ExifTags.TAGS.values(), ExifTags.TAGS.keys()))
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(tag_ids['RelatedImageWidth'], 0x1001)
|
||||||
|
self.assertEqual(tag_ids['RelatedImageLength'], 0x1002)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,8 +4,7 @@ import os
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
from PIL import Image, TiffImagePlugin
|
||||||
|
|
||||||
|
class LibTiffTestCase(PillowTestCase):
|
||||||
class TestFileLibTiff(PillowTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
codecs = dir(Image.core)
|
codecs = dir(Image.core)
|
||||||
|
@ -32,6 +31,8 @@ class TestFileLibTiff(PillowTestCase):
|
||||||
out = self.tempfile("temp.png")
|
out = self.tempfile("temp.png")
|
||||||
im.save(out)
|
im.save(out)
|
||||||
|
|
||||||
|
class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
def test_g4_tiff(self):
|
def test_g4_tiff(self):
|
||||||
"""Test the ordinary file path load path"""
|
"""Test the ordinary file path load path"""
|
||||||
|
|
||||||
|
@ -311,6 +312,35 @@ class TestFileLibTiff(PillowTestCase):
|
||||||
self.assertRaises(OSError, lambda: os.fstat(fn))
|
self.assertRaises(OSError, lambda: os.fstat(fn))
|
||||||
self.assertRaises(OSError, lambda: os.close(fn))
|
self.assertRaises(OSError, lambda: os.close(fn))
|
||||||
|
|
||||||
|
def test_multipage(self):
|
||||||
|
# issue #862
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = True
|
||||||
|
im = Image.open('Tests/images/multipage.tiff')
|
||||||
|
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
|
||||||
|
|
||||||
|
im.seek(0)
|
||||||
|
self.assertEqual(im.size, (10,10))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,128,0))
|
||||||
|
self.assertTrue(im.tag.next)
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
self.assertEqual(im.size, (10,10))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (255,0,0))
|
||||||
|
self.assertTrue(im.tag.next)
|
||||||
|
|
||||||
|
im.seek(2)
|
||||||
|
self.assertFalse(im.tag.next)
|
||||||
|
self.assertEqual(im.size, (20,20))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255))
|
||||||
|
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = False
|
||||||
|
|
||||||
|
def test__next(self):
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = True
|
||||||
|
im = Image.open('Tests/images/lena.tif')
|
||||||
|
self.assertFalse(im.tag.next)
|
||||||
|
im.load()
|
||||||
|
self.assertFalse(im.tag.next)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -2,12 +2,10 @@ from helper import unittest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from test_file_libtiff import TestFileLibTiff
|
from test_file_libtiff import LibTiffTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestFileLibTiffSmall(TestFileLibTiff):
|
class TestFileLibTiffSmall(LibTiffTestCase):
|
||||||
|
|
||||||
# Inherits TestFileLibTiff's setUp() and self._assert_noerr()
|
|
||||||
|
|
||||||
""" The small lena image was failing on open in the libtiff
|
""" The small lena image was failing on open in the libtiff
|
||||||
decoder because the file pointer was set to the wrong place
|
decoder because the file pointer was set to the wrong place
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from helper import unittest, PillowTestCase, lena
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@ codecs = dir(Image.core)
|
||||||
|
|
||||||
# sample png stream
|
# sample png stream
|
||||||
|
|
||||||
file = "Tests/images/lena.png"
|
TEST_PNG_FILE = "Tests/images/hopper.png"
|
||||||
data = open(file, "rb").read()
|
TEST_DATA = open(TEST_PNG_FILE, "rb").read()
|
||||||
|
|
||||||
# stuff to create inline PNG images
|
# stuff to create inline PNG images
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class TestFilePng(PillowTestCase):
|
||||||
|
|
||||||
file = self.tempfile("temp.png")
|
file = self.tempfile("temp.png")
|
||||||
|
|
||||||
lena("RGB").save(file)
|
hopper("RGB").save(file)
|
||||||
|
|
||||||
im = Image.open(file)
|
im = Image.open(file)
|
||||||
im.load()
|
im.load()
|
||||||
|
@ -66,19 +66,19 @@ class TestFilePng(PillowTestCase):
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
self.assertEqual(im.format, "PNG")
|
self.assertEqual(im.format, "PNG")
|
||||||
|
|
||||||
lena("1").save(file)
|
hopper("1").save(file)
|
||||||
im = Image.open(file)
|
im = Image.open(file)
|
||||||
|
|
||||||
lena("L").save(file)
|
hopper("L").save(file)
|
||||||
im = Image.open(file)
|
im = Image.open(file)
|
||||||
|
|
||||||
lena("P").save(file)
|
hopper("P").save(file)
|
||||||
im = Image.open(file)
|
im = Image.open(file)
|
||||||
|
|
||||||
lena("RGB").save(file)
|
hopper("RGB").save(file)
|
||||||
im = Image.open(file)
|
im = Image.open(file)
|
||||||
|
|
||||||
lena("I").save(file)
|
hopper("I").save(file)
|
||||||
im = Image.open(file)
|
im = Image.open(file)
|
||||||
|
|
||||||
def test_broken(self):
|
def test_broken(self):
|
||||||
|
@ -240,17 +240,17 @@ class TestFilePng(PillowTestCase):
|
||||||
def test_load_verify(self):
|
def test_load_verify(self):
|
||||||
# Check open/load/verify exception (@PIL150)
|
# Check open/load/verify exception (@PIL150)
|
||||||
|
|
||||||
im = Image.open("Tests/images/lena.png")
|
im = Image.open(TEST_PNG_FILE)
|
||||||
im.verify()
|
im.verify()
|
||||||
|
|
||||||
im = Image.open("Tests/images/lena.png")
|
im = Image.open(TEST_PNG_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertRaises(RuntimeError, lambda: im.verify())
|
self.assertRaises(RuntimeError, lambda: im.verify())
|
||||||
|
|
||||||
def test_roundtrip_dpi(self):
|
def test_roundtrip_dpi(self):
|
||||||
# Check dpi roundtripping
|
# Check dpi roundtripping
|
||||||
|
|
||||||
im = Image.open(file)
|
im = Image.open(TEST_PNG_FILE)
|
||||||
|
|
||||||
im = roundtrip(im, dpi=(100, 100))
|
im = roundtrip(im, dpi=(100, 100))
|
||||||
self.assertEqual(im.info["dpi"], (100, 100))
|
self.assertEqual(im.info["dpi"], (100, 100))
|
||||||
|
@ -258,7 +258,7 @@ class TestFilePng(PillowTestCase):
|
||||||
def test_roundtrip_text(self):
|
def test_roundtrip_text(self):
|
||||||
# Check text roundtripping
|
# Check text roundtripping
|
||||||
|
|
||||||
im = Image.open(file)
|
im = Image.open(TEST_PNG_FILE)
|
||||||
|
|
||||||
info = PngImagePlugin.PngInfo()
|
info = PngImagePlugin.PngInfo()
|
||||||
info.add_text("TXT", "VALUE")
|
info.add_text("TXT", "VALUE")
|
||||||
|
@ -342,7 +342,7 @@ class TestFilePng(PillowTestCase):
|
||||||
|
|
||||||
def test_trns_p(self):
|
def test_trns_p(self):
|
||||||
# Check writing a transparency of 0, issue #528
|
# Check writing a transparency of 0, issue #528
|
||||||
im = lena('P')
|
im = hopper('P')
|
||||||
im.info['transparency'] = 0
|
im.info['transparency'] = 0
|
||||||
|
|
||||||
f = self.tempfile("temp.png")
|
f = self.tempfile("temp.png")
|
||||||
|
@ -364,7 +364,7 @@ class TestFilePng(PillowTestCase):
|
||||||
|
|
||||||
def test_roundtrip_icc_profile(self):
|
def test_roundtrip_icc_profile(self):
|
||||||
# check that we can roundtrip the icc profile
|
# check that we can roundtrip the icc profile
|
||||||
im = lena('RGB')
|
im = hopper('RGB')
|
||||||
|
|
||||||
jpeg_image = Image.open('Tests/images/flower2.jpg')
|
jpeg_image = Image.open('Tests/images/flower2.jpg')
|
||||||
expected_icc = jpeg_image.info['icc_profile']
|
expected_icc = jpeg_image.info['icc_profile']
|
||||||
|
|
|
@ -8,8 +8,8 @@ class TestFileSgi(PillowTestCase):
|
||||||
def test_rgb(self):
|
def test_rgb(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
# Created with ImageMagick then renamed:
|
# Created with ImageMagick then renamed:
|
||||||
# convert lena.ppm lena.sgi
|
# convert hopper.ppm hopper.sgi
|
||||||
test_file = "Tests/images/lena.rgb"
|
test_file = "Tests/images/hopper.rgb"
|
||||||
|
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
self.assertRaises(ValueError, lambda: Image.open(test_file))
|
self.assertRaises(ValueError, lambda: Image.open(test_file))
|
||||||
|
@ -17,8 +17,8 @@ class TestFileSgi(PillowTestCase):
|
||||||
def test_l(self):
|
def test_l(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
# Created with ImageMagick then renamed:
|
# Created with ImageMagick then renamed:
|
||||||
# convert lena.ppm -monochrome lena.sgi
|
# convert hopper.ppm -monochrome hopper.sgi
|
||||||
test_file = "Tests/images/lena.bw"
|
test_file = "Tests/images/hopper.bw"
|
||||||
|
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
self.assertRaises(ValueError, lambda: Image.open(test_file))
|
self.assertRaises(ValueError, lambda: Image.open(test_file))
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from helper import unittest, PillowTestCase, lena
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL import SpiderImagePlugin
|
from PIL import SpiderImagePlugin
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/lena.spider"
|
TEST_FILE = "Tests/images/hopper.spider"
|
||||||
|
|
||||||
|
|
||||||
class TestImageSpider(PillowTestCase):
|
class TestImageSpider(PillowTestCase):
|
||||||
|
@ -18,7 +18,7 @@ class TestImageSpider(PillowTestCase):
|
||||||
def test_save(self):
|
def test_save(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
temp = self.tempfile('temp.spider')
|
temp = self.tempfile('temp.spider')
|
||||||
im = lena()
|
im = hopper()
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
im.save(temp, "SPIDER")
|
im.save(temp, "SPIDER")
|
||||||
|
@ -44,7 +44,7 @@ class TestImageSpider(PillowTestCase):
|
||||||
|
|
||||||
def test_loadImageSeries(self):
|
def test_loadImageSeries(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
not_spider_file = "Tests/images/lena.ppm"
|
not_spider_file = "Tests/images/hopper.ppm"
|
||||||
file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"]
|
file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"]
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
|
|
@ -7,8 +7,8 @@ class TestFileSun(PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
# Created with ImageMagick: convert lena.ppm lena.ras
|
# Created with ImageMagick: convert hopper.jpg hopper.ras
|
||||||
test_file = "Tests/images/lena.ras"
|
test_file = "Tests/images/hopper.ras"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
|
|
|
@ -4,8 +4,8 @@ from PIL import Image, TarIO
|
||||||
|
|
||||||
codecs = dir(Image.core)
|
codecs = dir(Image.core)
|
||||||
|
|
||||||
# sample ppm stream
|
# Sample tar archive
|
||||||
tarfile = "Tests/images/lena.tar"
|
TEST_TAR_FILE = "Tests/images/hopper.tar"
|
||||||
|
|
||||||
|
|
||||||
class TestFileTar(PillowTestCase):
|
class TestFileTar(PillowTestCase):
|
||||||
|
@ -16,7 +16,7 @@ class TestFileTar(PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
if "zip_decoder" in codecs:
|
if "zip_decoder" in codecs:
|
||||||
tar = TarIO.TarIO(tarfile, 'lena.png')
|
tar = TarIO.TarIO(TEST_TAR_FILE, 'hopper.png')
|
||||||
im = Image.open(tar)
|
im = Image.open(tar)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertEqual(im.mode, "RGB")
|
self.assertEqual(im.mode, "RGB")
|
||||||
|
@ -24,7 +24,7 @@ class TestFileTar(PillowTestCase):
|
||||||
self.assertEqual(im.format, "PNG")
|
self.assertEqual(im.format, "PNG")
|
||||||
|
|
||||||
if "jpeg_decoder" in codecs:
|
if "jpeg_decoder" in codecs:
|
||||||
tar = TarIO.TarIO(tarfile, 'lena.jpg')
|
tar = TarIO.TarIO(TEST_TAR_FILE, 'hopper.jpg')
|
||||||
im = Image.open(tar)
|
im = Image.open(tar)
|
||||||
im.load()
|
im.load()
|
||||||
self.assertEqual(im.mode, "RGB")
|
self.assertEqual(im.mode, "RGB")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from helper import unittest, PillowTestCase, lena, py3
|
from helper import unittest, PillowTestCase, lena, py3
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, TiffImagePlugin
|
||||||
|
|
||||||
|
|
||||||
class TestFileTiff(PillowTestCase):
|
class TestFileTiff(PillowTestCase):
|
||||||
|
@ -141,6 +141,32 @@ class TestFileTiff(PillowTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
im.getextrema(), (-3.140936851501465, 3.140684127807617))
|
im.getextrema(), (-3.140936851501465, 3.140684127807617))
|
||||||
|
|
||||||
|
def test_multipage(self):
|
||||||
|
# issue #862
|
||||||
|
im = Image.open('Tests/images/multipage.tiff')
|
||||||
|
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
|
||||||
|
|
||||||
|
im.seek(0)
|
||||||
|
self.assertEqual(im.size, (10,10))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,128,0))
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.size, (10,10))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (255,0,0))
|
||||||
|
|
||||||
|
im.seek(2)
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.size, (20,20))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255))
|
||||||
|
|
||||||
|
def test_multipage_last_frame(self):
|
||||||
|
im = Image.open('Tests/images/multipage-lastframe.tif')
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.size, (20,20))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255))
|
||||||
|
|
||||||
|
|
||||||
def test___str__(self):
|
def test___str__(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
file = "Tests/images/pil136.tiff"
|
file = "Tests/images/pil136.tiff"
|
||||||
|
|
|
@ -8,7 +8,7 @@ tag_ids = dict(zip(TiffTags.TAGS.values(), TiffTags.TAGS.keys()))
|
||||||
class TestFileTiffMetadata(PillowTestCase):
|
class TestFileTiffMetadata(PillowTestCase):
|
||||||
|
|
||||||
def test_rt_metadata(self):
|
def test_rt_metadata(self):
|
||||||
""" Test writing arbitray metadata into the tiff image directory
|
""" Test writing arbitrary metadata into the tiff image directory
|
||||||
Use case is ImageJ private tags, one numeric, one arbitrary
|
Use case is ImageJ private tags, one numeric, one arbitrary
|
||||||
data. https://github.com/python-pillow/Pillow/issues/291
|
data. https://github.com/python-pillow/Pillow/issues/291
|
||||||
"""
|
"""
|
||||||
|
@ -87,6 +87,10 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
value, reloaded[tag], "%s didn't roundtrip" % tag)
|
value, reloaded[tag], "%s didn't roundtrip" % tag)
|
||||||
|
|
||||||
|
def test_no_duplicate_50741_tag(self):
|
||||||
|
self.assertEqual(tag_ids['MakerNoteSafety'], 50741)
|
||||||
|
self.assertEqual(tag_ids['BestQualityScale'], 50780)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from helper import unittest, PillowTestCase, lena
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class TestImage(PillowTestCase):
|
class TestImage(PillowTestCase):
|
||||||
|
@ -57,7 +58,7 @@ class TestImage(PillowTestCase):
|
||||||
|
|
||||||
def test_expand_x(self):
|
def test_expand_x(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = lena()
|
im = hopper()
|
||||||
orig_size = im.size
|
orig_size = im.size
|
||||||
xmargin = 5
|
xmargin = 5
|
||||||
|
|
||||||
|
@ -70,7 +71,7 @@ class TestImage(PillowTestCase):
|
||||||
|
|
||||||
def test_expand_xy(self):
|
def test_expand_xy(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = lena()
|
im = hopper()
|
||||||
orig_size = im.size
|
orig_size = im.size
|
||||||
xmargin = 5
|
xmargin = 5
|
||||||
ymargin = 3
|
ymargin = 3
|
||||||
|
@ -84,7 +85,7 @@ class TestImage(PillowTestCase):
|
||||||
|
|
||||||
def test_getbands(self):
|
def test_getbands(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = lena()
|
im = hopper()
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
bands = im.getbands()
|
bands = im.getbands()
|
||||||
|
@ -94,7 +95,7 @@ class TestImage(PillowTestCase):
|
||||||
|
|
||||||
def test_getbbox(self):
|
def test_getbbox(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = lena()
|
im = hopper()
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
bbox = im.getbbox()
|
bbox = im.getbbox()
|
||||||
|
@ -140,6 +141,60 @@ class TestImage(PillowTestCase):
|
||||||
img_colors = sorted(img.getcolors())
|
img_colors = sorted(img.getcolors())
|
||||||
self.assertEqual(img_colors, expected_colors)
|
self.assertEqual(img_colors, expected_colors)
|
||||||
|
|
||||||
|
def test_effect_mandelbrot(self):
|
||||||
|
# Arrange
|
||||||
|
size = (512, 512)
|
||||||
|
extent = (-3, -2.5, 2, 2.5)
|
||||||
|
quality = 100
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im = Image.effect_mandelbrot(size, extent, quality)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(im.size, (512, 512))
|
||||||
|
im2 = Image.open('Tests/images/effect_mandelbrot.png')
|
||||||
|
self.assert_image_equal(im, im2)
|
||||||
|
|
||||||
|
def test_effect_mandelbrot_bad_arguments(self):
|
||||||
|
# Arrange
|
||||||
|
size = (512, 512)
|
||||||
|
# Get coordinates the wrong way round:
|
||||||
|
extent = (+3, +2.5, -2, -2.5)
|
||||||
|
# Quality < 2:
|
||||||
|
quality = 1
|
||||||
|
|
||||||
|
# Act/Assert
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
lambda: Image.effect_mandelbrot(size, extent, quality))
|
||||||
|
|
||||||
|
@unittest.skipUnless(sys.platform.startswith('win32'),
|
||||||
|
"Stalls on Travis CI, passes on Windows")
|
||||||
|
def test_effect_noise(self):
|
||||||
|
# Arrange
|
||||||
|
size = (100, 100)
|
||||||
|
sigma = 128
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im = Image.effect_noise(size, sigma)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(im.size, (100, 100))
|
||||||
|
self.assertEqual(im.getpixel((0, 0)), 60)
|
||||||
|
self.assertEqual(im.getpixel((0, 1)), 28)
|
||||||
|
|
||||||
|
def test_effect_spread(self):
|
||||||
|
# Arrange
|
||||||
|
im = hopper()
|
||||||
|
distance = 10
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im2 = im.effect_spread(distance)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(im.size, (128, 128))
|
||||||
|
im3 = Image.open('Tests/images/effect_spread.png')
|
||||||
|
self.assert_image_similar(im2, im3, 110)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from helper import unittest, PillowTestCase, lena
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
@ -9,21 +9,21 @@ class TestImageLoad(PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
|
|
||||||
im = lena()
|
im = hopper()
|
||||||
|
|
||||||
pix = im.load()
|
pix = im.load()
|
||||||
|
|
||||||
self.assertEqual(pix[0, 0], (223, 162, 133))
|
self.assertEqual(pix[0, 0], (20, 20, 70))
|
||||||
|
|
||||||
def test_close(self):
|
def test_close(self):
|
||||||
im = Image.open("Tests/images/lena.gif")
|
im = Image.open("Tests/images/hopper.gif")
|
||||||
im.close()
|
im.close()
|
||||||
self.assertRaises(ValueError, lambda: im.load())
|
self.assertRaises(ValueError, lambda: im.load())
|
||||||
self.assertRaises(ValueError, lambda: im.getpixel((0, 0)))
|
self.assertRaises(ValueError, lambda: im.getpixel((0, 0)))
|
||||||
|
|
||||||
def test_contextmanager(self):
|
def test_contextmanager(self):
|
||||||
fn = None
|
fn = None
|
||||||
with Image.open("Tests/images/lena.gif") as im:
|
with Image.open("Tests/images/hopper.gif") as im:
|
||||||
fn = im.fp.fileno()
|
fn = im.fp.fileno()
|
||||||
os.fstat(fn)
|
os.fstat(fn)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from helper import unittest, PillowTestCase, lena
|
from helper import unittest, PillowTestCase, lena
|
||||||
|
|
||||||
from PIL import ImageSequence
|
from PIL import Image, ImageSequence, TiffImagePlugin
|
||||||
|
|
||||||
|
|
||||||
class TestImageSequence(PillowTestCase):
|
class TestImageSequence(PillowTestCase):
|
||||||
|
@ -22,7 +22,31 @@ class TestImageSequence(PillowTestCase):
|
||||||
|
|
||||||
self.assertEqual(index, 1)
|
self.assertEqual(index, 1)
|
||||||
|
|
||||||
|
def _test_multipage_tiff(self, dbg=False):
|
||||||
|
# debug had side effect of calling fp.tell.
|
||||||
|
Image.DEBUG=dbg
|
||||||
|
im = Image.open('Tests/images/multipage.tiff')
|
||||||
|
for index, frame in enumerate(ImageSequence.Iterator(im)):
|
||||||
|
frame.load()
|
||||||
|
self.assertEqual(index, im.tell())
|
||||||
|
frame.convert('RGB')
|
||||||
|
Image.DEBUG=False
|
||||||
|
|
||||||
|
def test_tiff(self):
|
||||||
|
#self._test_multipage_tiff(True)
|
||||||
|
self._test_multipage_tiff(False)
|
||||||
|
|
||||||
|
def test_libtiff(self):
|
||||||
|
codecs = dir(Image.core)
|
||||||
|
|
||||||
|
if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs:
|
||||||
|
self.skipTest("tiff support not available")
|
||||||
|
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = True
|
||||||
|
#self._test_multipage_tiff(True)
|
||||||
|
self._test_multipage_tiff(False)
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = False
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ import shutil
|
||||||
|
|
||||||
from PIL import Image, JpegImagePlugin, GifImagePlugin
|
from PIL import Image, JpegImagePlugin, GifImagePlugin
|
||||||
|
|
||||||
test_jpg = "Tests/images/lena.jpg"
|
TEST_JPG = "Tests/images/hopper.jpg"
|
||||||
test_gif = "Tests/images/lena.gif"
|
TEST_GIF = "Tests/images/hopper.gif"
|
||||||
|
|
||||||
test_filenames = (
|
test_filenames = (
|
||||||
"temp_';",
|
"temp_';",
|
||||||
|
@ -32,24 +32,24 @@ class TestShellInjection(PillowTestCase):
|
||||||
def test_load_djpeg_filename(self):
|
def test_load_djpeg_filename(self):
|
||||||
for filename in test_filenames:
|
for filename in test_filenames:
|
||||||
src_file = self.tempfile(filename)
|
src_file = self.tempfile(filename)
|
||||||
shutil.copy(test_jpg, src_file)
|
shutil.copy(TEST_JPG, src_file)
|
||||||
|
|
||||||
im = Image.open(src_file)
|
im = Image.open(src_file)
|
||||||
im.load_djpeg()
|
im.load_djpeg()
|
||||||
|
|
||||||
@unittest.skipUnless(cjpeg_available(), "cjpeg not available")
|
@unittest.skipUnless(cjpeg_available(), "cjpeg not available")
|
||||||
def test_save_cjpeg_filename(self):
|
def test_save_cjpeg_filename(self):
|
||||||
im = Image.open(test_jpg)
|
im = Image.open(TEST_JPG)
|
||||||
self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg)
|
self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg)
|
||||||
|
|
||||||
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
||||||
def test_save_netpbm_filename_bmp_mode(self):
|
def test_save_netpbm_filename_bmp_mode(self):
|
||||||
im = Image.open(test_gif).convert("RGB")
|
im = Image.open(TEST_GIF).convert("RGB")
|
||||||
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
|
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
|
||||||
|
|
||||||
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
||||||
def test_save_netpbm_filename_l_mode(self):
|
def test_save_netpbm_filename_l_mode(self):
|
||||||
im = Image.open(test_gif).convert("L")
|
im = Image.open(TEST_GIF).convert("L")
|
||||||
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
|
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
|
||||||
|
|
||||||
|
|
||||||
|
|
7
decode.c
|
@ -103,6 +103,8 @@ PyImaging_DecoderNew(int contextsize)
|
||||||
static void
|
static void
|
||||||
_dealloc(ImagingDecoderObject* decoder)
|
_dealloc(ImagingDecoderObject* decoder)
|
||||||
{
|
{
|
||||||
|
if (decoder->cleanup)
|
||||||
|
decoder->cleanup(&decoder->state);
|
||||||
free(decoder->state.buffer);
|
free(decoder->state.buffer);
|
||||||
free(decoder->state.context);
|
free(decoder->state.context);
|
||||||
Py_XDECREF(decoder->lock);
|
Py_XDECREF(decoder->lock);
|
||||||
|
@ -442,8 +444,9 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args)
|
||||||
char* rawmode;
|
char* rawmode;
|
||||||
char* compname;
|
char* compname;
|
||||||
int fp;
|
int fp;
|
||||||
|
int ifdoffset;
|
||||||
|
|
||||||
if (! PyArg_ParseTuple(args, "sssi", &mode, &rawmode, &compname, &fp))
|
if (! PyArg_ParseTuple(args, "sssii", &mode, &rawmode, &compname, &fp, &ifdoffset))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
TRACE(("new tiff decoder %s\n", compname));
|
TRACE(("new tiff decoder %s\n", compname));
|
||||||
|
@ -455,7 +458,7 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args)
|
||||||
if (get_unpacker(decoder, mode, rawmode) < 0)
|
if (get_unpacker(decoder, mode, rawmode) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (! ImagingLibTiffInit(&decoder->state, fp)) {
|
if (! ImagingLibTiffInit(&decoder->state, fp, ifdoffset)) {
|
||||||
Py_DECREF(decoder);
|
Py_DECREF(decoder);
|
||||||
PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed");
|
PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -11,9 +11,9 @@ rm -r openjpeg-2.1.0
|
||||||
tar -xvzf openjpeg-2.1.0.tar.gz
|
tar -xvzf openjpeg-2.1.0.tar.gz
|
||||||
|
|
||||||
|
|
||||||
pushd openjpeg-2.1.0
|
pushd openjpeg-2.1.0
|
||||||
|
|
||||||
cmake -DCMAKE_INSTALL_PREFIX=/usr . && make && sudo make install
|
cmake -DCMAKE_INSTALL_PREFIX=/usr . && make -j4 && sudo make -j4 install
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install webp
|
# install webp
|
||||||
|
|
||||||
|
if [ ! -f libwebp-0.4.1.tar.gz ]; then
|
||||||
if [ ! -f libwebp-0.4.0.tar.gz ]; then
|
wget 'http://downloads.webmproject.org/releases/webp/libwebp-0.4.1.tar.gz'
|
||||||
wget 'https://webp.googlecode.com/files/libwebp-0.4.0.tar.gz'
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -r libwebp-0.4.0
|
rm -r libwebp-0.4.1
|
||||||
tar -xvzf libwebp-0.4.0.tar.gz
|
tar -xvzf libwebp-0.4.1.tar.gz
|
||||||
|
|
||||||
|
pushd libwebp-0.4.1
|
||||||
|
|
||||||
pushd libwebp-0.4.0
|
./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make -j4 && sudo make -j4 install
|
||||||
|
|
||||||
./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make && sudo make install
|
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
|
|
@ -670,6 +670,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.
|
||||||
|
|
23
encode.c
|
@ -99,6 +99,18 @@ _dealloc(ImagingEncoderObject* encoder)
|
||||||
PyObject_Del(encoder);
|
PyObject_Del(encoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
_encode_cleanup(ImagingEncoderObject* encoder, PyObject* args)
|
||||||
|
{
|
||||||
|
int status = 0;
|
||||||
|
|
||||||
|
if (encoder->cleanup){
|
||||||
|
status = encoder->cleanup(&encoder->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Py_BuildValue("i", status);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
_encode(ImagingEncoderObject* encoder, PyObject* args)
|
_encode(ImagingEncoderObject* encoder, PyObject* args)
|
||||||
{
|
{
|
||||||
|
@ -239,6 +251,7 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args)
|
||||||
|
|
||||||
static struct PyMethodDef methods[] = {
|
static struct PyMethodDef methods[] = {
|
||||||
{"encode", (PyCFunction)_encode, 1},
|
{"encode", (PyCFunction)_encode, 1},
|
||||||
|
{"cleanup", (PyCFunction)_encode_cleanup, 1},
|
||||||
{"encode_to_file", (PyCFunction)_encode_to_file, 1},
|
{"encode_to_file", (PyCFunction)_encode_to_file, 1},
|
||||||
{"setimage", (PyCFunction)_setimage, 1},
|
{"setimage", (PyCFunction)_setimage, 1},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
|
@ -540,8 +553,8 @@ static unsigned int** get_qtables_arrays(PyObject* qtables) {
|
||||||
|
|
||||||
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 < 2 || 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 2 and 4.");
|
PyErr_SetString(PyExc_ValueError, "Not a valid numbers of quantization tables. Should be between 1 and 4.");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
qarrays = (unsigned int**) PyMem_Malloc(num_tables * sizeof(unsigned int*));
|
qarrays = (unsigned int**) PyMem_Malloc(num_tables * sizeof(unsigned int*));
|
||||||
|
@ -760,7 +773,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
|
||||||
(ttag_t) PyInt_AsLong(key),
|
(ttag_t) PyInt_AsLong(key),
|
||||||
intav);
|
intav);
|
||||||
free(intav);
|
free(intav);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TRACE((" %d elements, setting as floats \n", len));
|
TRACE((" %d elements, setting as floats \n", len));
|
||||||
floatav = malloc(sizeof(float)*len);
|
floatav = malloc(sizeof(float)*len);
|
||||||
|
@ -903,7 +916,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
|
||||||
j2k_decode_coord_tuple(tile_offset,
|
j2k_decode_coord_tuple(tile_offset,
|
||||||
&context->tile_offset_x,
|
&context->tile_offset_x,
|
||||||
&context->tile_offset_y);
|
&context->tile_offset_y);
|
||||||
j2k_decode_coord_tuple(tile_size,
|
j2k_decode_coord_tuple(tile_size,
|
||||||
&context->tile_size_x,
|
&context->tile_size_x,
|
||||||
&context->tile_size_y);
|
&context->tile_size_y);
|
||||||
|
|
||||||
|
@ -918,7 +931,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
|
||||||
|
|
||||||
if (context->tile_offset_x > context->offset_x
|
if (context->tile_offset_x > context->offset_x
|
||||||
|| context->tile_offset_y > context->offset_y) {
|
|| context->tile_offset_y > context->offset_y) {
|
||||||
PyErr_SetString(PyExc_ValueError,
|
PyErr_SetString(PyExc_ValueError,
|
||||||
"JPEG 2000 tile offset too large to cover image area");
|
"JPEG 2000 tile offset too large to cover image area");
|
||||||
Py_DECREF(encoder);
|
Py_DECREF(encoder);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -49,6 +49,12 @@
|
||||||
#define L(rgb)\
|
#define L(rgb)\
|
||||||
((INT32) (rgb)[0]*299 + (INT32) (rgb)[1]*587 + (INT32) (rgb)[2]*114)
|
((INT32) (rgb)[0]*299 + (INT32) (rgb)[1]*587 + (INT32) (rgb)[2]*114)
|
||||||
|
|
||||||
|
#ifndef round
|
||||||
|
double round(double x) {
|
||||||
|
return floor(x+0.5);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ------------------- */
|
/* ------------------- */
|
||||||
/* 1 (bit) conversions */
|
/* 1 (bit) conversions */
|
||||||
/* ------------------- */
|
/* ------------------- */
|
||||||
|
|
|
@ -48,25 +48,25 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality)
|
||||||
|
|
||||||
for (y = 0; y < ysize; y++) {
|
for (y = 0; y < ysize; y++) {
|
||||||
UINT8* buf = im->image8[y];
|
UINT8* buf = im->image8[y];
|
||||||
for (x = 0; x < xsize; x++) {
|
for (x = 0; x < xsize; x++) {
|
||||||
x1 = y1 = xi2 = yi2 = 0.0;
|
x1 = y1 = xi2 = yi2 = 0.0;
|
||||||
cr = x*dr + extent[0];
|
cr = x*dr + extent[0];
|
||||||
ci = y*di + extent[1];
|
ci = y*di + extent[1];
|
||||||
for (k = 1;; k++) {
|
for (k = 1;; k++) {
|
||||||
y1 = 2*x1*y1 + ci;
|
y1 = 2*x1*y1 + ci;
|
||||||
x1 = xi2 - yi2 + cr;
|
x1 = xi2 - yi2 + cr;
|
||||||
xi2 = x1*x1;
|
xi2 = x1*x1;
|
||||||
yi2 = y1*y1;
|
yi2 = y1*y1;
|
||||||
if ((xi2 + yi2) > radius) {
|
if ((xi2 + yi2) > radius) {
|
||||||
buf[x] = k*255/quality;
|
buf[x] = k*255/quality;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (k > quality) {
|
if (k > quality) {
|
||||||
buf[x] = 0;
|
buf[x] = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality)
|
||||||
Imaging
|
Imaging
|
||||||
ImagingEffectNoise(int xsize, int ysize, float sigma)
|
ImagingEffectNoise(int xsize, int ysize, float sigma)
|
||||||
{
|
{
|
||||||
/* Generate gaussian noise centered around 128 */
|
/* Generate Gaussian noise centered around 128 */
|
||||||
|
|
||||||
Imaging imOut;
|
Imaging imOut;
|
||||||
int x, y;
|
int x, y;
|
||||||
|
@ -83,19 +83,19 @@ ImagingEffectNoise(int xsize, int ysize, float sigma)
|
||||||
|
|
||||||
imOut = ImagingNew("L", xsize, ysize);
|
imOut = ImagingNew("L", xsize, ysize);
|
||||||
if (!imOut)
|
if (!imOut)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
next = 0.0;
|
next = 0.0;
|
||||||
nextok = 0;
|
nextok = 0;
|
||||||
|
|
||||||
for (y = 0; y < imOut->ysize; y++) {
|
for (y = 0; y < imOut->ysize; y++) {
|
||||||
UINT8* out = imOut->image8[y];
|
UINT8* out = imOut->image8[y];
|
||||||
for (x = 0; x < imOut->xsize; x++) {
|
for (x = 0; x < imOut->xsize; x++) {
|
||||||
if (nextok) {
|
if (nextok) {
|
||||||
this = next;
|
this = next;
|
||||||
nextok = 0;
|
nextok = 0;
|
||||||
} else {
|
} else {
|
||||||
/* after numerical recepies */
|
/* after numerical recipes */
|
||||||
double v1, v2, radius, factor;
|
double v1, v2, radius, factor;
|
||||||
do {
|
do {
|
||||||
v1 = rand()*(2.0/32767.0) - 1.0;
|
v1 = rand()*(2.0/32767.0) - 1.0;
|
||||||
|
@ -113,14 +113,6 @@ ImagingEffectNoise(int xsize, int ysize, float sigma)
|
||||||
return imOut;
|
return imOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
|
||||||
ImagingEffectPerlinTurbulence(int xsize, int ysize)
|
|
||||||
{
|
|
||||||
/* Perlin turbulence (In progress) */
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingEffectSpread(Imaging imIn, int distance)
|
ImagingEffectSpread(Imaging imIn, int distance)
|
||||||
{
|
{
|
||||||
|
@ -132,11 +124,11 @@ ImagingEffectSpread(Imaging imIn, int distance)
|
||||||
imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize);
|
imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize);
|
||||||
|
|
||||||
if (!imOut)
|
if (!imOut)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
#define SPREAD(type, image)\
|
#define SPREAD(type, image)\
|
||||||
for (y = 0; y < imIn->ysize; y++)\
|
for (y = 0; y < imIn->ysize; y++)\
|
||||||
for (x = 0; x < imIn->xsize; x++) {\
|
for (x = 0; x < imIn->xsize; x++) {\
|
||||||
int xx = x + (rand() % distance) - distance/2;\
|
int xx = x + (rand() % distance) - distance/2;\
|
||||||
int yy = y + (rand() % distance) - distance/2;\
|
int yy = y + (rand() % distance) - distance/2;\
|
||||||
if (xx >= 0 && xx < imIn->xsize && yy >= 0 && yy < imIn->ysize) {\
|
if (xx >= 0 && xx < imIn->xsize && yy >= 0 && yy < imIn->ysize) {\
|
||||||
|
@ -147,9 +139,9 @@ ImagingEffectSpread(Imaging imIn, int distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imIn->image8) {
|
if (imIn->image8) {
|
||||||
SPREAD(UINT8, image8);
|
SPREAD(UINT8, image8);
|
||||||
} else {
|
} else {
|
||||||
SPREAD(INT32, image32);
|
SPREAD(INT32, image32);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImagingCopyInfo(imOut, imIn);
|
ImagingCopyInfo(imOut, imIn);
|
||||||
|
@ -157,217 +149,4 @@ ImagingEffectSpread(Imaging imIn, int distance)
|
||||||
return imOut;
|
return imOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
// End of file
|
||||||
/* Taken from the "C" code in the W3C SVG specification. Translated
|
|
||||||
to C89 by Fredrik Lundh */
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
|
|
||||||
/* Produces results in the range [1, 2**31 - 2].
|
|
||||||
Algorithm is: r = (a * r) mod m
|
|
||||||
where a = 16807 and m = 2**31 - 1 = 2147483647
|
|
||||||
See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988
|
|
||||||
To test: the algorithm should produce the result 1043618065
|
|
||||||
as the 10,000th generated number if the original seed is 1.
|
|
||||||
*/
|
|
||||||
#define RAND_m 2147483647 /* 2**31 - 1 */
|
|
||||||
#define RAND_a 16807 /* 7**5; primitive root of m */
|
|
||||||
#define RAND_q 127773 /* m / a */
|
|
||||||
#define RAND_r 2836 /* m % a */
|
|
||||||
|
|
||||||
static long
|
|
||||||
perlin_setup_seed(long lSeed)
|
|
||||||
{
|
|
||||||
if (lSeed <= 0) lSeed = -(lSeed % (RAND_m - 1)) + 1;
|
|
||||||
if (lSeed > RAND_m - 1) lSeed = RAND_m - 1;
|
|
||||||
return lSeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static long
|
|
||||||
perlin_random(long lSeed)
|
|
||||||
{
|
|
||||||
long result;
|
|
||||||
result = RAND_a * (lSeed % RAND_q) - RAND_r * (lSeed / RAND_q);
|
|
||||||
if (result <= 0) result += RAND_m;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define BSize 0x100
|
|
||||||
#define BM 0xff
|
|
||||||
#define PerlinN 0x1000
|
|
||||||
#define NP 12 /* 2^PerlinN */
|
|
||||||
#define NM 0xfff
|
|
||||||
static int perlin_uLatticeSelector[BSize + BSize + 2];
|
|
||||||
static double perlin_fGradient[4][BSize + BSize + 2][2];
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int nWidth; /* How much to subtract to wrap for stitching. */
|
|
||||||
int nHeight;
|
|
||||||
int nWrapX; /* Minimum value to wrap. */
|
|
||||||
int nWrapY;
|
|
||||||
} StitchInfo;
|
|
||||||
|
|
||||||
static void
|
|
||||||
perlin_init(long lSeed)
|
|
||||||
{
|
|
||||||
double s;
|
|
||||||
int i, j, k;
|
|
||||||
lSeed = perlin_setup_seed(lSeed);
|
|
||||||
for(k = 0; k < 4; k++)
|
|
||||||
{
|
|
||||||
for(i = 0; i < BSize; i++)
|
|
||||||
{
|
|
||||||
perlin_uLatticeSelector[i] = i;
|
|
||||||
for (j = 0; j < 2; j++)
|
|
||||||
perlin_fGradient[k][i][j] = (double)(((lSeed = perlin_random(lSeed)) % (BSize + BSize)) - BSize) / BSize;
|
|
||||||
s = (double) (sqrt(perlin_fGradient[k][i][0] * perlin_fGradient[k][i][0] + perlin_fGradient[k][i][1] * perlin_fGradient[k][i][1]));
|
|
||||||
perlin_fGradient[k][i][0] /= s;
|
|
||||||
perlin_fGradient[k][i][1] /= s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while(--i)
|
|
||||||
{
|
|
||||||
k = perlin_uLatticeSelector[i];
|
|
||||||
perlin_uLatticeSelector[i] = perlin_uLatticeSelector[j = (lSeed = perlin_random(lSeed)) % BSize];
|
|
||||||
perlin_uLatticeSelector[j] = k;
|
|
||||||
}
|
|
||||||
for(i = 0; i < BSize + 2; i++)
|
|
||||||
{
|
|
||||||
perlin_uLatticeSelector[BSize + i] = perlin_uLatticeSelector[i];
|
|
||||||
for(k = 0; k < 4; k++)
|
|
||||||
for(j = 0; j < 2; j++)
|
|
||||||
perlin_fGradient[k][BSize + i][j] = perlin_fGradient[k][i][j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define s_curve(t) ( t * t * (3. - 2. * t) )
|
|
||||||
#define lerp(t, a, b) ( a + t * (b - a) )
|
|
||||||
static double
|
|
||||||
perlin_noise2(int nColorChannel, double vec[2], StitchInfo *pStitchInfo)
|
|
||||||
{
|
|
||||||
int bx0, bx1, by0, by1, b00, b10, b01, b11;
|
|
||||||
double rx0, rx1, ry0, ry1, *q, sx, sy, a, b, t, u, v;
|
|
||||||
register int i, j;
|
|
||||||
|
|
||||||
t = vec[0] + (double) PerlinN;
|
|
||||||
bx0 = (int)t;
|
|
||||||
bx1 = bx0+1;
|
|
||||||
rx0 = t - (int)t;
|
|
||||||
rx1 = rx0 - 1.0f;
|
|
||||||
t = vec[1] + (double) PerlinN;
|
|
||||||
by0 = (int)t;
|
|
||||||
by1 = by0+1;
|
|
||||||
ry0 = t - (int)t;
|
|
||||||
ry1 = ry0 - 1.0f;
|
|
||||||
|
|
||||||
/* If stitching, adjust lattice points accordingly. */
|
|
||||||
if(pStitchInfo != NULL)
|
|
||||||
{
|
|
||||||
if(bx0 >= pStitchInfo->nWrapX)
|
|
||||||
bx0 -= pStitchInfo->nWidth;
|
|
||||||
if(bx1 >= pStitchInfo->nWrapX)
|
|
||||||
bx1 -= pStitchInfo->nWidth;
|
|
||||||
if(by0 >= pStitchInfo->nWrapY)
|
|
||||||
by0 -= pStitchInfo->nHeight;
|
|
||||||
if(by1 >= pStitchInfo->nWrapY)
|
|
||||||
by1 -= pStitchInfo->nHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
bx0 &= BM;
|
|
||||||
bx1 &= BM;
|
|
||||||
by0 &= BM;
|
|
||||||
by1 &= BM;
|
|
||||||
|
|
||||||
i = perlin_uLatticeSelector[bx0];
|
|
||||||
j = perlin_uLatticeSelector[bx1];
|
|
||||||
b00 = perlin_uLatticeSelector[i + by0];
|
|
||||||
b10 = perlin_uLatticeSelector[j + by0];
|
|
||||||
b01 = perlin_uLatticeSelector[i + by1];
|
|
||||||
b11 = perlin_uLatticeSelector[j + by1];
|
|
||||||
sx = (double) (s_curve(rx0));
|
|
||||||
sy = (double) (s_curve(ry0));
|
|
||||||
q = perlin_fGradient[nColorChannel][b00]; u = rx0 * q[0] + ry0 * q[1];
|
|
||||||
q = perlin_fGradient[nColorChannel][b10]; v = rx1 * q[0] + ry0 * q[1];
|
|
||||||
a = lerp(sx, u, v);
|
|
||||||
q = perlin_fGradient[nColorChannel][b01]; u = rx0 * q[0] + ry1 * q[1];
|
|
||||||
q = perlin_fGradient[nColorChannel][b11]; v = rx1 * q[0] + ry1 * q[1];
|
|
||||||
b = lerp(sx, u, v);
|
|
||||||
return lerp(sy, a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
double
|
|
||||||
perlin_turbulence(
|
|
||||||
int nColorChannel, double *point, double fBaseFreqX, double fBaseFreqY,
|
|
||||||
int nNumOctaves, int bFractalSum, int bDoStitching,
|
|
||||||
double fTileX, double fTileY, double fTileWidth, double fTileHeight)
|
|
||||||
{
|
|
||||||
StitchInfo stitch;
|
|
||||||
StitchInfo *pStitchInfo = NULL; /* Not stitching when NULL. */
|
|
||||||
|
|
||||||
double fSum = 0.0f;
|
|
||||||
double vec[2];
|
|
||||||
double ratio = 1;
|
|
||||||
|
|
||||||
int nOctave;
|
|
||||||
|
|
||||||
vec[0] = point[0] * fBaseFreqX;
|
|
||||||
vec[1] = point[1] * fBaseFreqY;
|
|
||||||
|
|
||||||
/* Adjust the base frequencies if necessary for stitching. */
|
|
||||||
if(bDoStitching)
|
|
||||||
{
|
|
||||||
/* When stitching tiled turbulence, the frequencies must be adjusted */
|
|
||||||
/* so that the tile borders will be continuous. */
|
|
||||||
if(fBaseFreqX != 0.0)
|
|
||||||
{
|
|
||||||
double fLoFreq = (double) (floor(fTileWidth * fBaseFreqX)) / fTileWidth;
|
|
||||||
double fHiFreq = (double) (ceil(fTileWidth * fBaseFreqX)) / fTileWidth;
|
|
||||||
if(fBaseFreqX / fLoFreq < fHiFreq / fBaseFreqX)
|
|
||||||
fBaseFreqX = fLoFreq;
|
|
||||||
else
|
|
||||||
fBaseFreqX = fHiFreq;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fBaseFreqY != 0.0)
|
|
||||||
{
|
|
||||||
double fLoFreq = (double) (floor(fTileHeight * fBaseFreqY)) / fTileHeight;
|
|
||||||
double fHiFreq = (double) (ceil(fTileHeight * fBaseFreqY)) / fTileHeight;
|
|
||||||
if(fBaseFreqY / fLoFreq < fHiFreq / fBaseFreqY)
|
|
||||||
fBaseFreqY = fLoFreq;
|
|
||||||
else
|
|
||||||
fBaseFreqY = fHiFreq;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set up initial stitch values. */
|
|
||||||
pStitchInfo = &stitch;
|
|
||||||
stitch.nWidth = (int) (fTileWidth * fBaseFreqX + 0.5f);
|
|
||||||
stitch.nWrapX = (int) (fTileX * fBaseFreqX + PerlinN + stitch.nWidth);
|
|
||||||
stitch.nHeight = (int) (fTileHeight * fBaseFreqY + 0.5f);
|
|
||||||
stitch.nWrapY = (int) (fTileY * fBaseFreqY + PerlinN + stitch.nHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(nOctave = 0; nOctave < nNumOctaves; nOctave++)
|
|
||||||
{
|
|
||||||
if(bFractalSum)
|
|
||||||
fSum += (double) (perlin_noise2(nColorChannel, vec, pStitchInfo) / ratio);
|
|
||||||
else
|
|
||||||
fSum += (double) (fabs(perlin_noise2(nColorChannel, vec, pStitchInfo)) / ratio);
|
|
||||||
|
|
||||||
vec[0] *= 2;
|
|
||||||
vec[1] *= 2;
|
|
||||||
ratio *= 2;
|
|
||||||
|
|
||||||
if(pStitchInfo != NULL)
|
|
||||||
{
|
|
||||||
/* Update stitch values. Subtracting PerlinN before the multiplication and */
|
|
||||||
/* adding it afterward simplifies to subtracting it once. */
|
|
||||||
stitch.nWidth *= 2;
|
|
||||||
stitch.nWrapX = 2 * stitch.nWrapX - PerlinN;
|
|
||||||
stitch.nHeight *= 2;
|
|
||||||
stitch.nWrapY = 2 * stitch.nWrapY - PerlinN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fSum;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -800,6 +800,11 @@ ImagingJpeg2KDecodeCleanup(ImagingCodecState state) {
|
||||||
if (context->decoder)
|
if (context->decoder)
|
||||||
ImagingIncrementalCodecDestroy(context->decoder);
|
ImagingIncrementalCodecDestroy(context->decoder);
|
||||||
|
|
||||||
|
context->error_msg = NULL;
|
||||||
|
|
||||||
|
/* Prevent multiple calls to ImagingIncrementalCodecDestroy */
|
||||||
|
context->decoder = NULL;
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
7
libImaging/Jpeg2KEncode.c
Normal file → Executable file
|
@ -576,15 +576,20 @@ int
|
||||||
ImagingJpeg2KEncodeCleanup(ImagingCodecState state) {
|
ImagingJpeg2KEncodeCleanup(ImagingCodecState state) {
|
||||||
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
|
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
|
||||||
|
|
||||||
if (context->quality_layers)
|
if (context->quality_layers && context->encoder)
|
||||||
Py_DECREF(context->quality_layers);
|
Py_DECREF(context->quality_layers);
|
||||||
|
|
||||||
if (context->error_msg)
|
if (context->error_msg)
|
||||||
free ((void *)context->error_msg);
|
free ((void *)context->error_msg);
|
||||||
|
|
||||||
|
context->error_msg = NULL;
|
||||||
|
|
||||||
if (context->encoder)
|
if (context->encoder)
|
||||||
ImagingIncrementalCodecDestroy(context->encoder);
|
ImagingIncrementalCodecDestroy(context->encoder);
|
||||||
|
|
||||||
|
/* Prevent multiple calls to ImagingIncrementalCodecDestroy */
|
||||||
|
context->encoder = NULL;
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
#include "TiffDecode.h"
|
#include "TiffDecode.h"
|
||||||
|
|
||||||
void dump_state(const TIFFSTATE *state){
|
void dump_state(const TIFFSTATE *state){
|
||||||
TRACE(("State: Location %u size %d eof %d data: %p \n", (uint)state->loc,
|
TRACE(("State: Location %u size %d eof %d data: %p ifd: %d\n", (uint)state->loc,
|
||||||
(int)state->size, (uint)state->eof, state->data));
|
(int)state->size, (uint)state->eof, state->data, state->ifd));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -142,7 +142,7 @@ void _tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) {
|
||||||
(void) hdata; (void) base; (void) size;
|
(void) hdata; (void) base; (void) size;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ImagingLibTiffInit(ImagingCodecState state, int fp) {
|
int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset) {
|
||||||
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
|
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
|
||||||
|
|
||||||
TRACE(("initing libtiff\n"));
|
TRACE(("initing libtiff\n"));
|
||||||
|
@ -158,6 +158,7 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp) {
|
||||||
clientstate->size = 0;
|
clientstate->size = 0;
|
||||||
clientstate->data = 0;
|
clientstate->data = 0;
|
||||||
clientstate->fp = fp;
|
clientstate->fp = fp;
|
||||||
|
clientstate->ifd = offset;
|
||||||
clientstate->eof = 0;
|
clientstate->eof = 0;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -195,7 +196,6 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
|
||||||
clientstate->loc = 0;
|
clientstate->loc = 0;
|
||||||
clientstate->data = (tdata_t)buffer;
|
clientstate->data = (tdata_t)buffer;
|
||||||
clientstate->flrealloc = 0;
|
clientstate->flrealloc = 0;
|
||||||
|
|
||||||
dump_state(clientstate);
|
dump_state(clientstate);
|
||||||
|
|
||||||
TIFFSetWarningHandler(NULL);
|
TIFFSetWarningHandler(NULL);
|
||||||
|
@ -220,6 +220,16 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clientstate->ifd){
|
||||||
|
unsigned int ifdoffset = clientstate->ifd;
|
||||||
|
TRACE(("reading tiff ifd %d\n", ifdoffset));
|
||||||
|
int rv = TIFFSetSubDirectory(tiff, ifdoffset);
|
||||||
|
if (!rv){
|
||||||
|
TRACE(("error in TIFFSetSubDirectory"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
size = TIFFScanlineSize(tiff);
|
size = TIFFScanlineSize(tiff);
|
||||||
TRACE(("ScanlineSize: %d \n", size));
|
TRACE(("ScanlineSize: %d \n", size));
|
||||||
if (size > state->bytes) {
|
if (size > state->bytes) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ typedef struct {
|
||||||
toff_t loc; /* toff_t == uint32 */
|
toff_t loc; /* toff_t == uint32 */
|
||||||
tsize_t size; /* tsize_t == int32 */
|
tsize_t size; /* tsize_t == int32 */
|
||||||
int fp;
|
int fp;
|
||||||
|
int ifd; /* offset of the ifd, used for multipage */
|
||||||
TIFF *tiff; /* Used in write */
|
TIFF *tiff; /* Used in write */
|
||||||
toff_t eof;
|
toff_t eof;
|
||||||
int flrealloc; /* may we realloc */
|
int flrealloc; /* may we realloc */
|
||||||
|
@ -33,7 +34,7 @@ typedef struct {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
extern int ImagingLibTiffInit(ImagingCodecState state, int fp);
|
extern int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset);
|
||||||
extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
|
extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
|
||||||
extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
|
extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
|
||||||
|
|
||||||
|
@ -50,5 +51,4 @@ extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
|
||||||
|
|
||||||
#define TRACE(args)
|
#define TRACE(args)
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from multiprocessing import Pool, cpu_count
|
from multiprocessing import Pool, cpu_count
|
||||||
from distutils.ccompiler import CCompiler
|
from distutils.ccompiler import CCompiler
|
||||||
import os
|
import os, sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
MAX_PROCS = int(os.environ.get('MAX_CONCURRENCY', cpu_count()))
|
MAX_PROCS = int(os.environ.get('MAX_CONCURRENCY', cpu_count()))
|
||||||
|
@ -50,7 +50,7 @@ def _mp_compile(self, sources, output_dir=None, macros=None,
|
||||||
return objects
|
return objects
|
||||||
|
|
||||||
# explicitly don't enable if environment says 1 processor
|
# explicitly don't enable if environment says 1 processor
|
||||||
if MAX_PROCS != 1:
|
if MAX_PROCS != 1 and not sys.platform.startswith('win'):
|
||||||
try:
|
try:
|
||||||
# bug, only enable if we can make a Pool. see issue #790 and
|
# bug, only enable if we can make a Pool. see issue #790 and
|
||||||
# http://stackoverflow.com/questions/6033599/oserror-38-errno-38-with-multiprocessing
|
# http://stackoverflow.com/questions/6033599/oserror-38-errno-38-with-multiprocessing
|
||||||
|
|
14
selftest.py
|
@ -49,13 +49,13 @@ def testimage():
|
||||||
|
|
||||||
Or open existing files:
|
Or open existing files:
|
||||||
|
|
||||||
>>> im = Image.open(os.path.join(ROOT, "Tests/images/lena.gif"))
|
>>> im = Image.open(os.path.join(ROOT, "Tests/images/hopper.gif"))
|
||||||
>>> _info(im)
|
>>> _info(im)
|
||||||
('GIF', 'P', (128, 128))
|
('GIF', 'P', (128, 128))
|
||||||
>>> _info(Image.open(os.path.join(ROOT, "Tests/images/lena.ppm")))
|
>>> _info(Image.open(os.path.join(ROOT, "Tests/images/hopper.ppm")))
|
||||||
('PPM', 'RGB', (128, 128))
|
('PPM', 'RGB', (128, 128))
|
||||||
>>> try:
|
>>> try:
|
||||||
... _info(Image.open(os.path.join(ROOT, "Tests/images/lena.jpg")))
|
... _info(Image.open(os.path.join(ROOT, "Tests/images/hopper.jpg")))
|
||||||
... except IOError as v:
|
... except IOError as v:
|
||||||
... print(v)
|
... print(v)
|
||||||
('JPEG', 'RGB', (128, 128))
|
('JPEG', 'RGB', (128, 128))
|
||||||
|
@ -63,7 +63,7 @@ def testimage():
|
||||||
PIL doesn't actually load the image data until it's needed,
|
PIL doesn't actually load the image data until it's needed,
|
||||||
or you call the "load" method:
|
or you call the "load" method:
|
||||||
|
|
||||||
>>> im = Image.open(os.path.join(ROOT, "Tests/images/lena.ppm"))
|
>>> im = Image.open(os.path.join(ROOT, "Tests/images/hopper.ppm"))
|
||||||
>>> print(im.im) # internal image attribute
|
>>> print(im.im) # internal image attribute
|
||||||
None
|
None
|
||||||
>>> a = im.load()
|
>>> a = im.load()
|
||||||
|
@ -73,7 +73,7 @@ def testimage():
|
||||||
You can apply many different operations on images. Most
|
You can apply many different operations on images. Most
|
||||||
operations return a new image:
|
operations return a new image:
|
||||||
|
|
||||||
>>> im = Image.open(os.path.join(ROOT, "Tests/images/lena.ppm"))
|
>>> im = Image.open(os.path.join(ROOT, "Tests/images/hopper.ppm"))
|
||||||
>>> _info(im.convert("L"))
|
>>> _info(im.convert("L"))
|
||||||
(None, 'L', (128, 128))
|
(None, 'L', (128, 128))
|
||||||
>>> _info(im.copy())
|
>>> _info(im.copy())
|
||||||
|
@ -89,9 +89,9 @@ def testimage():
|
||||||
>>> len(im.getdata())
|
>>> len(im.getdata())
|
||||||
16384
|
16384
|
||||||
>>> im.getextrema()
|
>>> im.getextrema()
|
||||||
((61, 255), (26, 234), (44, 223))
|
((0, 255), (0, 255), (0, 255))
|
||||||
>>> im.getpixel((0, 0))
|
>>> im.getpixel((0, 0))
|
||||||
(223, 162, 133)
|
(20, 20, 70)
|
||||||
>>> len(im.getprojection())
|
>>> len(im.getprojection())
|
||||||
2
|
2
|
||||||
>>> len(im.histogram())
|
>>> len(im.histogram())
|
||||||
|
|
11
setup.py
|
@ -487,6 +487,8 @@ class pil_build_ext(build_ext):
|
||||||
# In Google's precompiled zip it is call "libwebp":
|
# In Google's precompiled zip it is call "libwebp":
|
||||||
if _find_library_file(self, "webp"):
|
if _find_library_file(self, "webp"):
|
||||||
feature.webp = "webp"
|
feature.webp = "webp"
|
||||||
|
elif _find_library_file(self, "libwebp"):
|
||||||
|
feature.webp = "libwebp"
|
||||||
|
|
||||||
if feature.want('webpmux'):
|
if feature.want('webpmux'):
|
||||||
if (_find_include_file(self, "webp/mux.h") and
|
if (_find_include_file(self, "webp/mux.h") and
|
||||||
|
@ -494,6 +496,9 @@ class pil_build_ext(build_ext):
|
||||||
if (_find_library_file(self, "webpmux") and
|
if (_find_library_file(self, "webpmux") and
|
||||||
_find_library_file(self, "webpdemux")):
|
_find_library_file(self, "webpdemux")):
|
||||||
feature.webpmux = "webpmux"
|
feature.webpmux = "webpmux"
|
||||||
|
if (_find_library_file(self, "libwebpmux") and
|
||||||
|
_find_library_file(self, "libwebpdemux")):
|
||||||
|
feature.webpmux = "libwebpmux"
|
||||||
|
|
||||||
for f in feature:
|
for f in feature:
|
||||||
if not getattr(feature, f) and feature.require(f):
|
if not getattr(feature, f) and feature.require(f):
|
||||||
|
@ -559,13 +564,13 @@ class pil_build_ext(build_ext):
|
||||||
libraries=["lcms2"] + extra))
|
libraries=["lcms2"] + extra))
|
||||||
|
|
||||||
if os.path.isfile("_webp.c") and feature.webp:
|
if os.path.isfile("_webp.c") and feature.webp:
|
||||||
libs = ["webp"]
|
libs = [feature.webp]
|
||||||
defs = []
|
defs = []
|
||||||
|
|
||||||
if feature.webpmux:
|
if feature.webpmux:
|
||||||
defs.append(("HAVE_WEBPMUX", None))
|
defs.append(("HAVE_WEBPMUX", None))
|
||||||
libs.append("webpmux")
|
libs.append(feature.webpmux)
|
||||||
libs.append("webpdemux")
|
libs.append(feature.webpmux.replace('pmux','pdemux'))
|
||||||
|
|
||||||
exts.append(Extension(
|
exts.append(Extension(
|
||||||
"PIL._webp", ["_webp.c"], libraries=libs, define_macros=defs))
|
"PIL._webp", ["_webp.c"], libraries=libs, define_macros=defs))
|
||||||
|
|