mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-27 16:39:49 +03:00
Merge branch 'main' into ios-build
This commit is contained in:
commit
ec5e5d0791
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
|
@ -60,7 +60,7 @@ jobs:
|
||||||
platform: macos
|
platform: macos
|
||||||
os: macos-13
|
os: macos-13
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "cp3{12,13}*"
|
build: "cp3{12,13,14}*"
|
||||||
macosx_deployment_target: "10.13"
|
macosx_deployment_target: "10.13"
|
||||||
- name: "macOS 10.15 x86_64"
|
- name: "macOS 10.15 x86_64"
|
||||||
platform: macos
|
platform: macos
|
||||||
|
|
|
@ -100,11 +100,11 @@ class TestFilePng:
|
||||||
assert im.format == "PNG"
|
assert im.format == "PNG"
|
||||||
assert im.get_format_mimetype() == "image/png"
|
assert im.get_format_mimetype() == "image/png"
|
||||||
|
|
||||||
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
|
for mode in ["1", "L", "P", "RGB", "I;16", "I;16B"]:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
if mode in ("I", "I;16B"):
|
if mode == "I;16B":
|
||||||
reloaded = reloaded.convert(mode)
|
reloaded = reloaded.convert(mode)
|
||||||
assert_image_equal(reloaded, im)
|
assert_image_equal(reloaded, im)
|
||||||
|
|
||||||
|
@ -801,6 +801,16 @@ class TestFilePng:
|
||||||
with Image.open("Tests/images/truncated_end_chunk.png") as im:
|
with Image.open("Tests/images/truncated_end_chunk.png") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||||
|
|
||||||
|
def test_deprecation(self, tmp_path: Path) -> None:
|
||||||
|
test_file = tmp_path / "out.png"
|
||||||
|
|
||||||
|
im = hopper("I")
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
im.save(test_file)
|
||||||
|
|
||||||
|
with Image.open(test_file) as reloaded:
|
||||||
|
assert_image_equal(im, reloaded.convert("I"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
|
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
|
||||||
@skip_unless_feature("zlib")
|
@skip_unless_feature("zlib")
|
||||||
|
|
|
@ -193,6 +193,20 @@ Image.Image.get_child_images()
|
||||||
method uses an image's file pointer, and so child images could only be retrieved from
|
method uses an image's file pointer, and so child images could only be retrieved from
|
||||||
an :py:class:`PIL.ImageFile.ImageFile` instance.
|
an :py:class:`PIL.ImageFile.ImageFile` instance.
|
||||||
|
|
||||||
|
Saving I mode images as PNG
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.3.0
|
||||||
|
|
||||||
|
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
|
||||||
|
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
|
||||||
|
changing the data, this is now deprecated. Instead, the image can be converted to
|
||||||
|
another mode before saving::
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
im = Image.new("I", (1, 1))
|
||||||
|
im.convert("I;16").save("out.png")
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,17 @@ TODO
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
TODO
|
Saving I mode images as PNG
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
|
||||||
|
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
|
||||||
|
changing the data, this is now deprecated. Instead, the image can be converted to
|
||||||
|
another mode before saving::
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
im = Image.new("I", (1, 1))
|
||||||
|
im.convert("I;16").save("out.png")
|
||||||
|
|
||||||
API changes
|
API changes
|
||||||
===========
|
===========
|
||||||
|
|
9
setup.py
9
setup.py
|
@ -16,7 +16,6 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from setuptools import Extension, setup
|
from setuptools import Extension, setup
|
||||||
from setuptools.command.build_ext import build_ext
|
from setuptools.command.build_ext import build_ext
|
||||||
|
@ -148,7 +147,7 @@ class RequiredDependencyException(Exception):
|
||||||
PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version
|
PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version
|
||||||
|
|
||||||
|
|
||||||
def _dbg(s: str, tp: Any = None) -> None:
|
def _dbg(s: str, tp: str | tuple[str, ...] | None = None) -> None:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
if tp:
|
if tp:
|
||||||
print(s % tp)
|
print(s % tp)
|
||||||
|
@ -522,11 +521,11 @@ class pil_build_ext(build_ext):
|
||||||
|
|
||||||
if root is None and pkg_config:
|
if root is None and pkg_config:
|
||||||
if isinstance(lib_name, str):
|
if isinstance(lib_name, str):
|
||||||
_dbg(f"Looking for `{lib_name}` using pkg-config.")
|
_dbg("Looking for `%s` using pkg-config.", lib_name)
|
||||||
root = pkg_config(lib_name)
|
root = pkg_config(lib_name)
|
||||||
else:
|
else:
|
||||||
for lib_name2 in lib_name:
|
for lib_name2 in lib_name:
|
||||||
_dbg(f"Looking for `{lib_name2}` using pkg-config.")
|
_dbg("Looking for `%s` using pkg-config.", lib_name2)
|
||||||
root = pkg_config(lib_name2)
|
root = pkg_config(lib_name2)
|
||||||
if root:
|
if root:
|
||||||
break
|
break
|
||||||
|
@ -757,7 +756,7 @@ class pil_build_ext(build_ext):
|
||||||
best_path = os.path.join(directory, name)
|
best_path = os.path.join(directory, name)
|
||||||
_dbg(
|
_dbg(
|
||||||
"Best openjpeg version %s so far in %s",
|
"Best openjpeg version %s so far in %s",
|
||||||
(best_version, best_path),
|
(str(best_version), best_path),
|
||||||
)
|
)
|
||||||
|
|
||||||
if best_version and _find_library_file(self, "openjp2"):
|
if best_version and _find_library_file(self, "openjp2"):
|
||||||
|
|
|
@ -248,6 +248,9 @@ class ImageCmsProfile:
|
||||||
low-level profile object
|
low-level profile object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.filename = None
|
||||||
|
self.product_name = None # profile.product_name
|
||||||
|
self.product_info = None # profile.product_info
|
||||||
|
|
||||||
if isinstance(profile, str):
|
if isinstance(profile, str):
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
@ -256,23 +259,18 @@ class ImageCmsProfile:
|
||||||
profile_bytes_path.decode("ascii")
|
profile_bytes_path.decode("ascii")
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
with open(profile, "rb") as f:
|
with open(profile, "rb") as f:
|
||||||
self._set(core.profile_frombytes(f.read()))
|
self.profile = core.profile_frombytes(f.read())
|
||||||
return
|
return
|
||||||
self._set(core.profile_open(profile), profile)
|
self.filename = profile
|
||||||
|
self.profile = core.profile_open(profile)
|
||||||
elif hasattr(profile, "read"):
|
elif hasattr(profile, "read"):
|
||||||
self._set(core.profile_frombytes(profile.read()))
|
self.profile = core.profile_frombytes(profile.read())
|
||||||
elif isinstance(profile, core.CmsProfile):
|
elif isinstance(profile, core.CmsProfile):
|
||||||
self._set(profile)
|
self.profile = profile
|
||||||
else:
|
else:
|
||||||
msg = "Invalid type for Profile" # type: ignore[unreachable]
|
msg = "Invalid type for Profile" # type: ignore[unreachable]
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None:
|
|
||||||
self.profile = profile
|
|
||||||
self.filename = filename
|
|
||||||
self.product_name = None # profile.product_name
|
|
||||||
self.product_info = None # profile.product_info
|
|
||||||
|
|
||||||
def tobytes(self) -> bytes:
|
def tobytes(self) -> bytes:
|
||||||
"""
|
"""
|
||||||
Returns the profile in a format suitable for embedding in
|
Returns the profile in a format suitable for embedding in
|
||||||
|
|
|
@ -48,6 +48,7 @@ from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._binary import o16be as o16
|
from ._binary import o16be as o16
|
||||||
from ._binary import o32be as o32
|
from ._binary import o32be as o32
|
||||||
|
from ._deprecate import deprecate
|
||||||
from ._util import DeferredError
|
from ._util import DeferredError
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
|
@ -1368,6 +1369,8 @@ def _save(
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
msg = f"cannot write mode {mode} as PNG"
|
msg = f"cannot write mode {mode} as PNG"
|
||||||
raise OSError(msg) from e
|
raise OSError(msg) from e
|
||||||
|
if outmode == "I":
|
||||||
|
deprecate("Saving I mode images as PNG", 13, stacklevel=4)
|
||||||
|
|
||||||
#
|
#
|
||||||
# write minimal PNG file
|
# write minimal PNG file
|
||||||
|
|
|
@ -12,6 +12,7 @@ def deprecate(
|
||||||
*,
|
*,
|
||||||
action: str | None = None,
|
action: str | None = None,
|
||||||
plural: bool = False,
|
plural: bool = False,
|
||||||
|
stacklevel: int = 3,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Deprecations helper.
|
Deprecations helper.
|
||||||
|
@ -67,5 +68,5 @@ def deprecate(
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
|
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
stacklevel=3,
|
stacklevel=stacklevel,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1032,7 +1032,10 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
|
||||||
TRACE(("Encode Error, row %d\n", state->y));
|
TRACE(("Encode Error, row %d\n", state->y));
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
|
||||||
if (!clientstate->fp) {
|
if (clientstate->fp) {
|
||||||
|
TIFFCleanup(tiff);
|
||||||
|
clientstate->tiff = NULL;
|
||||||
|
} else {
|
||||||
free(clientstate->data);
|
free(clientstate->data);
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -385,8 +385,8 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||||
"bins": [r"*.dll"],
|
"bins": [r"*.dll"],
|
||||||
},
|
},
|
||||||
"libavif": {
|
"libavif": {
|
||||||
"url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.zip",
|
"url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.tar.gz",
|
||||||
"filename": f"libavif-{V['LIBAVIF']}.zip",
|
"filename": f"libavif-{V['LIBAVIF']}.tar.gz",
|
||||||
"license": "LICENSE",
|
"license": "LICENSE",
|
||||||
"build": [
|
"build": [
|
||||||
"rustup update",
|
"rustup update",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user