mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
Merge pull request #8182 from hugovk/rm-deprecated-psfile
Remove PSFile, PyAccess and USE_CFFI_ACCESS
This commit is contained in:
commit
585bd6ad48
|
@ -28,8 +28,6 @@ fi
|
|||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade wheel
|
||||
# TODO Update condition when cffi supports 3.13
|
||||
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
|
||||
python3 -m pip install coverage
|
||||
python3 -m pip install defusedxml
|
||||
python3 -m pip install olefile
|
||||
|
|
|
@ -19,6 +19,5 @@ exclude_also =
|
|||
[run]
|
||||
omit =
|
||||
Tests/32bit_segfault_check.py
|
||||
Tests/bench_cffi_access.py
|
||||
Tests/check_*.py
|
||||
Tests/createfontdatachunk.py
|
||||
|
|
3
.github/workflows/macos-install.sh
vendored
3
.github/workflows/macos-install.sh
vendored
|
@ -18,9 +18,6 @@ else
|
|||
fi
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||
|
||||
# TODO Update condition when cffi supports 3.13
|
||||
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
|
||||
|
||||
python3 -m pip install coverage
|
||||
python3 -m pip install defusedxml
|
||||
python3 -m pip install olefile
|
||||
|
|
1
.github/workflows/test-cygwin.yml
vendored
1
.github/workflows/test-cygwin.yml
vendored
|
@ -72,7 +72,6 @@ jobs:
|
|||
make
|
||||
netpbm
|
||||
perl
|
||||
python3${{ matrix.python-minor-version }}-cffi
|
||||
python3${{ matrix.python-minor-version }}-cython
|
||||
python3${{ matrix.python-minor-version }}-devel
|
||||
python3${{ matrix.python-minor-version }}-numpy
|
||||
|
|
1
.github/workflows/test-mingw.yml
vendored
1
.github/workflows/test-mingw.yml
vendored
|
@ -64,7 +64,6 @@ jobs:
|
|||
mingw-w64-x86_64-libtiff \
|
||||
mingw-w64-x86_64-libwebp \
|
||||
mingw-w64-x86_64-openjpeg2 \
|
||||
mingw-w64-x86_64-python3-cffi \
|
||||
mingw-w64-x86_64-python3-numpy \
|
||||
mingw-w64-x86_64-python3-olefile \
|
||||
mingw-w64-x86_64-python3-setuptools \
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from PIL import PyAccess
|
||||
|
||||
from .helper import hopper
|
||||
|
||||
# Not running this test by default. No DOS against CI.
|
||||
|
||||
|
||||
def iterate_get(size, access) -> None:
|
||||
(w, h) = size
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
access[(x, y)]
|
||||
|
||||
|
||||
def iterate_set(size, access) -> None:
|
||||
(w, h) = size
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
access[(x, y)] = (x % 256, y % 256, 0)
|
||||
|
||||
|
||||
def timer(func, label, *args) -> None:
|
||||
iterations = 5000
|
||||
starttime = time.time()
|
||||
for x in range(iterations):
|
||||
func(*args)
|
||||
if time.time() - starttime > 10:
|
||||
break
|
||||
endtime = time.time()
|
||||
print(
|
||||
f"{label}: completed {x + 1} iterations in {endtime - starttime:.4f}s, "
|
||||
f"{(endtime - starttime) / (x + 1.0):.6f}s per iteration"
|
||||
)
|
||||
|
||||
|
||||
def test_direct() -> None:
|
||||
im = hopper()
|
||||
im.load()
|
||||
# im = Image.new("RGB", (2000, 2000), (1, 3, 2))
|
||||
caccess = im.im.pixel_access(False)
|
||||
access = PyAccess.new(im, False)
|
||||
|
||||
assert access is not None
|
||||
assert caccess[(0, 0)] == access[(0, 0)]
|
||||
|
||||
print(f"Size: {im.width}x{im.height}")
|
||||
timer(iterate_get, "PyAccess - get", im.size, access)
|
||||
timer(iterate_set, "PyAccess - set", im.size, access)
|
||||
timer(iterate_get, "C-api - get", im.size, caccess)
|
||||
timer(iterate_set, "C-api - set", im.size, caccess)
|
|
@ -9,9 +9,9 @@ from PIL import _deprecate
|
|||
"version, expected",
|
||||
[
|
||||
(
|
||||
11,
|
||||
"Old thing is deprecated and will be removed in Pillow 11 "
|
||||
r"\(2024-10-15\)\. Use new thing instead\.",
|
||||
12,
|
||||
"Old thing is deprecated and will be removed in Pillow 12 "
|
||||
r"\(2025-10-15\)\. Use new thing instead\.",
|
||||
),
|
||||
(
|
||||
None,
|
||||
|
@ -54,18 +54,18 @@ def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
|||
|
||||
def test_plural() -> None:
|
||||
expected = (
|
||||
r"Old things are deprecated and will be removed in Pillow 11 \(2024-10-15\)\. "
|
||||
r"Old things are deprecated and will be removed in Pillow 12 \(2025-10-15\)\. "
|
||||
r"Use new thing instead\."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old things", 11, "new thing", plural=True)
|
||||
_deprecate.deprecate("Old things", 12, "new thing", plural=True)
|
||||
|
||||
|
||||
def test_replacement_and_action() -> None:
|
||||
expected = "Use only one of 'replacement' and 'action'"
|
||||
with pytest.raises(ValueError, match=expected):
|
||||
_deprecate.deprecate(
|
||||
"Old thing", 11, replacement="new thing", action="Upgrade to new thing"
|
||||
"Old thing", 12, replacement="new thing", action="Upgrade to new thing"
|
||||
)
|
||||
|
||||
|
||||
|
@ -78,16 +78,16 @@ def test_replacement_and_action() -> None:
|
|||
)
|
||||
def test_action(action: str) -> None:
|
||||
expected = (
|
||||
r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)\. "
|
||||
r"Old thing is deprecated and will be removed in Pillow 12 \(2025-10-15\)\. "
|
||||
r"Upgrade to new thing\."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old thing", 11, action=action)
|
||||
_deprecate.deprecate("Old thing", 12, action=action)
|
||||
|
||||
|
||||
def test_no_replacement_or_action() -> None:
|
||||
expected = (
|
||||
r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)"
|
||||
r"Old thing is deprecated and will be removed in Pillow 12 \(2025-10-15\)"
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old thing", 11)
|
||||
_deprecate.deprecate("Old thing", 12)
|
||||
|
|
|
@ -329,46 +329,6 @@ def test_read_binary_preview() -> None:
|
|||
pass
|
||||
|
||||
|
||||
def test_readline_psfile(tmp_path: Path) -> None:
|
||||
# check all the freaking line endings possible from the spec
|
||||
# test_string = u'something\r\nelse\n\rbaz\rbif\n'
|
||||
line_endings = ["\r\n", "\n", "\n\r", "\r"]
|
||||
strings = ["something", "else", "baz", "bif"]
|
||||
|
||||
def _test_readline(t: EpsImagePlugin.PSFile, ending: str) -> None:
|
||||
ending = f"Failure with line ending: {''.join(str(ord(s)) for s in ending)}"
|
||||
assert t.readline().strip("\r\n") == "something", ending
|
||||
assert t.readline().strip("\r\n") == "else", ending
|
||||
assert t.readline().strip("\r\n") == "baz", ending
|
||||
assert t.readline().strip("\r\n") == "bif", ending
|
||||
|
||||
def _test_readline_io_psfile(test_string: str, ending: str) -> None:
|
||||
f = io.BytesIO(test_string.encode("latin-1"))
|
||||
with pytest.warns(DeprecationWarning):
|
||||
t = EpsImagePlugin.PSFile(f)
|
||||
_test_readline(t, ending)
|
||||
|
||||
def _test_readline_file_psfile(test_string: str, ending: str) -> None:
|
||||
f = str(tmp_path / "temp.txt")
|
||||
with open(f, "wb") as w:
|
||||
w.write(test_string.encode("latin-1"))
|
||||
|
||||
with open(f, "rb") as r:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
t = EpsImagePlugin.PSFile(r)
|
||||
_test_readline(t, ending)
|
||||
|
||||
for ending in line_endings:
|
||||
s = ending.join(strings)
|
||||
_test_readline_io_psfile(s, ending)
|
||||
_test_readline_file_psfile(s, ending)
|
||||
|
||||
|
||||
def test_psfile_deprecation() -> None:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
EpsImagePlugin.PSFile(None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
|
||||
@pytest.mark.parametrize(
|
||||
"line_ending",
|
||||
|
|
|
@ -12,19 +12,6 @@ from PIL import Image
|
|||
|
||||
from .helper import assert_image_equal, hopper, is_win32
|
||||
|
||||
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
|
||||
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
|
||||
cffi: ModuleType | None
|
||||
if os.environ.get("PYTHONOPTIMIZE") == "2":
|
||||
cffi = None
|
||||
else:
|
||||
try:
|
||||
import cffi
|
||||
|
||||
from PIL import PyAccess
|
||||
except ImportError:
|
||||
cffi = None
|
||||
|
||||
numpy: ModuleType | None
|
||||
try:
|
||||
import numpy
|
||||
|
@ -32,21 +19,7 @@ except ImportError:
|
|||
numpy = None
|
||||
|
||||
|
||||
class AccessTest:
|
||||
# Initial value
|
||||
_init_cffi_access = Image.USE_CFFI_ACCESS
|
||||
_need_cffi_access = False
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls) -> None:
|
||||
Image.USE_CFFI_ACCESS = cls._need_cffi_access
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls) -> None:
|
||||
Image.USE_CFFI_ACCESS = cls._init_cffi_access
|
||||
|
||||
|
||||
class TestImagePutPixel(AccessTest):
|
||||
class TestImagePutPixel:
|
||||
def test_sanity(self) -> None:
|
||||
im1 = hopper()
|
||||
im2 = Image.new(im1.mode, im1.size, 0)
|
||||
|
@ -131,7 +104,7 @@ class TestImagePutPixel(AccessTest):
|
|||
assert pix[numpy.int32(1), numpy.int32(2)] == (18, 20, 59)
|
||||
|
||||
|
||||
class TestImageGetPixel(AccessTest):
|
||||
class TestImageGetPixel:
|
||||
@staticmethod
|
||||
def color(mode: str) -> int | tuple[int, ...]:
|
||||
bands = Image.getmodebands(mode)
|
||||
|
@ -144,9 +117,6 @@ class TestImageGetPixel(AccessTest):
|
|||
return tuple(range(1, bands + 1))
|
||||
|
||||
def check(self, mode: str, expected_color_int: int | None = None) -> None:
|
||||
if self._need_cffi_access and mode.startswith("BGR;"):
|
||||
pytest.skip("Support not added to deprecated module for BGR;* modes")
|
||||
|
||||
expected_color = (
|
||||
self.color(mode) if expected_color_int is None else expected_color_int
|
||||
)
|
||||
|
@ -171,15 +141,14 @@ class TestImageGetPixel(AccessTest):
|
|||
# Check 0x0 image with None initial color
|
||||
im = Image.new(mode, (0, 0), None)
|
||||
assert im.load() is not None
|
||||
error = ValueError if self._need_cffi_access else IndexError
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.putpixel((0, 0), expected_color)
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.getpixel((0, 0))
|
||||
# Check negative index
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.putpixel((-1, -1), expected_color)
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.getpixel((-1, -1))
|
||||
|
||||
# Check initial color
|
||||
|
@ -199,10 +168,10 @@ class TestImageGetPixel(AccessTest):
|
|||
|
||||
# Check 0x0 image with initial color
|
||||
im = Image.new(mode, (0, 0), expected_color)
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.getpixel((0, 0))
|
||||
# Check negative index
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.getpixel((-1, -1))
|
||||
|
||||
@pytest.mark.parametrize("mode", Image.MODES)
|
||||
|
@ -235,126 +204,7 @@ class TestImageGetPixel(AccessTest):
|
|||
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||
class TestCffiPutPixel(TestImagePutPixel):
|
||||
_need_cffi_access = True
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||
class TestCffiGetPixel(TestImageGetPixel):
|
||||
_need_cffi_access = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||
class TestCffi(AccessTest):
|
||||
_need_cffi_access = True
|
||||
|
||||
def _test_get_access(self, im: Image.Image) -> None:
|
||||
"""Do we get the same thing as the old pixel access
|
||||
|
||||
Using private interfaces, forcing a capi access and
|
||||
a pyaccess for the same image"""
|
||||
caccess = im.im.pixel_access(False)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, False)
|
||||
assert access is not None
|
||||
|
||||
w, h = im.size
|
||||
for x in range(0, w, 10):
|
||||
for y in range(0, h, 10):
|
||||
assert access[(x, y)] == caccess[(x, y)]
|
||||
|
||||
# Access an out-of-range pixel
|
||||
with pytest.raises(ValueError):
|
||||
access[(access.xsize + 1, access.ysize + 1)]
|
||||
|
||||
def test_get_vs_c(self) -> None:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
rgb = hopper("RGB")
|
||||
rgb.load()
|
||||
self._test_get_access(rgb)
|
||||
for mode in ("RGBA", "L", "LA", "1", "P", "F"):
|
||||
self._test_get_access(hopper(mode))
|
||||
|
||||
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
|
||||
im = Image.new(mode, (10, 10), 40000)
|
||||
self._test_get_access(im)
|
||||
|
||||
def _test_set_access(self, im: Image.Image, color: tuple[int, ...] | float) -> None:
|
||||
"""Are we writing the correct bits into the image?
|
||||
|
||||
Using private interfaces, forcing a capi access and
|
||||
a pyaccess for the same image"""
|
||||
caccess = im.im.pixel_access(False)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, False)
|
||||
assert access is not None
|
||||
|
||||
w, h = im.size
|
||||
for x in range(0, w, 10):
|
||||
for y in range(0, h, 10):
|
||||
access[(x, y)] = color
|
||||
assert color == caccess[(x, y)]
|
||||
|
||||
# Attempt to set the value on a read-only image
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, True)
|
||||
assert access is not None
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
access[(0, 0)] = color
|
||||
|
||||
def test_set_vs_c(self) -> None:
|
||||
rgb = hopper("RGB")
|
||||
with pytest.warns(DeprecationWarning):
|
||||
rgb.load()
|
||||
self._test_set_access(rgb, (255, 128, 0))
|
||||
self._test_set_access(hopper("RGBA"), (255, 192, 128, 0))
|
||||
self._test_set_access(hopper("L"), 128)
|
||||
self._test_set_access(hopper("LA"), (128, 128))
|
||||
self._test_set_access(hopper("1"), 255)
|
||||
self._test_set_access(hopper("P"), 128)
|
||||
self._test_set_access(hopper("PA"), (128, 128))
|
||||
self._test_set_access(hopper("F"), 1024.0)
|
||||
|
||||
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
|
||||
im = Image.new(mode, (10, 10), 40000)
|
||||
self._test_set_access(im, 45000)
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
def test_not_implemented(self) -> None:
|
||||
assert PyAccess.new(hopper("BGR;15")) is None
|
||||
|
||||
# Ref https://github.com/python-pillow/Pillow/pull/2009
|
||||
def test_reference_counting(self) -> None:
|
||||
size = 10
|
||||
|
||||
for _ in range(10):
|
||||
# Do not save references to the image, only to the access object
|
||||
with pytest.warns(DeprecationWarning):
|
||||
px = Image.new("L", (size, 1), 0).load()
|
||||
for i in range(size):
|
||||
# Pixels can contain garbage if image is released
|
||||
assert px[i, 0] == 0
|
||||
|
||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||
def test_p_putpixel_rgb_rgba(self, mode: str) -> None:
|
||||
for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)):
|
||||
im = Image.new(mode, (1, 1))
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, False)
|
||||
assert access is not None
|
||||
|
||||
access.putpixel((0, 0), color)
|
||||
|
||||
if len(color) == 3:
|
||||
color += (255,)
|
||||
assert im.convert("RGBA").getpixel((0, 0)) == color
|
||||
|
||||
|
||||
class TestImagePutPixelError(AccessTest):
|
||||
class TestImagePutPixelError:
|
||||
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
|
||||
IMAGE_MODES2 = ["L", "I", "I;16"]
|
||||
INVALID_TYPES = ["foo", 1.0, None]
|
||||
|
|
|
@ -17,6 +17,5 @@ coverage:
|
|||
# Matches 'omit:' in .coveragerc
|
||||
ignore:
|
||||
- "Tests/32bit_segfault_check.py"
|
||||
- "Tests/bench_cffi_access.py"
|
||||
- "Tests/check_*.py"
|
||||
- "Tests/createfontdatachunk.py"
|
||||
|
|
|
@ -12,28 +12,6 @@ Deprecated features
|
|||
Below are features which are considered deprecated. Where appropriate,
|
||||
a :py:exc:`DeprecationWarning` is issued.
|
||||
|
||||
PSFile
|
||||
~~~~~~
|
||||
|
||||
.. deprecated:: 9.5.0
|
||||
|
||||
The :py:class:`~PIL.EpsImagePlugin.PSFile` class has been deprecated and will
|
||||
be removed in Pillow 11 (2024-10-15). This class was only made as a helper to
|
||||
be used internally, so there is no replacement. If you need this functionality
|
||||
though, it is a very short class that can easily be recreated in your own code.
|
||||
|
||||
PyAccess and Image.USE_CFFI_ACCESS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 10.0.0
|
||||
|
||||
Since Pillow's C API is now faster than PyAccess on PyPy,
|
||||
:py:mod:`~PIL.PyAccess` has been deprecated and will be removed in Pillow
|
||||
11.0.0 (2024-10-15). Pillow's C API will now be used by default on PyPy instead.
|
||||
|
||||
``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, is
|
||||
similarly deprecated.
|
||||
|
||||
ImageFile.raise_oserror
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -137,6 +115,29 @@ Removed features
|
|||
Deprecated features are only removed in major releases after an appropriate
|
||||
period of deprecation has passed.
|
||||
|
||||
PSFile
|
||||
~~~~~~
|
||||
|
||||
.. deprecated:: 9.5.0
|
||||
.. versionremoved:: 11.0.0
|
||||
|
||||
The :py:class:`!PSFile` class was removed in Pillow 11 (2024-10-15).
|
||||
This class was only made as a helper to be used internally,
|
||||
so there is no replacement. If you need this functionality though,
|
||||
it is a very short class that can easily be recreated in your own code.
|
||||
|
||||
PyAccess and Image.USE_CFFI_ACCESS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 10.0.0
|
||||
.. versionremoved:: 11.0.0
|
||||
|
||||
Since Pillow's C API is now faster than PyAccess on PyPy, ``PyAccess`` has been
|
||||
removed. Pillow's C API will now be used on PyPy instead.
|
||||
|
||||
``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, was
|
||||
similarly removed.
|
||||
|
||||
Tk/Tcl 8.4
|
||||
~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
.. py:module:: PIL.PyAccess
|
||||
.. py:currentmodule:: PIL.PyAccess
|
||||
|
||||
:py:mod:`~PIL.PyAccess` Module
|
||||
==============================
|
||||
|
||||
The :py:mod:`~PIL.PyAccess` module provides a CFFI/Python implementation of the :ref:`PixelAccess`. This implementation is far faster on PyPy than the PixelAccess version.
|
||||
|
||||
.. note:: Accessing individual pixels is fairly slow. If you are
|
||||
looping over all of the pixels in an image, there is likely
|
||||
a faster way using other parts of the Pillow API.
|
||||
|
||||
:mod:`~PIL.Image`, :mod:`~PIL.ImageChops` and :mod:`~PIL.ImageOps`
|
||||
have methods for many standard operations. If you wish to perform
|
||||
a custom mapping, check out :py:meth:`~PIL.Image.Image.point`.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
The following script loads an image, accesses one pixel from it, then changes it. ::
|
||||
|
||||
from PIL import Image
|
||||
|
||||
with Image.open("hopper.jpg") as im:
|
||||
px = im.load()
|
||||
print(px[4, 4])
|
||||
px[4, 4] = (0, 0, 0)
|
||||
print(px[4, 4])
|
||||
|
||||
Results in the following::
|
||||
|
||||
(23, 24, 68)
|
||||
(0, 0, 0)
|
||||
|
||||
Access using negative indexes is also possible. ::
|
||||
|
||||
px[-1, -1] = (0, 0, 0)
|
||||
print(px[-1, -1])
|
||||
|
||||
|
||||
|
||||
:py:class:`PyAccess` Class
|
||||
--------------------------
|
||||
|
||||
.. autoclass:: PIL.PyAccess.PyAccess()
|
||||
:members:
|
||||
:special-members: __getitem__, __setitem__
|
|
@ -32,7 +32,6 @@ Reference
|
|||
JpegPresets
|
||||
PSDraw
|
||||
PixelAccess
|
||||
PyAccess
|
||||
features
|
||||
../PIL
|
||||
plugins
|
||||
|
|
|
@ -158,7 +158,7 @@ PyAccess and Image.USE_CFFI_ACCESS
|
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Since Pillow's C API is now faster than PyAccess on PyPy,
|
||||
:py:mod:`~PIL.PyAccess` has been deprecated and will be removed in Pillow
|
||||
:py:mod:`!PyAccess` has been deprecated and will be removed in Pillow
|
||||
11.0.0 (2024-10-15). Pillow's C API will now be used by default on PyPy instead.
|
||||
|
||||
``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, is
|
||||
|
|
67
docs/releasenotes/11.0.0.rst
Normal file
67
docs/releasenotes/11.0.0.rst
Normal file
|
@ -0,0 +1,67 @@
|
|||
11.0.0
|
||||
------
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
:cve:`YYYY-XXXXX`: TODO
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
PSFile
|
||||
^^^^^^
|
||||
|
||||
The :py:class:`!PSFile` class was removed in Pillow 11 (2024-10-15).
|
||||
This class was only made as a helper to be used internally,
|
||||
so there is no replacement. If you need this functionality though,
|
||||
it is a very short class that can easily be recreated in your own code.
|
||||
|
||||
PyAccess and Image.USE_CFFI_ACCESS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since Pillow's C API is now faster than PyAccess on PyPy, ``PyAccess`` has been
|
||||
removed. Pillow's C API will now be used on PyPy instead.
|
||||
|
||||
``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, was
|
||||
similarly removed.
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Additions
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Other Changes
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
|
@ -32,7 +32,7 @@ Deprecations
|
|||
PSFile
|
||||
^^^^^^
|
||||
|
||||
The :py:class:`~PIL.EpsImagePlugin.PSFile` class has been deprecated and will
|
||||
The :py:class:`!PSFile` class has been deprecated and will
|
||||
be removed in Pillow 11 (2024-10-15). This class was only made as a helper to
|
||||
be used internally, so there is no replacement. If you need this functionality
|
||||
though, it is a very short class that can easily be recreated in your own code.
|
||||
|
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
11.0.0
|
||||
10.4.0
|
||||
10.3.0
|
||||
10.2.0
|
||||
|
|
|
@ -31,7 +31,6 @@ from typing import IO
|
|||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32le as i32
|
||||
from ._deprecate import deprecate
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
@ -159,43 +158,6 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
|||
return im
|
||||
|
||||
|
||||
class PSFile:
|
||||
"""
|
||||
Wrapper for bytesio object that treats either CR or LF as end of line.
|
||||
This class is no longer used internally, but kept for backwards compatibility.
|
||||
"""
|
||||
|
||||
def __init__(self, fp):
|
||||
deprecate(
|
||||
"PSFile",
|
||||
11,
|
||||
action="If you need the functionality of this class "
|
||||
"you will need to implement it yourself.",
|
||||
)
|
||||
self.fp = fp
|
||||
self.char = None
|
||||
|
||||
def seek(self, offset, whence=io.SEEK_SET):
|
||||
self.char = None
|
||||
self.fp.seek(offset, whence)
|
||||
|
||||
def readline(self) -> str:
|
||||
s = [self.char or b""]
|
||||
self.char = None
|
||||
|
||||
c = self.fp.read(1)
|
||||
while (c not in b"\r\n") and len(c):
|
||||
s.append(c)
|
||||
c = self.fp.read(1)
|
||||
|
||||
self.char = self.fp.read(1)
|
||||
# line endings can be 1 or 2 of \r \n, in either order
|
||||
if self.char in b"\r\n":
|
||||
self.char = None
|
||||
|
||||
return b"".join(s).decode("latin-1")
|
||||
|
||||
|
||||
def _accept(prefix: bytes) -> bool:
|
||||
return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
|
||||
|
||||
|
|
|
@ -331,7 +331,6 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
||||
or palette
|
||||
):
|
||||
self.pyaccess = None
|
||||
if "transparency" in self.info:
|
||||
self.im.putpalettealpha(self.info["transparency"], 0)
|
||||
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
||||
|
|
|
@ -329,7 +329,6 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
# if tile is PNG, it won't really be loaded yet
|
||||
im.load()
|
||||
self.im = im.im
|
||||
self.pyaccess = None
|
||||
self._mode = im.mode
|
||||
if im.palette:
|
||||
self.palette = im.palette
|
||||
|
|
|
@ -125,14 +125,6 @@ except ImportError as v:
|
|||
raise
|
||||
|
||||
|
||||
USE_CFFI_ACCESS = False
|
||||
cffi: ModuleType | None
|
||||
try:
|
||||
import cffi
|
||||
except ImportError:
|
||||
cffi = None
|
||||
|
||||
|
||||
def isImageType(t: Any) -> TypeGuard[Image]:
|
||||
"""
|
||||
Checks if an object is an image object.
|
||||
|
@ -229,7 +221,7 @@ if hasattr(core, "DEFAULT_STRATEGY"):
|
|||
# Registries
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ImageFile, PyAccess
|
||||
from . import ImageFile
|
||||
ID: list[str] = []
|
||||
OPEN: dict[
|
||||
str,
|
||||
|
@ -549,7 +541,6 @@ class Image:
|
|||
self.palette = None
|
||||
self.info = {}
|
||||
self.readonly = 0
|
||||
self.pyaccess = None
|
||||
self._exif = None
|
||||
|
||||
@property
|
||||
|
@ -631,7 +622,6 @@ class Image:
|
|||
def _copy(self) -> None:
|
||||
self.load()
|
||||
self.im = self.im.copy()
|
||||
self.pyaccess = None
|
||||
self.readonly = 0
|
||||
|
||||
def _ensure_mutable(self) -> None:
|
||||
|
@ -882,7 +872,7 @@ class Image:
|
|||
msg = "cannot decode image data"
|
||||
raise ValueError(msg)
|
||||
|
||||
def load(self) -> core.PixelAccess | PyAccess.PyAccess | None:
|
||||
def load(self) -> core.PixelAccess | None:
|
||||
"""
|
||||
Allocates storage for the image and loads the pixel data. In
|
||||
normal cases, you don't need to call this method, since the
|
||||
|
@ -895,7 +885,7 @@ class Image:
|
|||
operations. See :ref:`file-handling` for more information.
|
||||
|
||||
:returns: An image access object.
|
||||
:rtype: :py:class:`.PixelAccess` or :py:class:`.PyAccess`
|
||||
:rtype: :py:class:`.PixelAccess`
|
||||
"""
|
||||
if self.im is not None and self.palette and self.palette.dirty:
|
||||
# realize palette
|
||||
|
@ -915,14 +905,6 @@ class Image:
|
|||
)
|
||||
|
||||
if self.im is not None:
|
||||
if cffi and USE_CFFI_ACCESS:
|
||||
if self.pyaccess:
|
||||
return self.pyaccess
|
||||
from . import PyAccess
|
||||
|
||||
self.pyaccess = PyAccess.new(self, self.readonly)
|
||||
if self.pyaccess:
|
||||
return self.pyaccess
|
||||
return self.im.pixel_access(self.readonly)
|
||||
return None
|
||||
|
||||
|
@ -1685,8 +1667,6 @@ class Image:
|
|||
"""
|
||||
|
||||
self.load()
|
||||
if self.pyaccess:
|
||||
return self.pyaccess.getpixel(xy)
|
||||
return self.im.getpixel(tuple(xy))
|
||||
|
||||
def getprojection(self) -> tuple[list[int], list[int]]:
|
||||
|
@ -1983,7 +1963,6 @@ class Image:
|
|||
msg = "alpha channel could not be added"
|
||||
raise ValueError(msg) from e # sanity check
|
||||
self.im = im
|
||||
self.pyaccess = None
|
||||
self._mode = self.im.mode
|
||||
except KeyError as e:
|
||||
msg = "illegal image mode"
|
||||
|
@ -2101,9 +2080,6 @@ class Image:
|
|||
self._copy()
|
||||
self.load()
|
||||
|
||||
if self.pyaccess:
|
||||
return self.pyaccess.putpixel(xy, value)
|
||||
|
||||
if (
|
||||
self.mode in ("P", "PA")
|
||||
and isinstance(value, (list, tuple))
|
||||
|
@ -2768,7 +2744,6 @@ class Image:
|
|||
self._mode = self.im.mode
|
||||
|
||||
self.readonly = 0
|
||||
self.pyaccess = None
|
||||
|
||||
# FIXME: the different transform methods need further explanation
|
||||
# instead of bloating the method docs, add a separate chapter.
|
||||
|
|
|
@ -698,7 +698,6 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
|
|||
transposed_image = image.transpose(method)
|
||||
if in_place:
|
||||
image.im = transposed_image.im
|
||||
image.pyaccess = None
|
||||
image._size = transposed_image._size
|
||||
exif_image = image if in_place else transposed_image
|
||||
|
||||
|
|
|
@ -851,8 +851,6 @@ class PngImageFile(ImageFile.ImageFile):
|
|||
self.png.rewind()
|
||||
self.__prepare_idat = self.__rewind_idat
|
||||
self.im = None
|
||||
if self.pyaccess:
|
||||
self.pyaccess = None
|
||||
self.info = self.png.im_info
|
||||
self.tile = self.png.im_tile
|
||||
self.fp = self._fp
|
||||
|
@ -1039,8 +1037,6 @@ class PngImageFile(ImageFile.ImageFile):
|
|||
mask = updated.convert("RGBA")
|
||||
self._prev_im.paste(updated, self.dispose_extent, mask)
|
||||
self.im = self._prev_im
|
||||
if self.pyaccess:
|
||||
self.pyaccess = None
|
||||
|
||||
def _getexif(self) -> dict[str, Any] | None:
|
||||
if "exif" not in self.info:
|
||||
|
|
|
@ -1,381 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# Pillow fork
|
||||
#
|
||||
# Python implementation of the PixelAccess Object
|
||||
#
|
||||
# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved.
|
||||
# Copyright (c) 1995-2009 by Fredrik Lundh.
|
||||
# Copyright (c) 2013 Eric Soroos
|
||||
#
|
||||
# See the README file for information on usage and redistribution
|
||||
#
|
||||
|
||||
# Notes:
|
||||
#
|
||||
# * Implements the pixel access object following Access.c
|
||||
# * Taking only the tuple form, which is used from python.
|
||||
# * Fill.c uses the integer form, but it's still going to use the old
|
||||
# Access.c implementation.
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ._deprecate import deprecate
|
||||
|
||||
FFI: type
|
||||
try:
|
||||
from cffi import FFI
|
||||
|
||||
defs = """
|
||||
struct Pixel_RGBA {
|
||||
unsigned char r,g,b,a;
|
||||
};
|
||||
struct Pixel_I16 {
|
||||
unsigned char l,r;
|
||||
};
|
||||
"""
|
||||
ffi = FFI()
|
||||
ffi.cdef(defs)
|
||||
except ImportError as ex:
|
||||
# Allow error import for doc purposes, but error out when accessing
|
||||
# anything in core.
|
||||
from ._util import DeferredError
|
||||
|
||||
FFI = ffi = DeferredError.new(ex)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Image
|
||||
|
||||
|
||||
class PyAccess:
|
||||
def __init__(self, img: Image.Image, readonly: bool = False) -> None:
|
||||
deprecate("PyAccess", 11)
|
||||
vals = dict(img.im.unsafe_ptrs)
|
||||
self.readonly = readonly
|
||||
self.image8 = ffi.cast("unsigned char **", vals["image8"])
|
||||
self.image32 = ffi.cast("int **", vals["image32"])
|
||||
self.image = ffi.cast("unsigned char **", vals["image"])
|
||||
self.xsize, self.ysize = img.im.size
|
||||
self._img = img
|
||||
|
||||
# Keep pointer to im object to prevent dereferencing.
|
||||
self._im = img.im
|
||||
if self._im.mode in ("P", "PA"):
|
||||
self._palette = img.palette
|
||||
|
||||
# Debugging is polluting test traces, only useful here
|
||||
# when hacking on PyAccess
|
||||
# logger.debug("%s", vals)
|
||||
self._post_init()
|
||||
|
||||
def _post_init(self) -> None:
|
||||
pass
|
||||
|
||||
def __setitem__(
|
||||
self,
|
||||
xy: tuple[int, int] | list[int],
|
||||
color: float | tuple[int, ...] | list[int],
|
||||
) -> None:
|
||||
"""
|
||||
Modifies the pixel at x,y. The color is given as a single
|
||||
numerical value for single band images, and a tuple for
|
||||
multi-band images. In addition to this, RGB and RGBA tuples
|
||||
are accepted for P and PA images.
|
||||
|
||||
:param xy: The pixel coordinate, given as (x, y). See
|
||||
:ref:`coordinate-system`.
|
||||
:param color: The pixel value.
|
||||
"""
|
||||
if self.readonly:
|
||||
msg = "Attempt to putpixel a read only image"
|
||||
raise ValueError(msg)
|
||||
(x, y) = xy
|
||||
if x < 0:
|
||||
x = self.xsize + x
|
||||
if y < 0:
|
||||
y = self.ysize + y
|
||||
(x, y) = self.check_xy((x, y))
|
||||
|
||||
if (
|
||||
self._im.mode in ("P", "PA")
|
||||
and isinstance(color, (list, tuple))
|
||||
and len(color) in [3, 4]
|
||||
):
|
||||
# RGB or RGBA value for a P or PA image
|
||||
if self._im.mode == "PA":
|
||||
alpha = color[3] if len(color) == 4 else 255
|
||||
color = color[:3]
|
||||
palette_index = self._palette.getcolor(color, self._img)
|
||||
color = (palette_index, alpha) if self._im.mode == "PA" else palette_index
|
||||
|
||||
return self.set_pixel(x, y, color)
|
||||
|
||||
def __getitem__(self, xy: tuple[int, int] | list[int]) -> float | tuple[int, ...]:
|
||||
"""
|
||||
Returns the pixel at x,y. The pixel is returned as a single
|
||||
value for single band images or a tuple for multiple band
|
||||
images
|
||||
|
||||
:param xy: The pixel coordinate, given as (x, y). See
|
||||
:ref:`coordinate-system`.
|
||||
:returns: a pixel value for single band images, a tuple of
|
||||
pixel values for multiband images.
|
||||
"""
|
||||
(x, y) = xy
|
||||
if x < 0:
|
||||
x = self.xsize + x
|
||||
if y < 0:
|
||||
y = self.ysize + y
|
||||
(x, y) = self.check_xy((x, y))
|
||||
return self.get_pixel(x, y)
|
||||
|
||||
putpixel = __setitem__
|
||||
getpixel = __getitem__
|
||||
|
||||
def check_xy(self, xy: tuple[int, int]) -> tuple[int, int]:
|
||||
(x, y) = xy
|
||||
if not (0 <= x < self.xsize and 0 <= y < self.ysize):
|
||||
msg = "pixel location out of range"
|
||||
raise ValueError(msg)
|
||||
return xy
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> float | tuple[int, ...]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_pixel(
|
||||
self, x: int, y: int, color: float | tuple[int, ...] | list[int]
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class _PyAccess32_2(PyAccess):
|
||||
"""PA, LA, stored in first and last bytes of a 32 bit word"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> tuple[int, int]:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.r, pixel.a
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
# tuple
|
||||
pixel.r = min(color[0], 255)
|
||||
pixel.a = min(color[1], 255)
|
||||
|
||||
|
||||
class _PyAccess32_3(PyAccess):
|
||||
"""RGB and friends, stored in the first three bytes of a 32 bit word"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> tuple[int, int, int]:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.r, pixel.g, pixel.b
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
# tuple
|
||||
pixel.r = min(color[0], 255)
|
||||
pixel.g = min(color[1], 255)
|
||||
pixel.b = min(color[2], 255)
|
||||
pixel.a = 255
|
||||
|
||||
|
||||
class _PyAccess32_4(PyAccess):
|
||||
"""RGBA etc, all 4 bytes of a 32 bit word"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> tuple[int, int, int, int]:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.r, pixel.g, pixel.b, pixel.a
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
# tuple
|
||||
pixel.r = min(color[0], 255)
|
||||
pixel.g = min(color[1], 255)
|
||||
pixel.b = min(color[2], 255)
|
||||
pixel.a = min(color[3], 255)
|
||||
|
||||
|
||||
class _PyAccess8(PyAccess):
|
||||
"""1, L, P, 8 bit images stored as uint8"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = self.image8
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
return self.pixels[y][x]
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
try:
|
||||
# integer
|
||||
self.pixels[y][x] = min(color, 255)
|
||||
except TypeError:
|
||||
# tuple
|
||||
self.pixels[y][x] = min(color[0], 255)
|
||||
|
||||
|
||||
class _PyAccessI16_N(PyAccess):
|
||||
"""I;16 access, native bitendian without conversion"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("unsigned short **", self.image)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
return self.pixels[y][x]
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
try:
|
||||
# integer
|
||||
self.pixels[y][x] = min(color, 65535)
|
||||
except TypeError:
|
||||
# tuple
|
||||
self.pixels[y][x] = min(color[0], 65535)
|
||||
|
||||
|
||||
class _PyAccessI16_L(PyAccess):
|
||||
"""I;16L access, with conversion"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.l + pixel.r * 256
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
try:
|
||||
color = min(color, 65535)
|
||||
except TypeError:
|
||||
color = min(color[0], 65535)
|
||||
|
||||
pixel.l = color & 0xFF
|
||||
pixel.r = color >> 8
|
||||
|
||||
|
||||
class _PyAccessI16_B(PyAccess):
|
||||
"""I;16B access, with conversion"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.l * 256 + pixel.r
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
try:
|
||||
color = min(color, 65535)
|
||||
except Exception:
|
||||
color = min(color[0], 65535)
|
||||
|
||||
pixel.l = color >> 8
|
||||
pixel.r = color & 0xFF
|
||||
|
||||
|
||||
class _PyAccessI32_N(PyAccess):
|
||||
"""Signed Int32 access, native endian"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = self.image32
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
return self.pixels[y][x]
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
self.pixels[y][x] = color
|
||||
|
||||
|
||||
class _PyAccessI32_Swap(PyAccess):
|
||||
"""I;32L/B access, with byteswapping conversion"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = self.image32
|
||||
|
||||
def reverse(self, i):
|
||||
orig = ffi.new("int *", i)
|
||||
chars = ffi.cast("unsigned char *", orig)
|
||||
chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], chars[1], chars[0]
|
||||
return ffi.cast("int *", chars)[0]
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
return self.reverse(self.pixels[y][x])
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
self.pixels[y][x] = self.reverse(color)
|
||||
|
||||
|
||||
class _PyAccessF(PyAccess):
|
||||
"""32 bit float access"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("float **", self.image32)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> float:
|
||||
return self.pixels[y][x]
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
try:
|
||||
# not a tuple
|
||||
self.pixels[y][x] = color
|
||||
except TypeError:
|
||||
# tuple
|
||||
self.pixels[y][x] = color[0]
|
||||
|
||||
|
||||
mode_map = {
|
||||
"1": _PyAccess8,
|
||||
"L": _PyAccess8,
|
||||
"P": _PyAccess8,
|
||||
"I;16N": _PyAccessI16_N,
|
||||
"LA": _PyAccess32_2,
|
||||
"La": _PyAccess32_2,
|
||||
"PA": _PyAccess32_2,
|
||||
"RGB": _PyAccess32_3,
|
||||
"LAB": _PyAccess32_3,
|
||||
"HSV": _PyAccess32_3,
|
||||
"YCbCr": _PyAccess32_3,
|
||||
"RGBA": _PyAccess32_4,
|
||||
"RGBa": _PyAccess32_4,
|
||||
"RGBX": _PyAccess32_4,
|
||||
"CMYK": _PyAccess32_4,
|
||||
"F": _PyAccessF,
|
||||
"I": _PyAccessI32_N,
|
||||
}
|
||||
|
||||
if sys.byteorder == "little":
|
||||
mode_map["I;16"] = _PyAccessI16_N
|
||||
mode_map["I;16L"] = _PyAccessI16_N
|
||||
mode_map["I;16B"] = _PyAccessI16_B
|
||||
|
||||
mode_map["I;32L"] = _PyAccessI32_N
|
||||
mode_map["I;32B"] = _PyAccessI32_Swap
|
||||
else:
|
||||
mode_map["I;16"] = _PyAccessI16_L
|
||||
mode_map["I;16L"] = _PyAccessI16_L
|
||||
mode_map["I;16B"] = _PyAccessI16_N
|
||||
|
||||
mode_map["I;32L"] = _PyAccessI32_Swap
|
||||
mode_map["I;32B"] = _PyAccessI32_N
|
||||
|
||||
|
||||
def new(img: Image.Image, readonly: bool = False) -> PyAccess | None:
|
||||
access_type = mode_map.get(img.mode, None)
|
||||
if not access_type:
|
||||
logger.debug("PyAccess Not Implemented: %s", img.mode)
|
||||
return None
|
||||
return access_type(img, readonly)
|
|
@ -45,8 +45,6 @@ def deprecate(
|
|||
elif when <= int(__version__.split(".")[0]):
|
||||
msg = f"{deprecated} {is_} deprecated and should be removed."
|
||||
raise RuntimeError(msg)
|
||||
elif when == 11:
|
||||
removed = "Pillow 11 (2024-10-15)"
|
||||
elif when == 12:
|
||||
removed = "Pillow 12 (2025-10-15)"
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue
Block a user