mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-06-27 16:33:15 +03:00
Merge branch 'main' into jpeg-app-segments
This commit is contained in:
commit
520bf6397f
|
@ -10,18 +10,25 @@ from .helper import assert_image_equal, assert_image_similar, hopper
|
||||||
|
|
||||||
class TestImageTransform:
|
class TestImageTransform:
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
im = Image.new("L", (100, 100))
|
im = hopper()
|
||||||
|
|
||||||
seq = tuple(range(10))
|
for transform in (
|
||||||
|
ImageTransform.AffineTransform((1, 0, 0, 0, 1, 0)),
|
||||||
transform = ImageTransform.AffineTransform(seq[:6])
|
ImageTransform.PerspectiveTransform((1, 0, 0, 0, 1, 0, 0, 0)),
|
||||||
im.transform((100, 100), transform)
|
ImageTransform.ExtentTransform((0, 0) + im.size),
|
||||||
transform = ImageTransform.ExtentTransform(seq[:4])
|
ImageTransform.QuadTransform(
|
||||||
im.transform((100, 100), transform)
|
(0, 0, 0, im.height, im.width, im.height, im.width, 0)
|
||||||
transform = ImageTransform.QuadTransform(seq[:8])
|
),
|
||||||
im.transform((100, 100), transform)
|
ImageTransform.MeshTransform(
|
||||||
transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])])
|
[
|
||||||
im.transform((100, 100), transform)
|
(
|
||||||
|
(0, 0) + im.size,
|
||||||
|
(0, 0, 0, im.height, im.width, im.height, im.width, 0),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
):
|
||||||
|
assert_image_equal(im, im.transform(im.size, transform))
|
||||||
|
|
||||||
def test_info(self):
|
def test_info(self):
|
||||||
comment = b"File written by Adobe Photoshop\xa8 4.0"
|
comment = b"File written by Adobe Photoshop\xa8 4.0"
|
||||||
|
|
|
@ -19,6 +19,11 @@ The :py:mod:`~PIL.ImageTransform` module contains implementations of
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: PerspectiveTransform
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
.. autoclass:: ExtentTransform
|
.. autoclass:: ExtentTransform
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
|
@ -33,6 +33,13 @@ When saving JPEG files, ``no_default_app_segments`` can now be set to ``True`` t
|
||||||
the image without default JFIF and Adobe application segments. The JFIF segment will
|
the image without default JFIF and Adobe application segments. The JFIF segment will
|
||||||
still be stored if ``dpi`` is also specified.
|
still be stored if ``dpi`` is also specified.
|
||||||
|
|
||||||
|
Added PerspectiveTransform
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:py:class:`~PIL.ImageTransform.PerspectiveTransform` has been added, meaning
|
||||||
|
that all of the :py:data:`~PIL.Image.Transform` values now have a corresponding
|
||||||
|
subclass of :py:class:`~PIL.ImageTransform.Transform`.
|
||||||
|
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,26 @@ class AffineTransform(Transform):
|
||||||
method = Image.Transform.AFFINE
|
method = Image.Transform.AFFINE
|
||||||
|
|
||||||
|
|
||||||
|
class PerspectiveTransform(Transform):
|
||||||
|
"""
|
||||||
|
Define a perspective image transform.
|
||||||
|
|
||||||
|
This function takes an 8-tuple (a, b, c, d, e, f, g, h). For each pixel
|
||||||
|
(x, y) in the output image, the new value is taken from a position
|
||||||
|
((a x + b y + c) / (g x + h y + 1), (d x + e y + f) / (g x + h y + 1)) in
|
||||||
|
the input image, rounded to nearest pixel.
|
||||||
|
|
||||||
|
This function can be used to scale, translate, rotate, and shear the
|
||||||
|
original image.
|
||||||
|
|
||||||
|
See :py:meth:`.Image.transform`
|
||||||
|
|
||||||
|
:param matrix: An 8-tuple (a, b, c, d, e, f, g, h).
|
||||||
|
"""
|
||||||
|
|
||||||
|
method = Image.Transform.PERSPECTIVE
|
||||||
|
|
||||||
|
|
||||||
class ExtentTransform(Transform):
|
class ExtentTransform(Transform):
|
||||||
"""
|
"""
|
||||||
Define a transform to extract a subregion from an image.
|
Define a transform to extract a subregion from an image.
|
||||||
|
|
|
@ -100,6 +100,7 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
msg = "not a PPM file"
|
msg = "not a PPM file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
self._mode = mode
|
||||||
|
|
||||||
if magic_number in (b"P1", b"P4"):
|
if magic_number in (b"P1", b"P4"):
|
||||||
self.custom_mimetype = "image/x-portable-bitmap"
|
self.custom_mimetype = "image/x-portable-bitmap"
|
||||||
|
@ -108,38 +109,31 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
elif magic_number in (b"P3", b"P6"):
|
elif magic_number in (b"P3", b"P6"):
|
||||||
self.custom_mimetype = "image/x-portable-pixmap"
|
self.custom_mimetype = "image/x-portable-pixmap"
|
||||||
|
|
||||||
maxval = None
|
self._size = int(self._read_token()), int(self._read_token())
|
||||||
|
|
||||||
decoder_name = "raw"
|
decoder_name = "raw"
|
||||||
if magic_number in (b"P1", b"P2", b"P3"):
|
if magic_number in (b"P1", b"P2", b"P3"):
|
||||||
decoder_name = "ppm_plain"
|
decoder_name = "ppm_plain"
|
||||||
for ix in range(3):
|
if mode == "1":
|
||||||
if mode == "F" and ix == 2:
|
args = "1;I"
|
||||||
|
elif mode == "F":
|
||||||
scale = float(self._read_token())
|
scale = float(self._read_token())
|
||||||
if scale == 0.0 or not math.isfinite(scale):
|
if scale == 0.0 or not math.isfinite(scale):
|
||||||
msg = "scale must be finite and non-zero"
|
msg = "scale must be finite and non-zero"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
rawmode = "F;32F" if scale < 0 else "F;32BF"
|
|
||||||
self.info["scale"] = abs(scale)
|
self.info["scale"] = abs(scale)
|
||||||
continue
|
|
||||||
token = int(self._read_token())
|
rawmode = "F;32F" if scale < 0 else "F;32BF"
|
||||||
if ix == 0: # token is the x size
|
args = (rawmode, 0, -1)
|
||||||
xsize = token
|
|
||||||
elif ix == 1: # token is the y size
|
|
||||||
ysize = token
|
|
||||||
if mode == "1":
|
|
||||||
self._mode = "1"
|
|
||||||
rawmode = "1;I"
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
self._mode = rawmode = mode
|
maxval = int(self._read_token())
|
||||||
elif ix == 2: # token is maxval
|
|
||||||
maxval = token
|
|
||||||
if not 0 < maxval < 65536:
|
if not 0 < maxval < 65536:
|
||||||
msg = "maxval must be greater than 0 and less than 65536"
|
msg = "maxval must be greater than 0 and less than 65536"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
if maxval > 255 and mode == "L":
|
if maxval > 255 and mode == "L":
|
||||||
self._mode = "I"
|
self._mode = "I"
|
||||||
|
|
||||||
|
rawmode = mode
|
||||||
if decoder_name != "ppm_plain":
|
if decoder_name != "ppm_plain":
|
||||||
# If maxval matches a bit depth, use the raw decoder directly
|
# If maxval matches a bit depth, use the raw decoder directly
|
||||||
if maxval == 65535 and mode == "L":
|
if maxval == 65535 and mode == "L":
|
||||||
|
@ -147,10 +141,8 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
elif maxval != 255:
|
elif maxval != 255:
|
||||||
decoder_name = "ppm"
|
decoder_name = "ppm"
|
||||||
|
|
||||||
row_order = -1 if mode == "F" else 1
|
args = rawmode if decoder_name == "raw" else (rawmode, maxval)
|
||||||
args = (rawmode, 0, row_order) if decoder_name == "raw" else (rawmode, maxval)
|
self.tile = [(decoder_name, (0, 0) + self.size, self.fp.tell(), args)]
|
||||||
self._size = xsize, ysize
|
|
||||||
self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -319,7 +311,6 @@ class PpmDecoder(ImageFile.PyDecoder):
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
row_order = 1
|
|
||||||
if im.mode == "1":
|
if im.mode == "1":
|
||||||
rawmode, head = "1;I", b"P4"
|
rawmode, head = "1;I", b"P4"
|
||||||
elif im.mode == "L":
|
elif im.mode == "L":
|
||||||
|
@ -330,7 +321,6 @@ def _save(im, fp, filename):
|
||||||
rawmode, head = "RGB", b"P6"
|
rawmode, head = "RGB", b"P6"
|
||||||
elif im.mode == "F":
|
elif im.mode == "F":
|
||||||
rawmode, head = "F;32F", b"Pf"
|
rawmode, head = "F;32F", b"Pf"
|
||||||
row_order = -1
|
|
||||||
else:
|
else:
|
||||||
msg = f"cannot write mode {im.mode} as PPM"
|
msg = f"cannot write mode {im.mode} as PPM"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
@ -344,6 +334,7 @@ def _save(im, fp, filename):
|
||||||
fp.write(b"65535\n")
|
fp.write(b"65535\n")
|
||||||
elif head == b"Pf":
|
elif head == b"Pf":
|
||||||
fp.write(b"-1.0\n")
|
fp.write(b"-1.0\n")
|
||||||
|
row_order = -1 if im.mode == "F" else 1
|
||||||
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, row_order))])
|
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, row_order))])
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user