Merge branch 'master' into cleanup-test_image_toqimage

This commit is contained in:
Hugo 2017-02-22 13:02:26 +02:00 committed by GitHub
commit c97229466c
21 changed files with 184 additions and 101 deletions

View File

@ -9,22 +9,21 @@ notifications:
matrix: matrix:
fast_finish: true fast_finish: true
allow_failures:
- python: nightly
include: include:
- python: "pypy" - python: "pypy"
- python: "pypy3" - python: "pypy3"
- python: '3.6' - python: '3.6'
- python: '2.7' - python: '2.7'
- env: DOCKER="alpine" - env: DOCKER="alpine"
- env: DOCKER="arch" # contains PyQt5
- env: DOCKER="ubuntu-trusty-x86" - env: DOCKER="ubuntu-trusty-x86"
- env: DOCKER="ubuntu-xenial-amd64" - env: DOCKER="ubuntu-xenial-amd64"
- env: DOCKER="ubuntu-precise-amd64" - env: DOCKER="ubuntu-precise-amd64"
- env: DOCKER="debian-stretch-x86"
- python: "2.7_with_system_site_packages" # For PyQt4 - python: "2.7_with_system_site_packages" # For PyQt4
- python: '3.5' - python: '3.5'
- python: '3.4' - python: '3.4'
- python: '3.3' - python: '3.3'
- python: 'nightly'
dist: trusty dist: trusty

View File

@ -4,6 +4,33 @@ Changelog (Pillow)
4.1.0 (unreleased) 4.1.0 (unreleased)
------------------ ------------------
- Removed use of spaces in TIFF kwargs names, deprecated in 2.7 #1390
[radarhere]
- Removed deprecated ImageDraw setink, setfill, setfont methods #2220
[jdufresne]
- Send unwanted subprocess output to /dev/null #2253
[jdufresne]
- Fix division by zero when creating 0x0 image from numpy array #2419
[hugovk]
- Test: Added matrix convert tests #2381
[hugovk]
- Replaced broken URL to partners.adobe.com #2413
[radarhere]
- Removed unused private functions in setup.py and build_dep.py #2414
[radarhere]
- Test: Fixed Qt tests for QT5, Arch, and saving 1 bit PNG #2394
[wiredfool]
- Test: docker builds for Arch and Debian Stretch #2394
[wiredfool]
- Updated libwebp to 0.6.0 on appveyor #2395 - Updated libwebp to 0.6.0 on appveyor #2395
[radarhere] [radarhere]

View File

@ -22,6 +22,7 @@
import re import re
import io import io
import os
import sys import sys
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i32le as i32, o32le as o32 from ._binary import i32le as i32, o32le as o32
@ -57,8 +58,8 @@ def has_ghostscript():
if not sys.platform.startswith('win'): if not sys.platform.startswith('win'):
import subprocess import subprocess
try: try:
gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE) with open(os.devnull, 'wb') as devnull:
gs.stdout.read() subprocess.check_call(['gs', '--version'], stdout=devnull)
return True return True
except OSError: except OSError:
# no ghostscript # no ghostscript
@ -137,12 +138,8 @@ def Ghostscript(tile, size, fp, scale=1):
# push data through ghostscript # push data through ghostscript
try: try:
gs = subprocess.Popen(command, stdin=subprocess.PIPE, with open(os.devnull, 'w+b') as devnull:
stdout=subprocess.PIPE) subprocess.check_call(command, stdin=devnull, stdout=devnull)
gs.stdin.close()
status = gs.wait()
if status:
raise IOError("gs failed (status %d)" % status)
im = Image.open(outfile) im = Image.open(outfile)
im.load() im.load()
finally: finally:
@ -321,7 +318,7 @@ class EpsImageFile(ImageFile.ImageFile):
# EPS can contain binary data # EPS can contain binary data
# or start directly with latin coding # or start directly with latin coding
# more info see: # more info see:
# http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
offset = i32(s[4:8]) offset = i32(s[4:8])
length = i32(s[8:12]) length = i32(s[8:12])
else: else:

View File

@ -519,18 +519,17 @@ def _save_netpbm(im, fp, filename):
with open(filename, 'wb') as f: with open(filename, 'wb') as f:
if im.mode != "RGB": if im.mode != "RGB":
with tempfile.TemporaryFile() as stderr: with open(os.devnull, 'wb') as devnull:
check_call(["ppmtogif", file], stdout=f, stderr=stderr) check_call(["ppmtogif", file], stdout=f, stderr=devnull)
else: else:
# Pipe ppmquant output into ppmtogif # Pipe ppmquant output into ppmtogif
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename) # "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
quant_cmd = ["ppmquant", "256", file] quant_cmd = ["ppmquant", "256", file]
togif_cmd = ["ppmtogif"] togif_cmd = ["ppmtogif"]
with tempfile.TemporaryFile() as stderr: with open(os.devnull, 'wb') as devnull:
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr) quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
with tempfile.TemporaryFile() as stderr:
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout,
stdout=f, stderr=stderr) stdout=f, stderr=devnull)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits # Allow ppmquant to receive SIGPIPE if ppmtogif exits
quant_proc.stdout.close() quant_proc.stdout.close()

View File

@ -329,8 +329,8 @@ def _save(im, fp, filename):
from subprocess import Popen, PIPE, CalledProcessError from subprocess import Popen, PIPE, CalledProcessError
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
with tempfile.TemporaryFile() as stderr: with open(os.devnull, 'wb') as devnull:
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr) convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=devnull)
convert_proc.stdout.close() convert_proc.stdout.close()

View File

@ -2036,7 +2036,7 @@ def _check_size(size):
if len(size) != 2: if len(size) != 2:
raise ValueError("Size must be a tuple of length 2") raise ValueError("Size must be a tuple of length 2")
if size[0] < 0 or size[1] < 0: if size[0] < 0 or size[1] < 0:
raise ValueError("Width and Height must be => 0") raise ValueError("Width and height must be >= 0")
return True return True

View File

@ -31,7 +31,6 @@
# #
import numbers import numbers
import warnings
from . import Image, ImageColor from . import Image, ImageColor
from ._util import isStringType from ._util import isStringType
@ -87,20 +86,6 @@ class ImageDraw(object):
self.fill = 0 self.fill = 0
self.font = None self.font = None
def setink(self, ink):
raise NotImplementedError("setink() has been removed. " +
"Please use keyword arguments instead.")
def setfill(self, onoff):
raise NotImplementedError("setfill() has been removed. " +
"Please use keyword arguments instead.")
def setfont(self, font):
warnings.warn("setfont() is deprecated. " +
"Please set the attribute directly instead.")
# compatibility
self.font = font
def getfont(self): def getfont(self):
"""Get the current default font.""" """Get the current default font."""
if not self.font: if not self.font:

View File

@ -62,7 +62,7 @@ The tables format between im.quantization and quantization in presets differ in
You can convert the dict format to the preset format with the You can convert the dict format to the preset format with the
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function. `JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
Libjpeg ref.: http://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
""" """

View File

@ -1377,11 +1377,6 @@ def _save(im, fp, filename):
(DATE_TIME, "date_time"), (DATE_TIME, "date_time"),
(ARTIST, "artist"), (ARTIST, "artist"),
(COPYRIGHT, "copyright")]: (COPYRIGHT, "copyright")]:
name_with_spaces = name.replace("_", " ")
if "_" in name and name_with_spaces in im.encoderinfo:
warnings.warn("%r is deprecated; use %r instead" %
(name_with_spaces, name), DeprecationWarning)
ifd[key] = im.encoderinfo[name.replace("_", " ")]
if name in im.encoderinfo: if name in im.encoderinfo:
ifd[key] = im.encoderinfo[name] ifd[key] = im.encoderinfo[name]

View File

@ -218,25 +218,25 @@ def command_succeeds(cmd):
command succeeds, or False if an OSError was raised by subprocess.Popen. command succeeds, or False if an OSError was raised by subprocess.Popen.
""" """
import subprocess import subprocess
with open(os.devnull, 'w') as f: with open(os.devnull, 'wb') as f:
try: try:
subprocess.Popen(cmd, stdout=f, stderr=subprocess.STDOUT).wait() subprocess.call(cmd, stdout=f, stderr=subprocess.STDOUT)
except OSError: except OSError:
return False return False
return True return True
def djpeg_available(): def djpeg_available():
return command_succeeds(['djpeg', '--help']) return command_succeeds(['djpeg', '-version'])
def cjpeg_available(): def cjpeg_available():
return command_succeeds(['cjpeg', '--help']) return command_succeeds(['cjpeg', '-version'])
def netpbm_available(): def netpbm_available():
return (command_succeeds(["ppmquant", "--help"]) and return (command_succeeds(["ppmquant", "--version"]) and
command_succeeds(["ppmtogif", "--help"])) command_succeeds(["ppmtogif", "--version"]))
def imagemagick_available(): def imagemagick_available():
@ -253,6 +253,12 @@ if sys.platform == 'win32':
else: else:
IMCONVERT = 'convert' IMCONVERT = 'convert'
def distro():
if os.path.exists('/etc/os-release'):
with open('/etc/os-release', 'r') as f:
for line in f:
if 'ID=' in line:
return line.strip().split('=')[1]
class cached_property(object): class cached_property(object):
def __init__(self, func): def __init__(self, func):

BIN
Tests/images/hopper-XYZ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -417,25 +417,6 @@ class TestFileTiff(PillowTestCase):
self.assertEqual(im.tag_v2[X_RESOLUTION], 72) self.assertEqual(im.tag_v2[X_RESOLUTION], 72)
self.assertEqual(im.tag_v2[Y_RESOLUTION], 36) self.assertEqual(im.tag_v2[Y_RESOLUTION], 36)
def test_deprecation_warning_with_spaces(self):
kwargs = {'resolution unit': 'inch',
'x resolution': 36,
'y resolution': 72}
filename = self.tempfile("temp.tif")
self.assert_warning(DeprecationWarning,
lambda: hopper("RGB").save(filename, **kwargs))
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
im = Image.open(filename)
# legacy interface
self.assertEqual(im.tag[X_RESOLUTION][0][0], 36)
self.assertEqual(im.tag[Y_RESOLUTION][0][0], 72)
# v2 interface
self.assertEqual(im.tag_v2[X_RESOLUTION], 36)
self.assertEqual(im.tag_v2[Y_RESOLUTION], 72)
def test_lzw(self): def test_lzw(self):
# Act # Act
im = Image.open("Tests/images/hopper_lzw.tif") im = Image.open("Tests/images/hopper_lzw.tif")

View File

@ -20,7 +20,7 @@ class TestImageConvert(PillowTestCase):
convert(im, mode) convert(im, mode)
# Check 0 # Check 0
im = Image.new(mode, (0,0)) im = Image.new(mode, (0, 0))
for mode in modes: for mode in modes:
convert(im, mode) convert(im, mode)
@ -137,6 +137,77 @@ class TestImageConvert(PillowTestCase):
self.assert_image_similar(alpha, comparable, 5) self.assert_image_similar(alpha, comparable, 5)
def test_matrix_illegal_conversion(self):
# Arrange
im = hopper('CMYK')
matrix = (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0)
self.assertNotEqual(im.mode, 'RGB')
# Act / Assert
self.assertRaises(ValueError,
lambda: im.convert(mode='CMYK', matrix=matrix))
def test_matrix_wrong_mode(self):
# Arrange
im = hopper('L')
matrix = (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0)
self.assertEqual(im.mode, 'L')
# Act / Assert
self.assertRaises(ValueError,
lambda: im.convert(mode='L', matrix=matrix))
def test_matrix_xyz(self):
def matrix_convert(mode):
# Arrange
im = hopper('RGB')
matrix = (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0)
self.assertEqual(im.mode, 'RGB')
# Act
# Convert an RGB image to the CIE XYZ colour space
converted_im = im.convert(mode=mode, matrix=matrix)
# Assert
self.assertEqual(converted_im.mode, mode)
self.assertEqual(converted_im.size, im.size)
target = Image.open('Tests/images/hopper-XYZ.png')
if converted_im.mode == 'RGB':
self.assert_image_similar(converted_im, target, 3)
else:
self.assert_image_similar(converted_im, target.split()[0], 1)
matrix_convert('RGB')
matrix_convert('L')
def test_matrix_identity(self):
# Arrange
im = hopper('RGB')
identity_matrix = (
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0)
self.assertEqual(im.mode, 'RGB')
# Act
# Convert with an identity matrix
converted_im = im.convert(mode='RGB', matrix=identity_matrix)
# Assert
# No change
self.assert_image_equal(converted_im, im)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,9 +1,10 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper, distro
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase
from PIL import ImageQt from PIL import ImageQt
@unittest.skipIf(ImageQt.qt_version == '5' and distro() == 'arch',
"Topixmap fails on Arch + QT5")
class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase): class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase):
def roundtrip(self, expected): def roundtrip(self, expected):

View File

@ -1,7 +1,7 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper
from test_imageqt import PillowQtTestCase from test_imageqt import PillowQtTestCase
from PIL import ImageQt from PIL import ImageQt, Image
if ImageQt.qt_is_installed: if ImageQt.qt_is_installed:
@ -9,38 +9,66 @@ if ImageQt.qt_is_installed:
try: try:
from PyQt5 import QtGui from PyQt5 import QtGui
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication
QT_VERSION = 5
except (ImportError, RuntimeError): except (ImportError, RuntimeError):
try: try:
from PyQt4 import QtGui from PyQt4 import QtGui
from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, QApplication
QT_VERSION = 4
except (ImportError, RuntimeError): except (ImportError, RuntimeError):
from PySide import QtGui from PySide import QtGui
from PySide.QtGui import QWidget, QHBoxLayout, QLabel, QApplication
QT_VERSION = 4
class TestToQImage(PillowQtTestCase, PillowTestCase): class TestToQImage(PillowQtTestCase, PillowTestCase):
def test_sanity(self): def test_sanity(self):
PillowQtTestCase.setUp(self) PillowQtTestCase.setUp(self)
for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): for mode in ('RGB', 'RGBA', 'L', 'P', '1'):
data = ImageQt.toqimage(hopper(mode)) src = hopper(mode)
data = ImageQt.toqimage(src)
self.assertIsInstance(data, QImage) self.assertIsInstance(data, QImage)
self.assertFalse(data.isNull()) self.assertFalse(data.isNull())
# reload directly from the qimage
rt = ImageQt.fromqimage(data)
if mode in ('L', 'P', '1'):
self.assert_image_equal(rt, src.convert('RGB'))
else:
self.assert_image_equal(rt, src)
if mode == '1':
# BW appears to not save correctly on QT4 and QT5
# kicks out errors on console:
# libpng warning: Invalid color type/bit depth combination in IHDR
# libpng error: Invalid IHDR data
continue
# Test saving the file # Test saving the file
tempfile = self.tempfile('temp_{}.png'.format(mode)) tempfile = self.tempfile('temp_{}.png'.format(mode))
data.save(tempfile) data.save(tempfile)
# Check that it actually worked.
reloaded = Image.open(tempfile)
# Gray images appear to come back in palette mode.
# They're roughly equivalent
if QT_VERSION == 4 and mode == 'L':
src = src.convert('P')
self.assert_image_equal(reloaded, src)
def test_segfault(self): def test_segfault(self):
PillowQtTestCase.setUp(self) PillowQtTestCase.setUp(self)
app = QtGui.QApplication([]) app = QApplication([])
ex = Example() ex = Example()
assert(app) # Silence warning assert(app) # Silence warning
assert(ex) # Silence warning assert(ex) # Silence warning
if ImageQt.qt_is_installed: if ImageQt.qt_is_installed:
class Example(QtGui.QWidget): class Example(QWidget):
def __init__(self): def __init__(self):
super(Example, self).__init__() super(Example, self).__init__()
@ -51,9 +79,9 @@ if ImageQt.qt_is_installed:
pixmap1 = QtGui.QPixmap.fromImage(qimage) pixmap1 = QtGui.QPixmap.fromImage(qimage)
hbox = QtGui.QHBoxLayout(self) hbox = QHBoxLayout(self)
lbl = QtGui.QLabel(self) lbl = QLabel(self)
# Segfault in the problem # Segfault in the problem
lbl.setPixmap(pixmap1.copy()) lbl.setPixmap(pixmap1.copy())

View File

@ -1,4 +1,4 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper, distro
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase
from PIL import ImageQt from PIL import ImageQt
@ -9,6 +9,8 @@ if ImageQt.qt_is_installed:
class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase):
@unittest.skipIf(ImageQt.qt_version == '5' and distro() == 'arch',
"Topixmap fails on Arch + QT5")
def test_sanity(self): def test_sanity(self):
PillowQtTestCase.setUp(self) PillowQtTestCase.setUp(self)

View File

@ -44,14 +44,6 @@ class TestImageDraw(PillowTestCase):
draw.polygon(list(range(100))) draw.polygon(list(range(100)))
draw.rectangle(list(range(4))) draw.rectangle(list(range(4)))
def test_removed_methods(self):
im = hopper()
draw = ImageDraw.Draw(im)
self.assertRaises(Exception, lambda: draw.setink(0))
self.assertRaises(Exception, lambda: draw.setfill(0))
def test_valueerror(self): def test_valueerror(self):
im = Image.open("Tests/images/chi.gif") im = Image.open("Tests/images/chi.gif")

View File

@ -204,6 +204,14 @@ class TestNumpy(PillowTestCase):
self.assertEqual(len(im.getdata()), len(arr)) self.assertEqual(len(im.getdata()), len(arr))
def test_zero_size(self):
# Shouldn't cause floating point exception
# See https://github.com/python-pillow/Pillow/issues/2259
im = Image.fromarray(numpy.empty((0, 0), dtype=numpy.uint8))
self.assertEqual(im.size, (0, 0))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

2
map.c
View File

@ -342,7 +342,7 @@ PyImaging_MapBuffer(PyObject* self, PyObject* args)
stride = xsize * 4; stride = xsize * 4;
} }
if (ysize > INT_MAX / stride) { if (stride > 0 && ysize > INT_MAX / stride) {
PyErr_SetString(PyExc_MemoryError, "Integer overflow in ysize"); PyErr_SetString(PyExc_MemoryError, "Integer overflow in ysize");
return NULL; return NULL;
} }

View File

@ -84,10 +84,6 @@ def _find_library_file(self, library):
return ret return ret
def _lib_include(root):
# map root to (root/lib, root/include)
return os.path.join(root, "lib"), os.path.join(root, "include")
def _cmd_exists(cmd): def _cmd_exists(cmd):
return any( return any(
os.access(os.path.join(path, cmd), os.X_OK) os.access(os.path.join(path, cmd), os.X_OK)

View File

@ -10,10 +10,6 @@ def _relpath(*args):
return os.path.join(os.getcwd(), *args) return os.path.join(os.getcwd(), *args)
def _relbuild(*args):
return _relpath('build', *args)
build_dir = _relpath('build') build_dir = _relpath('build')
inc_dir = _relpath('depends') inc_dir = _relpath('depends')