Merge from current master

This commit is contained in:
wiredfool 2014-05-20 11:47:53 -07:00
commit 3b227d1a05
24 changed files with 409 additions and 271 deletions

View File

@ -1,18 +1,15 @@
language: python language: python
# for python-qt4
virtualenv:
system_site_packages: true
notifications: notifications:
irc: "chat.freenode.net#pil" irc: "chat.freenode.net#pil"
python: python:
- "pypy"
- 2.6 - 2.6
- 2.7 - 2.7
- 3.2 - 3.2
- 3.3 - 3.3
- "pypy" - 3.4
install: install:
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake"
@ -29,8 +26,14 @@ script:
- coverage erase - coverage erase
- python setup.py clean - python setup.py clean
- python setup.py build_ext --inplace - python setup.py build_ext --inplace
- coverage run --append --include=PIL/* selftest.py
- python Tests/run.py --coverage # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640)
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi
# Cover the others
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi
after_success: after_success:
- coverage report - coverage report
@ -38,7 +41,5 @@ after_success:
- pip install pep8 pyflakes - pip install pep8 pyflakes
- pep8 PIL/*.py - pep8 PIL/*.py
- pyflakes PIL/*.py - pyflakes PIL/*.py
- pep8 Tests/*.py
matrix: - pyflakes Tests/*.py
allow_failures:
- python: "pypy"

View File

@ -4,6 +4,27 @@ Changelog (Pillow)
2.5.0 (unreleased) 2.5.0 (unreleased)
------------------ ------------------
- Rename variables not to use built-in function names
[hugovk]
- Ignore junk JPEG markers
[hugovk]
- Change default interpolation for Image.thumbnail to Image.ANTIALIAS
[hugovk]
- Add tests and fixes for saving PDFs
[hugovk]
- Remove transparency resource after P->RGBA conversion
[hugovk]
- Clean up preprocessor cruft for Windows
[CounterPillow]
- Adjust Homebrew freetype detection logic
[jacknagel]
- Added Image.close, context manager support. - Added Image.close, context manager support.
[wiredfool] [wiredfool]

View File

@ -92,8 +92,10 @@ except ImportError:
builtins = __builtin__ builtins = __builtin__
from PIL import ImageMode from PIL import ImageMode
from PIL._binary import i8, o8 from PIL._binary import i8
from PIL._util import isPath, isStringType, deferred_error from PIL._util import isPath
from PIL._util import isStringType
from PIL._util import deferred_error
import os import os
import sys import sys
@ -531,7 +533,7 @@ class Image:
self.fp.close() self.fp.close()
except Exception as msg: except Exception as msg:
if Image.DEBUG: if Image.DEBUG:
print("Error closing: %s" % msg) print ("Error closing: %s" % msg)
# Instead of simply setting to None, we're setting up a # Instead of simply setting to None, we're setting up a
# deferred error that will better explain that the core image # deferred error that will better explain that the core image
@ -545,8 +547,8 @@ class Image:
self.readonly = 0 self.readonly = 0
def _dump(self, file=None, format=None): def _dump(self, file=None, format=None):
import tempfile
import os import os
import tempfile
suffix = '' suffix = ''
if format: if format:
suffix = '.'+format suffix = '.'+format
@ -836,9 +838,8 @@ class Image:
t = self.info['transparency'] t = self.info['transparency']
if isinstance(t, bytes): if isinstance(t, bytes):
# Dragons. This can't be represented by a single color # Dragons. This can't be represented by a single color
warnings.warn( warnings.warn('Palette images with Transparency expressed ' +
'Palette images with Transparency expressed ' + ' in bytes should be converted to RGBA images')
' in bytes should be converted to RGBA images')
delete_trns = True delete_trns = True
else: else:
# get the new transparency color. # get the new transparency color.
@ -854,7 +855,10 @@ class Image:
# can't just retrieve the palette number, got to do it # can't just retrieve the palette number, got to do it
# after quantization. # after quantization.
trns_im = trns_im.convert('RGB') trns_im = trns_im.convert('RGB')
trns = trns_im.getpixel((0, 0)) trns = trns_im.getpixel((0,0))
elif self.mode == 'P' and mode == 'RGBA':
delete_trns = True
if mode == "P" and palette == ADAPTIVE: if mode == "P" and palette == ADAPTIVE:
im = self.im.quantize(colors) im = self.im.quantize(colors)
@ -1546,6 +1550,7 @@ class Image:
math.cos(angle), math.sin(angle), 0.0, math.cos(angle), math.sin(angle), 0.0,
-math.sin(angle), math.cos(angle), 0.0 -math.sin(angle), math.cos(angle), 0.0
] ]
def transform(x, y, matrix=matrix): def transform(x, y, matrix=matrix):
(a, b, c, d, e, f) = matrix (a, b, c, d, e, f) = matrix
@ -1722,7 +1727,7 @@ class Image:
""" """
return 0 return 0
def thumbnail(self, size, resample=NEAREST): def thumbnail(self, size, resample=ANTIALIAS):
""" """
Make this image into a thumbnail. This method modifies the Make this image into a thumbnail. This method modifies the
image to contain a thumbnail version of itself, no larger than image to contain a thumbnail version of itself, no larger than
@ -1738,22 +1743,19 @@ class Image:
Also note that this function modifies the :py:class:`~PIL.Image.Image` Also note that this function modifies the :py:class:`~PIL.Image.Image`
object in place. If you need to use the full resolution image as well, object in place. If you need to use the full resolution image as well,
apply this method to a :py:meth:`~PIL.Image.Image.copy` of the apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
original image. image.
:param size: Requested size. :param size: Requested size.
:param resample: Optional resampling filter. This can be one :param resample: Optional resampling filter. This can be one
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`, of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS` :py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS`
(best quality). If omitted, it defaults to (best quality). If omitted, it defaults to
:py:attr:`PIL.Image.NEAREST` (this will be changed to ANTIALIAS in a :py:attr:`PIL.Image.ANTIALIAS`. (was :py:attr:`PIL.Image.NEAREST`
future version). prior to version 2.5.0)
:returns: None :returns: None
""" """
# FIXME: the default resampling filter will be changed
# to ANTIALIAS in future versions
# preserve aspect ratio # preserve aspect ratio
x, y = self.size x, y = self.size
if x > size[0]: if x > size[0]:

View File

@ -34,7 +34,8 @@
__version__ = "0.6" __version__ = "0.6"
import array, struct import array
import struct
from PIL import Image, ImageFile, _binary from PIL import Image, ImageFile, _binary
from PIL.JpegPresets import presets from PIL.JpegPresets import presets
from PIL._util import isStringType from PIL._util import isStringType
@ -44,6 +45,7 @@ o8 = _binary.o8
i16 = _binary.i16be i16 = _binary.i16be
i32 = _binary.i32be i32 = _binary.i32be
# #
# Parser # Parser
@ -51,6 +53,7 @@ def Skip(self, marker):
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2))-2
ImageFile._safe_read(self.fp, n) ImageFile._safe_read(self.fp, n)
def APP(self, marker): def APP(self, marker):
# #
# Application marker. Store these in the APP dictionary. # Application marker. Store these in the APP dictionary.
@ -59,14 +62,14 @@ def APP(self, marker):
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2))-2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
app = "APP%d" % (marker&15) app = "APP%d" % (marker & 15)
self.app[app] = s # compatibility self.app[app] = s # compatibility
self.applist.append((app, s)) self.applist.append((app, s))
if marker == 0xFFE0 and s[:4] == b"JFIF": if marker == 0xFFE0 and s[:4] == b"JFIF":
# extract JFIF information # extract JFIF information
self.info["jfif"] = version = i16(s, 5) # version self.info["jfif"] = version = i16(s, 5) # version
self.info["jfif_version"] = divmod(version, 256) self.info["jfif_version"] = divmod(version, 256)
# extract JFIF properties # extract JFIF properties
try: try:
@ -81,10 +84,10 @@ def APP(self, marker):
self.info["jfif_density"] = jfif_density self.info["jfif_density"] = jfif_density
elif marker == 0xFFE1 and s[:5] == b"Exif\0": elif marker == 0xFFE1 and s[:5] == b"Exif\0":
# extract Exif information (incomplete) # extract Exif information (incomplete)
self.info["exif"] = s # FIXME: value will change self.info["exif"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:5] == b"FPXR\0": elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
# extract FlashPix information (incomplete) # extract FlashPix information (incomplete)
self.info["flashpix"] = s # FIXME: value will change self.info["flashpix"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0": elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0":
# Since an ICC profile can be larger than the maximum size of # Since an ICC profile can be larger than the maximum size of
# a JPEG marker (64K), we need provisions to split it into # a JPEG marker (64K), we need provisions to split it into
@ -108,16 +111,17 @@ def APP(self, marker):
else: else:
self.info["adobe_transform"] = adobe_transform self.info["adobe_transform"] = adobe_transform
def COM(self, marker): def COM(self, marker):
# #
# Comment marker. Store these in the APP dictionary. # Comment marker. Store these in the APP dictionary.
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2))-2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
self.app["COM"] = s # compatibility self.app["COM"] = s # compatibility
self.applist.append(("COM", s)) self.applist.append(("COM", s))
def SOF(self, marker): def SOF(self, marker):
# #
# Start of frame marker. Defines the size and mode of the # Start of frame marker. Defines the size and mode of the
@ -149,21 +153,22 @@ def SOF(self, marker):
if self.icclist: if self.icclist:
# fixup icc profile # fixup icc profile
self.icclist.sort() # sort by sequence number self.icclist.sort() # sort by sequence number
if i8(self.icclist[0][13]) == len(self.icclist): if i8(self.icclist[0][13]) == len(self.icclist):
profile = [] profile = []
for p in self.icclist: for p in self.icclist:
profile.append(p[14:]) profile.append(p[14:])
icc_profile = b"".join(profile) icc_profile = b"".join(profile)
else: else:
icc_profile = None # wrong number of fragments icc_profile = None # wrong number of fragments
self.info["icc_profile"] = icc_profile self.info["icc_profile"] = icc_profile
self.icclist = None self.icclist = None
for i in range(6, len(s), 3): for i in range(6, len(s), 3):
t = s[i:i+3] t = s[i:i+3]
# 4-tuples: id, vsamp, hsamp, qtable # 4-tuples: id, vsamp, hsamp, qtable
self.layer.append((t[0], i8(t[1])//16, i8(t[1])&15, i8(t[2]))) self.layer.append((t[0], i8(t[1])//16, i8(t[1]) & 15, i8(t[2])))
def DQT(self, marker): def DQT(self, marker):
# #
@ -181,10 +186,10 @@ def DQT(self, marker):
raise SyntaxError("bad quantization table marker") raise SyntaxError("bad quantization table marker")
v = i8(s[0]) v = i8(s[0])
if v//16 == 0: if v//16 == 0:
self.quantization[v&15] = array.array("b", s[1:65]) self.quantization[v & 15] = array.array("b", s[1:65])
s = s[65:] s = s[65:]
else: else:
return # FIXME: add code to read 16-bit tables! return # FIXME: add code to read 16-bit tables!
# raise SyntaxError, "bad quantization table element size" # raise SyntaxError, "bad quantization table element size"
@ -261,6 +266,7 @@ MARKER = {
def _accept(prefix): def _accept(prefix):
return prefix[0:1] == b"\377" return prefix[0:1] == b"\377"
## ##
# Image plugin for JPEG and JFIF images. # Image plugin for JPEG and JFIF images.
@ -284,32 +290,37 @@ class JpegImageFile(ImageFile.ImageFile):
self.huffman_dc = {} self.huffman_dc = {}
self.huffman_ac = {} self.huffman_ac = {}
self.quantization = {} self.quantization = {}
self.app = {} # compatibility self.app = {} # compatibility
self.applist = [] self.applist = []
self.icclist = [] self.icclist = []
while True: while True:
s = s + self.fp.read(1) i = i8(s)
if i == 0xFF:
i = i16(s) s = s + self.fp.read(1)
i = i16(s)
else:
# Skip non-0xFF junk
s = b"\xff"
continue
if i in MARKER: if i in MARKER:
name, description, handler = MARKER[i] name, description, handler = MARKER[i]
# print hex(i), name, description # print hex(i), name, description
if handler is not None: if handler is not None:
handler(self, i) handler(self, i)
if i == 0xFFDA: # start of scan if i == 0xFFDA: # start of scan
rawmode = self.mode rawmode = self.mode
if self.mode == "CMYK": if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0,0) + self.size, 0, (rawmode, ""))] self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
# self.__offset = self.fp.tell() # self.__offset = self.fp.tell()
break break
s = self.fp.read(1) s = self.fp.read(1)
elif i == 0 or i == 65535: elif i == 0 or i == 0xFFFF:
# padded marker or junk; move on # padded marker or junk; move on
s = "\xff" s = b"\xff"
else: else:
raise SyntaxError("no marker found") raise SyntaxError("no marker found")
@ -343,7 +354,8 @@ class JpegImageFile(ImageFile.ImageFile):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities # ALTERNATIVE: handle JPEGs via the IJG command line utilities
import tempfile, os import tempfile
import os
f, path = tempfile.mkstemp() f, path = tempfile.mkstemp()
os.close(f) os.close(f)
if os.path.exists(self.filename): if os.path.exists(self.filename):
@ -354,8 +366,10 @@ class JpegImageFile(ImageFile.ImageFile):
try: try:
self.im = Image.core.open_ppm(path) self.im = Image.core.open_ppm(path)
finally: finally:
try: os.unlink(path) try:
except: pass os.unlink(path)
except:
pass
self.mode = self.im.mode self.mode = self.im.mode
self.size = self.im.size self.size = self.im.size
@ -372,6 +386,7 @@ def _getexif(self):
# version. # version.
from PIL import TiffImagePlugin from PIL import TiffImagePlugin
import io import io
def fixup(value): def fixup(value):
if len(value) == 1: if len(value) == 1:
return value[0] return value[0]
@ -422,7 +437,7 @@ RAWMODE = {
"RGB": "RGB", "RGB": "RGB",
"RGBA": "RGB", "RGBA": "RGB",
"RGBX": "RGB", "RGBX": "RGB",
"CMYK": "CMYK;I", # assume adobe conventions "CMYK": "CMYK;I", # assume adobe conventions
"YCbCr": "YCbCr", "YCbCr": "YCbCr",
} }
@ -441,16 +456,19 @@ samplings = {
(2, 2, 1, 1, 1, 1): 2, (2, 2, 1, 1, 1, 1): 2,
} }
def convert_dict_qtables(qtables): def convert_dict_qtables(qtables):
qtables = [qtables[key] for key in range(len(qtables)) if key in qtables] qtables = [qtables[key] for key in range(len(qtables)) if key in qtables]
for idx, table in enumerate(qtables): for idx, table in enumerate(qtables):
qtables[idx] = [table[i] for i in zigzag_index] qtables[idx] = [table[i] for i in zigzag_index]
return qtables return qtables
def get_sampling(im): def get_sampling(im):
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)
def _save(im, fp, filename): def _save(im, fp, filename):
try: try:
@ -563,12 +581,11 @@ def _save(im, fp, filename):
info.get("exif", b"") info.get("exif", b"")
) )
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
# if we optimize, libjpeg needs a buffer big enough to hold the whole image in a shot. # in a shot. Guessing on the size, at im.size bytes. (raw pizel size is
# Guessing on the size, at im.size bytes. (raw pizel size is channels*size, this # channels*size, this is a value that's been used in a django patch.
# is a value that's been used in a django patch.
# https://github.com/jdriscoll/django-imagekit/issues/50 # https://github.com/jdriscoll/django-imagekit/issues/50
bufsize=0 bufsize = 0
if "optimize" in info or "progressive" in info or "progression" in info: if "optimize" in info or "progressive" in info or "progression" in info:
if quality >= 95: if quality >= 95:
bufsize = 2 * im.size[0] * im.size[1] bufsize = 2 * im.size[0] * im.size[1]
@ -577,17 +594,20 @@ def _save(im, fp, filename):
# The exif info needs to be written as one block, + APP1, + one spare byte. # The exif info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough # Ensure that our buffer is big enough
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif",b"")) + 5 ) bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5)
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)], bufsize)
def _save_cjpeg(im, fp, filename): def _save_cjpeg(im, fp, filename):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities. # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
import os import os
file = im._dump() file = im._dump()
os.system("cjpeg %s >%s" % (file, filename)) os.system("cjpeg %s >%s" % (file, filename))
try: os.unlink(file) try:
except: pass os.unlink(file)
except:
pass
# -------------------------------------------------------------------q- # -------------------------------------------------------------------q-
# Registry stuff # Registry stuff

View File

@ -46,9 +46,11 @@ def _obj(fp, obj, **dict):
fp.write("/%s %s\n" % (k, v)) fp.write("/%s %s\n" % (k, v))
fp.write(">>\n") fp.write(">>\n")
def _endobj(fp): def _endobj(fp):
fp.write("endobj\n") fp.write("endobj\n")
## ##
# (Internal) Image save plugin for the PDF format. # (Internal) Image save plugin for the PDF format.
@ -59,13 +61,15 @@ def _save(im, fp, filename):
# make sure image data is available # make sure image data is available
im.load() im.load()
xref = [0]*(5+1) # placeholders xref = [0]*(5+1) # placeholders
class TextWriter: class TextWriter:
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.fp, name) return getattr(self.fp, name)
def write(self, value): def write(self, value):
self.fp.write(value.encode('latin-1')) self.fp.write(value.encode('latin-1'))
@ -89,13 +93,13 @@ def _save(im, fp, filename):
if im.mode == "1": if im.mode == "1":
filter = "/ASCIIHexDecode" filter = "/ASCIIHexDecode"
colorspace = "/DeviceGray" colorspace = "/DeviceGray"
procset = "/ImageB" # grayscale procset = "/ImageB" # grayscale
bits = 1 bits = 1
elif im.mode == "L": elif im.mode == "L":
filter = "/DCTDecode" filter = "/DCTDecode"
# params = "<< /Predictor 15 /Columns %d >>" % (width-2) # params = "<< /Predictor 15 /Columns %d >>" % (width-2)
colorspace = "/DeviceGray" colorspace = "/DeviceGray"
procset = "/ImageB" # grayscale procset = "/ImageB" # grayscale
elif im.mode == "P": elif im.mode == "P":
filter = "/ASCIIHexDecode" filter = "/ASCIIHexDecode"
colorspace = "[ /Indexed /DeviceRGB 255 <" colorspace = "[ /Indexed /DeviceRGB 255 <"
@ -105,16 +109,16 @@ def _save(im, fp, filename):
g = i8(palette[i*3+1]) g = i8(palette[i*3+1])
b = i8(palette[i*3+2]) b = i8(palette[i*3+2])
colorspace = colorspace + "%02x%02x%02x " % (r, g, b) colorspace = colorspace + "%02x%02x%02x " % (r, g, b)
colorspace = colorspace + b"> ]" colorspace = colorspace + "> ]"
procset = "/ImageI" # indexed color procset = "/ImageI" # indexed color
elif im.mode == "RGB": elif im.mode == "RGB":
filter = "/DCTDecode" filter = "/DCTDecode"
colorspace = "/DeviceRGB" colorspace = "/DeviceRGB"
procset = "/ImageC" # color images procset = "/ImageC" # color images
elif im.mode == "CMYK": elif im.mode == "CMYK":
filter = "/DCTDecode" filter = "/DCTDecode"
colorspace = "/DeviceCMYK" colorspace = "/DeviceCMYK"
procset = "/ImageC" # color images procset = "/ImageC" # color images
else: else:
raise ValueError("cannot save mode %s" % im.mode) raise ValueError("cannot save mode %s" % im.mode)
@ -122,17 +126,21 @@ def _save(im, fp, filename):
# catalogue # catalogue
xref[1] = fp.tell() xref[1] = fp.tell()
_obj(fp, 1, Type = "/Catalog", _obj(
Pages = "2 0 R") fp, 1,
Type="/Catalog",
Pages="2 0 R")
_endobj(fp) _endobj(fp)
# #
# pages # pages
xref[2] = fp.tell() xref[2] = fp.tell()
_obj(fp, 2, Type = "/Pages", _obj(
Count = 1, fp, 2,
Kids = "[4 0 R]") Type="/Pages",
Count=1,
Kids="[4 0 R]")
_endobj(fp) _endobj(fp)
# #
@ -144,29 +152,31 @@ def _save(im, fp, filename):
if bits == 1: if bits == 1:
# FIXME: the hex encoder doesn't support packed 1-bit # FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way... # images; do things the hard way...
data = im.tostring("raw", "1") data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None) im = Image.new("L", (len(data), 1), None)
im.putdata(data) im.putdata(data)
ImageFile._save(im, op, [("hex", (0,0)+im.size, 0, im.mode)]) ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode": elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename) Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode": elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0,0)+im.size, 0, im.mode)]) ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode": elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0,0)+im.size, 0, im.mode)]) ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else: else:
raise ValueError("unsupported PDF filter (%s)" % filter) raise ValueError("unsupported PDF filter (%s)" % filter)
xref[3] = fp.tell() xref[3] = fp.tell()
_obj(fp, 3, Type = "/XObject", _obj(
Subtype = "/Image", fp, 3,
Width = width, # * 72.0 / resolution, Type="/XObject",
Height = height, # * 72.0 / resolution, Subtype="/Image",
Length = len(op.getvalue()), Width=width, # * 72.0 / resolution,
Filter = filter, Height=height, # * 72.0 / resolution,
BitsPerComponent = bits, Length=len(op.getvalue()),
DecodeParams = params, Filter=filter,
ColorSpace = colorspace) BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
fp.write("stream\n") fp.write("stream\n")
fp.fp.write(op.getvalue()) fp.fp.write(op.getvalue())
@ -179,11 +189,14 @@ def _save(im, fp, filename):
xref[4] = fp.tell() xref[4] = fp.tell()
_obj(fp, 4) _obj(fp, 4)
fp.write("<<\n/Type /Page\n/Parent 2 0 R\n"\ fp.write(
"/Resources <<\n/ProcSet [ /PDF %s ]\n"\ "<<\n/Type /Page\n/Parent 2 0 R\n"
"/XObject << /image 3 0 R >>\n>>\n"\ "/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" %\ "/XObject << /image 3 0 R >>\n>>\n"
(procset, int(width * 72.0 /resolution) , int(height * 72.0 / resolution))) "/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % (
procset,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
_endobj(fp) _endobj(fp)
# #
@ -191,10 +204,13 @@ def _save(im, fp, filename):
op = TextWriter(io.BytesIO()) op = TextWriter(io.BytesIO())
op.write("q %d 0 0 %d 0 0 cm /image Do Q\n" % (int(width * 72.0 / resolution), int(height * 72.0 / resolution))) op.write(
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
xref[5] = fp.tell() xref[5] = fp.tell()
_obj(fp, 5, Length = len(op.fp.getvalue())) _obj(fp, 5, Length=len(op.fp.getvalue()))
fp.write("stream\n") fp.write("stream\n")
fp.fp.write(op.fp.getvalue()) fp.fp.write(op.fp.getvalue())

View File

@ -89,33 +89,33 @@ class ChunkStream:
"Fetch a new chunk. Returns header information." "Fetch a new chunk. Returns header information."
if self.queue: if self.queue:
cid, pos, len = self.queue[-1] cid, pos, length = self.queue[-1]
del self.queue[-1] del self.queue[-1]
self.fp.seek(pos) self.fp.seek(pos)
else: else:
s = self.fp.read(8) s = self.fp.read(8)
cid = s[4:] cid = s[4:]
pos = self.fp.tell() pos = self.fp.tell()
len = i32(s) length = i32(s)
if not is_cid(cid): if not is_cid(cid):
raise SyntaxError("broken PNG file (chunk %s)" % repr(cid)) raise SyntaxError("broken PNG file (chunk %s)" % repr(cid))
return cid, pos, len return cid, pos, length
def close(self): def close(self):
self.queue = self.crc = self.fp = None self.queue = self.crc = self.fp = None
def push(self, cid, pos, len): def push(self, cid, pos, length):
self.queue.append((cid, pos, len)) self.queue.append((cid, pos, length))
def call(self, cid, pos, len): def call(self, cid, pos, length):
"Call the appropriate chunk handler" "Call the appropriate chunk handler"
if Image.DEBUG: if Image.DEBUG:
print("STREAM", cid, pos, len) print("STREAM", cid, pos, length)
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, len) return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length)
def crc(self, cid, data): def crc(self, cid, data):
"Read and verify checksum" "Read and verify checksum"
@ -139,10 +139,10 @@ class ChunkStream:
cids = [] cids = []
while True: while True:
cid, pos, len = self.read() cid, pos, length = self.read()
if cid == endchunk: if cid == endchunk:
break break
self.crc(cid, ImageFile._safe_read(self.fp, len)) self.crc(cid, ImageFile._safe_read(self.fp, length))
cids.append(cid) cids.append(cid)
return cids return cids
@ -190,10 +190,10 @@ class PngStream(ChunkStream):
self.im_tile = None self.im_tile = None
self.im_palette = None self.im_palette = None
def chunk_iCCP(self, pos, len): def chunk_iCCP(self, pos, length):
# ICC profile # ICC profile
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
# according to PNG spec, the iCCP chunk contains: # according to PNG spec, the iCCP chunk contains:
# Profile name 1-79 bytes (character string) # Profile name 1-79 bytes (character string)
# Null separator 1 byte (null character) # Null separator 1 byte (null character)
@ -213,10 +213,10 @@ class PngStream(ChunkStream):
self.im_info["icc_profile"] = icc_profile self.im_info["icc_profile"] = icc_profile
return s return s
def chunk_IHDR(self, pos, len): def chunk_IHDR(self, pos, length):
# image header # image header
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
self.im_size = i32(s), i32(s[4:]) self.im_size = i32(s), i32(s[4:])
try: try:
self.im_mode, self.im_rawmode = _MODES[(i8(s[8]), i8(s[9]))] self.im_mode, self.im_rawmode = _MODES[(i8(s[8]), i8(s[9]))]
@ -228,30 +228,30 @@ class PngStream(ChunkStream):
raise SyntaxError("unknown filter category") raise SyntaxError("unknown filter category")
return s return s
def chunk_IDAT(self, pos, len): def chunk_IDAT(self, pos, length):
# image data # image data
self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)] self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
self.im_idat = len self.im_idat = length
raise EOFError raise EOFError
def chunk_IEND(self, pos, len): def chunk_IEND(self, pos, length):
# end of PNG image # end of PNG image
raise EOFError raise EOFError
def chunk_PLTE(self, pos, len): def chunk_PLTE(self, pos, length):
# palette # palette
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P": if self.im_mode == "P":
self.im_palette = "RGB", s self.im_palette = "RGB", s
return s return s
def chunk_tRNS(self, pos, len): def chunk_tRNS(self, pos, length):
# transparency # transparency
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P": if self.im_mode == "P":
if _simple_palette.match(s): if _simple_palette.match(s):
i = s.find(b"\0") i = s.find(b"\0")
@ -265,17 +265,17 @@ class PngStream(ChunkStream):
self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:]) self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
return s return s
def chunk_gAMA(self, pos, len): def chunk_gAMA(self, pos, length):
# gamma setting # gamma setting
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
self.im_info["gamma"] = i32(s) / 100000.0 self.im_info["gamma"] = i32(s) / 100000.0
return s return s
def chunk_pHYs(self, pos, len): def chunk_pHYs(self, pos, length):
# pixels per unit # pixels per unit
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
px, py = i32(s), i32(s[4:]) px, py = i32(s), i32(s[4:])
unit = i8(s[8]) unit = i8(s[8])
if unit == 1: # meter if unit == 1: # meter
@ -285,10 +285,10 @@ class PngStream(ChunkStream):
self.im_info["aspect"] = px, py self.im_info["aspect"] = px, py
return s return s
def chunk_tEXt(self, pos, len): def chunk_tEXt(self, pos, length):
# text # text
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
@ -301,10 +301,10 @@ class PngStream(ChunkStream):
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
return s return s
def chunk_zTXt(self, pos, len): def chunk_zTXt(self, pos, length):
# compressed text # compressed text
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
@ -358,16 +358,16 @@ class PngImageFile(ImageFile.ImageFile):
# #
# get next chunk # get next chunk
cid, pos, len = self.png.read() cid, pos, length = self.png.read()
try: try:
s = self.png.call(cid, pos, len) s = self.png.call(cid, pos, length)
except EOFError: except EOFError:
break break
except AttributeError: except AttributeError:
if Image.DEBUG: if Image.DEBUG:
print(cid, pos, len, "(unknown)") print(cid, pos, length, "(unknown)")
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
self.png.crc(cid, s) self.png.crc(cid, s)
@ -388,7 +388,7 @@ class PngImageFile(ImageFile.ImageFile):
rawmode, data = self.png.im_palette rawmode, data = self.png.im_palette
self.palette = ImagePalette.raw(rawmode, data) self.palette = ImagePalette.raw(rawmode, data)
self.__idat = len # used by load_read() self.__idat = length # used by load_read()
def verify(self): def verify(self):
@ -413,7 +413,7 @@ class PngImageFile(ImageFile.ImageFile):
ImageFile.ImageFile.load_prepare(self) ImageFile.ImageFile.load_prepare(self)
def load_read(self, bytes): def load_read(self, read_bytes):
"internal: read more image data" "internal: read more image data"
while self.__idat == 0: while self.__idat == 0:
@ -421,23 +421,23 @@ class PngImageFile(ImageFile.ImageFile):
self.fp.read(4) # CRC self.fp.read(4) # CRC
cid, pos, len = self.png.read() cid, pos, length = self.png.read()
if cid not in [b"IDAT", b"DDAT"]: if cid not in [b"IDAT", b"DDAT"]:
self.png.push(cid, pos, len) self.png.push(cid, pos, length)
return b"" return b""
self.__idat = len # empty chunks are allowed self.__idat = length # empty chunks are allowed
# read more data from this chunk # read more data from this chunk
if bytes <= 0: if read_bytes <= 0:
bytes = self.__idat read_bytes = self.__idat
else: else:
bytes = min(bytes, self.__idat) read_bytes = min(read_bytes, self.__idat)
self.__idat = self.__idat - bytes self.__idat = self.__idat - read_bytes
return self.fp.read(bytes) return self.fp.read(read_bytes)
def load_end(self): def load_end(self):
@ -560,7 +560,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
chunk(fp, b"PLTE", palette_bytes) chunk(fp, b"PLTE", palette_bytes)
transparency = im.encoderinfo.get('transparency',im.info.get('transparency', None)) transparency = im.encoderinfo.get('transparency',im.info.get('transparency', None))
if transparency or transparency == 0: if transparency or transparency == 0:
if im.mode == "P": if im.mode == "P":
# limit to actual palette size # limit to actual palette size
@ -580,7 +580,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
else: else:
if "transparency" in im.encoderinfo: if "transparency" in im.encoderinfo:
# don't bother with transparency if it's an RGBA # don't bother with transparency if it's an RGBA
# and it's in the info dict. It's probably just stale. # and it's in the info dict. It's probably just stale.
raise IOError("cannot use transparency for this mode") raise IOError("cannot use transparency for this mode")
else: else:
if im.mode == "P" and im.im.getpalettemode() == "RGBA": if im.mode == "P" and im.im.getpalettemode() == "RGBA":

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@ -12,17 +12,19 @@ if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs:
test_file = "Images/lena.jpg" test_file = "Images/lena.jpg"
def roundtrip(im, **options): def roundtrip(im, **options):
out = BytesIO() out = BytesIO()
im.save(out, "JPEG", **options) im.save(out, "JPEG", **options)
bytes = out.tell() bytes = out.tell()
out.seek(0) out.seek(0)
im = Image.open(out) im = Image.open(out)
im.bytes = bytes # for testing only im.bytes = bytes # for testing only
return im return im
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def test_sanity(): def test_sanity():
# internal version number # internal version number
@ -34,6 +36,7 @@ def test_sanity():
assert_equal(im.size, (128, 128)) assert_equal(im.size, (128, 128))
assert_equal(im.format, "JPEG") assert_equal(im.format, "JPEG")
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def test_app(): def test_app():
@ -44,6 +47,7 @@ def test_app():
assert_equal(im.applist[1], ("COM", b"Python Imaging Library")) assert_equal(im.applist[1], ("COM", b"Python Imaging Library"))
assert_equal(len(im.applist), 2) assert_equal(len(im.applist), 2)
def test_cmyk(): def test_cmyk():
# Test CMYK handling. Thanks to Tim and Charlie for test data, # Test CMYK handling. Thanks to Tim and Charlie for test data,
# Michael for getting me to look one more time. # Michael for getting me to look one more time.
@ -62,6 +66,7 @@ def test_cmyk():
c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, im.size[1]-1))] c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, im.size[1]-1))]
assert_true(k > 0.9) assert_true(k > 0.9)
def test_dpi(): def test_dpi():
def test(xdpi, ydpi=None): def test(xdpi, ydpi=None):
im = Image.open(test_file) im = Image.open(test_file)
@ -70,7 +75,8 @@ def test_dpi():
assert_equal(test(72), (72, 72)) assert_equal(test(72), (72, 72))
assert_equal(test(300), (300, 300)) assert_equal(test(300), (300, 300))
assert_equal(test(100, 200), (100, 200)) assert_equal(test(100, 200), (100, 200))
assert_equal(test(0), None) # square pixels assert_equal(test(0), None) # square pixels
def test_icc(): def test_icc():
# Test ICC support # Test ICC support
@ -89,6 +95,7 @@ def test_icc():
assert_false(im1.info.get("icc_profile")) assert_false(im1.info.get("icc_profile"))
assert_true(im2.info.get("icc_profile")) assert_true(im2.info.get("icc_profile"))
def test_icc_big(): def test_icc_big():
# Make sure that the "extra" support handles large blocks # Make sure that the "extra" support handles large blocks
def test(n): def test(n):
@ -96,16 +103,20 @@ def test_icc_big():
# using a 4-byte test code should allow us to detect out of # using a 4-byte test code should allow us to detect out of
# order issues. # order issues.
icc_profile = (b"Test"*int(n/4+1))[:n] icc_profile = (b"Test"*int(n/4+1))[:n]
assert len(icc_profile) == n # sanity assert len(icc_profile) == n # sanity
im1 = roundtrip(lena(), icc_profile=icc_profile) im1 = roundtrip(lena(), icc_profile=icc_profile)
assert_equal(im1.info.get("icc_profile"), icc_profile or None) assert_equal(im1.info.get("icc_profile"), icc_profile or None)
test(0); test(1) test(0)
test(3); test(4); test(5) test(1)
test(65533-14) # full JPEG marker block test(3)
test(65533-14+1) # full block plus one byte test(4)
test(ImageFile.MAXBLOCK) # full buffer block test(5)
test(ImageFile.MAXBLOCK+1) # full buffer block plus one byte test(65533-14) # full JPEG marker block
test(ImageFile.MAXBLOCK*4+3) # large block test(65533-14+1) # full block plus one byte
test(ImageFile.MAXBLOCK) # full buffer block
test(ImageFile.MAXBLOCK+1) # full buffer block plus one byte
test(ImageFile.MAXBLOCK*4+3) # large block
def test_optimize(): def test_optimize():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
@ -113,25 +124,29 @@ def test_optimize():
assert_image_equal(im1, im2) assert_image_equal(im1, im2)
assert_true(im1.bytes >= im2.bytes) assert_true(im1.bytes >= im2.bytes)
def test_optimize_large_buffer(): def test_optimize_large_buffer():
#https://github.com/python-imaging/Pillow/issues/148 # https://github.com/python-imaging/Pillow/issues/148
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
# this requires ~ 1.5x Image.MAXBLOCK # this requires ~ 1.5x Image.MAXBLOCK
im = Image.new("RGB", (4096,4096), 0xff3333) im = Image.new("RGB", (4096, 4096), 0xff3333)
im.save(f, format="JPEG", optimize=True) im.save(f, format="JPEG", optimize=True)
def test_progressive(): def test_progressive():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
im2 = roundtrip(lena(), progressive=True) im2 = roundtrip(lena(), progressive=True)
assert_image_equal(im1, im2) assert_image_equal(im1, im2)
assert_true(im1.bytes >= im2.bytes) assert_true(im1.bytes >= im2.bytes)
def test_progressive_large_buffer(): def test_progressive_large_buffer():
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
# this requires ~ 1.5x Image.MAXBLOCK # this requires ~ 1.5x Image.MAXBLOCK
im = Image.new("RGB", (4096,4096), 0xff3333) im = Image.new("RGB", (4096, 4096), 0xff3333)
im.save(f, format="JPEG", progressive=True) im.save(f, format="JPEG", progressive=True)
def test_progressive_large_buffer_highest_quality(): def test_progressive_large_buffer_highest_quality():
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
if py3: if py3:
@ -142,16 +157,18 @@ def test_progressive_large_buffer_highest_quality():
# 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)
def test_large_exif(): def test_large_exif():
#https://github.com/python-imaging/Pillow/issues/148 # https://github.com/python-imaging/Pillow/issues/148
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
im = lena() im = lena()
im.save(f,'JPEG', quality=90, exif=b"1"*65532) im.save(f, 'JPEG', quality=90, exif=b"1"*65532)
def test_progressive_compat(): def test_progressive_compat():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
im2 = roundtrip(lena(), progressive=1) im2 = roundtrip(lena(), progressive=1)
im3 = roundtrip(lena(), progression=1) # compatibility im3 = roundtrip(lena(), progression=1) # compatibility
assert_image_equal(im1, im2) assert_image_equal(im1, im2)
assert_image_equal(im1, im3) assert_image_equal(im1, im3)
assert_false(im1.info.get("progressive")) assert_false(im1.info.get("progressive"))
@ -161,31 +178,34 @@ def test_progressive_compat():
assert_true(im3.info.get("progressive")) assert_true(im3.info.get("progressive"))
assert_true(im3.info.get("progression")) assert_true(im3.info.get("progression"))
def test_quality(): def test_quality():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
im2 = roundtrip(lena(), quality=50) im2 = roundtrip(lena(), quality=50)
assert_image(im1, im2.mode, im2.size) assert_image(im1, im2.mode, im2.size)
assert_true(im1.bytes >= im2.bytes) assert_true(im1.bytes >= im2.bytes)
def test_smooth(): def test_smooth():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
im2 = roundtrip(lena(), smooth=100) im2 = roundtrip(lena(), smooth=100)
assert_image(im1, im2.mode, im2.size) assert_image(im1, im2.mode, im2.size)
def test_subsampling(): def test_subsampling():
def getsampling(im): def getsampling(im):
layer = im.layer layer = im.layer
return layer[0][1:3] + layer[1][1:3] + layer[2][1:3] return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]
# experimental API # experimental API
im = roundtrip(lena(), subsampling=-1) # default im = roundtrip(lena(), subsampling=-1) # default
assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1)) assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1))
im = roundtrip(lena(), subsampling=0) # 4:4:4 im = roundtrip(lena(), subsampling=0) # 4:4:4
assert_equal(getsampling(im), (1, 1, 1, 1, 1, 1)) assert_equal(getsampling(im), (1, 1, 1, 1, 1, 1))
im = roundtrip(lena(), subsampling=1) # 4:2:2 im = roundtrip(lena(), subsampling=1) # 4:2:2
assert_equal(getsampling(im), (2, 1, 1, 1, 1, 1)) assert_equal(getsampling(im), (2, 1, 1, 1, 1, 1))
im = roundtrip(lena(), subsampling=2) # 4:1:1 im = roundtrip(lena(), subsampling=2) # 4:1:1
assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1)) assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1))
im = roundtrip(lena(), subsampling=3) # default (undefined) im = roundtrip(lena(), subsampling=3) # default (undefined)
assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1)) assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1))
im = roundtrip(lena(), subsampling="4:4:4") im = roundtrip(lena(), subsampling="4:4:4")
@ -197,6 +217,7 @@ def test_subsampling():
assert_exception(TypeError, lambda: roundtrip(lena(), subsampling="1:1:1")) assert_exception(TypeError, lambda: roundtrip(lena(), subsampling="1:1:1"))
def test_exif(): def test_exif():
im = Image.open("Tests/images/pil_sample_rgb.jpg") im = Image.open("Tests/images/pil_sample_rgb.jpg")
info = im._getexif() info = im._getexif()
@ -207,3 +228,11 @@ def test_quality_keep():
im = Image.open("Images/lena.jpg") im = Image.open("Images/lena.jpg")
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
assert_no_exception(lambda: im.save(f, quality='keep')) assert_no_exception(lambda: im.save(f, quality='keep'))
def test_junk_jpeg_header():
# https://github.com/python-imaging/Pillow/issues/630
filename = "Tests/images/junk_jpeg_header.jpg"
assert_no_exception(lambda: Image.open(filename))
# End of file

58
Tests/test_file_pdf.py Normal file
View File

@ -0,0 +1,58 @@
from tester import *
import os.path
def helper_save_as_pdf(mode):
# Arrange
im = lena(mode)
outfile = tempfile("temp_" + mode + ".pdf")
# Act
im.save(outfile)
# Assert
assert_true(os.path.isfile(outfile))
assert_greater(os.path.getsize(outfile), 0)
def test_monochrome():
# Arrange
mode = "1"
# Act / Assert
helper_save_as_pdf(mode)
def test_greyscale():
# Arrange
mode = "L"
# Act / Assert
helper_save_as_pdf(mode)
def test_rgb():
# Arrange
mode = "RGB"
# Act / Assert
helper_save_as_pdf(mode)
def test_p_mode():
# Arrange
mode = "P"
# Act / Assert
helper_save_as_pdf(mode)
def test_cmyk_mode():
# Arrange
mode = "CMYK"
# Act / Assert
helper_save_as_pdf(mode)
# End of file

View File

@ -2,6 +2,7 @@ from tester import *
from PIL import Image from PIL import Image
def test_sanity(): def test_sanity():
def convert(im, mode): def convert(im, mode):
@ -16,6 +17,7 @@ def test_sanity():
for mode in modes: for mode in modes:
yield_test(convert, im, mode) yield_test(convert, im, mode)
def test_default(): def test_default():
im = lena("P") im = lena("P")
@ -26,26 +28,29 @@ def test_default():
assert_image(im, "RGB", im.size) assert_image(im, "RGB", im.size)
# ref https://github.com/python-imaging/Pillow/issues/274 # ref https://github.com/python-imaging/Pillow/issues/274
def _test_float_conversion(im): def _test_float_conversion(im):
orig = im.getpixel((5,5)) orig = im.getpixel((5, 5))
converted = im.convert('F').getpixel((5,5)) converted = im.convert('F').getpixel((5, 5))
assert_equal(orig, converted) assert_equal(orig, converted)
def test_8bit(): def test_8bit():
im = Image.open('Images/lena.jpg') im = Image.open('Images/lena.jpg')
_test_float_conversion(im.convert('L')) _test_float_conversion(im.convert('L'))
def test_16bit(): def test_16bit():
im = Image.open('Tests/images/16bit.cropped.tif') im = Image.open('Tests/images/16bit.cropped.tif')
_test_float_conversion(im) _test_float_conversion(im)
def test_16bit_workaround(): def test_16bit_workaround():
im = Image.open('Tests/images/16bit.cropped.tif') im = Image.open('Tests/images/16bit.cropped.tif')
_test_float_conversion(im.convert('I')) _test_float_conversion(im.convert('I'))
def test_rgba_p(): def test_rgba_p():
im = lena('RGBA') im = lena('RGBA')
im.putalpha(lena('L')) im.putalpha(lena('L'))
@ -54,30 +59,45 @@ def test_rgba_p():
comparable = converted.convert('RGBA') comparable = converted.convert('RGBA')
assert_image_similar(im, comparable, 20) assert_image_similar(im, comparable, 20)
def test_trns_p():
def test_trns_p():
im = lena('P') im = lena('P')
im.info['transparency']=0 im.info['transparency'] = 0
f = tempfile('temp.png') f = tempfile('temp.png')
l = im.convert('L') l = im.convert('L')
assert_equal(l.info['transparency'], 0) # undone assert_equal(l.info['transparency'], 0) # undone
assert_no_exception(lambda: l.save(f)) assert_no_exception(lambda: l.save(f))
rgb = im.convert('RGB') rgb = im.convert('RGB')
assert_equal(rgb.info['transparency'], (0,0,0)) # undone assert_equal(rgb.info['transparency'], (0, 0, 0)) # undone
assert_no_exception(lambda: rgb.save(f)) assert_no_exception(lambda: rgb.save(f))
# ref https://github.com/python-imaging/Pillow/issues/664
def test_trns_p_rgba():
# Arrange
im = lena('P')
im.info['transparency'] = 128
# Act
rgba = im.convert('RGBA')
# Assert
assert_false('transparency' in rgba.info)
def test_trns_l(): def test_trns_l():
im = lena('L') im = lena('L')
im.info['transparency'] = 128 im.info['transparency'] = 128
f = tempfile('temp.png') f = tempfile('temp.png')
rgb = im.convert('RGB') rgb = im.convert('RGB')
assert_equal(rgb.info['transparency'], (128,128,128)) # undone assert_equal(rgb.info['transparency'], (128, 128, 128)) # undone
assert_no_exception(lambda: rgb.save(f)) assert_no_exception(lambda: rgb.save(f))
p = im.convert('P') p = im.convert('P')
@ -85,28 +105,26 @@ def test_trns_l():
assert_no_exception(lambda: p.save(f)) assert_no_exception(lambda: p.save(f))
p = assert_warning(UserWarning, p = assert_warning(UserWarning,
lambda: im.convert('P', palette = Image.ADAPTIVE)) lambda: im.convert('P', palette=Image.ADAPTIVE))
assert_false('transparency' in p.info) assert_false('transparency' in p.info)
assert_no_exception(lambda: p.save(f)) assert_no_exception(lambda: p.save(f))
def test_trns_RGB(): def test_trns_RGB():
im = lena('RGB') im = lena('RGB')
im.info['transparency'] = im.getpixel((0,0)) im.info['transparency'] = im.getpixel((0, 0))
f = tempfile('temp.png') f = tempfile('temp.png')
l = im.convert('L') l = im.convert('L')
assert_equal(l.info['transparency'], l.getpixel((0,0))) # undone assert_equal(l.info['transparency'], l.getpixel((0, 0))) # undone
assert_no_exception(lambda: l.save(f)) assert_no_exception(lambda: l.save(f))
p = im.convert('P') p = im.convert('P')
assert_true('transparency' in p.info) assert_true('transparency' in p.info)
assert_no_exception(lambda: p.save(f)) assert_no_exception(lambda: p.save(f))
p = assert_warning(UserWarning, p = assert_warning(UserWarning,
lambda: im.convert('P', palette = Image.ADAPTIVE)) lambda: im.convert('P', palette=Image.ADAPTIVE))
assert_false('transparency' in p.info) assert_false('transparency' in p.info)
assert_no_exception(lambda: p.save(f)) assert_no_exception(lambda: p.save(f))

View File

@ -351,7 +351,10 @@ def _setup():
import sys import sys
if "--coverage" in sys.argv: if "--coverage" in sys.argv:
import coverage # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
import coverage
cov = coverage.coverage(auto_data=True, include="PIL/*") cov = coverage.coverage(auto_data=True, include="PIL/*")
cov.start() cov.start()

View File

@ -3350,7 +3350,7 @@ extern PyObject* PyImaging_ZipEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args);
/* Display support etc (in display.c) */ /* Display support etc (in display.c) */
#ifdef WIN32 #ifdef _WIN32
extern PyObject* PyImaging_CreateWindowWin32(PyObject* self, PyObject* args); extern PyObject* PyImaging_CreateWindowWin32(PyObject* self, PyObject* args);
extern PyObject* PyImaging_DisplayWin32(PyObject* self, PyObject* args); extern PyObject* PyImaging_DisplayWin32(PyObject* self, PyObject* args);
extern PyObject* PyImaging_DisplayModeWin32(PyObject* self, PyObject* args); extern PyObject* PyImaging_DisplayModeWin32(PyObject* self, PyObject* args);
@ -3423,14 +3423,14 @@ static PyMethodDef functions[] = {
/* Memory mapping */ /* Memory mapping */
#ifdef WITH_MAPPING #ifdef WITH_MAPPING
#ifdef WIN32 #ifdef _WIN32
{"map", (PyCFunction)PyImaging_Mapper, 1}, {"map", (PyCFunction)PyImaging_Mapper, 1},
#endif #endif
{"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1}, {"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1},
#endif #endif
/* Display support */ /* Display support */
#ifdef WIN32 #ifdef _WIN32
{"display", (PyCFunction)PyImaging_DisplayWin32, 1}, {"display", (PyCFunction)PyImaging_DisplayWin32, 1},
{"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, 1}, {"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, 1},
{"grabscreen", (PyCFunction)PyImaging_GrabScreenWin32, 1}, {"grabscreen", (PyCFunction)PyImaging_GrabScreenWin32, 1},

View File

@ -28,12 +28,6 @@ http://www.cazabon.com\n\
#include "Imaging.h" #include "Imaging.h"
#include "py3.h" #include "py3.h"
#ifdef WIN32
#include <windows.h>
#include <windef.h>
#include <wingdi.h>
#endif
#define PYCMSVERSION "1.0.0 pil" #define PYCMSVERSION "1.0.0 pil"
/* version history */ /* version history */
@ -450,7 +444,7 @@ cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args)
return PyInt_FromLong(result != 0); return PyInt_FromLong(result != 0);
} }
#ifdef WIN32 #ifdef _WIN32
static PyObject * static PyObject *
cms_get_display_profile_win32(PyObject* self, PyObject* args) cms_get_display_profile_win32(PyObject* self, PyObject* args)
{ {
@ -496,7 +490,7 @@ static PyMethodDef pyCMSdll_methods[] = {
{"createProfile", createProfile, 1}, {"createProfile", createProfile, 1},
/* platform specific tools */ /* platform specific tools */
#ifdef WIN32 #ifdef _WIN32
{"get_display_profile_win32", cms_get_display_profile_win32, 1}, {"get_display_profile_win32", cms_get_display_profile_win32, 1},
#endif #endif

View File

@ -433,9 +433,6 @@ PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args)
#include "TiffDecode.h" #include "TiffDecode.h"
#include <string.h> #include <string.h>
#ifdef __WIN32__
#define strcasecmp(s1, s2) stricmp(s1, s2)
#endif
PyObject* PyObject*
PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args) PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args)

View File

@ -31,7 +31,7 @@
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/* Windows DIB support */ /* Windows DIB support */
#ifdef WIN32 #ifdef _WIN32
#include "ImDib.h" #include "ImDib.h"
@ -864,4 +864,4 @@ error:
return buffer; return buffer;
} }
#endif /* WIN32 */ #endif /* _WIN32 */

View File

@ -670,9 +670,6 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
#include "TiffDecode.h" #include "TiffDecode.h"
#include <string.h> #include <string.h>
#ifdef __WIN32__
#define strcasecmp(s1, s2) stricmp(s1, s2)
#endif
PyObject* PyObject*
PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)

View File

@ -22,7 +22,7 @@
#include "Imaging.h" #include "Imaging.h"
#ifdef WIN32 #ifdef _WIN32
#include "ImDib.h" #include "ImDib.h"
@ -308,4 +308,4 @@ ImagingDeleteDIB(ImagingDIB dib)
free(dib->info); free(dib->info);
} }
#endif /* WIN32 */ #endif /* _WIN32 */

View File

@ -59,7 +59,7 @@ typedef struct {
unsigned char buffer[GIFTABLE]; unsigned char buffer[GIFTABLE];
/* Symbol table */ /* Symbol table */
unsigned INT16 link[GIFTABLE]; UINT16 link[GIFTABLE];
unsigned char data[GIFTABLE]; unsigned char data[GIFTABLE];
int next; int next;

View File

@ -10,20 +10,9 @@
* See the README file for information on usage and redistribution. * See the README file for information on usage and redistribution.
*/ */
#ifdef WIN32 #ifdef _WIN32
#if (defined(_MSC_VER) && _MSC_VER >= 1200) || (defined __GNUC__) #include "ImPlatform.h"
/* already defined in basetsd.h */
#undef INT8
#undef UINT8
#undef INT16
#undef UINT16
#undef INT32
#undef INT64
#undef UINT32
#endif
#include <windows.h>
#if defined(__cplusplus) #if defined(__cplusplus)
extern "C" { extern "C" {

View File

@ -17,26 +17,22 @@
#error Sorry, this library requires ANSI header files. #error Sorry, this library requires ANSI header files.
#endif #endif
#if defined(_MSC_VER) #if defined(_MSC_VER) && !defined(__GNUC__)
#ifndef WIN32 #define inline __inline
#define WIN32
#endif
/* VC++ 4.0 is a bit annoying when it comes to precision issues (like
claiming that "float a = 0.0;" would lead to loss of precision). I
don't like to see warnings from my code, but since I still want to
keep it readable, I simply switch off a few warnings instead of adding
the tons of casts that VC++ seem to require. This code is compiled
with numerous other compilers as well, so any real errors are likely
to be catched anyway. */
#pragma warning(disable: 4244) /* conversion from 'float' to 'int' */
#endif #endif
#if defined(_MSC_VER) #if !defined(PIL_USE_INLINE)
#define inline __inline #define inline
#elif !defined(USE_INLINE)
#define inline
#endif #endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
/* For System that are not Windows, we'll need to define these. */
#if SIZEOF_SHORT == 2 #if SIZEOF_SHORT == 2
#define INT16 short #define INT16 short
#elif SIZEOF_INT == 2 #elif SIZEOF_INT == 2
@ -61,12 +57,16 @@
#define INT64 long #define INT64 long
#endif #endif
/* assume IEEE; tweak if necessary (patches are welcome) */
#define FLOAT32 float
#define FLOAT64 double
#define INT8 signed char #define INT8 signed char
#define UINT8 unsigned char #define UINT8 unsigned char
#define UINT16 unsigned INT16 #define UINT16 unsigned INT16
#define UINT32 unsigned INT32 #define UINT32 unsigned INT32
#endif
/* assume IEEE; tweak if necessary (patches are welcome) */
#define FLOAT32 float
#define FLOAT64 double

View File

@ -41,7 +41,6 @@
two cases. */ two cases. */
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h>
#include <process.h> #include <process.h>
#else #else
#include <pthread.h> #include <pthread.h>

View File

@ -45,7 +45,7 @@ typedef struct {
unsigned char buffer[LZWTABLE]; unsigned char buffer[LZWTABLE];
/* Symbol table */ /* Symbol table */
unsigned INT16 link[LZWTABLE]; UINT16 link[LZWTABLE];
unsigned char data[LZWTABLE]; unsigned char data[LZWTABLE];
int next; int next;

18
map.c
View File

@ -22,18 +22,6 @@
#include "Imaging.h" #include "Imaging.h"
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#undef INT8
#undef UINT8
#undef INT16
#undef UINT16
#undef INT32
#undef INT64
#undef UINT32
#include "windows.h"
#endif
#include "py3.h" #include "py3.h"
/* compatibility wrappers (defined in _imaging.c) */ /* compatibility wrappers (defined in _imaging.c) */
@ -48,7 +36,7 @@ typedef struct {
char* base; char* base;
int size; int size;
int offset; int offset;
#ifdef WIN32 #ifdef _WIN32
HANDLE hFile; HANDLE hFile;
HANDLE hMap; HANDLE hMap;
#endif #endif
@ -71,7 +59,7 @@ PyImaging_MapperNew(const char* filename, int readonly)
mapper->base = NULL; mapper->base = NULL;
mapper->size = mapper->offset = 0; mapper->size = mapper->offset = 0;
#ifdef WIN32 #ifdef _WIN32
mapper->hFile = (HANDLE)-1; mapper->hFile = (HANDLE)-1;
mapper->hMap = (HANDLE)-1; mapper->hMap = (HANDLE)-1;
@ -114,7 +102,7 @@ PyImaging_MapperNew(const char* filename, int readonly)
static void static void
mapping_dealloc(ImagingMapperObject* mapper) mapping_dealloc(ImagingMapperObject* mapper)
{ {
#ifdef WIN32 #ifdef _WIN32
if (mapper->base != 0) if (mapper->base != 0)
UnmapViewOfFile(mapper->base); UnmapViewOfFile(mapper->base);
if (mapper->hMap != (HANDLE)-1) if (mapper->hMap != (HANDLE)-1)

View File

@ -205,25 +205,31 @@ class pil_build_ext(build_ext):
# darwin ports installation directories # darwin ports installation directories
_add_directory(library_dirs, "/opt/local/lib") _add_directory(library_dirs, "/opt/local/lib")
_add_directory(include_dirs, "/opt/local/include") _add_directory(include_dirs, "/opt/local/include")
# if homebrew is installed, use its lib and include directories # if Homebrew is installed, use its lib and include directories
import subprocess import subprocess
try: try:
prefix = subprocess.check_output(['brew', '--prefix']) prefix = subprocess.check_output(['brew', '--prefix']).strip()
if prefix:
prefix = prefix.strip()
_add_directory(library_dirs, os.path.join(prefix, 'lib'))
_add_directory(include_dirs, os.path.join(prefix, 'include'))
# freetype2 is a key-only brew under opt/
_add_directory(library_dirs, os.path.join(prefix, 'opt', 'freetype', 'lib'))
_add_directory(include_dirs, os.path.join(prefix, 'opt', 'freetype', 'include'))
except: except:
pass # homebrew not installed # Homebrew not installed
prefix = None
# freetype2 ships with X11 (after homebrew, so that homebrew freetype is preferred)
_add_directory(library_dirs, "/usr/X11/lib") ft_prefix = None
_add_directory(include_dirs, "/usr/X11/include")
if prefix:
# add Homebrew's include and lib directories
_add_directory(library_dirs, os.path.join(prefix, 'lib'))
_add_directory(include_dirs, os.path.join(prefix, 'include'))
ft_prefix = os.path.join(prefix, 'opt', 'freetype')
if ft_prefix and os.path.isdir(ft_prefix):
# freetype might not be linked into Homebrew's prefix
_add_directory(library_dirs, os.path.join(ft_prefix, 'lib'))
_add_directory(include_dirs, os.path.join(ft_prefix, 'include'))
else:
# fall back to freetype from XQuartz if Homebrew's freetype is missing
_add_directory(library_dirs, "/usr/X11/lib")
_add_directory(include_dirs, "/usr/X11/include")
elif sys.platform.startswith("linux"): elif sys.platform.startswith("linux"):
arch_tp = (plat.processor(), plat.architecture()[0]) arch_tp = (plat.processor(), plat.architecture()[0])