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:
fast_finish: true
allow_failures:
- python: nightly
include:
- python: "pypy"
- python: "pypy3"
- python: '3.6'
- python: '2.7'
- env: DOCKER="alpine"
- env: DOCKER="arch" # contains PyQt5
- env: DOCKER="ubuntu-trusty-x86"
- 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: '3.5'
- python: '3.4'
- python: '3.3'
- python: 'nightly'
dist: trusty

View File

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

View File

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

View File

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

View File

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

View File

@ -2036,7 +2036,7 @@ def _check_size(size):
if len(size) != 2:
raise ValueError("Size must be a tuple of length 2")
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

View File

@ -31,7 +31,6 @@
#
import numbers
import warnings
from . import Image, ImageColor
from ._util import isStringType
@ -87,20 +86,6 @@ class ImageDraw(object):
self.fill = 0
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):
"""Get the current default 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
`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"),
(ARTIST, "artist"),
(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:
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.
"""
import subprocess
with open(os.devnull, 'w') as f:
with open(os.devnull, 'wb') as f:
try:
subprocess.Popen(cmd, stdout=f, stderr=subprocess.STDOUT).wait()
subprocess.call(cmd, stdout=f, stderr=subprocess.STDOUT)
except OSError:
return False
return True
def djpeg_available():
return command_succeeds(['djpeg', '--help'])
return command_succeeds(['djpeg', '-version'])
def cjpeg_available():
return command_succeeds(['cjpeg', '--help'])
return command_succeeds(['cjpeg', '-version'])
def netpbm_available():
return (command_succeeds(["ppmquant", "--help"]) and
command_succeeds(["ppmtogif", "--help"]))
return (command_succeeds(["ppmquant", "--version"]) and
command_succeeds(["ppmtogif", "--version"]))
def imagemagick_available():
@ -253,6 +253,12 @@ if sys.platform == 'win32':
else:
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):
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[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):
# Act
im = Image.open("Tests/images/hopper_lzw.tif")
@ -480,12 +461,12 @@ class TestFileTiff(PillowTestCase):
# however does.
im = Image.new('RGB', (1, 1))
im.info['icc_profile'] = 'Dummy value'
# Try save-load round trip to make sure both handle icc_profile.
tmpfile = self.tempfile('temp.tif')
im.save(tmpfile, 'TIFF', compression='raw')
reloaded = Image.open(tmpfile)
self.assertEqual(b'Dummy value', reloaded.info['icc_profile'])

View File

@ -20,10 +20,10 @@ class TestImageConvert(PillowTestCase):
convert(im, mode)
# Check 0
im = Image.new(mode, (0,0))
im = Image.new(mode, (0, 0))
for mode in modes:
convert(im, mode)
def test_default(self):
im = hopper("P")
@ -137,6 +137,77 @@ class TestImageConvert(PillowTestCase):
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__':
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 PIL import ImageQt
@unittest.skipIf(ImageQt.qt_version == '5' and distro() == 'arch',
"Topixmap fails on Arch + QT5")
class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase):
def roundtrip(self, expected):

View File

@ -1,7 +1,7 @@
from helper import unittest, PillowTestCase, hopper
from test_imageqt import PillowQtTestCase
from PIL import ImageQt
from PIL import ImageQt, Image
if ImageQt.qt_is_installed:
@ -9,38 +9,66 @@ if ImageQt.qt_is_installed:
try:
from PyQt5 import QtGui
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication
QT_VERSION = 5
except (ImportError, RuntimeError):
try:
from PyQt4 import QtGui
from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, QApplication
QT_VERSION = 4
except (ImportError, RuntimeError):
from PySide import QtGui
from PySide.QtGui import QWidget, QHBoxLayout, QLabel, QApplication
QT_VERSION = 4
class TestToQImage(PillowQtTestCase, PillowTestCase):
def test_sanity(self):
PillowQtTestCase.setUp(self)
for mode in ('1', 'RGB', 'RGBA', 'L', 'P'):
data = ImageQt.toqimage(hopper(mode))
for mode in ('RGB', 'RGBA', 'L', 'P', '1'):
src = hopper(mode)
data = ImageQt.toqimage(src)
self.assertIsInstance(data, QImage)
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
tempfile = self.tempfile('temp_{}.png'.format(mode))
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):
PillowQtTestCase.setUp(self)
app = QtGui.QApplication([])
app = QApplication([])
ex = Example()
assert(app) # Silence warning
assert(ex) # Silence warning
if ImageQt.qt_is_installed:
class Example(QtGui.QWidget):
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
@ -51,9 +79,9 @@ if ImageQt.qt_is_installed:
pixmap1 = QtGui.QPixmap.fromImage(qimage)
hbox = QtGui.QHBoxLayout(self)
hbox = QHBoxLayout(self)
lbl = QtGui.QLabel(self)
lbl = QLabel(self)
# Segfault in the problem
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 PIL import ImageQt
@ -9,6 +9,8 @@ if ImageQt.qt_is_installed:
class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase):
@unittest.skipIf(ImageQt.qt_version == '5' and distro() == 'arch',
"Topixmap fails on Arch + QT5")
def test_sanity(self):
PillowQtTestCase.setUp(self)

View File

@ -44,14 +44,6 @@ class TestImageDraw(PillowTestCase):
draw.polygon(list(range(100)))
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):
im = Image.open("Tests/images/chi.gif")

View File

@ -204,6 +204,14 @@ class TestNumpy(PillowTestCase):
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__':
unittest.main()

4
map.c
View File

@ -342,7 +342,7 @@ PyImaging_MapBuffer(PyObject* self, PyObject* args)
stride = xsize * 4;
}
if (ysize > INT_MAX / stride) {
if (stride > 0 && ysize > INT_MAX / stride) {
PyErr_SetString(PyExc_MemoryError, "Integer overflow in ysize");
return NULL;
}
@ -352,7 +352,7 @@ PyImaging_MapBuffer(PyObject* self, PyObject* args)
if (offset > PY_SSIZE_T_MAX - size) {
PyErr_SetString(PyExc_MemoryError, "Integer overflow in offset");
return NULL;
}
}
/* check buffer size */
if (PyImaging_GetBuffer(target, &view) < 0)

View File

@ -84,10 +84,6 @@ def _find_library_file(self, library):
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):
return any(
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)
def _relbuild(*args):
return _relpath('build', *args)
build_dir = _relpath('build')
inc_dir = _relpath('depends')