From 7966c344ac4cc572edf1fc19246e650547b76cb6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 1 Sep 2022 22:26:35 +1000 Subject: [PATCH 01/13] Improved documentation of return values --- docs/reference/ImageDraw.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 1ef9079fb..75ef0f5cc 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -489,6 +489,8 @@ Methods .. versionadded:: 6.2.0 + :returns: A ``(width, height)`` tuple. + .. py:method:: ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) .. deprecated:: 9.2.0 @@ -541,6 +543,8 @@ Methods .. versionadded:: 6.2.0 + :returns: A ``(width, height)`` tuple. + .. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False) Returns length (in pixels with 1/64 precision) of given text when rendered @@ -608,6 +612,7 @@ Methods It should be a `BCP 47 language code`_. Requires libraqm. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). + :returns: A single float, the length of the text. .. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -657,6 +662,8 @@ Methods Requires libraqm. :param stroke_width: The width of the text stroke. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). + :returns: An ``(x0, y0, x1, y1)`` tuple, describing the top left and lower right + corners of the text. .. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -700,6 +707,8 @@ Methods Requires libraqm. :param stroke_width: The width of the text stroke. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). + :returns: An ``(x0, y0, x1, y1)`` tuple, describing the top left and lower right + corners of the text. .. py:method:: getdraw(im=None, hints=None) From 4783ecf41c1413a05e70d325268141fce6788ac0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 3 Sep 2022 21:09:44 +1000 Subject: [PATCH 02/13] Updated return values to match ImageFont --- docs/reference/ImageDraw.rst | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 75ef0f5cc..e19e87a0d 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -489,7 +489,7 @@ Methods .. versionadded:: 6.2.0 - :returns: A ``(width, height)`` tuple. + :return: (width, height) .. py:method:: ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) @@ -543,7 +543,7 @@ Methods .. versionadded:: 6.2.0 - :returns: A ``(width, height)`` tuple. + :return: (width, height) .. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False) @@ -612,7 +612,7 @@ Methods It should be a `BCP 47 language code`_. Requires libraqm. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). - :returns: A single float, the length of the text. + :return: Width for horizontal, height for vertical text. .. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -662,8 +662,7 @@ Methods Requires libraqm. :param stroke_width: The width of the text stroke. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). - :returns: An ``(x0, y0, x1, y1)`` tuple, describing the top left and lower right - corners of the text. + :return: ``(left, top, right, bottom)`` bounding box .. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -707,8 +706,7 @@ Methods Requires libraqm. :param stroke_width: The width of the text stroke. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). - :returns: An ``(x0, y0, x1, y1)`` tuple, describing the top left and lower right - corners of the text. + :return: ``(left, top, right, bottom)`` bounding box .. py:method:: getdraw(im=None, hints=None) From 780de80e5c56e8466faec54f8c0ebe95e543e9ea Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 3 Sep 2022 22:23:05 +1000 Subject: [PATCH 03/13] Added examples for updating code --- docs/deprecations.rst | 34 ++++++++++++++++++++++++++++++++++ docs/reference/ImageDraw.rst | 6 ++++++ docs/releasenotes/9.2.0.rst | 34 ++++++++++++++++++++++++++++++++++ src/PIL/ImageFont.py | 15 +++++++++++++++ 4 files changed, 89 insertions(+) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 9be92770a..92f116846 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -197,6 +197,40 @@ Deprecated Use :py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength` =========================================================================== ============================================================================================================= +Previous code: + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + width, height = font.getsize("Hello world") + left, top = font.getoffset("Hello world") + + im = Image.new("RGB", (100, 100)) + draw = ImageDraw.Draw(im) + width, height = draw.textsize("Hello world") + + width, height = font.getsize_multiline("Hello\nworld") + width, height = draw.multiline_textsize("Hello\nworld") + +Use instead: + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + left, top, right, bottom = font.getbbox("Hello world") + width, height = right - left, bottom - top + + im = Image.new("RGB", (100, 100)) + draw = ImageDraw.Draw(im) + width = draw.textlength("Hello world") + + left, top, right, bottom = draw.multiline_textbbox((0, 0), "Hello\nworld") + width, height = right - left, bottom - top + Removed features ---------------- diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index e19e87a0d..623f601ef 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -443,6 +443,9 @@ Methods .. deprecated:: 9.2.0 + See https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods + for more information. + Use :py:meth:`textlength()` to measure the offset of following text with 1/64 pixel precision. Use :py:meth:`textbbox()` to get the exact bounding box based on an anchor. @@ -495,6 +498,9 @@ Methods .. deprecated:: 9.2.0 + See https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods + for more information. + Use :py:meth:`.multiline_textbbox` instead. Return the size of the given string, in pixels. diff --git a/docs/releasenotes/9.2.0.rst b/docs/releasenotes/9.2.0.rst index 9c102f177..6dbfa2702 100644 --- a/docs/releasenotes/9.2.0.rst +++ b/docs/releasenotes/9.2.0.rst @@ -59,6 +59,40 @@ Deprecated Use :py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength` =========================================================================== ============================================================================================================= +Previous code: + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + width, height = font.getsize("Hello world") + left, top = font.getoffset("Hello world") + + im = Image.new("RGB", (100, 100)) + draw = ImageDraw.Draw(im) + width, height = draw.textsize("Hello world") + + width, height = font.getsize_multiline("Hello\nworld") + width, height = draw.multiline_textsize("Hello\nworld") + +Use instead: + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + left, top, right, bottom = font.getbbox("Hello world") + width, height = right - left, bottom - top + + im = Image.new("RGB", (100, 100)) + draw = ImageDraw.Draw(im) + width = draw.textlength("Hello world") + + left, top, right, bottom = draw.multiline_textbbox((0, 0), "Hello\nworld") + width, height = right - left, bottom - top + API Additions ============= diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 9386d0086..3d1e4b0bf 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -139,6 +139,9 @@ class ImageFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. Returns width and height (in pixels) of given text. @@ -428,6 +431,9 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`getlength()` to measure the offset of following text with 1/64 pixel precision. Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor. @@ -498,6 +504,9 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`.ImageDraw.multiline_textbbox` instead. Returns width and height (in pixels) of given text if rendered in font @@ -557,6 +566,9 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`.getbbox` instead. Returns the offset of given text. This is the gap between the @@ -851,6 +863,9 @@ class TransposedFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. """ deprecate("getsize", 10, "getbbox or getlength") From 209ec9da470ccee20e23620e77972be14016dd7c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 6 Sep 2022 06:43:52 +1000 Subject: [PATCH 04/13] Use target --- docs/deprecations.rst | 2 ++ docs/reference/ImageDraw.rst | 6 ++---- src/PIL/ImageFont.py | 15 +++++---------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 92f116846..05de19c9e 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -178,6 +178,8 @@ Image.coerce_e This undocumented method has been deprecated and will be removed in Pillow 10 (2023-07-01). +.. _Font size and offset methods: + Font size and offset methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 623f601ef..61242b35a 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -443,8 +443,7 @@ Methods .. deprecated:: 9.2.0 - See https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods - for more information. + See :ref:`deprecations ` for more information. Use :py:meth:`textlength()` to measure the offset of following text with 1/64 pixel precision. @@ -498,8 +497,7 @@ Methods .. deprecated:: 9.2.0 - See https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods - for more information. + See :ref:`deprecations ` for more information. Use :py:meth:`.multiline_textbbox` instead. diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 3d1e4b0bf..c1b7435f3 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -139,8 +139,7 @@ class ImageFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. @@ -431,8 +430,7 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`getlength()` to measure the offset of following text with 1/64 pixel precision. @@ -504,8 +502,7 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`.ImageDraw.multiline_textbbox` instead. @@ -566,8 +563,7 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`.getbbox` instead. @@ -863,8 +859,7 @@ class TransposedFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. """ From 7359af91f05e72a20b35ae62cc0cdc95e5424ea1 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 6 Sep 2022 16:18:55 +1000 Subject: [PATCH 05/13] Rearranged text Co-authored-by: Hugo van Kemenade --- src/PIL/ImageFont.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index c1b7435f3..4df89755b 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -430,12 +430,12 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`getlength()` to measure the offset of following text with 1/64 pixel precision. Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor. + See :ref:`deprecations ` for more information. + Returns width and height (in pixels) of given text if rendered in font with provided direction, features, and language. @@ -502,10 +502,10 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`.ImageDraw.multiline_textbbox` instead. + See :ref:`deprecations ` for more information. + Returns width and height (in pixels) of given text if rendered in font with provided direction, features, and language, while respecting newline characters. @@ -563,10 +563,10 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`.getbbox` instead. + See :ref:`deprecations ` for more information. + Returns the offset of given text. This is the gap between the starting coordinate and the first marking. Note that this gap is included in the result of :py:func:`~PIL.ImageFont.FreeTypeFont.getsize`. @@ -859,9 +859,9 @@ class TransposedFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. + + See :ref:`deprecations ` for more information. """ deprecate("getsize", 10, "getbbox or getlength") with warnings.catch_warnings(): From bce9df62f1dd17c93c2615a24b34b22f2688cfac Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 6 Sep 2022 16:19:47 +1000 Subject: [PATCH 06/13] Rearranged text Co-authored-by: Hugo van Kemenade --- src/PIL/ImageFont.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 4df89755b..04da64b9f 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -139,10 +139,10 @@ class ImageFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. + See :ref:`deprecations ` for more information. + Returns width and height (in pixels) of given text. :param text: Text to measure. From 64d11d9123fe3de62a1ed50247635bb2743b9197 Mon Sep 17 00:00:00 2001 From: David Walker Date: Wed, 28 Sep 2022 20:08:49 -0700 Subject: [PATCH 07/13] Improve documentation for ImageDraw.rectangle and rounded_rectangle Fixes #1668 --- docs/reference/ImageDraw.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 64066528d..d102056a5 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -285,8 +285,8 @@ Methods Draws a rectangle. :param xy: Two points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The second point - is just outside the drawn rectangle. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The bounding box + is inclusive of both endpoints. :param outline: Color to use for the outline. :param fill: Color to use for the fill. :param width: The line width, in pixels. @@ -298,8 +298,8 @@ Methods Draws a rounded rectangle. :param xy: Two points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The second point - is just outside the drawn rectangle. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The bounding box + is inclusive of both endpoints. :param radius: Radius of the corners. :param outline: Color to use for the outline. :param fill: Color to use for the fill. From 15b2b7a9deff44e8c3bb31f65a726dc70e65f398 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Oct 2022 19:54:35 +1000 Subject: [PATCH 08/13] Added headings before listing options --- docs/handbook/image-file-formats.rst | 109 +++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index ff54853a3..dc629666c 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -31,6 +31,9 @@ BLP is the Blizzard Mipmap Format, a texture format used in World of Warcraft. Pillow supports reading ``JPEG`` Compressed or raw ``BLP1`` images, and all types of ``BLP2`` images. +Saving +~~~~~~ + Pillow supports writing BLP images. The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: @@ -46,6 +49,9 @@ or ``RGB`` data. 16-colour images are read as ``P`` images. 4-bit run-length enc is not supported. Support for reading 8-bit run-length encoding was added in Pillow 9.1.0. +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -78,6 +84,9 @@ EPS images. The EPS driver can read EPS images in ``L``, ``LAB``, ``RGB`` and than leaving them in the original color space. The EPS driver can write images in ``L``, ``RGB`` and ``CMYK`` modes. +Loading +~~~~~~~ + If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load` method with the following parameters to affect how Ghostscript renders the EPS @@ -134,6 +143,11 @@ To restore the default behavior, where ``P`` mode images are only converted to from PIL import GifImagePlugin GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST +.. _gif-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -171,6 +185,8 @@ to seek to the next frame (``im.seek(im.tell() + 1)``). ``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame. +.. _gif-saving: + Saving ~~~~~~ @@ -278,6 +294,11 @@ sets the following :py:attr:`~PIL.Image.Image.info` property: ask for ``(512, 512, 2)``, the final value of :py:attr:`~PIL.Image.Image.size` will be ``(1024, 1024)``). +.. _icns-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: **append_images** @@ -292,6 +313,11 @@ ICO ICO is used to store icons on Windows. The largest available icon is read. +.. _ico-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method supports the following options: **sizes** @@ -337,6 +363,11 @@ their original size while loading them. By default Pillow doesn't allow loading of truncated JPEG files, set :data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this. +.. _jpeg-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method may set the following :py:attr:`~PIL.Image.Image.info` properties if available: @@ -383,6 +414,10 @@ The :py:meth:`~PIL.Image.open` method may set the following .. versionadded:: 7.1.0 +.. _jpeg-saving: + +Saving +~~~~~~ The :py:meth:`~PIL.Image.Image.save` method supports the following options: @@ -464,6 +499,11 @@ itself. It is also possible to set ``reduce`` to the number of resolutions to discard (each one reduces the size of the resulting image by a factor of 2), and ``layers`` to specify the number of quality layers to load. +.. _jpeg-2000-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method supports the following options: **offset** @@ -575,6 +615,11 @@ called. By default Pillow doesn't allow loading of truncated PNG files, set :data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this. +.. _png-opening: + +Opening +~~~~~~~ + The :py:func:`~PIL.Image.open` function sets the following :py:attr:`~PIL.Image.Image.info` properties, when appropriate: @@ -613,6 +658,11 @@ decompression bombs. Additionally, the total size of all of the text chunks is limited to :data:`.PngImagePlugin.MAX_TEXT_MEMORY`, defaulting to 64MB. +.. _png-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method supports the following options: **optimize** @@ -803,6 +853,11 @@ Pillow also reads SPIDER stack files containing sequences of SPIDER images. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and random access is allowed. +.. _spider-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following attributes: **format** @@ -819,8 +874,10 @@ is provided for converting floating point data to byte data (mode ``L``):: im = Image.open("image001.spi").convert2byte() -Writing files in SPIDER format -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _spider-saving: + +Saving +~~~~~~ The extension of SPIDER files may be any 3 alphanumeric characters. Therefore the output format must be specified explicitly:: @@ -837,6 +894,11 @@ Pillow reads and writes TGA images containing ``L``, ``LA``, ``P``, ``RGB``, and ``RGBA`` data. Pillow can read and write both uncompressed and run-length encoded TGAs. +.. _tga-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: **compression** @@ -871,6 +933,11 @@ uncompressed files. support for reading Packbits, LZW and JPEG compressed TIFFs without using libtiff. +.. _tiff-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -922,8 +989,10 @@ and can be accessed in any order. ``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame. -Saving Tiff Images -~~~~~~~~~~~~~~~~~~ +.. _tiff-saving: + +Saving +~~~~~~ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: @@ -1035,6 +1104,11 @@ WebP Pillow reads and writes WebP files. The specifics of Pillow's capabilities with this format are currently undocumented. +.. _webp-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method supports the following options: **lossless** @@ -1058,7 +1132,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: the system WebP library was built with webpmux support. Saving sequences -~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~ .. note:: @@ -1173,6 +1247,11 @@ GBR The GBR decoder reads GIMP brush files, version 1 and 2. +.. _gbr-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -1188,6 +1267,11 @@ GD Pillow reads uncompressed GD2 files. Note that you must use :py:func:`PIL.GdImageFile.open` to read such a file. +.. _gd-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -1227,6 +1311,11 @@ image when first opened. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL methods may be used to read other pictures from the file. The pictures are zero-indexed and random access is supported. +.. _mpo-saving: + +Saving +~~~~~~ + When calling :py:meth:`~PIL.Image.Image.save` to write an MPO file, by default only the first frame of a multiframe image will be saved. If the ``save_all`` argument is present and true, then all frames will be saved, and the following @@ -1326,6 +1415,11 @@ XPM Pillow reads X pixmap files (mode ``P``) with 256 colors or less. +.. _xpm-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -1350,6 +1444,11 @@ Pillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4 files, using either JPEG or HEX encoding depending on the image mode (and whether JPEG support is available or not). +.. _pdf-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: **save_all** From 2a296be9861cb36096d236ab4a6c6c9964bffb79 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 13:37:56 +1100 Subject: [PATCH 09/13] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fb3879d79..c3e60acff 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Added reading of TIFF child images #6569 + [radarhere] + - Improved ImageOps palette handling #6596 [PososikTeam, radarhere] From 985fec2f563b719c2c8818014756ee23416d3644 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 16:15:29 +1100 Subject: [PATCH 10/13] Removed duplicate test --- Tests/test_file_eps.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 766c50649..cbb89aeda 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -124,14 +124,6 @@ def test_file_object(tmp_path): image1.save(fh, "EPS") -@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_iobase_object(tmp_path): - # issue 479 - with Image.open(FILE1) as image1: - with open(str(tmp_path / "temp_iobase.eps"), "wb") as fh: - image1.save(fh, "EPS") - - @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") def test_bytesio_object(): with open(FILE1, "rb") as f: From c259ac492fd69571c658cc3b094605d22c202f13 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 16:57:42 +1100 Subject: [PATCH 11/13] Parametrized tests --- Tests/test_features.py | 12 ++-- Tests/test_file_apng.py | 13 ++-- Tests/test_file_eps.py | 45 +++++++------- Tests/test_file_gif.py | 32 +++++----- Tests/test_file_jpeg.py | 61 ++++++++++--------- Tests/test_file_jpeg2k.py | 29 ++++----- Tests/test_file_libtiff.py | 9 +-- Tests/test_file_palm.py | 16 +---- Tests/test_file_pcx.py | 10 +-- Tests/test_file_pdf.py | 37 ++---------- Tests/test_file_tiff.py | 48 +++++++-------- Tests/test_font_pcf_charsets.py | 47 +++------------ Tests/test_image_reduce.py | 8 +-- Tests/test_image_resample.py | 72 +++++++++++----------- Tests/test_image_split.py | 21 +++---- Tests/test_imagedraw.py | 104 +++++++------------------------- Tests/test_imagedraw2.py | 47 +++------------ Tests/test_imageenhance.py | 20 +++--- Tests/test_imagefont.py | 34 +++++------ Tests/test_imagemorph.py | 16 ++--- Tests/test_imageshow.py | 40 ++++++------ Tests/test_imagetk.py | 36 +++++------ Tests/test_mode_i16.py | 73 ++++++++++------------ Tests/test_numpy.py | 30 +++++---- Tests/test_pickle.py | 8 +-- 25 files changed, 348 insertions(+), 520 deletions(-) diff --git a/Tests/test_features.py b/Tests/test_features.py index 284f72205..c4e9cd368 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -70,14 +70,14 @@ def test_libimagequant_version(): assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant")) -def test_check_modules(): - for feature in features.modules: - assert features.check_module(feature) in [True, False] +@pytest.mark.parametrize("feature", features.modules) +def test_check_modules(feature): + assert features.check_module(feature) in [True, False] -def test_check_codecs(): - for feature in features.codecs: - assert features.check_codec(feature) in [True, False] +@pytest.mark.parametrize("feature", features.codecs) +def test_check_codecs(feature): + assert features.check_codec(feature) in [True, False] def test_check_warns_on_nonexistent(): diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 0ff05f608..cdaad5940 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -39,13 +39,12 @@ def test_apng_basic(): assert im.getpixel((64, 32)) == (0, 255, 0, 255) -def test_apng_fdat(): - with Image.open("Tests/images/apng/split_fdat.png") as im: - im.seek(im.n_frames - 1) - assert im.getpixel((0, 0)) == (0, 255, 0, 255) - assert im.getpixel((64, 32)) == (0, 255, 0, 255) - - with Image.open("Tests/images/apng/split_fdat_zero_chunk.png") as im: +@pytest.mark.parametrize( + "filename", + ("Tests/images/apng/split_fdat.png", "Tests/images/apng/split_fdat_zero_chunk.png"), +) +def test_apng_fdat(filename): + with Image.open(filename) as im: im.seek(im.n_frames - 1) assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index cbb89aeda..015dda992 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -195,25 +195,23 @@ def test_render_scale2(): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_resize(): - files = [FILE1, FILE2, "Tests/images/illu10_preview.eps"] - for fn in files: - with Image.open(fn) as im: - new_size = (100, 100) - im = im.resize(new_size) - assert im.size == new_size +@pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps")) +def test_resize(filename): + with Image.open(filename) as im: + new_size = (100, 100) + im = im.resize(new_size) + assert im.size == new_size @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_thumbnail(): +@pytest.mark.parametrize("filename", (FILE1, FILE2)) +def test_thumbnail(filename): # Issue #619 # Arrange - files = [FILE1, FILE2] - for fn in files: - with Image.open(FILE1) as im: - new_size = (100, 100) - im.thumbnail(new_size) - assert max(im.size) == max(new_size) + with Image.open(filename) as im: + new_size = (100, 100) + im.thumbnail(new_size) + assert max(im.size) == max(new_size) def test_read_binary_preview(): @@ -258,20 +256,19 @@ def test_readline(tmp_path): _test_readline_file_psfile(s, ending) -def test_open_eps(): - # https://github.com/python-pillow/Pillow/issues/1104 - # Arrange - FILES = [ +@pytest.mark.parametrize( + "filename", + ( "Tests/images/illu10_no_preview.eps", "Tests/images/illu10_preview.eps", "Tests/images/illuCS6_no_preview.eps", "Tests/images/illuCS6_preview.eps", - ] - - # Act / Assert - for filename in FILES: - with Image.open(filename) as img: - assert img.mode == "RGB" + ), +) +def test_open_eps(filename): + # https://github.com/python-pillow/Pillow/issues/1104 + with Image.open(filename) as img: + assert img.mode == "RGB" @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 4e967faec..637be2be4 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -793,24 +793,24 @@ def test_identical_frames(tmp_path): assert reread.info["duration"] == 4500 -def test_identical_frames_to_single_frame(tmp_path): - for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500): - out = str(tmp_path / "temp.gif") - im_list = [ - Image.new("L", (100, 100), "#000"), - Image.new("L", (100, 100), "#000"), - Image.new("L", (100, 100), "#000"), - ] +@pytest.mark.parametrize( + "duration", ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500) +) +def test_identical_frames_to_single_frame(duration, tmp_path): + out = str(tmp_path / "temp.gif") + im_list = [ + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#000"), + ] - im_list[0].save( - out, save_all=True, append_images=im_list[1:], duration=duration - ) - with Image.open(out) as reread: - # Assert that all frames were combined - assert reread.n_frames == 1 + im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration) + with Image.open(out) as reread: + # Assert that all frames were combined + assert reread.n_frames == 1 - # Assert that the new duration is the total of the identical frames - assert reread.info["duration"] == 8500 + # Assert that the new duration is the total of the identical frames + assert reread.info["duration"] == 8500 def test_number_of_loops(tmp_path): diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 12edd7582..adbb72aa5 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -150,27 +150,30 @@ class TestFileJpeg: assert not im1.info.get("icc_profile") assert im2.info.get("icc_profile") - def test_icc_big(self): + @pytest.mark.parametrize( + "n", + ( + 0, + 1, + 3, + 4, + 5, + 65533 - 14, # full JPEG marker block + 65533 - 14 + 1, # full block plus one byte + ImageFile.MAXBLOCK, # full buffer block + ImageFile.MAXBLOCK + 1, # full buffer block plus one byte + ImageFile.MAXBLOCK * 4 + 3, # large block + ), + ) + def test_icc_big(self, n): # Make sure that the "extra" support handles large blocks - def test(n): - # The ICC APP marker can store 65519 bytes per marker, so - # using a 4-byte test code should allow us to detect out of - # order issues. - icc_profile = (b"Test" * int(n / 4 + 1))[:n] - assert len(icc_profile) == n # sanity - im1 = self.roundtrip(hopper(), icc_profile=icc_profile) - assert im1.info.get("icc_profile") == (icc_profile or None) - - test(0) - test(1) - test(3) - test(4) - test(5) - test(65533 - 14) # full JPEG marker block - test(65533 - 14 + 1) # full block plus one byte - test(ImageFile.MAXBLOCK) # full buffer block - test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte - test(ImageFile.MAXBLOCK * 4 + 3) # large block + # The ICC APP marker can store 65519 bytes per marker, so + # using a 4-byte test code should allow us to detect out of + # order issues. + icc_profile = (b"Test" * int(n / 4 + 1))[:n] + assert len(icc_profile) == n # sanity + im1 = self.roundtrip(hopper(), icc_profile=icc_profile) + assert im1.info.get("icc_profile") == (icc_profile or None) @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" @@ -649,19 +652,19 @@ class TestFileJpeg: # Assert assert im.format == "JPEG" - def test_save_correct_modes(self): + @pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr")) + def test_save_correct_modes(self, mode): out = BytesIO() - for mode in ["1", "L", "RGB", "RGBX", "CMYK", "YCbCr"]: - img = Image.new(mode, (20, 20)) - img.save(out, "JPEG") + img = Image.new(mode, (20, 20)) + img.save(out, "JPEG") - def test_save_wrong_modes(self): + @pytest.mark.parametrize("mode", ("LA", "La", "RGBA", "RGBa", "P")) + def test_save_wrong_modes(self, mode): # ref https://github.com/python-pillow/Pillow/issues/2005 out = BytesIO() - for mode in ["LA", "La", "RGBA", "RGBa", "P"]: - img = Image.new(mode, (20, 20)) - with pytest.raises(OSError): - img.save(out, "JPEG") + img = Image.new(mode, (20, 20)) + with pytest.raises(OSError): + img.save(out, "JPEG") def test_save_tiff_with_dpi(self, tmp_path): # Arrange diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 7942d6b9a..cd142e67f 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -126,14 +126,14 @@ def test_prog_res_rt(): assert_image_equal(im, test_card) -def test_default_num_resolutions(): - for num_resolutions in range(2, 6): - d = 1 << (num_resolutions - 1) - im = test_card.resize((d - 1, d - 1)) - with pytest.raises(OSError): - roundtrip(im, num_resolutions=num_resolutions) - reloaded = roundtrip(im) - assert_image_equal(im, reloaded) +@pytest.mark.parametrize("num_resolutions", range(2, 6)) +def test_default_num_resolutions(num_resolutions): + d = 1 << (num_resolutions - 1) + im = test_card.resize((d - 1, d - 1)) + with pytest.raises(OSError): + roundtrip(im, num_resolutions=num_resolutions) + reloaded = roundtrip(im) + assert_image_equal(im, reloaded) def test_reduce(): @@ -266,14 +266,11 @@ def test_rgba(): assert jp2.mode == "RGBA" -def test_16bit_monochrome_has_correct_mode(): - with Image.open("Tests/images/16bit.cropped.j2k") as j2k: - j2k.load() - assert j2k.mode == "I;16" - - with Image.open("Tests/images/16bit.cropped.jp2") as jp2: - jp2.load() - assert jp2.mode == "I;16" +@pytest.mark.parametrize("ext", (".j2k", ".jp2")) +def test_16bit_monochrome_has_correct_mode(ext): + with Image.open("Tests/images/16bit.cropped" + ext) as im: + im.load() + assert im.mode == "I;16" def test_16bit_monochrome_jp2_like_tiff(): diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 86a0fda04..6015a8fec 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -509,7 +509,8 @@ class TestFileLibTiff(LibTiffTestCase): # colormap/palette tag assert len(reloaded.tag_v2[320]) == 768 - def xtest_bw_compression_w_rgb(self, tmp_path): + @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) + def xtest_bw_compression_w_rgb(self, compression, tmp_path): """This test passes, but when running all tests causes a failure due to output on stderr from the error thrown by libtiff. We need to capture that but not now""" @@ -518,11 +519,7 @@ class TestFileLibTiff(LibTiffTestCase): out = str(tmp_path / "temp.tif") with pytest.raises(OSError): - im.save(out, compression="tiff_ccitt") - with pytest.raises(OSError): - im.save(out, compression="group3") - with pytest.raises(OSError): - im.save(out, compression="group4") + im.save(out, compression=compression) def test_fp_leak(self): im = Image.open("Tests/images/hopper_g4_500.tif") diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index e1c1c361b..be7c8d0c8 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -63,19 +63,7 @@ def test_p_mode(tmp_path): roundtrip(tmp_path, mode) -def test_l_oserror(tmp_path): - # Arrange - mode = "L" - - # Act / Assert - with pytest.raises(OSError): - helper_save_as_palm(tmp_path, mode) - - -def test_rgb_oserror(tmp_path): - # Arrange - mode = "RGB" - - # Act / Assert +@pytest.mark.parametrize("mode", ("L", "RGB")) +def test_oserror(tmp_path, mode): with pytest.raises(OSError): helper_save_as_palm(tmp_path, mode) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index ba6663cd3..485adf785 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -39,14 +39,14 @@ def test_invalid_file(): PcxImagePlugin.PcxImageFile(invalid_file) -def test_odd(tmp_path): +@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB")) +def test_odd(tmp_path, mode): # See issue #523, odd sized images should have a stride that's even. # Not that ImageMagick or GIMP write PCX that way. # We were not handling properly. - for mode in ("1", "L", "P", "RGB"): - # larger, odd sized images are better here to ensure that - # we handle interrupted scan lines properly. - _roundtrip(tmp_path, hopper(mode).resize((511, 511))) + # larger, odd sized images are better here to ensure that + # we handle interrupted scan lines properly. + _roundtrip(tmp_path, hopper(mode).resize((511, 511))) def test_odd_read(): diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index b5273353c..4129e8783 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -37,6 +37,11 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs): return outfile +@pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK")) +def test_save(tmp_path, mode): + helper_save_as_pdf(tmp_path, mode) + + @pytest.mark.valgrind_known_error(reason="Temporary skip") def test_monochrome(tmp_path): # Arrange @@ -47,38 +52,6 @@ def test_monochrome(tmp_path): assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000) -def test_greyscale(tmp_path): - # Arrange - mode = "L" - - # Act / Assert - helper_save_as_pdf(tmp_path, mode) - - -def test_rgb(tmp_path): - # Arrange - mode = "RGB" - - # Act / Assert - helper_save_as_pdf(tmp_path, mode) - - -def test_p_mode(tmp_path): - # Arrange - mode = "P" - - # Act / Assert - helper_save_as_pdf(tmp_path, mode) - - -def test_cmyk_mode(tmp_path): - # Arrange - mode = "CMYK" - - # Act / Assert - helper_save_as_pdf(tmp_path, mode) - - def test_unsupported_mode(tmp_path): im = hopper("LA") outfile = str(tmp_path / "temp_LA.pdf") diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index ac0bd7f60..1a5ba594f 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -311,14 +311,17 @@ class TestFileTiff: with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"): pass - def test_n_frames(self): - for path, n_frames in [ - ["Tests/images/multipage-lastframe.tif", 1], - ["Tests/images/multipage.tiff", 3], - ]: - with Image.open(path) as im: - assert im.n_frames == n_frames - assert im.is_animated == (n_frames != 1) + @pytest.mark.parametrize( + "path, n_frames", + ( + ("Tests/images/multipage-lastframe.tif", 1), + ("Tests/images/multipage.tiff", 3), + ), + ) + def test_n_frames(self, path, n_frames): + with Image.open(path) as im: + assert im.n_frames == n_frames + assert im.is_animated == (n_frames != 1) def test_eoferror(self): with Image.open("Tests/images/multipage-lastframe.tif") as im: @@ -434,12 +437,12 @@ class TestFileTiff: len_after = len(dict(im.ifd)) assert len_before == len_after + 1 - def test_load_byte(self): - for legacy_api in [False, True]: - ifd = TiffImagePlugin.ImageFileDirectory_v2() - data = b"abc" - ret = ifd.load_byte(data, legacy_api) - assert ret == b"abc" + @pytest.mark.parametrize("legacy_api", (False, True)) + def test_load_byte(self, legacy_api): + ifd = TiffImagePlugin.ImageFileDirectory_v2() + data = b"abc" + ret = ifd.load_byte(data, legacy_api) + assert ret == b"abc" def test_load_string(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() @@ -685,18 +688,15 @@ class TestFileTiff: with Image.open(outfile) as reloaded: assert_image_equal_tofile(reloaded, infile) - def test_palette(self, tmp_path): - def roundtrip(mode): - outfile = str(tmp_path / "temp.tif") + @pytest.mark.parametrize("mode", ("P", "PA")) + def test_palette(self, mode, tmp_path): + outfile = str(tmp_path / "temp.tif") - im = hopper(mode) - im.save(outfile) + im = hopper(mode) + im.save(outfile) - with Image.open(outfile) as reloaded: - assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) - - for mode in ["P", "PA"]: - roundtrip(mode) + with Image.open(outfile) as reloaded: + assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) def test_tiff_save_all(self): mp = BytesIO() diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index 4477ee29d..664663fd6 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -1,5 +1,7 @@ import os +import pytest + from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile from .helper import ( @@ -59,23 +61,13 @@ def save_font(request, tmp_path, encoding): return tempname -def _test_sanity(request, tmp_path, encoding): +@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) +def test_sanity(request, tmp_path, encoding): save_font(request, tmp_path, encoding) -def test_sanity_iso8859_1(request, tmp_path): - _test_sanity(request, tmp_path, "iso8859-1") - - -def test_sanity_iso8859_2(request, tmp_path): - _test_sanity(request, tmp_path, "iso8859-2") - - -def test_sanity_cp1250(request, tmp_path): - _test_sanity(request, tmp_path, "cp1250") - - -def _test_draw(request, tmp_path, encoding): +@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) +def test_draw(request, tmp_path, encoding): tempname = save_font(request, tmp_path, encoding) font = ImageFont.load(tempname) im = Image.new("L", (150, 30), "white") @@ -85,19 +77,8 @@ def _test_draw(request, tmp_path, encoding): assert_image_similar_tofile(im, charsets[encoding]["image1"], 0) -def test_draw_iso8859_1(request, tmp_path): - _test_draw(request, tmp_path, "iso8859-1") - - -def test_draw_iso8859_2(request, tmp_path): - _test_draw(request, tmp_path, "iso8859-2") - - -def test_draw_cp1250(request, tmp_path): - _test_draw(request, tmp_path, "cp1250") - - -def _test_textsize(request, tmp_path, encoding): +@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) +def test_textsize(request, tmp_path, encoding): tempname = save_font(request, tmp_path, encoding) font = ImageFont.load(tempname) for i in range(255): @@ -112,15 +93,3 @@ def _test_textsize(request, tmp_path, encoding): msg = message[: i + 1] assert font.getlength(msg) == len(msg) * 10 assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20) - - -def test_textsize_iso8859_1(request, tmp_path): - _test_textsize(request, tmp_path, "iso8859-1") - - -def test_textsize_iso8859_2(request, tmp_path): - _test_textsize(request, tmp_path, "iso8859-2") - - -def test_textsize_cp1250(request, tmp_path): - _test_textsize(request, tmp_path, "cp1250") diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 801161511..ae8d740a0 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -196,11 +196,11 @@ def assert_compare_images(a, b, max_average_diff, max_diff=255): ) -def test_mode_L(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_L(factor): im = get_image("L") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 5ce98a235..53ceb6df0 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -554,44 +554,48 @@ class TestCoreResampleBox: # check that the difference at least that much assert_image_similar(res, im.crop(box), 20, f">>> {size} {box}") - def test_skip_horizontal(self): + @pytest.mark.parametrize( + "flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC) + ) + def test_skip_horizontal(self, flt): # Can skip resize for one dimension im = hopper() - for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]: - for size, box in [ - ((40, 50), (0, 0, 40, 90)), - ((40, 50), (0, 20, 40, 90)), - ((40, 50), (10, 0, 50, 90)), - ((40, 50), (10, 20, 50, 90)), - ]: - res = im.resize(size, flt, box) - assert res.size == size - # Borders should be slightly different - assert_image_similar( - res, - im.crop(box).resize(size, flt), - 0.4, - f">>> {size} {box} {flt}", - ) + for size, box in [ + ((40, 50), (0, 0, 40, 90)), + ((40, 50), (0, 20, 40, 90)), + ((40, 50), (10, 0, 50, 90)), + ((40, 50), (10, 20, 50, 90)), + ]: + res = im.resize(size, flt, box) + assert res.size == size + # Borders should be slightly different + assert_image_similar( + res, + im.crop(box).resize(size, flt), + 0.4, + f">>> {size} {box} {flt}", + ) - def test_skip_vertical(self): + @pytest.mark.parametrize( + "flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC) + ) + def test_skip_vertical(self, flt): # Can skip resize for one dimension im = hopper() - for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]: - for size, box in [ - ((40, 50), (0, 0, 90, 50)), - ((40, 50), (20, 0, 90, 50)), - ((40, 50), (0, 10, 90, 60)), - ((40, 50), (20, 10, 90, 60)), - ]: - res = im.resize(size, flt, box) - assert res.size == size - # Borders should be slightly different - assert_image_similar( - res, - im.crop(box).resize(size, flt), - 0.4, - f">>> {size} {box} {flt}", - ) + for size, box in [ + ((40, 50), (0, 0, 90, 50)), + ((40, 50), (20, 0, 90, 50)), + ((40, 50), (0, 10, 90, 60)), + ((40, 50), (20, 10, 90, 60)), + ]: + res = im.resize(size, flt, box) + assert res.size == size + # Borders should be slightly different + assert_image_similar( + res, + im.crop(box).resize(size, flt), + 0.4, + f">>> {size} {box} {flt}", + ) diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index fbed276b8..5cb7c9a8b 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -1,3 +1,5 @@ +import pytest + from PIL import Image, features from .helper import assert_image_equal, hopper @@ -29,19 +31,12 @@ def test_split(): assert split("YCbCr") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)] -def test_split_merge(): - def split_merge(mode): - return Image.merge(mode, hopper(mode).split()) - - assert_image_equal(hopper("1"), split_merge("1")) - assert_image_equal(hopper("L"), split_merge("L")) - assert_image_equal(hopper("I"), split_merge("I")) - assert_image_equal(hopper("F"), split_merge("F")) - assert_image_equal(hopper("P"), split_merge("P")) - assert_image_equal(hopper("RGB"), split_merge("RGB")) - assert_image_equal(hopper("RGBA"), split_merge("RGBA")) - assert_image_equal(hopper("CMYK"), split_merge("CMYK")) - assert_image_equal(hopper("YCbCr"), split_merge("YCbCr")) +@pytest.mark.parametrize( + "mode", ("1", "L", "I", "F", "P", "RGB", "RGBA", "CMYK", "YCbCr") +) +def test_split_merge(mode): + expected = Image.merge(mode, hopper(mode).split()) + assert_image_equal(hopper(mode), expected) def test_split_open(tmp_path): diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index d1dd1e47c..76b7c65cc 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -64,7 +64,9 @@ def test_mode_mismatch(): ImageDraw.ImageDraw(im, mode="L") -def helper_arc(bbox, start, end): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4))) +def test_arc(bbox, start, end): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -76,16 +78,6 @@ def helper_arc(bbox, start, end): assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1) -def test_arc1(): - helper_arc(BBOX1, 0, 180) - helper_arc(BBOX1, 0.5, 180.4) - - -def test_arc2(): - helper_arc(BBOX2, 0, 180) - helper_arc(BBOX2, 0.5, 180.4) - - def test_arc_end_le_start(): # Arrange im = Image.new("RGB", (W, H)) @@ -192,29 +184,21 @@ def test_bitmap(): assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png") -def helper_chord(mode, bbox, start, end): +@pytest.mark.parametrize("mode", ("RGB", "L")) +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_chord(mode, bbox): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) expected = f"Tests/images/imagedraw_chord_{mode}.png" # Act - draw.chord(bbox, start, end, fill="red", outline="yellow") + draw.chord(bbox, 0, 180, fill="red", outline="yellow") # Assert assert_image_similar_tofile(im, expected, 1) -def test_chord1(): - for mode in ["RGB", "L"]: - helper_chord(mode, BBOX1, 0, 180) - - -def test_chord2(): - for mode in ["RGB", "L"]: - helper_chord(mode, BBOX2, 0, 180) - - def test_chord_width(): # Arrange im = Image.new("RGB", (W, H)) @@ -263,7 +247,9 @@ def test_chord_too_fat(): assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_too_fat.png") -def helper_ellipse(mode, bbox): +@pytest.mark.parametrize("mode", ("RGB", "L")) +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_ellipse(mode, bbox): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -276,16 +262,6 @@ def helper_ellipse(mode, bbox): assert_image_similar_tofile(im, expected, 1) -def test_ellipse1(): - for mode in ["RGB", "L"]: - helper_ellipse(mode, BBOX1) - - -def test_ellipse2(): - for mode in ["RGB", "L"]: - helper_ellipse(mode, BBOX2) - - def test_ellipse_translucent(): # Arrange im = Image.new("RGB", (W, H)) @@ -405,7 +381,8 @@ def test_ellipse_various_sizes_filled(): ) -def helper_line(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_line(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -417,14 +394,6 @@ def helper_line(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") -def test_line1(): - helper_line(POINTS1) - - -def test_line2(): - helper_line(POINTS2) - - def test_shape1(): # Arrange im = Image.new("RGB", (100, 100), "white") @@ -484,7 +453,9 @@ def test_transform(): assert_image_equal(im, expected) -def helper_pieslice(bbox, start, end): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2))) +def test_pieslice(bbox, start, end): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -496,16 +467,6 @@ def helper_pieslice(bbox, start, end): assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1) -def test_pieslice1(): - helper_pieslice(BBOX1, -92, 46) - helper_pieslice(BBOX1, -92.2, 46.2) - - -def test_pieslice2(): - helper_pieslice(BBOX2, -92, 46) - helper_pieslice(BBOX2, -92.2, 46.2) - - def test_pieslice_width(): # Arrange im = Image.new("RGB", (W, H)) @@ -585,7 +546,8 @@ def test_pieslice_no_spikes(): assert_image_equal(im, im_pre_erase) -def helper_point(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_point(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -597,15 +559,8 @@ def helper_point(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png") -def test_point1(): - helper_point(POINTS1) - - -def test_point2(): - helper_point(POINTS2) - - -def helper_polygon(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_polygon(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -617,14 +572,6 @@ def helper_polygon(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") -def test_polygon1(): - helper_polygon(POINTS1) - - -def test_polygon2(): - helper_polygon(POINTS2) - - @pytest.mark.parametrize("mode", ("RGB", "L")) def test_polygon_kite(mode): # Test drawing lines of different gradients (dx>dy, dy>dx) and @@ -682,7 +629,8 @@ def test_polygon_translucent(): assert_image_equal_tofile(im, expected) -def helper_rectangle(bbox): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_rectangle(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -694,14 +642,6 @@ def helper_rectangle(bbox): assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") -def test_rectangle1(): - helper_rectangle(BBOX1) - - -def test_rectangle2(): - helper_rectangle(BBOX2) - - def test_big_rectangle(): # Test drawing a rectangle bigger than the image # Arrange @@ -1503,7 +1443,7 @@ def test_discontiguous_corners_polygon(): assert_image_similar_tofile(img, expected, 1) -def test_polygon(): +def test_polygon2(): im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red") diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index e4e8a38cb..6fc829f1a 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -52,27 +52,19 @@ def test_sanity(): draw.line(list(range(10)), pen) -def helper_ellipse(mode, bbox): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_ellipse(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) pen = ImageDraw2.Pen("blue", width=2) brush = ImageDraw2.Brush("green") - expected = f"Tests/images/imagedraw_ellipse_{mode}.png" # Act draw.ellipse(bbox, pen, brush) # Assert - assert_image_similar_tofile(im, expected, 1) - - -def test_ellipse1(): - helper_ellipse("RGB", BBOX1) - - -def test_ellipse2(): - helper_ellipse("RGB", BBOX2) + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_RGB.png", 1) def test_ellipse_edge(): @@ -88,7 +80,8 @@ def test_ellipse_edge(): assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1) -def helper_line(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_line(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -101,14 +94,6 @@ def helper_line(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") -def test_line1_pen(): - helper_line(POINTS1) - - -def test_line2_pen(): - helper_line(POINTS2) - - def test_line_pen_as_brush(): # Arrange im = Image.new("RGB", (W, H)) @@ -124,7 +109,8 @@ def test_line_pen_as_brush(): assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") -def helper_polygon(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_polygon(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -138,15 +124,8 @@ def helper_polygon(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") -def test_polygon1(): - helper_polygon(POINTS1) - - -def test_polygon2(): - helper_polygon(POINTS2) - - -def helper_rectangle(bbox): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_rectangle(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -160,14 +139,6 @@ def helper_rectangle(bbox): assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") -def test_rectangle1(): - helper_rectangle(BBOX1) - - -def test_rectangle2(): - helper_rectangle(BBOX2) - - def test_big_rectangle(): # Test drawing a rectangle bigger than the image # Arrange diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 8bc94401e..221ef8cdb 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -1,3 +1,5 @@ +import pytest + from PIL import Image, ImageEnhance from .helper import assert_image_equal, hopper @@ -39,17 +41,17 @@ def _check_alpha(im, original, op, amount): ) -def test_alpha(): +@pytest.mark.parametrize("op", ("Color", "Brightness", "Contrast", "Sharpness")) +def test_alpha(op): # Issue https://github.com/python-pillow/Pillow/issues/899 # Is alpha preserved through image enhancement? original = _half_transparent_image() - for op in ["Color", "Brightness", "Contrast", "Sharpness"]: - for amount in [0, 0.5, 1.0]: - _check_alpha( - getattr(ImageEnhance, op)(original).enhance(amount), - original, - op, - amount, - ) + for amount in [0, 0.5, 1.0]: + _check_alpha( + getattr(ImageEnhance, op)(original).enhance(amount), + original, + op, + amount, + ) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 09e5370e2..a374e24c5 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -632,24 +632,24 @@ def test_imagefont_getters(font): assert len(log) == 11 -def test_getsize_stroke(font): - for stroke_width in [0, 2]: - assert font.getbbox("A", stroke_width=stroke_width) == ( - 0 - stroke_width, - 4 - stroke_width, - 12 + stroke_width, - 16 + stroke_width, +@pytest.mark.parametrize("stroke_width", (0, 2)) +def test_getsize_stroke(font, stroke_width): + assert font.getbbox("A", stroke_width=stroke_width) == ( + 0 - stroke_width, + 4 - stroke_width, + 12 + stroke_width, + 16 + stroke_width, + ) + with pytest.warns(DeprecationWarning) as log: + assert font.getsize("A", stroke_width=stroke_width) == ( + 12 + stroke_width * 2, + 16 + stroke_width * 2, ) - with pytest.warns(DeprecationWarning) as log: - assert font.getsize("A", stroke_width=stroke_width) == ( - 12 + stroke_width * 2, - 16 + stroke_width * 2, - ) - assert font.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width) == ( - 48 + stroke_width * 2, - 36 + stroke_width * 4, - ) - assert len(log) == 2 + assert font.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width) == ( + 48 + stroke_width * 2, + 36 + stroke_width * 4, + ) + assert len(log) == 2 def test_complex_font_settings(): diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 6de953068..29c71f917 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -65,14 +65,16 @@ def create_lut(): # create_lut() -def test_lut(): - for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): - lb = ImageMorph.LutBuilder(op_name=op) - assert lb.get_lut() is None +@pytest.mark.parametrize( + "op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge") +) +def test_lut(op): + lb = ImageMorph.LutBuilder(op_name=op) + assert lb.get_lut() is None - lut = lb.build_lut() - with open(f"Tests/images/{op}.lut", "rb") as f: - assert lut == bytearray(f.read()) + lut = lb.build_lut() + with open(f"Tests/images/{op}.lut", "rb") as f: + assert lut == bytearray(f.read()) def test_no_operator_loaded(): diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 55d7c9479..3e147a9ef 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -45,10 +45,10 @@ def test_viewer_show(order): not on_ci() or is_win32(), reason="Only run on CIs; hangs on Windows CIs", ) -def test_show(): - for mode in ("1", "I;16", "LA", "RGB", "RGBA"): - im = hopper(mode) - assert ImageShow.show(im) +@pytest.mark.parametrize("mode", ("1", "I;16", "LA", "RGB", "RGBA")) +def test_show(mode): + im = hopper(mode) + assert ImageShow.show(im) def test_show_without_viewers(): @@ -70,12 +70,12 @@ def test_viewer(): viewer.get_command(None) -def test_viewers(): - for viewer in ImageShow._viewers: - try: - viewer.get_command("test.jpg") - except NotImplementedError: - pass +@pytest.mark.parametrize("viewer", ImageShow._viewers) +def test_viewers(viewer): + try: + viewer.get_command("test.jpg") + except NotImplementedError: + pass def test_ipythonviewer(): @@ -95,14 +95,14 @@ def test_ipythonviewer(): not on_ci() or is_win32(), reason="Only run on CIs; hangs on Windows CIs", ) -def test_file_deprecated(tmp_path): +@pytest.mark.parametrize("viewer", ImageShow._viewers) +def test_file_deprecated(tmp_path, viewer): f = str(tmp_path / "temp.jpg") - for viewer in ImageShow._viewers: - hopper().save(f) - with pytest.warns(DeprecationWarning): - try: - viewer.show_file(file=f) - except NotImplementedError: - pass - with pytest.raises(TypeError): - viewer.show_file() + hopper().save(f) + with pytest.warns(DeprecationWarning): + try: + viewer.show_file(file=f) + except NotImplementedError: + pass + with pytest.raises(TypeError): + viewer.show_file() diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index a848c786f..995d0ee1f 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -54,19 +54,19 @@ def test_kw(): assert im is None -def test_photoimage(): - for mode in TK_MODES: - # test as image: - im = hopper(mode) +@pytest.mark.parametrize("mode", TK_MODES) +def test_photoimage(mode): + # test as image: + im = hopper(mode) - # this should not crash - im_tk = ImageTk.PhotoImage(im) + # this should not crash + im_tk = ImageTk.PhotoImage(im) - assert im_tk.width() == im.width - assert im_tk.height() == im.height + assert im_tk.width() == im.width + assert im_tk.height() == im.height - reloaded = ImageTk.getimage(im_tk) - assert_image_equal(reloaded, im.convert("RGBA")) + reloaded = ImageTk.getimage(im_tk) + assert_image_equal(reloaded, im.convert("RGBA")) def test_photoimage_apply_transparency(): @@ -76,17 +76,17 @@ def test_photoimage_apply_transparency(): assert_image_equal(reloaded, im.convert("RGBA")) -def test_photoimage_blank(): +@pytest.mark.parametrize("mode", TK_MODES) +def test_photoimage_blank(mode): # test a image using mode/size: - for mode in TK_MODES: - im_tk = ImageTk.PhotoImage(mode, (100, 100)) + im_tk = ImageTk.PhotoImage(mode, (100, 100)) - assert im_tk.width() == 100 - assert im_tk.height() == 100 + assert im_tk.width() == 100 + assert im_tk.height() == 100 - im = Image.new(mode, (100, 100)) - reloaded = ImageTk.getimage(im_tk) - assert_image_equal(reloaded.convert(mode), im) + im = Image.new(mode, (100, 100)) + reloaded = ImageTk.getimage(im_tk) + assert_image_equal(reloaded.convert(mode), im) def test_box_deprecation(): diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index 6e8a2ac58..efcdab9ec 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -1,3 +1,5 @@ +import pytest + from PIL import Image from .helper import hopper @@ -20,65 +22,56 @@ def verify(im1): ), f"got {repr(p1)} from mode {im1.mode} at {xy}, expected {repr(p2)}" -def test_basic(tmp_path): +@pytest.mark.parametrize("mode", ("L", "I;16", "I;16B", "I;16L", "I")) +def test_basic(tmp_path, mode): # PIL 1.1 has limited support for 16-bit image data. Check that # create/copy/transform and save works as expected. - def basic(mode): + im_in = original.convert(mode) + verify(im_in) - im_in = original.convert(mode) - verify(im_in) + w, h = im_in.size - w, h = im_in.size + im_out = im_in.copy() + verify(im_out) # copy - im_out = im_in.copy() - verify(im_out) # copy + im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h)) + verify(im_out) # transform - im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h)) - verify(im_out) # transform + filename = str(tmp_path / "temp.im") + im_in.save(filename) - filename = str(tmp_path / "temp.im") - im_in.save(filename) - - with Image.open(filename) as im_out: - - verify(im_in) - verify(im_out) - - im_out = im_in.crop((0, 0, w, h)) - verify(im_out) - - im_out = Image.new(mode, (w, h), None) - im_out.paste(im_in.crop((0, 0, w // 2, h)), (0, 0)) - im_out.paste(im_in.crop((w // 2, 0, w, h)), (w // 2, 0)) + with Image.open(filename) as im_out: verify(im_in) verify(im_out) - im_in = Image.new(mode, (1, 1), 1) - assert im_in.getpixel((0, 0)) == 1 + im_out = im_in.crop((0, 0, w, h)) + verify(im_out) - im_in.putpixel((0, 0), 2) - assert im_in.getpixel((0, 0)) == 2 + im_out = Image.new(mode, (w, h), None) + im_out.paste(im_in.crop((0, 0, w // 2, h)), (0, 0)) + im_out.paste(im_in.crop((w // 2, 0, w, h)), (w // 2, 0)) - if mode == "L": - maximum = 255 - else: - maximum = 32767 + verify(im_in) + verify(im_out) - im_in = Image.new(mode, (1, 1), 256) - assert im_in.getpixel((0, 0)) == min(256, maximum) + im_in = Image.new(mode, (1, 1), 1) + assert im_in.getpixel((0, 0)) == 1 - im_in.putpixel((0, 0), 512) - assert im_in.getpixel((0, 0)) == min(512, maximum) + im_in.putpixel((0, 0), 2) + assert im_in.getpixel((0, 0)) == 2 - basic("L") + if mode == "L": + maximum = 255 + else: + maximum = 32767 - basic("I;16") - basic("I;16B") - basic("I;16L") + im_in = Image.new(mode, (1, 1), 256) + assert im_in.getpixel((0, 0)) == min(256, maximum) - basic("I") + im_in.putpixel((0, 0), 512) + assert im_in.getpixel((0, 0)) == min(512, maximum) def test_tobytes(): diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 9735837bc..185e477ec 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -137,19 +137,9 @@ def test_save_tiff_uint16(): assert img_px[0, 0] == pixel_value -def test_to_array(): - def _to_array(mode, dtype): - img = hopper(mode) - - # Resize to non-square - img = img.crop((3, 0, 124, 127)) - assert img.size == (121, 127) - - np_img = numpy.array(img) - _test_img_equals_nparray(img, np_img) - assert np_img.dtype == dtype - - modes = [ +@pytest.mark.parametrize( + "mode, dtype", + ( ("L", numpy.uint8), ("I", numpy.int32), ("F", numpy.float32), @@ -163,10 +153,18 @@ def test_to_array(): ("I;16B", ">u2"), ("I;16L", " Date: Mon, 3 Oct 2022 17:48:27 +1100 Subject: [PATCH 12/13] Enabled test --- Tests/test_file_libtiff.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 6015a8fec..d9066c589 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -510,11 +510,7 @@ class TestFileLibTiff(LibTiffTestCase): assert len(reloaded.tag_v2[320]) == 768 @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) - def xtest_bw_compression_w_rgb(self, compression, tmp_path): - """This test passes, but when running all tests causes a failure due - to output on stderr from the error thrown by libtiff. We need to - capture that but not now""" - + def test_bw_compression_w_rgb(self, compression, tmp_path): im = hopper("RGB") out = str(tmp_path / "temp.tif") From 92cb0af71f6af611ae5be28a6bf42642cef24726 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 19:54:37 +1100 Subject: [PATCH 13/13] Do not import PIL.Image --- Tests/test_000_sanity.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py index 59fbac527..3fd982474 100644 --- a/Tests/test_000_sanity.py +++ b/Tests/test_000_sanity.py @@ -1,19 +1,18 @@ -import PIL -import PIL.Image +from PIL import Image def test_sanity(): # Make sure we have the binary extension - PIL.Image.core.new("L", (100, 100)) + Image.core.new("L", (100, 100)) # Create an image and do stuff with it. - im = PIL.Image.new("1", (100, 100)) + im = Image.new("1", (100, 100)) assert (im.mode, im.size) == ("1", (100, 100)) assert len(im.tobytes()) == 1300 # Create images in all remaining major modes. - PIL.Image.new("L", (100, 100)) - PIL.Image.new("P", (100, 100)) - PIL.Image.new("RGB", (100, 100)) - PIL.Image.new("I", (100, 100)) - PIL.Image.new("F", (100, 100)) + Image.new("L", (100, 100)) + Image.new("P", (100, 100)) + Image.new("RGB", (100, 100)) + Image.new("I", (100, 100)) + Image.new("F", (100, 100))