Merge remote-tracking branch 'upstream/main' into winbuild-ninja
1
.github/workflows/test-docker.yml
vendored
|
@ -87,6 +87,7 @@ jobs:
|
|||
with:
|
||||
flags: GHA_Docker
|
||||
name: ${{ matrix.docker }}
|
||||
gcov: true
|
||||
|
||||
success:
|
||||
permissions:
|
||||
|
|
2
.github/workflows/test.yml
vendored
|
@ -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:
|
||||
|
|
15
CHANGES.rst
|
@ -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]
|
||||
|
||||
|
|
4
LICENSE
|
@ -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
|
||||
|
|
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnnn.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnny.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnyn.png
Normal file
After Width: | Height: | Size: 649 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnyy.png
Normal file
After Width: | Height: | Size: 755 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nynn.png
Normal file
After Width: | Height: | Size: 643 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyny.png
Normal file
After Width: | Height: | Size: 775 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyyn.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyyy.png
Normal file
After Width: | Height: | Size: 844 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynnn.png
Normal file
After Width: | Height: | Size: 656 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynny.png
Normal file
After Width: | Height: | Size: 785 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynyn.png
Normal file
After Width: | Height: | Size: 752 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynyy.png
Normal file
After Width: | Height: | Size: 856 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yynn.png
Normal file
After Width: | Height: | Size: 737 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyny.png
Normal file
After Width: | Height: | Size: 870 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyyn.png
Normal file
After Width: | Height: | Size: 835 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyyy.png
Normal file
After Width: | Height: | Size: 934 B |
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
[
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 document’s title. If not appending to an existing PDF file, this will
|
||||
default to the filename.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -10,7 +10,7 @@ for image enhancement.
|
|||
Example: Vary the sharpness of an image
|
||||
---------------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
::
|
||||
|
||||
from PIL import ImageEnhance
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ and **xmllib** modules.
|
|||
Example: Parse an image
|
||||
-----------------------
|
||||
|
||||
.. code-block:: python
|
||||
::
|
||||
|
||||
from PIL import ImageFile
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ the imToolkit package.
|
|||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
::
|
||||
|
||||
from PIL import ImageFont, ImageDraw
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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"):
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -295,29 +295,37 @@ 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 = d >= x1 - x0
|
||||
if full_x:
|
||||
# The two left and two right corners are joined
|
||||
d = x1 - x0
|
||||
full_y = d >= y1 - y0
|
||||
if full_y:
|
||||
# The two top and two bottom corners are joined
|
||||
d = y1 - y0
|
||||
if full_x and full_y:
|
||||
# If all corners are joined, that is a circle
|
||||
return self.ellipse(xy, fill, outline, width)
|
||||
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
|
||||
d = x1 - x0
|
||||
full_y = d >= y1 - y0
|
||||
if full_y:
|
||||
# The two top and two bottom corners are joined
|
||||
d = y1 - y0
|
||||
if full_x and full_y:
|
||||
# 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 = (
|
||||
((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),
|
||||
)
|
||||
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),
|
||||
)
|
||||
):
|
||||
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."""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|