Merge branch 'master' into box-in-thumbnail

# Conflicts:
#	docs/releasenotes/7.0.0.rst
This commit is contained in:
Alexander 2019-12-19 16:36:37 +03:00
commit b112e55070
18 changed files with 61 additions and 18 deletions

View File

@ -5,6 +5,9 @@ Changelog (Pillow)
7.0.0 (unreleased) 7.0.0 (unreleased)
------------------ ------------------
- Better thumbnail aspect ratio preservation #4256
[homm]
- Add La mode packing and unpacking #4248 - Add La mode packing and unpacking #4248
[homm] [homm]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -754,7 +754,7 @@ class TestFileGif(PillowTestCase):
def test_getdata(self): def test_getdata(self):
# test getheader/getdata against legacy values # test getheader/getdata against legacy values
# Create a 'P' image with holes in the palette # Create a 'P' image with holes in the palette
im = Image._wedge().resize((16, 16)) im = Image._wedge().resize((16, 16), Image.NEAREST)
im.putpalette(ImagePalette.ImagePalette("RGB")) im.putpalette(ImagePalette.ImagePalette("RGB"))
im.info = {"background": 0} im.info = {"background": 0}

View File

@ -1,3 +1,5 @@
from PIL import Image
from .helper import PillowTestCase, hopper from .helper import PillowTestCase, hopper
@ -13,7 +15,7 @@ class TestImageGetData(PillowTestCase):
def test_roundtrip(self): def test_roundtrip(self):
def getdata(mode): def getdata(mode):
im = hopper(mode).resize((32, 30)) im = hopper(mode).resize((32, 30), Image.NEAREST)
data = im.getdata() data = im.getdata()
return data[0], len(data), len(list(data)) return data[0], len(data), len(list(data))

View File

@ -212,6 +212,11 @@ class TestImagingCoreResampleAccuracy(PillowTestCase):
for channel in case.split(): for channel in case.split():
self.check_case(channel, self.make_sample(data, (12, 12))) self.check_case(channel, self.make_sample(data, (12, 12)))
def test_box_filter_correct_range(self):
im = Image.new("RGB", (8, 8), "#1688ff").resize((100, 100), Image.BOX)
ref = Image.new("RGB", (100, 100), "#1688ff")
self.assert_image_equal(im, ref)
class CoreResampleConsistencyTest(PillowTestCase): class CoreResampleConsistencyTest(PillowTestCase):
def make_case(self, mode, fill): def make_case(self, mode, fill):

View File

@ -150,3 +150,12 @@ class TestImageResize(PillowTestCase):
# Test unknown resampling filter # Test unknown resampling filter
with hopper() as im: with hopper() as im:
self.assertRaises(ValueError, im.resize, (10, 10), "unknown") self.assertRaises(ValueError, im.resize, (10, 10), "unknown")
def test_default_filter(self):
for mode in "L", "RGB", "I", "F":
im = hopper(mode)
self.assertEqual(im.resize((20, 20), Image.BICUBIC), im.resize((20, 20)))
for mode in "1", "P":
im = hopper(mode)
self.assertEqual(im.resize((20, 20), Image.NEAREST), im.resize((20, 20)))

View File

@ -43,6 +43,11 @@ class TestImageThumbnail(PillowTestCase):
im.thumbnail((33, 33)) im.thumbnail((33, 33))
self.assertEqual(im.size, (21, 33)) # ratio is 0.6363636364 self.assertEqual(im.size, (21, 33)) # ratio is 0.6363636364
def test_float(self):
im = Image.new("L", (128, 128))
im.thumbnail((99.9, 99.9))
self.assertEqual(im.size, (100, 100))
def test_no_resize(self): def test_no_resize(self):
# Check that draft() can resize the image to the destination size # Check that draft() can resize the image to the destination size
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:

View File

@ -159,7 +159,8 @@ class TestImageDraw(PillowTestCase):
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
with Image.open("Tests/images/pil123rgba.png").resize((50, 50)) as small: with Image.open("Tests/images/pil123rgba.png") as small:
small = small.resize((50, 50), Image.NEAREST)
# Act # Act
draw.bitmap((10, 10), small) draw.bitmap((10, 10), small)

View File

@ -24,7 +24,7 @@ class TestImageFile(PillowTestCase):
def test_parser(self): def test_parser(self):
def roundtrip(format): def roundtrip(format):
im = hopper("L").resize((1000, 1000)) im = hopper("L").resize((1000, 1000), Image.NEAREST)
if format in ("MSP", "XBM"): if format in ("MSP", "XBM"):
im = im.convert("1") im = im.convert("1")

View File

@ -47,6 +47,17 @@ Setting the size of TIFF images
Setting the size of a TIFF image directly (eg. ``im.size = (256, 256)``) throws Setting the size of a TIFF image directly (eg. ``im.size = (256, 256)``) throws
an error. Use ``Image.resize`` instead. an error. Use ``Image.resize`` instead.
Default resampling filter
^^^^^^^^^^^^^^^^^^^^^^^^^
The default resampling filter has been changed to the high-quality convolution
``Image.BICUBIC`` instead of ``Image.NEAREST``, for the :py:meth:`~PIL.Image.Image.resize`
method and the :py:meth:`~PIL.ImageOps.pad``, :py:meth:`~PIL.ImageOps.scale``
and :py:meth:`~PIL.ImageOps.fit`` functions.
``Image.NEAREST`` is still always used for images in "P" and "1" modes.
See :ref:`concept-filters` to learn the difference. In short,
``Image.NEAREST`` is a very fast filter, but simple and low-quality.
Image.draft() return value Image.draft() return value
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -106,3 +117,9 @@ Use instead:
with Image.open("hopper.png") as im: with Image.open("hopper.png") as im:
im.save("out.jpg") im.save("out.jpg")
Better thumbnail aspect ratio preservation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When calculating the new dimensions in ``Image.thumbnail``, round to the
nearest integer, instead of always rounding down.

View File

@ -1764,7 +1764,7 @@ class Image:
return m_im return m_im
def resize(self, size, resample=NEAREST, box=None): def resize(self, size, resample=BICUBIC, box=None):
""" """
Returns a resized copy of this image. Returns a resized copy of this image.
@ -1774,8 +1774,9 @@ class Image:
one of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BOX`, one of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BOX`,
:py:attr:`PIL.Image.BILINEAR`, :py:attr:`PIL.Image.HAMMING`, :py:attr:`PIL.Image.BILINEAR`, :py:attr:`PIL.Image.HAMMING`,
:py:attr:`PIL.Image.BICUBIC` or :py:attr:`PIL.Image.LANCZOS`. :py:attr:`PIL.Image.BICUBIC` or :py:attr:`PIL.Image.LANCZOS`.
If omitted, or if the image has mode "1" or "P", it is Default filter is :py:attr:`PIL.Image.BICUBIC`.
set :py:attr:`PIL.Image.NEAREST`. If the image has mode "1" or "P", it is
always set to :py:attr:`PIL.Image.NEAREST`.
See: :ref:`concept-filters`. See: :ref:`concept-filters`.
:param box: An optional 4-tuple of floats giving the region :param box: An optional 4-tuple of floats giving the region
of the source image which should be scaled. of the source image which should be scaled.
@ -1845,7 +1846,7 @@ class Image:
environment), or :py:attr:`PIL.Image.BICUBIC` environment), or :py:attr:`PIL.Image.BICUBIC`
(cubic spline interpolation in a 4x4 environment). (cubic spline interpolation in a 4x4 environment).
If omitted, or if the image has mode "1" or "P", it is If omitted, or if the image has mode "1" or "P", it is
set :py:attr:`PIL.Image.NEAREST`. See :ref:`concept-filters`. set to :py:attr:`PIL.Image.NEAREST`. See :ref:`concept-filters`.
:param expand: Optional expansion flag. If true, expands the output :param expand: Optional expansion flag. If true, expands the output
image to make it large enough to hold the entire rotated image. image to make it large enough to hold the entire rotated image.
If false or omitted, make the output image the same size as the If false or omitted, make the output image the same size as the
@ -2141,10 +2142,10 @@ class Image:
x, y = self.size x, y = self.size
if x > size[0]: if x > size[0]:
y = max(round(y * size[0] / x), 1) y = max(round(y * size[0] / x), 1)
x = size[0] x = round(size[0])
if y > size[1]: if y > size[1]:
x = max(round(x * size[1] / y), 1) x = max(round(x * size[1] / y), 1)
y = size[1] y = round(size[1])
size = x, y size = x, y
box = None box = None

View File

@ -221,7 +221,7 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi
return _lut(image, red + green + blue) return _lut(image, red + green + blue)
def pad(image, size, method=Image.NEAREST, color=None, centering=(0.5, 0.5)): def pad(image, size, method=Image.BICUBIC, color=None, centering=(0.5, 0.5)):
""" """
Returns a sized and padded version of the image, expanded to fill the Returns a sized and padded version of the image, expanded to fill the
requested aspect ratio and size. requested aspect ratio and size.
@ -230,7 +230,7 @@ def pad(image, size, method=Image.NEAREST, color=None, centering=(0.5, 0.5)):
:param size: The requested output size in pixels, given as a :param size: The requested output size in pixels, given as a
(width, height) tuple. (width, height) tuple.
:param method: What resampling method to use. Default is :param method: What resampling method to use. Default is
:py:attr:`PIL.Image.NEAREST`. :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`.
:param color: The background color of the padded image. :param color: The background color of the padded image.
:param centering: Control the position of the original image within the :param centering: Control the position of the original image within the
padded version. padded version.
@ -280,7 +280,7 @@ def crop(image, border=0):
return image.crop((left, top, image.size[0] - right, image.size[1] - bottom)) return image.crop((left, top, image.size[0] - right, image.size[1] - bottom))
def scale(image, factor, resample=Image.NEAREST): def scale(image, factor, resample=Image.BICUBIC):
""" """
Returns a rescaled image by a specific factor given in parameter. Returns a rescaled image by a specific factor given in parameter.
A factor greater than 1 expands the image, between 0 and 1 contracts the A factor greater than 1 expands the image, between 0 and 1 contracts the
@ -288,8 +288,8 @@ def scale(image, factor, resample=Image.NEAREST):
:param image: The image to rescale. :param image: The image to rescale.
:param factor: The expansion factor, as a float. :param factor: The expansion factor, as a float.
:param resample: An optional resampling filter. Same values possible as :param resample: What resampling method to use. Default is
in the PIL.Image.resize function. :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`.
:returns: An :py:class:`~PIL.Image.Image` object. :returns: An :py:class:`~PIL.Image.Image` object.
""" """
if factor == 1: if factor == 1:
@ -363,7 +363,7 @@ def expand(image, border=0, fill=0):
return out return out
def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)): def fit(image, size, method=Image.BICUBIC, bleed=0.0, centering=(0.5, 0.5)):
""" """
Returns a sized and cropped version of the image, cropped to the Returns a sized and cropped version of the image, cropped to the
requested aspect ratio and size. requested aspect ratio and size.
@ -374,7 +374,7 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
:param size: The requested output size in pixels, given as a :param size: The requested output size in pixels, given as a
(width, height) tuple. (width, height) tuple.
:param method: What resampling method to use. Default is :param method: What resampling method to use. Default is
:py:attr:`PIL.Image.NEAREST`. :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`.
:param bleed: Remove a border around the outside of the image from all :param bleed: Remove a border around the outside of the image from all
four edges. The value is a decimal percentage (use 0.01 for four edges. The value is a decimal percentage (use 0.01 for
one percent). The default value is 0 (no border). one percent). The default value is 0 (no border).

View File

@ -13,7 +13,7 @@ struct filter {
static inline double box_filter(double x) static inline double box_filter(double x)
{ {
if (x >= -0.5 && x < 0.5) if (x > -0.5 && x <= 0.5)
return 1.0; return 1.0;
return 0.0; return 0.0;
} }