mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-04-27 20:43:43 +03:00
Merge pull request #4231 from uploadcare/box-in-thumbnail
Fix thumbnail geometry when DCT scaling is used
This commit is contained in:
commit
b5d06baa5f
|
@ -13,7 +13,11 @@ class TestImageDraft(PillowTestCase):
|
||||||
im = Image.new(in_mode, in_size)
|
im = Image.new(in_mode, in_size)
|
||||||
data = tostring(im, "JPEG")
|
data = tostring(im, "JPEG")
|
||||||
im = fromstring(data)
|
im = fromstring(data)
|
||||||
im.draft(req_mode, req_size)
|
mode, box = im.draft(req_mode, req_size)
|
||||||
|
scale, _ = im.decoderconfig
|
||||||
|
self.assertEqual(box[:2], (0, 0))
|
||||||
|
self.assertTrue((im.width - scale) < box[2] <= im.width)
|
||||||
|
self.assertTrue((im.height - scale) < box[3] <= im.height)
|
||||||
return im
|
return im
|
||||||
|
|
||||||
def test_size(self):
|
def test_size(self):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase, hopper
|
from .helper import PillowTestCase, fromstring, hopper, tostring
|
||||||
|
|
||||||
|
|
||||||
class TestImageThumbnail(PillowTestCase):
|
class TestImageThumbnail(PillowTestCase):
|
||||||
|
@ -58,3 +58,15 @@ class TestImageThumbnail(PillowTestCase):
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
im.thumbnail((64, 64))
|
im.thumbnail((64, 64))
|
||||||
self.assertEqual(im.size, (64, 64))
|
self.assertEqual(im.size, (64, 64))
|
||||||
|
|
||||||
|
def test_DCT_scaling_edges(self):
|
||||||
|
# Make an image with red borders and size (N * 8) + 1 to cross DCT grid
|
||||||
|
im = Image.new("RGB", (257, 257), "red")
|
||||||
|
im.paste(Image.new("RGB", (235, 235)), (11, 11))
|
||||||
|
|
||||||
|
thumb = fromstring(tostring(im, "JPEG", quality=99, subsampling=0))
|
||||||
|
thumb.thumbnail((32, 32), Image.BICUBIC)
|
||||||
|
|
||||||
|
ref = im.resize((32, 32), Image.BICUBIC)
|
||||||
|
# This is still JPEG, some error is present. Without the fix it is 11.5
|
||||||
|
self.assert_image_similar(thumb, ref, 1.5)
|
||||||
|
|
|
@ -58,6 +58,20 @@ and :py:meth:`~PIL.ImageOps.fit` functions.
|
||||||
See :ref:`concept-filters` to learn the difference. In short,
|
See :ref:`concept-filters` to learn the difference. In short,
|
||||||
``Image.NEAREST`` is a very fast filter, but simple and low-quality.
|
``Image.NEAREST`` is a very fast filter, but simple and low-quality.
|
||||||
|
|
||||||
|
Image.draft() return value
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If the :py:meth:`~PIL.Image.Image.draft` method has no effect, it returns ``None``.
|
||||||
|
If it does have an effect, then it previously returned the image itself.
|
||||||
|
However, unlike other `chain methods`_, :py:meth:`~PIL.Image.Image.draft` does not
|
||||||
|
return a modified version of the image, but modifies it in-place. So instead, if
|
||||||
|
:py:meth:`~PIL.Image.Image.draft` has an effect, Pillow will now return a tuple
|
||||||
|
of the image mode and a co-ordinate box. The box is the original coordinates in the
|
||||||
|
bounds of resulting image. This may be useful in a subsequent
|
||||||
|
:py:meth:`~PIL.Image.Image.resize` call.
|
||||||
|
|
||||||
|
.. _chain methods: https://en.wikipedia.org/wiki/Method_chaining
|
||||||
|
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
@ -104,8 +118,12 @@ 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
|
Better thumbnail geometry
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
When calculating the new dimensions in :py:meth:`~PIL.Image.Image.thumbnail`,
|
When calculating the new dimensions in :py:meth:`~PIL.Image.Image.thumbnail`,
|
||||||
round to the nearest integer, instead of always rounding down.
|
round to the nearest integer, instead of always rounding down.
|
||||||
|
This better preserves the original aspect ratio.
|
||||||
|
|
||||||
|
When the image width or height is not divisible by 8 the last row and column
|
||||||
|
in the image get the correct weight after JPEG DCT scaling.
|
||||||
|
|
|
@ -1126,11 +1126,14 @@ class Image:
|
||||||
"""
|
"""
|
||||||
Configures the image file loader so it returns a version of the
|
Configures the image file loader so it returns a version of the
|
||||||
image that as closely as possible matches the given mode and
|
image that as closely as possible matches the given mode and
|
||||||
size. For example, you can use this method to convert a color
|
size. For example, you can use this method to convert a color
|
||||||
JPEG to greyscale while loading it.
|
JPEG to greyscale while loading it.
|
||||||
|
|
||||||
|
If any changes are made, returns a tuple with the chosen ``mode`` and
|
||||||
|
``box`` with coordinates of the original image within the altered one.
|
||||||
|
|
||||||
Note that this method modifies the :py:class:`~PIL.Image.Image` object
|
Note that this method modifies the :py:class:`~PIL.Image.Image` object
|
||||||
in place. If the image has already been loaded, this method has no
|
in place. If the image has already been loaded, this method has no
|
||||||
effect.
|
effect.
|
||||||
|
|
||||||
Note: This method is not implemented for most images. It is
|
Note: This method is not implemented for most images. It is
|
||||||
|
@ -2143,14 +2146,17 @@ class Image:
|
||||||
x = max(round(x * size[1] / y), 1)
|
x = max(round(x * size[1] / y), 1)
|
||||||
y = round(size[1])
|
y = round(size[1])
|
||||||
size = x, y
|
size = x, y
|
||||||
|
box = None
|
||||||
|
|
||||||
if size == self.size:
|
if size == self.size:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.draft(None, size)
|
res = self.draft(None, size)
|
||||||
|
if res is not None:
|
||||||
|
box = res[1]
|
||||||
|
|
||||||
if self.size != size:
|
if self.size != size:
|
||||||
im = self.resize(size, resample)
|
im = self.resize(size, resample, box=box)
|
||||||
|
|
||||||
self.im = im.im
|
self.im = im.im
|
||||||
self._size = size
|
self._size = size
|
||||||
|
|
|
@ -122,11 +122,6 @@ class ImageFile(Image.Image):
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def draft(self, mode, size):
|
|
||||||
"""Set draft mode"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_format_mimetype(self):
|
def get_format_mimetype(self):
|
||||||
if self.custom_mimetype:
|
if self.custom_mimetype:
|
||||||
return self.custom_mimetype
|
return self.custom_mimetype
|
||||||
|
|
|
@ -413,7 +413,8 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
return
|
return
|
||||||
|
|
||||||
d, e, o, a = self.tile[0]
|
d, e, o, a = self.tile[0]
|
||||||
scale = 0
|
scale = 1
|
||||||
|
original_size = self.size
|
||||||
|
|
||||||
if a[0] == "RGB" and mode in ["L", "YCbCr"]:
|
if a[0] == "RGB" and mode in ["L", "YCbCr"]:
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -436,7 +437,8 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
self.tile = [(d, e, o, a)]
|
self.tile = [(d, e, o, a)]
|
||||||
self.decoderconfig = (scale, 0)
|
self.decoderconfig = (scale, 0)
|
||||||
|
|
||||||
return self
|
box = (0, 0, original_size[0] / float(scale), original_size[1] / float(scale))
|
||||||
|
return (self.mode, box)
|
||||||
|
|
||||||
def load_djpeg(self):
|
def load_djpeg(self):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user