mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-17 18:54:46 +03:00
Merge 5bd776a621
into 489d6f32f1
This commit is contained in:
commit
c45b029717
|
@ -1,5 +1,6 @@
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -21,6 +22,22 @@ from .helper import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MyStrPathLike:
|
||||||
|
def __init__(self, path_str):
|
||||||
|
self.path_str = path_str
|
||||||
|
|
||||||
|
def __fspath__(self):
|
||||||
|
return self.path_str
|
||||||
|
|
||||||
|
|
||||||
|
class MyBytesPathLike:
|
||||||
|
def __init__(self, path_str):
|
||||||
|
self.path_bytes = os.fsencode(path_str)
|
||||||
|
|
||||||
|
def __fspath__(self):
|
||||||
|
return self.path_bytes
|
||||||
|
|
||||||
|
|
||||||
class TestImage:
|
class TestImage:
|
||||||
def test_image_modes_success(self):
|
def test_image_modes_success(self):
|
||||||
for mode in [
|
for mode in [
|
||||||
|
@ -149,22 +166,30 @@ class TestImage:
|
||||||
with Image.open(io.StringIO()):
|
with Image.open(io.StringIO()):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_pathlib(self, tmp_path):
|
@pytest.mark.parametrize(
|
||||||
from PIL.Image import Path
|
"PathCls",
|
||||||
|
(
|
||||||
with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im:
|
pathlib.Path,
|
||||||
|
MyStrPathLike, # Returns `str` on `os.fspath`
|
||||||
|
MyBytesPathLike, # Returns `bytes` on `os.fspath`
|
||||||
|
str,
|
||||||
|
os.fsencode, # Converts path to `bytes`
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_path_like(self, tmp_path, PathCls):
|
||||||
|
with Image.open(PathCls("Tests/images/multipage-mmap.tiff")) as im:
|
||||||
assert im.mode == "P"
|
assert im.mode == "P"
|
||||||
assert im.size == (10, 10)
|
assert im.size == (10, 10)
|
||||||
|
|
||||||
with Image.open(Path("Tests/images/hopper.jpg")) as im:
|
with Image.open(PathCls("Tests/images/hopper.jpg")) as im:
|
||||||
assert im.mode == "RGB"
|
assert im.mode == "RGB"
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
|
|
||||||
for ext in (".jpg", ".jp2"):
|
for ext in (".jpg", ".jp2"):
|
||||||
temp_file = str(tmp_path / ("temp." + ext))
|
temp_file = str((tmp_path / "temp").with_suffix(ext))
|
||||||
if os.path.exists(temp_file):
|
if os.path.exists(temp_file):
|
||||||
os.remove(temp_file)
|
os.remove(temp_file)
|
||||||
im.save(Path(temp_file))
|
im.save(PathCls(temp_file))
|
||||||
|
|
||||||
def test_fp_name(self, tmp_path):
|
def test_fp_name(self, tmp_path):
|
||||||
temp_file = str(tmp_path / "temp.jpg")
|
temp_file = str(tmp_path / "temp.jpg")
|
||||||
|
|
|
@ -4,6 +4,7 @@ import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
|
@ -87,6 +88,7 @@ class TestImageFont:
|
||||||
pytest.skip("Non-ASCII path could not be created")
|
pytest.skip("Non-ASCII path could not be created")
|
||||||
|
|
||||||
ImageFont.truetype(tempfile, FONT_SIZE)
|
ImageFont.truetype(tempfile, FONT_SIZE)
|
||||||
|
ImageFont.truetype(Path(tempfile), FONT_SIZE)
|
||||||
|
|
||||||
def _render(self, font):
|
def _render(self, font):
|
||||||
txt = "Hello World!"
|
txt = "Hello World!"
|
||||||
|
|
|
@ -27,6 +27,21 @@ def test_path_obj_is_path():
|
||||||
assert it_is
|
assert it_is
|
||||||
|
|
||||||
|
|
||||||
|
def test_path_like_is_path():
|
||||||
|
# Arrange
|
||||||
|
class PathThingy:
|
||||||
|
def __fspath__(self):
|
||||||
|
return "UNIMPORTANT" # pragma: no cover
|
||||||
|
|
||||||
|
test_path = PathThingy()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
it_is = _util.is_path(test_path)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert it_is
|
||||||
|
|
||||||
|
|
||||||
def test_is_not_path(tmp_path):
|
def test_is_not_path(tmp_path):
|
||||||
# Arrange
|
# Arrange
|
||||||
with (tmp_path / "temp.ext").open("w") as fp:
|
with (tmp_path / "temp.ext").open("w") as fp:
|
||||||
|
@ -39,6 +54,22 @@ def test_is_not_path(tmp_path):
|
||||||
assert not it_is_not
|
assert not it_is_not
|
||||||
|
|
||||||
|
|
||||||
|
def test_path_like_file_is_not_path(tmp_path):
|
||||||
|
# Arrange
|
||||||
|
import io
|
||||||
|
|
||||||
|
class PathAndFileThingy(io.TextIOWrapper):
|
||||||
|
def __fspath__(self):
|
||||||
|
return "UNIMPORTANT" # pragma: no cover
|
||||||
|
|
||||||
|
with PathAndFileThingy(open(tmp_path / "temp.ext", "wb")) as test_obj:
|
||||||
|
# Act
|
||||||
|
it_is_not = _util.is_path(test_obj)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert not it_is_not
|
||||||
|
|
||||||
|
|
||||||
def test_is_directory():
|
def test_is_directory():
|
||||||
# Arrange
|
# Arrange
|
||||||
directory = "Tests"
|
directory = "Tests"
|
||||||
|
|
|
@ -2182,9 +2182,9 @@ class Image:
|
||||||
|
|
||||||
def save(self, fp, format=None, **params):
|
def save(self, fp, format=None, **params):
|
||||||
"""
|
"""
|
||||||
Saves this image under the given filename. If no format is
|
Saves this image under the given file path. If no format is
|
||||||
specified, the format to use is determined from the filename
|
specified, the format to use is determined from the path's
|
||||||
extension, if possible.
|
file extension, if possible.
|
||||||
|
|
||||||
Keyword options can be used to provide additional instructions
|
Keyword options can be used to provide additional instructions
|
||||||
to the writer. If a writer doesn't recognise an option, it is
|
to the writer. If a writer doesn't recognise an option, it is
|
||||||
|
@ -2192,12 +2192,12 @@ class Image:
|
||||||
:doc:`image format documentation
|
:doc:`image format documentation
|
||||||
<../handbook/image-file-formats>` for each writer.
|
<../handbook/image-file-formats>` for each writer.
|
||||||
|
|
||||||
You can use a file object instead of a filename. In this case,
|
You can use a file object instead of a file path. In this case,
|
||||||
you must always specify the format. The file object must
|
you must always specify the format. The file object must
|
||||||
implement the ``seek``, ``tell``, and ``write``
|
implement the ``seek``, ``tell``, and ``write``
|
||||||
methods, and be opened in binary mode.
|
methods, and be opened in binary mode.
|
||||||
|
|
||||||
:param fp: A filename (string), pathlib.Path object or file object.
|
:param fp: A file path (str or bytes), path-like object or file object.
|
||||||
:param format: Optional format override. If omitted, the
|
:param format: Optional format override. If omitted, the
|
||||||
format to use is determined from the filename extension.
|
format to use is determined from the filename extension.
|
||||||
If a file object was used instead of a filename, this
|
If a file object was used instead of a filename, this
|
||||||
|
@ -2216,7 +2216,7 @@ class Image:
|
||||||
filename = str(fp)
|
filename = str(fp)
|
||||||
open_fp = True
|
open_fp = True
|
||||||
elif is_path(fp):
|
elif is_path(fp):
|
||||||
filename = fp
|
filename = os.fsdecode(fp)
|
||||||
open_fp = True
|
open_fp = True
|
||||||
elif fp == sys.stdout:
|
elif fp == sys.stdout:
|
||||||
try:
|
try:
|
||||||
|
@ -2992,7 +2992,7 @@ def open(fp, mode="r", formats=None):
|
||||||
:py:meth:`~PIL.Image.Image.load` method). See
|
:py:meth:`~PIL.Image.Image.load` method). See
|
||||||
:py:func:`~PIL.Image.new`. See :ref:`file-handling`.
|
:py:func:`~PIL.Image.new`. See :ref:`file-handling`.
|
||||||
|
|
||||||
:param fp: A filename (string), pathlib.Path object or a file object.
|
:param fp: A file path (str or bytes), path-like object or a file object.
|
||||||
The file object must implement ``file.read``,
|
The file object must implement ``file.read``,
|
||||||
``file.seek``, and ``file.tell`` methods,
|
``file.seek``, and ``file.tell`` methods,
|
||||||
and be opened in binary mode.
|
and be opened in binary mode.
|
||||||
|
|
|
@ -202,6 +202,7 @@ class FreeTypeFont:
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_path(font):
|
if is_path(font):
|
||||||
|
font = os.fspath(font)
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
font_bytes_path = font if isinstance(font, bytes) else font.encode()
|
font_bytes_path = font if isinstance(font, bytes) else font.encode()
|
||||||
try:
|
try:
|
||||||
|
@ -818,10 +819,10 @@ def load(filename):
|
||||||
|
|
||||||
|
|
||||||
def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
"""
|
r"""
|
||||||
Load a TrueType or OpenType font from a file or file-like object,
|
Load a TrueType or OpenType font from a file path or file-like object,
|
||||||
and create a font object.
|
and create a font object.
|
||||||
This function loads a font object from the given file or file-like
|
This function loads a font object from the given file path or file-like
|
||||||
object, and creates a font object for a font of the given size.
|
object, and creates a font object for a font of the given size.
|
||||||
|
|
||||||
Pillow uses FreeType to open font files. If you are opening many fonts
|
Pillow uses FreeType to open font files. If you are opening many fonts
|
||||||
|
@ -831,12 +832,12 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
|
|
||||||
This function requires the _imagingft service.
|
This function requires the _imagingft service.
|
||||||
|
|
||||||
:param font: A filename or file-like object containing a TrueType font.
|
:param font: A file path or file-like object containing a TrueType font.
|
||||||
If the file is not found in this filename, the loader may also
|
If the file is not found at this path, the loader may also
|
||||||
search in other directories, such as the :file:`fonts/`
|
search in other directories, such as the :file:`%WINDIR%\fonts\`
|
||||||
directory on Windows or :file:`/Library/Fonts/`,
|
directory on Windows or :file:`/Library/Fonts/`,
|
||||||
:file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
|
:file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/`
|
||||||
macOS.
|
on macOS.
|
||||||
|
|
||||||
:param size: The requested size, in pixels.
|
:param size: The requested size, in pixels.
|
||||||
:param index: Which font face to load (default is first available face).
|
:param index: Which font face to load (default is first available face).
|
||||||
|
@ -878,7 +879,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
except OSError:
|
except OSError:
|
||||||
if not is_path(font):
|
if not is_path(font):
|
||||||
raise
|
raise
|
||||||
ttf_filename = os.path.basename(font)
|
ttf_filename = os.fsdecode(os.path.basename(font))
|
||||||
|
|
||||||
dirs = []
|
dirs = []
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def is_path(f):
|
def is_path(f):
|
||||||
return isinstance(f, (bytes, str, Path))
|
"""
|
||||||
|
Checks whether the given object is a string or path-like object that
|
||||||
|
isn't also a file-like object
|
||||||
|
"""
|
||||||
|
return isinstance(f, (bytes, str, os.PathLike)) and not hasattr(f, "read")
|
||||||
|
|
||||||
|
|
||||||
def is_directory(f):
|
def is_directory(f):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user