mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46:18 +03:00
Merge pull request #120 from radarhere/rm-deprecated-psfile
This commit is contained in:
commit
4ab4fa4cd7
|
@ -28,8 +28,6 @@ fi
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade wheel
|
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 coverage
|
||||||
python3 -m pip install defusedxml
|
python3 -m pip install defusedxml
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
|
|
|
@ -19,6 +19,5 @@ exclude_also =
|
||||||
[run]
|
[run]
|
||||||
omit =
|
omit =
|
||||||
Tests/32bit_segfault_check.py
|
Tests/32bit_segfault_check.py
|
||||||
Tests/bench_cffi_access.py
|
|
||||||
Tests/check_*.py
|
Tests/check_*.py
|
||||||
Tests/createfontdatachunk.py
|
Tests/createfontdatachunk.py
|
||||||
|
|
3
.github/workflows/macos-install.sh
vendored
3
.github/workflows/macos-install.sh
vendored
|
@ -18,9 +18,6 @@ else
|
||||||
fi
|
fi
|
||||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
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 coverage
|
||||||
python3 -m pip install defusedxml
|
python3 -m pip install defusedxml
|
||||||
python3 -m pip install olefile
|
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
|
make
|
||||||
netpbm
|
netpbm
|
||||||
perl
|
perl
|
||||||
python3${{ matrix.python-minor-version }}-cffi
|
|
||||||
python3${{ matrix.python-minor-version }}-cython
|
python3${{ matrix.python-minor-version }}-cython
|
||||||
python3${{ matrix.python-minor-version }}-devel
|
python3${{ matrix.python-minor-version }}-devel
|
||||||
python3${{ matrix.python-minor-version }}-numpy
|
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-libtiff \
|
||||||
mingw-w64-x86_64-libwebp \
|
mingw-w64-x86_64-libwebp \
|
||||||
mingw-w64-x86_64-openjpeg2 \
|
mingw-w64-x86_64-openjpeg2 \
|
||||||
mingw-w64-x86_64-python3-cffi \
|
|
||||||
mingw-w64-x86_64-python3-numpy \
|
mingw-w64-x86_64-python3-numpy \
|
||||||
mingw-w64-x86_64-python3-olefile \
|
mingw-w64-x86_64-python3-olefile \
|
||||||
mingw-w64-x86_64-python3-setuptools \
|
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)
|
|
|
@ -12,19 +12,6 @@ from PIL import Image
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper, is_win32
|
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
|
numpy: ModuleType | None
|
||||||
try:
|
try:
|
||||||
import numpy
|
import numpy
|
||||||
|
@ -32,21 +19,7 @@ except ImportError:
|
||||||
numpy = None
|
numpy = None
|
||||||
|
|
||||||
|
|
||||||
class AccessTest:
|
class TestImagePutPixel:
|
||||||
# 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):
|
|
||||||
def test_sanity(self) -> None:
|
def test_sanity(self) -> None:
|
||||||
im1 = hopper()
|
im1 = hopper()
|
||||||
im2 = Image.new(im1.mode, im1.size, 0)
|
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)
|
assert pix[numpy.int32(1), numpy.int32(2)] == (18, 20, 59)
|
||||||
|
|
||||||
|
|
||||||
class TestImageGetPixel(AccessTest):
|
class TestImageGetPixel:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def color(mode: str) -> int | tuple[int, ...]:
|
def color(mode: str) -> int | tuple[int, ...]:
|
||||||
bands = Image.getmodebands(mode)
|
bands = Image.getmodebands(mode)
|
||||||
|
@ -144,9 +117,6 @@ class TestImageGetPixel(AccessTest):
|
||||||
return tuple(range(1, bands + 1))
|
return tuple(range(1, bands + 1))
|
||||||
|
|
||||||
def check(self, mode: str, expected_color_int: int | None = None) -> None:
|
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 = (
|
expected_color = (
|
||||||
self.color(mode) if expected_color_int is None else expected_color_int
|
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
|
# Check 0x0 image with None initial color
|
||||||
im = Image.new(mode, (0, 0), None)
|
im = Image.new(mode, (0, 0), None)
|
||||||
assert im.load() is not None
|
assert im.load() is not None
|
||||||
error = ValueError if self._need_cffi_access else IndexError
|
with pytest.raises(IndexError):
|
||||||
with pytest.raises(error):
|
|
||||||
im.putpixel((0, 0), expected_color)
|
im.putpixel((0, 0), expected_color)
|
||||||
with pytest.raises(error):
|
with pytest.raises(IndexError):
|
||||||
im.getpixel((0, 0))
|
im.getpixel((0, 0))
|
||||||
# Check negative index
|
# Check negative index
|
||||||
with pytest.raises(error):
|
with pytest.raises(IndexError):
|
||||||
im.putpixel((-1, -1), expected_color)
|
im.putpixel((-1, -1), expected_color)
|
||||||
with pytest.raises(error):
|
with pytest.raises(IndexError):
|
||||||
im.getpixel((-1, -1))
|
im.getpixel((-1, -1))
|
||||||
|
|
||||||
# Check initial color
|
# Check initial color
|
||||||
|
@ -199,10 +168,10 @@ class TestImageGetPixel(AccessTest):
|
||||||
|
|
||||||
# Check 0x0 image with initial color
|
# Check 0x0 image with initial color
|
||||||
im = Image.new(mode, (0, 0), expected_color)
|
im = Image.new(mode, (0, 0), expected_color)
|
||||||
with pytest.raises(error):
|
with pytest.raises(IndexError):
|
||||||
im.getpixel((0, 0))
|
im.getpixel((0, 0))
|
||||||
# Check negative index
|
# Check negative index
|
||||||
with pytest.raises(error):
|
with pytest.raises(IndexError):
|
||||||
im.getpixel((-1, -1))
|
im.getpixel((-1, -1))
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", Image.MODES)
|
@pytest.mark.parametrize("mode", Image.MODES)
|
||||||
|
@ -235,126 +204,7 @@ class TestImageGetPixel(AccessTest):
|
||||||
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
class TestImagePutPixelError:
|
||||||
@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):
|
|
||||||
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
|
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
|
||||||
IMAGE_MODES2 = ["L", "I", "I;16"]
|
IMAGE_MODES2 = ["L", "I", "I;16"]
|
||||||
INVALID_TYPES = ["foo", 1.0, None]
|
INVALID_TYPES = ["foo", 1.0, None]
|
||||||
|
|
|
@ -17,6 +17,5 @@ coverage:
|
||||||
# Matches 'omit:' in .coveragerc
|
# Matches 'omit:' in .coveragerc
|
||||||
ignore:
|
ignore:
|
||||||
- "Tests/32bit_segfault_check.py"
|
- "Tests/32bit_segfault_check.py"
|
||||||
- "Tests/bench_cffi_access.py"
|
|
||||||
- "Tests/check_*.py"
|
- "Tests/check_*.py"
|
||||||
- "Tests/createfontdatachunk.py"
|
- "Tests/createfontdatachunk.py"
|
||||||
|
|
|
@ -12,18 +12,6 @@ Deprecated features
|
||||||
Below are features which are considered deprecated. Where appropriate,
|
Below are features which are considered deprecated. Where appropriate,
|
||||||
a :py:exc:`DeprecationWarning` is issued.
|
a :py:exc:`DeprecationWarning` is issued.
|
||||||
|
|
||||||
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
|
ImageFile.raise_oserror
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -138,6 +126,18 @@ This class was only made as a helper to be used internally,
|
||||||
so there is no replacement. If you need this functionality though,
|
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.
|
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
|
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
|
JpegPresets
|
||||||
PSDraw
|
PSDraw
|
||||||
PixelAccess
|
PixelAccess
|
||||||
PyAccess
|
|
||||||
features
|
features
|
||||||
../PIL
|
../PIL
|
||||||
plugins
|
plugins
|
||||||
|
|
|
@ -158,7 +158,7 @@ PyAccess and Image.USE_CFFI_ACCESS
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Since Pillow's C API is now faster than PyAccess on PyPy,
|
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.
|
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
|
``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, is
|
||||||
|
|
|
@ -25,6 +25,15 @@ This class was only made as a helper to be used internally,
|
||||||
so there is no replacement. If you need this functionality though,
|
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.
|
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
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
|
|
@ -331,7 +331,6 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
||||||
or palette
|
or palette
|
||||||
):
|
):
|
||||||
self.pyaccess = None
|
|
||||||
if "transparency" in self.info:
|
if "transparency" in self.info:
|
||||||
self.im.putpalettealpha(self.info["transparency"], 0)
|
self.im.putpalettealpha(self.info["transparency"], 0)
|
||||||
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
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
|
# if tile is PNG, it won't really be loaded yet
|
||||||
im.load()
|
im.load()
|
||||||
self.im = im.im
|
self.im = im.im
|
||||||
self.pyaccess = None
|
|
||||||
self._mode = im.mode
|
self._mode = im.mode
|
||||||
if im.palette:
|
if im.palette:
|
||||||
self.palette = im.palette
|
self.palette = im.palette
|
||||||
|
|
|
@ -125,14 +125,6 @@ except ImportError as v:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
USE_CFFI_ACCESS = False
|
|
||||||
cffi: ModuleType | None
|
|
||||||
try:
|
|
||||||
import cffi
|
|
||||||
except ImportError:
|
|
||||||
cffi = None
|
|
||||||
|
|
||||||
|
|
||||||
def isImageType(t: Any) -> TypeGuard[Image]:
|
def isImageType(t: Any) -> TypeGuard[Image]:
|
||||||
"""
|
"""
|
||||||
Checks if an object is an image object.
|
Checks if an object is an image object.
|
||||||
|
@ -229,7 +221,7 @@ if hasattr(core, "DEFAULT_STRATEGY"):
|
||||||
# Registries
|
# Registries
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import ImageFile, PyAccess
|
from . import ImageFile
|
||||||
ID: list[str] = []
|
ID: list[str] = []
|
||||||
OPEN: dict[
|
OPEN: dict[
|
||||||
str,
|
str,
|
||||||
|
@ -549,7 +541,6 @@ class Image:
|
||||||
self.palette = None
|
self.palette = None
|
||||||
self.info = {}
|
self.info = {}
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
self.pyaccess = None
|
|
||||||
self._exif = None
|
self._exif = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -631,7 +622,6 @@ class Image:
|
||||||
def _copy(self) -> None:
|
def _copy(self) -> None:
|
||||||
self.load()
|
self.load()
|
||||||
self.im = self.im.copy()
|
self.im = self.im.copy()
|
||||||
self.pyaccess = None
|
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
|
|
||||||
def _ensure_mutable(self) -> None:
|
def _ensure_mutable(self) -> None:
|
||||||
|
@ -882,7 +872,7 @@ class Image:
|
||||||
msg = "cannot decode image data"
|
msg = "cannot decode image data"
|
||||||
raise ValueError(msg)
|
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
|
Allocates storage for the image and loads the pixel data. In
|
||||||
normal cases, you don't need to call this method, since the
|
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.
|
operations. See :ref:`file-handling` for more information.
|
||||||
|
|
||||||
:returns: An image access object.
|
: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:
|
if self.im is not None and self.palette and self.palette.dirty:
|
||||||
# realize palette
|
# realize palette
|
||||||
|
@ -915,14 +905,6 @@ class Image:
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.im is not None:
|
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 self.im.pixel_access(self.readonly)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -1685,8 +1667,6 @@ class Image:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
if self.pyaccess:
|
|
||||||
return self.pyaccess.getpixel(xy)
|
|
||||||
return self.im.getpixel(tuple(xy))
|
return self.im.getpixel(tuple(xy))
|
||||||
|
|
||||||
def getprojection(self) -> tuple[list[int], list[int]]:
|
def getprojection(self) -> tuple[list[int], list[int]]:
|
||||||
|
@ -1983,7 +1963,6 @@ class Image:
|
||||||
msg = "alpha channel could not be added"
|
msg = "alpha channel could not be added"
|
||||||
raise ValueError(msg) from e # sanity check
|
raise ValueError(msg) from e # sanity check
|
||||||
self.im = im
|
self.im = im
|
||||||
self.pyaccess = None
|
|
||||||
self._mode = self.im.mode
|
self._mode = self.im.mode
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
msg = "illegal image mode"
|
msg = "illegal image mode"
|
||||||
|
@ -2101,9 +2080,6 @@ class Image:
|
||||||
self._copy()
|
self._copy()
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
if self.pyaccess:
|
|
||||||
return self.pyaccess.putpixel(xy, value)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.mode in ("P", "PA")
|
self.mode in ("P", "PA")
|
||||||
and isinstance(value, (list, tuple))
|
and isinstance(value, (list, tuple))
|
||||||
|
@ -2768,7 +2744,6 @@ class Image:
|
||||||
self._mode = self.im.mode
|
self._mode = self.im.mode
|
||||||
|
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
self.pyaccess = None
|
|
||||||
|
|
||||||
# FIXME: the different transform methods need further explanation
|
# FIXME: the different transform methods need further explanation
|
||||||
# instead of bloating the method docs, add a separate chapter.
|
# 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)
|
transposed_image = image.transpose(method)
|
||||||
if in_place:
|
if in_place:
|
||||||
image.im = transposed_image.im
|
image.im = transposed_image.im
|
||||||
image.pyaccess = None
|
|
||||||
image._size = transposed_image._size
|
image._size = transposed_image._size
|
||||||
exif_image = image if in_place else transposed_image
|
exif_image = image if in_place else transposed_image
|
||||||
|
|
||||||
|
|
|
@ -851,8 +851,6 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.png.rewind()
|
self.png.rewind()
|
||||||
self.__prepare_idat = self.__rewind_idat
|
self.__prepare_idat = self.__rewind_idat
|
||||||
self.im = None
|
self.im = None
|
||||||
if self.pyaccess:
|
|
||||||
self.pyaccess = None
|
|
||||||
self.info = self.png.im_info
|
self.info = self.png.im_info
|
||||||
self.tile = self.png.im_tile
|
self.tile = self.png.im_tile
|
||||||
self.fp = self._fp
|
self.fp = self._fp
|
||||||
|
@ -1039,8 +1037,6 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
mask = updated.convert("RGBA")
|
mask = updated.convert("RGBA")
|
||||||
self._prev_im.paste(updated, self.dispose_extent, mask)
|
self._prev_im.paste(updated, self.dispose_extent, mask)
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
if self.pyaccess:
|
|
||||||
self.pyaccess = None
|
|
||||||
|
|
||||||
def _getexif(self) -> dict[str, Any] | None:
|
def _getexif(self) -> dict[str, Any] | None:
|
||||||
if "exif" not in self.info:
|
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]):
|
elif when <= int(__version__.split(".")[0]):
|
||||||
msg = f"{deprecated} {is_} deprecated and should be removed."
|
msg = f"{deprecated} {is_} deprecated and should be removed."
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
elif when == 11:
|
|
||||||
removed = "Pillow 11 (2024-10-15)"
|
|
||||||
elif when == 12:
|
elif when == 12:
|
||||||
removed = "Pillow 12 (2025-10-15)"
|
removed = "Pillow 12 (2025-10-15)"
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user