Merge remote-tracking branch 'upstream/main' into winbuild-ninja

This commit is contained in:
nulano 2023-03-01 09:06:43 +00:00
commit f2527dd5eb
No known key found for this signature in database
GPG Key ID: B650CDF63B705766
66 changed files with 347 additions and 287 deletions

View File

@ -87,6 +87,7 @@ jobs:
with:
flags: GHA_Docker
name: ${{ matrix.docker }}
gcov: true
success:
permissions:

View File

@ -108,9 +108,9 @@ jobs:
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
gcov: true
success:
permissions:

View File

@ -5,6 +5,21 @@ Changelog (Pillow)
9.5.0 (unreleased)
------------------
- Added "corners" argument to ImageDraw rounded_rectangle() #6954
[radarhere]
- Added memoryview support to frombytes() #6974
[radarhere]
- Allow comments in FITS images #6973
[radarhere]
- Support saving PDF with different X and Y resolutions #6961
[jvanderneutstulen, radarhere, hugovk]
- Fixed writing int as UNDEFINED tag #6950
[radarhere]
- Raise an error if EXIF data is too long when saving JPEG #6939
[radarhere]

View File

@ -13,8 +13,8 @@ By obtaining, using, and/or copying this software and/or its associated
documentation, you agree that you have read, understood, and will comply
with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and its
associated documentation for any purpose and without fee is hereby granted,
Permission to use, copy, modify and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies, and that
both that copyright notice and this permission notice appear in supporting
documentation, and that the name of Secret Labs AB or the author not be

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

View File

@ -44,6 +44,12 @@ def test_naxis_zero():
pass
def test_comment():
image_data = b"SIMPLE = T / comment string"
with pytest.raises(OSError):
FitsImagePlugin.FitsImageFile(BytesIO(image_data))
def test_stub_deprecated():
class Handler:
opened = False

View File

@ -80,6 +80,34 @@ def test_resolution(tmp_path):
assert size == (61.44, 61.44)
@pytest.mark.parametrize(
"params",
(
{"dpi": (75, 150)},
{"dpi": (75, 150), "resolution": 200},
),
)
def test_dpi(params, tmp_path):
im = hopper()
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, **params)
with open(outfile, "rb") as fp:
contents = fp.read()
size = tuple(
float(d)
for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ")
)
assert size == (122.88, 61.44)
size = tuple(
float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
)
assert size == (122.88, 61.44)
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)

View File

@ -216,6 +216,22 @@ def test_writing_other_types_to_bytes(value, tmp_path):
assert reloaded.tag_v2[700] == b"\x01"
def test_writing_other_types_to_undefined(tmp_path):
im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2()
tag = TiffTags.TAGS_V2[33723]
assert tag.type == TiffTags.UNDEFINED
info[33723] = 1
out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert reloaded.tag_v2[33723] == b"1"
def test_undefined_zero(tmp_path):
# Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059]

View File

@ -1,10 +1,17 @@
import pytest
from PIL import Image
from .helper import assert_image_equal, hopper
def test_sanity():
@pytest.mark.parametrize("data_type", ("bytes", "memoryview"))
def test_sanity(data_type):
im1 = hopper()
im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes())
data = im1.tobytes()
if data_type == "memoryview":
data = memoryview(data)
im2 = Image.frombytes(im1.mode, im1.size, data)
assert_image_equal(im1, im2)

View File

@ -735,6 +735,36 @@ def test_rounded_rectangle(xy):
assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png")
@pytest.mark.parametrize("top_left", (True, False))
@pytest.mark.parametrize("top_right", (True, False))
@pytest.mark.parametrize("bottom_right", (True, False))
@pytest.mark.parametrize("bottom_left", (True, False))
def test_rounded_rectangle_corners(top_left, top_right, bottom_right, bottom_left):
corners = (top_left, top_right, bottom_right, bottom_left)
# Arrange
im = Image.new("RGB", (200, 200))
draw = ImageDraw.Draw(im)
# Act
draw.rounded_rectangle(
(10, 20, 190, 180), 30, fill="red", outline="green", width=5, corners=corners
)
# Assert
suffix = "".join(
(
("y" if top_left else "n"),
("y" if top_right else "n"),
("y" if bottom_right else "n"),
("y" if bottom_left else "n"),
)
)
assert_image_equal_tofile(
im, "Tests/images/imagedraw_rounded_rectangle_corners_" + suffix + ".png"
)
@pytest.mark.parametrize(
"xy, radius, type",
[

View File

@ -48,7 +48,7 @@ if ImageQt.qt_is_installed:
def roundtrip(expected):
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
# Qt saves all pixmaps as rgb
assert_image_similar(result, expected.convert("RGB"), 0.3)
assert_image_similar(result, expected.convert("RGB"), 1)
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")

View File

@ -177,9 +177,7 @@ Deprecated Use
:py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength`
=========================================================================== =============================================================================================================
Previous code:
.. code-block:: python
Previous code::
from PIL import Image, ImageDraw, ImageFont
@ -194,9 +192,7 @@ Previous code:
width, height = font.getsize_multiline("Hello\nworld")
width, height = draw.multiline_textsize("Hello\nworld")
Use instead:
.. code-block:: python
Use instead::
from PIL import Image, ImageDraw, ImageFont
@ -336,16 +332,12 @@ Implicitly closing the image's underlying file in ``Image.__del__`` has been rem
Use a context manager or call ``Image.close()`` instead to close the file in a
deterministic way.
Previous method:
.. code-block:: python
Previous method::
im = Image.open("hopper.png")
im.save("out.jpg")
Use instead:
.. code-block:: python
Use instead::
with Image.open("hopper.png") as im:
im.save("out.jpg")

View File

@ -1104,7 +1104,7 @@ using the general tags available through tiffinfo.
Either an integer or a float.
**dpi**
A tuple of (x_resolution, y_resolution), with inches as the resolution
A tuple of ``(x_resolution, y_resolution)``, with inches as the resolution
unit. For consistency with other image formats, the x and y resolutions
of the dpi will be rounded to the nearest integer.
@ -1126,7 +1126,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
If present and true, instructs the WebP writer to use lossless compression.
**quality**
Integer, 1-100, Defaults to 80. For lossy, 0 gives the smallest
Integer, 0-100, Defaults to 80. For lossy, 0 gives the smallest
size and 100 the largest. For lossless, this parameter is the amount
of effort put into the compression: 0 is the fastest, but gives larger
files compared to the slowest, but best, 100.
@ -1147,6 +1147,10 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
The exif data to include in the saved file. Only supported if
the system WebP library was built with webpmux support.
**xmp**
The XMP data to include in the saved file. Only supported if
the system WebP library was built with webpmux support.
Saving sequences
~~~~~~~~~~~~~~~~
@ -1389,9 +1393,7 @@ WMF, EMF
Pillow can identify WMF and EMF files.
On Windows, it can read WMF and EMF files. By default, it will load the image
at 72 dpi. To load it at another resolution:
.. code-block:: python
at 72 dpi. To load it at another resolution::
from PIL import Image
@ -1400,9 +1402,7 @@ at 72 dpi. To load it at another resolution:
To add other read or write support, use
:py:func:`PIL.WmfImagePlugin.register_handler` to register a WMF and EMF
handler.
.. code-block:: python
handler. ::
from PIL import Image
from PIL import WmfImagePlugin
@ -1493,6 +1493,11 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
image, will determine the physical dimensions of the page that will be
saved in the PDF.
**dpi**
A tuple of ``(x_resolution, y_resolution)``, with inches as the resolution
unit. If both the ``resolution`` parameter and the ``dpi`` parameter are
present, ``resolution`` will be ignored.
**title**
The documents title. If not appending to an existing PDF file, this will
default to the filename.

View File

@ -29,7 +29,7 @@ For example, in the following image, the text is ``ms`` (middle-baseline) aligne
:alt: ms (middle-baseline) aligned text.
:align: left
.. code-block:: python
::
from PIL import Image, ImageDraw, ImageFont

View File

@ -108,9 +108,7 @@ Note that the image plugin must be explicitly registered using
:py:func:`PIL.Image.register_open`. Although not required, it is also a good
idea to register any extensions used by this format.
Once the plugin has been imported, it can be used:
.. code-block:: python
Once the plugin has been imported, it can be used::
from PIL import Image
import SpamImagePlugin
@ -169,9 +167,7 @@ The raw decoder
The ``raw`` decoder is used to read uncompressed data from an image file. It
can be used with most uncompressed file formats, such as PPM, BMP, uncompressed
TIFF, and many others. To use the raw decoder with the
:py:func:`PIL.Image.frombytes` function, use the following syntax:
.. code-block:: python
:py:func:`PIL.Image.frombytes` function, use the following syntax::
image = Image.frombytes(
mode, size, data, "raw",
@ -281,9 +277,7 @@ decoder that can be used to read various packed formats into a floating point
image memory.
To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use
the following syntax:
.. code-block:: python
the following syntax::
image = Image.frombytes(
mode, size, data, "bit",

View File

@ -150,7 +150,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.7-2.14**.
above uses liblcms2. Tested with **1.19** and **2.7-2.15**.
* **libwebp** provides the WebP format.

View File

@ -17,9 +17,7 @@ Open, rotate, and display an image (using the default viewer)
The following script loads an image, rotates it 45 degrees, and displays it
using an external viewer (usually xv on Unix, and the Paint program on
Windows).
.. code-block:: python
Windows). ::
from PIL import Image
with Image.open("hopper.jpg") as im:
@ -29,9 +27,7 @@ Create thumbnails
^^^^^^^^^^^^^^^^^
The following script creates nice thumbnails of all JPEG images in the
current directory preserving aspect ratios with 128x128 max resolution.
.. code-block:: python
current directory preserving aspect ratios with 128x128 max resolution. ::
from PIL import Image
import glob, os
@ -127,9 +123,7 @@ methods. Unless otherwise stated, all methods return a new instance of the
.. automethod:: PIL.Image.Image.convert
The following example converts an RGB image (linearly calibrated according to
ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
.. code-block:: python
ITU-R 709, using the D65 luminant) to the CIE XYZ color space::
rgb2xyz = (
0.412453, 0.357580, 0.180423, 0,
@ -140,9 +134,7 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
.. automethod:: PIL.Image.Image.copy
.. automethod:: PIL.Image.Image.crop
This crops the input image with the provided coordinates:
.. code-block:: python
This crops the input image with the provided coordinates::
from PIL import Image
@ -162,9 +154,7 @@ This crops the input image with the provided coordinates:
.. automethod:: PIL.Image.Image.entropy
.. automethod:: PIL.Image.Image.filter
This blurs the input image using a filter from the ``ImageFilter`` module:
.. code-block:: python
This blurs the input image using a filter from the ``ImageFilter`` module::
from PIL import Image, ImageFilter
@ -176,9 +166,7 @@ This blurs the input image using a filter from the ``ImageFilter`` module:
.. automethod:: PIL.Image.Image.frombytes
.. automethod:: PIL.Image.Image.getbands
This helps to get the bands of the input image:
.. code-block:: python
This helps to get the bands of the input image::
from PIL import Image
@ -187,9 +175,7 @@ This helps to get the bands of the input image:
.. automethod:: PIL.Image.Image.getbbox
This helps to get the bounding box coordinates of the input image:
.. code-block:: python
This helps to get the bounding box coordinates of the input image::
from PIL import Image
@ -217,9 +203,7 @@ This helps to get the bounding box coordinates of the input image:
.. automethod:: PIL.Image.Image.remap_palette
.. automethod:: PIL.Image.Image.resize
This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``:
.. code-block:: python
This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``::
from PIL import Image
@ -231,9 +215,7 @@ This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``
.. automethod:: PIL.Image.Image.rotate
This rotates the input image by ``theta`` degrees counter clockwise:
.. code-block:: python
This rotates the input image by ``theta`` degrees counter clockwise::
from PIL import Image
@ -256,9 +238,7 @@ This rotates the input image by ``theta`` degrees counter clockwise:
.. automethod:: PIL.Image.Image.transpose
This flips the input image by using the :data:`Transpose.FLIP_LEFT_RIGHT`
method.
.. code-block:: python
method. ::
from PIL import Image

View File

@ -16,7 +16,7 @@ For a more advanced drawing library for PIL, see the `aggdraw module`_.
Example: Draw a gray cross over an image
----------------------------------------
.. code-block:: python
::
import sys
from PIL import Image, ImageDraw
@ -78,7 +78,7 @@ libraries, and may not available in all PIL builds.
Example: Draw Partial Opacity Text
----------------------------------
.. code-block:: python
::
from PIL import Image, ImageDraw, ImageFont
@ -105,7 +105,7 @@ Example: Draw Partial Opacity Text
Example: Draw Multiline Text
----------------------------
.. code-block:: python
::
from PIL import Image, ImageDraw, ImageFont
@ -337,6 +337,8 @@ Methods
:param outline: Color to use for the outline.
:param fill: Color to use for the fill.
:param width: The line width, in pixels.
:param corners: A tuple of whether to round each corner,
`(top_left, top_right, bottom_right, bottom_left)`.
.. versionadded:: 8.2.0
@ -597,18 +599,14 @@ Methods
string due to kerning. If you need to adjust for kerning, include the following
character and subtract its length.
For example, instead of
.. code-block:: python
For example, instead of ::
hello = draw.textlength("Hello", font)
world = draw.textlength("World", font)
hello_world = hello + world # not adjusted for kerning
assert hello_world == draw.textlength("HelloWorld", font) # may fail
use
.. code-block:: python
use ::
hello = draw.textlength("HelloW", font) - draw.textlength(
"W", font
@ -617,9 +615,7 @@ Methods
hello_world = hello + world # adjusted for kerning
assert hello_world == draw.textlength("HelloWorld", font) # True
or disable kerning with (requires libraqm)
.. code-block:: python
or disable kerning with (requires libraqm) ::
hello = draw.textlength("Hello", font, features=["-kern"])
world = draw.textlength("World", font, features=["-kern"])

View File

@ -10,7 +10,7 @@ for image enhancement.
Example: Vary the sharpness of an image
---------------------------------------
.. code-block:: python
::
from PIL import ImageEnhance

View File

@ -15,7 +15,7 @@ and **xmllib** modules.
Example: Parse an image
-----------------------
.. code-block:: python
::
from PIL import ImageFile

View File

@ -11,7 +11,7 @@ filters, which can be be used with the :py:meth:`Image.filter()
Example: Filter an image
------------------------
.. code-block:: python
::
from PIL import ImageFilter

View File

@ -21,7 +21,7 @@ the imToolkit package.
Example
-------
.. code-block:: python
::
from PIL import ImageFont, ImageDraw

View File

@ -11,7 +11,7 @@ an expression string and one or more images.
Example: Using the :py:mod:`~PIL.ImageMath` module
--------------------------------------------------
.. code-block:: python
::
from PIL import Image, ImageMath

View File

@ -60,9 +60,7 @@ vector data. Path objects can be passed to the methods on the
.. py:method:: PIL.ImagePath.Path.transform(matrix)
Transforms the path in place, using an affine transform. The matrix is a
6-tuple (a, b, c, d, e, f), and each point is mapped as follows:
.. code-block:: python
6-tuple (a, b, c, d, e, f), and each point is mapped as follows::
xOut = xIn * a + yIn * b + c
yOut = xIn * d + yIn * e + f

View File

@ -10,7 +10,7 @@ iterate over the frames of an image sequence.
Extracting frames from an animation
-----------------------------------
.. code-block:: python
::
from PIL import Image, ImageSequence

View File

@ -9,9 +9,7 @@ Windows.
ImageWin can be used with PythonWin and other user interface toolkits that
provide access to Windows device contexts or window handles. For example,
Tkinter makes the window handle available via the winfo_id method:
.. code-block:: python
Tkinter makes the window handle available via the winfo_id method::
from PIL import ImageWin

View File

@ -18,9 +18,7 @@ Example
-------
The following script loads an image, accesses one pixel from it, then
changes it.
.. code-block:: python
changes it. ::
from PIL import Image
@ -35,9 +33,7 @@ Results in the following::
(23, 24, 68)
(0, 0, 0)
Access using negative indexes is also possible.
.. code-block:: python
Access using negative indexes is also possible. ::
px[-1, -1] = (0, 0, 0)
print(px[-1, -1])

View File

@ -17,9 +17,7 @@ The :py:mod:`~PIL.PyAccess` module provides a CFFI/Python implementation of the
Example
-------
The following script loads an image, accesses one pixel from it, then changes it.
.. code-block:: python
The following script loads an image, accesses one pixel from it, then changes it. ::
from PIL import Image
@ -34,9 +32,7 @@ Results in the following::
(23, 24, 68)
(0, 0, 0)
Access using negative indexes is also possible.
.. code-block:: python
Access using negative indexes is also possible. ::
px[-1, -1] = (0, 0, 0)
print(px[-1, -1])

View File

@ -61,9 +61,7 @@ Image Lifecycle
* ``Image.Image.close()`` Closes the file and destroys the core image object.
The Pillow context manager will also close the file, but will not destroy
the core image object. e.g.:
.. code-block:: python
the core image object. e.g.::
with Image.open("test.jpg") as img:
img.load()

View File

@ -13,16 +13,12 @@ Implicitly closing the image's underlying file in ``Image.__del__`` has been dep
Use a context manager or call ``Image.close()`` instead to close the file in a
deterministic way.
Deprecated:
.. code-block:: python
Deprecated::
im = Image.open("hopper.png")
im.save("out.jpg")
Use instead:
.. code-block:: python
Use instead::
with Image.open("hopper.png") as im:
im.save("out.jpg")
@ -79,9 +75,7 @@ Image quality for JPEG compressed TIFF
The TIFF encoder accepts a ``quality`` parameter for ``jpeg`` compressed TIFF files. A
value from 0 (worst) to 100 (best) controls the image quality, similar to the JPEG
encoder. The default is 75. For example:
.. code-block:: python
encoder. The default is 75. For example::
im.save("out.tif", compression="jpeg", quality=85)

View File

@ -10,9 +10,7 @@ Text stroking
``stroke_width`` and ``stroke_fill`` arguments have been added to text drawing
operations. They allow text to be outlined, setting the width of the stroke and
and the color respectively. If not provided, ``stroke_fill`` will default to
the ``fill`` parameter.
.. code-block:: python
the ``fill`` parameter. ::
from PIL import Image, ImageDraw, ImageFont
@ -28,9 +26,7 @@ the ``fill`` parameter.
draw.multiline_text((10, 10), "A\nB", "#f00", font,
stroke_width=2, stroke_fill="#0f0")
For example,
.. code-block:: python
For example, ::
from PIL import Image, ImageDraw, ImageFont

View File

@ -118,9 +118,7 @@ Loading WMF images at a given DPI
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
On Windows, Pillow can read WMF files, with a default DPI of 72. An image can
now also be loaded at another resolution:
.. code-block:: python
now also be loaded at another resolution::
from PIL import Image
with Image.open("drawing.wmf") as im:
@ -136,16 +134,12 @@ Implicitly closing the image's underlying file in ``Image.__del__`` has been rem
Use a context manager or call :py:meth:`~PIL.Image.Image.close` instead to close
the file in a deterministic way.
Previous method:
.. code-block:: python
Previous method::
im = Image.open("hopper.png")
im.save("out.jpg")
Use instead:
.. code-block:: python
Use instead::
with Image.open("hopper.png") as im:
im.save("out.jpg")

View File

@ -10,9 +10,7 @@ Allow saving of zero quality JPEG images
If no quality was specified when saving a JPEG, Pillow internally used a value
of zero to indicate that the default quality should be used. However, this
removed the ability to actually save a JPEG with zero quality. This has now
been resolved.
.. code-block:: python
been resolved. ::
from PIL import Image
im = Image.open("hopper.jpg")

View File

@ -76,9 +76,7 @@ ImageDraw.rounded_rectangle
Added :py:meth:`~PIL.ImageDraw.ImageDraw.rounded_rectangle`. It works the same as
:py:meth:`~PIL.ImageDraw.ImageDraw.rectangle`, except with an additional ``radius``
argument. ``radius`` is limited to half of the width or the height, so that users can
create a circle, but not any other ellipse.
.. code-block:: python
create a circle, but not any other ellipse. ::
from PIL import Image, ImageDraw
im = Image.new("RGB", (200, 200))

View File

@ -24,9 +24,7 @@ Added "transparency" argument for loading EPS images
This new argument switches the Ghostscript device from "ppmraw" to "pngalpha",
generating an RGBA image with a transparent background instead of an RGB image with a
white background.
.. code-block:: python
white background. ::
with Image.open("sample.eps") as im:
im.load(transparency=True)

View File

@ -155,9 +155,7 @@ altered slightly with this change.
Added support for pickling TrueType fonts
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TrueType fonts may now be pickled and unpickled. For example:
.. code-block:: python
TrueType fonts may now be pickled and unpickled. For example::
import pickle
from PIL import ImageFont

View File

@ -182,17 +182,13 @@ GifImagePlugin loading strategy
Pillow 9.0.0 introduced the conversion of subsequent GIF frames to ``RGB`` or ``RGBA``. This
behaviour can now be changed so that the first ``P`` frame is converted to ``RGB`` as
well.
.. code-block:: python
well. ::
from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
Or subsequent frames can be kept in ``P`` mode as long as there is only a single
palette.
.. code-block:: python
palette. ::
from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY

View File

@ -59,9 +59,7 @@ Deprecated Use
:py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength`
=========================================================================== =============================================================================================================
Previous code:
.. code-block:: python
Previous code::
from PIL import Image, ImageDraw, ImageFont
@ -76,9 +74,7 @@ Previous code:
width, height = font.getsize_multiline("Hello\nworld")
width, height = draw.multiline_textsize("Hello\nworld")
Use instead:
.. code-block:: python
Use instead::
from PIL import Image, ImageDraw, ImageFont

View File

@ -64,16 +64,27 @@ def bdf_char(f):
bitmap.append(s[:-1])
bitmap = b"".join(bitmap)
[x, y, l, d] = [int(p) for p in props["BBX"].split()]
[dx, dy] = [int(p) for p in props["DWIDTH"].split()]
# The word BBX
# followed by the width in x (BBw), height in y (BBh),
# and x and y displacement (BBxoff0, BByoff0)
# of the lower left corner from the origin of the character.
width, height, x_disp, y_disp = [int(p) for p in props["BBX"].split()]
bbox = (dx, dy), (l, -d - y, x + l, -d), (0, 0, x, y)
# The word DWIDTH
# followed by the width in x and y of the character in device pixels.
dwx, dwy = [int(p) for p in props["DWIDTH"].split()]
bbox = (
(dwx, dwy),
(x_disp, -y_disp - height, width + x_disp, -y_disp),
(0, 0, width, height),
)
try:
im = Image.frombytes("1", (x, y), bitmap, "hex", "1")
im = Image.frombytes("1", (width, height), bitmap, "hex", "1")
except ValueError:
# deal with zero-width characters
im = Image.new("1", (x, y))
im = Image.new("1", (width, height))
return id, int(props["ENCODING"]), bbox, im

View File

@ -32,7 +32,7 @@ class FitsImageFile(ImageFile.ImageFile):
keyword = header[:8].strip()
if keyword == b"END":
break
value = header[8:].strip()
value = header[8:].split(b"/")[0].strip()
if value.startswith(b"="):
value = value[1:].strip()
if not headers and (not _accept(keyword) or value != b"T"):

View File

@ -765,17 +765,17 @@ class Image:
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
data = []
output = []
while True:
l, s, d = e.encode(bufsize)
data.append(d)
if s:
bytes_consumed, errcode, data = e.encode(bufsize)
output.append(data)
if errcode:
break
if s < 0:
msg = f"encoder error {s} in tobytes"
if errcode < 0:
msg = f"encoder error {errcode} in tobytes"
raise RuntimeError(msg)
return b"".join(data)
return b"".join(output)
def tobitmap(self, name="image"):
"""

View File

@ -38,9 +38,7 @@ def duplicate(image):
def invert(image):
"""
Invert an image (channel).
.. code-block:: python
Invert an image (channel). ::
out = MAX - image
@ -54,9 +52,7 @@ def invert(image):
def lighter(image1, image2):
"""
Compares the two images, pixel by pixel, and returns a new image containing
the lighter values.
.. code-block:: python
the lighter values. ::
out = max(image1, image2)
@ -71,9 +67,7 @@ def lighter(image1, image2):
def darker(image1, image2):
"""
Compares the two images, pixel by pixel, and returns a new image containing
the darker values.
.. code-block:: python
the darker values. ::
out = min(image1, image2)
@ -88,9 +82,7 @@ def darker(image1, image2):
def difference(image1, image2):
"""
Returns the absolute value of the pixel-by-pixel difference between the two
images.
.. code-block:: python
images. ::
out = abs(image1 - image2)
@ -107,9 +99,7 @@ def multiply(image1, image2):
Superimposes two images on top of each other.
If you multiply an image with a solid black image, the result is black. If
you multiply with a solid white image, the image is unaffected.
.. code-block:: python
you multiply with a solid white image, the image is unaffected. ::
out = image1 * image2 / MAX
@ -123,9 +113,7 @@ def multiply(image1, image2):
def screen(image1, image2):
"""
Superimposes two inverted images on top of each other.
.. code-block:: python
Superimposes two inverted images on top of each other. ::
out = MAX - ((MAX - image1) * (MAX - image2) / MAX)
@ -176,9 +164,7 @@ def overlay(image1, image2):
def add(image1, image2, scale=1.0, offset=0):
"""
Adds two images, dividing the result by scale and adding the
offset. If omitted, scale defaults to 1.0, and offset to 0.0.
.. code-block:: python
offset. If omitted, scale defaults to 1.0, and offset to 0.0. ::
out = ((image1 + image2) / scale + offset)
@ -193,9 +179,7 @@ def add(image1, image2, scale=1.0, offset=0):
def subtract(image1, image2, scale=1.0, offset=0):
"""
Subtracts two images, dividing the result by scale and adding the offset.
If omitted, scale defaults to 1.0, and offset to 0.0.
.. code-block:: python
If omitted, scale defaults to 1.0, and offset to 0.0. ::
out = ((image1 - image2) / scale + offset)
@ -208,9 +192,7 @@ def subtract(image1, image2, scale=1.0, offset=0):
def add_modulo(image1, image2):
"""Add two images, without clipping the result.
.. code-block:: python
"""Add two images, without clipping the result. ::
out = ((image1 + image2) % MAX)
@ -223,9 +205,7 @@ def add_modulo(image1, image2):
def subtract_modulo(image1, image2):
"""Subtract two images, without clipping the result.
.. code-block:: python
"""Subtract two images, without clipping the result. ::
out = ((image1 - image2) % MAX)
@ -243,9 +223,7 @@ def logical_and(image1, image2):
Both of the images must have mode "1". If you would like to perform a
logical AND on an image with a mode other than "1", try
:py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask
as the second image.
.. code-block:: python
as the second image. ::
out = ((image1 and image2) % MAX)
@ -260,9 +238,7 @@ def logical_and(image1, image2):
def logical_or(image1, image2):
"""Logical OR between two images.
Both of the images must have mode "1".
.. code-block:: python
Both of the images must have mode "1". ::
out = ((image1 or image2) % MAX)
@ -277,9 +253,7 @@ def logical_or(image1, image2):
def logical_xor(image1, image2):
"""Logical XOR between two images.
Both of the images must have mode "1".
.. code-block:: python
Both of the images must have mode "1". ::
out = ((bool(image1) != bool(image2)) % MAX)

View File

@ -295,15 +295,21 @@ class ImageDraw:
if ink is not None and ink != fill and width != 0:
self.draw.draw_rectangle(xy, ink, 0, width)
def rounded_rectangle(self, xy, radius=0, fill=None, outline=None, width=1):
def rounded_rectangle(
self, xy, radius=0, fill=None, outline=None, width=1, *, corners=None
):
"""Draw a rounded rectangle."""
if isinstance(xy[0], (list, tuple)):
(x0, y0), (x1, y1) = xy
else:
x0, y0, x1, y1 = xy
if corners is None:
corners = (True, True, True, True)
d = radius * 2
full_x, full_y = False, False
if all(corners):
full_x = d >= x1 - x0
if full_x:
# The two left and two right corners are joined
@ -316,8 +322,10 @@ class ImageDraw:
# If all corners are joined, that is a circle
return self.ellipse(xy, fill, outline, width)
if d == 0:
# If the corners have no curve, that is a rectangle
if d == 0 or not any(corners):
# If the corners have no curve,
# or there are no corners,
# that is a rectangle
return self.rectangle(xy, fill, outline, width)
r = d // 2
@ -338,12 +346,17 @@ class ImageDraw:
)
else:
# Draw four separate corners
parts = (
parts = []
for i, part in enumerate(
(
((x0, y0, x0 + d, y0 + d), 180, 270),
((x1 - d, y0, x1, y0 + d), 270, 360),
((x1 - d, y1 - d, x1, y1), 0, 90),
((x0, y1 - d, x0 + d, y1), 90, 180),
((x0, y0, x0 + d, y0 + d), 180, 270),
)
):
if corners[i]:
parts.append(part)
for part in parts:
if pieslice:
self.draw.draw_pieslice(*(part + (fill, 1)))
@ -358,25 +371,50 @@ class ImageDraw:
else:
self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
if not full_x and not full_y:
self.draw.draw_rectangle((x0, y0 + r + 1, x0 + r, y1 - r - 1), fill, 1)
self.draw.draw_rectangle((x1 - r, y0 + r + 1, x1, y1 - r - 1), fill, 1)
left = [x0, y0, x0 + r, y1]
if corners[0]:
left[1] += r + 1
if corners[3]:
left[3] -= r + 1
self.draw.draw_rectangle(left, fill, 1)
right = [x1 - r, y0, x1, y1]
if corners[1]:
right[1] += r + 1
if corners[2]:
right[3] -= r + 1
self.draw.draw_rectangle(right, fill, 1)
if ink is not None and ink != fill and width != 0:
draw_corners(False)
if not full_x:
self.draw.draw_rectangle(
(x0 + r + 1, y0, x1 - r - 1, y0 + width - 1), ink, 1
)
self.draw.draw_rectangle(
(x0 + r + 1, y1 - width + 1, x1 - r - 1, y1), ink, 1
)
top = [x0, y0, x1, y0 + width - 1]
if corners[0]:
top[0] += r + 1
if corners[1]:
top[2] -= r + 1
self.draw.draw_rectangle(top, ink, 1)
bottom = [x0, y1 - width + 1, x1, y1]
if corners[3]:
bottom[0] += r + 1
if corners[2]:
bottom[2] -= r + 1
self.draw.draw_rectangle(bottom, ink, 1)
if not full_y:
self.draw.draw_rectangle(
(x0, y0 + r + 1, x0 + width - 1, y1 - r - 1), ink, 1
)
self.draw.draw_rectangle(
(x1 - width + 1, y0 + r + 1, x1, y1 - r - 1), ink, 1
)
left = [x0, y0, x0 + width - 1, y1]
if corners[0]:
left[1] += r + 1
if corners[3]:
left[3] -= r + 1
self.draw.draw_rectangle(left, ink, 1)
right = [x1 - width + 1, y0, x1, y1]
if corners[1]:
right[1] += r + 1
if corners[2]:
right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 1)
def _multiline_check(self, text):
"""Draw text."""

View File

@ -530,20 +530,20 @@ def _encode_tile(im, fp, tile, bufsize, fh, exc=None):
encoder.setimage(im.im, b)
if encoder.pushes_fd:
encoder.setfd(fp)
l, s = encoder.encode_to_pyfd()
errcode = encoder.encode_to_pyfd()[1]
else:
if exc:
# compress to Python file-compatible object
while True:
l, s, d = encoder.encode(bufsize)
fp.write(d)
if s:
errcode, data = encoder.encode(bufsize)[1:]
fp.write(data)
if errcode:
break
else:
# slight speedup: compress to real file object
s = encoder.encode_to_file(fh, bufsize)
if s < 0:
msg = f"encoder error {s} when writing image file"
errcode = encoder.encode_to_file(fh, bufsize)
if errcode < 0:
msg = f"encoder error {errcode} when writing image file"
raise OSError(msg) from exc
finally:
encoder.cleanup()

View File

@ -297,27 +297,21 @@ class FreeTypeFont:
string due to kerning. If you need to adjust for kerning, include the following
character and subtract its length.
For example, instead of
.. code-block:: python
For example, instead of ::
hello = font.getlength("Hello")
world = font.getlength("World")
hello_world = hello + world # not adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # may fail
use
.. code-block:: python
use ::
hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning
world = font.getlength("World")
hello_world = hello + world # adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # True
or disable kerning with (requires libraqm)
.. code-block:: python
or disable kerning with (requires libraqm) ::
hello = draw.textlength("Hello", font, features=["-kern"])
world = draw.textlength("World", font, features=["-kern"])

View File

@ -86,9 +86,22 @@ class PcfFontFile(FontFile.FontFile):
for ch, ix in enumerate(encoding):
if ix is not None:
x, y, l, r, w, a, d, f = metrics[ix]
glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix]
self.glyph[ch] = glyph
(
xsize,
ysize,
left,
right,
width,
ascent,
descent,
attributes,
) = metrics[ix]
self.glyph[ch] = (
(width, 0),
(left, descent - ysize, xsize + left, descent),
(0, 0, xsize, ysize),
bitmaps[ix],
)
def _getformat(self, tag):
format, size, offset = self.toc[tag]
@ -206,9 +219,11 @@ class PcfFontFile(FontFile.FontFile):
mode = "1"
for i in range(nbitmaps):
x, y, l, r, w, a, d, f = metrics[i]
b, e = offsets[i], offsets[i + 1]
bitmaps.append(Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x)))
xsize, ysize = metrics[i][:2]
b, e = offsets[i : i + 2]
bitmaps.append(
Image.frombytes("1", (xsize, ysize), data[b:e], "raw", mode, pad(xsize))
)
return bitmaps

View File

@ -53,7 +53,12 @@ def _save(im, fp, filename, save_all=False):
else:
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b")
resolution = im.encoderinfo.get("resolution", 72.0)
dpi = im.encoderinfo.get("dpi")
if dpi:
x_resolution = dpi[0]
y_resolution = dpi[1]
else:
x_resolution = y_resolution = im.encoderinfo.get("resolution", 72.0)
info = {
"title": None
@ -214,8 +219,8 @@ def _save(im, fp, filename, save_all=False):
stream=stream,
Type=PdfParser.PdfName("XObject"),
Subtype=PdfParser.PdfName("Image"),
Width=width, # * 72.0 / resolution,
Height=height, # * 72.0 / resolution,
Width=width, # * 72.0 / x_resolution,
Height=height, # * 72.0 / y_resolution,
Filter=filter,
BitsPerComponent=bits,
Decode=decode,
@ -235,8 +240,8 @@ def _save(im, fp, filename, save_all=False):
MediaBox=[
0,
0,
width * 72.0 / resolution,
height * 72.0 / resolution,
width * 72.0 / x_resolution,
height * 72.0 / y_resolution,
],
Contents=contents_refs[page_number],
)
@ -245,8 +250,8 @@ def _save(im, fp, filename, save_all=False):
# page contents
page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % (
width * 72.0 / resolution,
height * 72.0 / resolution,
width * 72.0 / x_resolution,
height * 72.0 / y_resolution,
)
existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)

View File

@ -764,6 +764,8 @@ class ImageFileDirectory_v2(MutableMapping):
@_register_writer(7)
def write_undefined(self, value):
if isinstance(value, int):
value = str(value).encode("ascii", "replace")
return value
@_register_loader(10, 8)
@ -1843,13 +1845,13 @@ def _save(im, fp, filename):
e.setimage(im.im, (0, 0) + im.size)
while True:
# undone, change to self.decodermaxblock:
l, s, d = e.encode(16 * 1024)
errcode, data = e.encode(16 * 1024)[1:]
if not _fp:
fp.write(d)
if s:
fp.write(data)
if errcode:
break
if s < 0:
msg = f"encoder error {s} when writing image file"
if errcode < 0:
msg = f"encoder error {errcode} when writing image file"
raise OSError(msg)
else:

View File

@ -116,12 +116,11 @@ _dealloc(ImagingDecoderObject *decoder) {
static PyObject *
_decode(ImagingDecoderObject *decoder, PyObject *args) {
UINT8 *buffer;
Py_ssize_t bufsize;
Py_buffer buffer;
int status;
ImagingSectionCookie cookie;
if (!PyArg_ParseTuple(args, "y#", &buffer, &bufsize)) {
if (!PyArg_ParseTuple(args, "y*", &buffer)) {
return NULL;
}
@ -129,7 +128,7 @@ _decode(ImagingDecoderObject *decoder, PyObject *args) {
ImagingSectionEnter(&cookie);
}
status = decoder->decode(decoder->im, &decoder->state, buffer, bufsize);
status = decoder->decode(decoder->im, &decoder->state, buffer.buf, buffer.len);
if (!decoder->pulls_fd) {
ImagingSectionLeave(&cookie);

View File

@ -108,9 +108,7 @@ directory.
Example
-------
The following is a simplified version of the script used on AppVeyor:
.. code-block::
The following is a simplified version of the script used on AppVeyor::
set PYTHON=C:\Python38\bin
cd /D C:\Pillow\winbuild

View File

@ -273,9 +273,9 @@ deps = {
# "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"],
},
"lcms2": {
"url": SF_PROJECTS + "/lcms/files/lcms/2.14/lcms2-2.14.tar.gz/download",
"filename": "lcms2-2.14.tar.gz",
"dir": "lcms2-2.14",
"url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download",
"filename": "lcms2-2.15.tar.gz",
"dir": "lcms2-2.15",
"license": "COPYING",
"patch": {
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
@ -337,9 +337,9 @@ deps = {
"libs": [r"imagequant.lib"],
},
"harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/7.0.0.zip",
"filename": "harfbuzz-7.0.0.zip",
"dir": "harfbuzz-7.0.0",
"url": "https://github.com/harfbuzz/harfbuzz/archive/7.0.1.zip",
"filename": "harfbuzz-7.0.1.zip",
"dir": "harfbuzz-7.0.1",
"license": "COPYING",
"build": [
*cmds_cmake(