Deprecate getdata(), in favour of new get_flattened_data() (#9292)

This commit is contained in:
Andrew Murray 2026-01-02 10:59:56 +11:00 committed by GitHub
parent b51a036685
commit 3baedf2648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 117 additions and 66 deletions

View File

@ -55,8 +55,8 @@ def convert_to_comparable(
if a.mode == "P": if a.mode == "P":
new_a = Image.new("L", a.size) new_a = Image.new("L", a.size)
new_b = Image.new("L", b.size) new_b = Image.new("L", b.size)
new_a.putdata(a.getdata()) new_a.putdata(a.get_flattened_data())
new_b.putdata(b.getdata()) new_b.putdata(b.get_flattened_data())
elif a.mode == "I;16": elif a.mode == "I;16":
new_a = a.convert("I") new_a = a.convert("I")
new_b = b.convert("I") new_b = b.convert("I")

View File

@ -28,9 +28,13 @@ def box_blur(image: Image.Image, radius: float = 1, n: int = 1) -> Image.Image:
def assert_image(im: Image.Image, data: list[list[int]], delta: int = 0) -> None: def assert_image(im: Image.Image, data: list[list[int]], delta: int = 0) -> None:
it = iter(im.getdata()) it = iter(im.get_flattened_data())
for data_row in data: for data_row in data:
im_row = [next(it) for _ in range(im.size[0])] im_row = []
for _ in range(im.width):
im_v = next(it)
assert isinstance(im_v, (int, float))
im_row.append(im_v)
if any(abs(data_v - im_v) > delta for data_v, im_v in zip(data_row, im_row)): if any(abs(data_v - im_v) > delta for data_v, im_v in zip(data_row, im_row)):
assert im_row == data_row assert im_row == data_row
with pytest.raises(StopIteration): with pytest.raises(StopIteration):

View File

@ -121,7 +121,6 @@ class TestFileAvif:
assert image.size == (128, 128) assert image.size == (128, 128)
assert image.format == "AVIF" assert image.format == "AVIF"
assert image.get_format_mimetype() == "image/avif" assert image.get_format_mimetype() == "image/avif"
image.getdata()
# generated with: # generated with:
# avifdec hopper.avif hopper_avif_write.png # avifdec hopper.avif hopper_avif_write.png
@ -143,7 +142,6 @@ class TestFileAvif:
assert reloaded.mode == "RGB" assert reloaded.mode == "RGB"
assert reloaded.size == (128, 128) assert reloaded.size == (128, 128)
assert reloaded.format == "AVIF" assert reloaded.format == "AVIF"
reloaded.getdata()
# avifdec hopper.avif avif/hopper_avif_write.png # avifdec hopper.avif avif/hopper_avif_write.png
assert_image_similar_tofile( assert_image_similar_tofile(

View File

@ -42,7 +42,6 @@ class LibTiffTestCase:
# Does the data actually load # Does the data actually load
im.load() im.load()
im.getdata()
assert isinstance(im, TiffImagePlugin.TiffImageFile) assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im._compression == "group4" assert im._compression == "group4"

View File

@ -60,7 +60,6 @@ class TestFileWebp:
assert image.size == (128, 128) assert image.size == (128, 128)
assert image.format == "WEBP" assert image.format == "WEBP"
image.load() image.load()
image.getdata()
# generated with: # generated with:
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
@ -77,7 +76,6 @@ class TestFileWebp:
assert image.size == (128, 128) assert image.size == (128, 128)
assert image.format == "WEBP" assert image.format == "WEBP"
image.load() image.load()
image.getdata()
if mode == self.rgb_mode: if mode == self.rgb_mode:
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm

View File

@ -29,7 +29,6 @@ def test_read_rgba() -> None:
assert image.size == (200, 150) assert image.size == (200, 150)
assert image.format == "WEBP" assert image.format == "WEBP"
image.load() image.load()
image.getdata()
image.tobytes() image.tobytes()
@ -60,7 +59,6 @@ def test_write_lossless_rgb(tmp_path: Path) -> None:
assert image.size == pil_image.size assert image.size == pil_image.size
assert image.format == "WEBP" assert image.format == "WEBP"
image.load() image.load()
image.getdata()
assert_image_equal(image, pil_image) assert_image_equal(image, pil_image)
@ -83,7 +81,6 @@ def test_write_rgba(tmp_path: Path) -> None:
assert image.size == (10, 10) assert image.size == (10, 10)
assert image.format == "WEBP" assert image.format == "WEBP"
image.load() image.load()
image.getdata()
assert_image_similar(image, pil_image, 1.0) assert_image_similar(image, pil_image, 1.0)
@ -133,7 +130,6 @@ def test_write_unsupported_mode_PA(tmp_path: Path) -> None:
assert image.format == "WEBP" assert image.format == "WEBP"
image.load() image.load()
image.getdata()
with Image.open(file_path) as im: with Image.open(file_path) as im:
target = im.convert("RGBA") target = im.convert("RGBA")

View File

@ -24,6 +24,5 @@ def test_write_lossless_rgb(tmp_path: Path) -> None:
assert image.size == (128, 128) assert image.size == (128, 128)
assert image.format == "WEBP" assert image.format == "WEBP"
image.load() image.load()
image.getdata()
assert_image_equal(image, hopper(RGB_MODE)) assert_image_equal(image, hopper(RGB_MODE))

View File

@ -13,15 +13,15 @@ def test_white() -> None:
k = i.getpixel((0, 0)) k = i.getpixel((0, 0))
L = i.getdata(0) L = i.get_flattened_data(0)
a = i.getdata(1) a = i.get_flattened_data(1)
b = i.getdata(2) b = i.get_flattened_data(2)
assert k == (255, 128, 128) assert k == (255, 128, 128)
assert list(L) == [255] * 100 assert L == (255,) * 100
assert list(a) == [128] * 100 assert a == (128,) * 100
assert list(b) == [128] * 100 assert b == (128,) * 100
def test_green() -> None: def test_green() -> None:

View File

@ -1181,10 +1181,10 @@ class TestImageBytes:
assert reloaded.tobytes() == source_bytes assert reloaded.tobytes() == source_bytes
@pytest.mark.parametrize("mode", Image.MODES) @pytest.mark.parametrize("mode", Image.MODES)
def test_getdata_putdata(self, mode: str) -> None: def test_get_flattened_data_putdata(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
reloaded = Image.new(mode, im.size) reloaded = Image.new(mode, im.size)
reloaded.putdata(im.getdata()) reloaded.putdata(im.get_flattened_data())
assert_image_equal(im, reloaded) assert_image_equal(im, reloaded)

View File

@ -78,7 +78,7 @@ def test_fromarray() -> None:
}, },
) )
out = Image.fromarray(wrapped) out = Image.fromarray(wrapped)
return out.mode, out.size, list(i.getdata()) == list(out.getdata()) return out.mode, out.size, i.get_flattened_data() == out.get_flattened_data()
# assert test("1") == ("1", (128, 100), True) # assert test("1") == ("1", (128, 100), True)
assert test("L") == ("L", (128, 100), True) assert test("L") == ("L", (128, 100), True)

View File

@ -95,10 +95,10 @@ def test_crop_zero() -> None:
cropped = im.crop((10, 10, 20, 20)) cropped = im.crop((10, 10, 20, 20))
assert cropped.size == (10, 10) assert cropped.size == (10, 10)
assert cropped.getdata()[0] == (0, 0, 0) assert cropped.getpixel((0, 0)) == (0, 0, 0)
im = Image.new("RGB", (0, 0)) im = Image.new("RGB", (0, 0))
cropped = im.crop((10, 10, 20, 20)) cropped = im.crop((10, 10, 20, 20))
assert cropped.size == (10, 10) assert cropped.size == (10, 10)
assert cropped.getdata()[2] == (0, 0, 0) assert cropped.getpixel((2, 0)) == (0, 0, 0)

View File

@ -1,23 +1,23 @@
from __future__ import annotations from __future__ import annotations
import pytest
from PIL import Image from PIL import Image
from .helper import hopper from .helper import hopper
def test_sanity() -> None: def test_sanity() -> None:
data = hopper().getdata() data = hopper().get_flattened_data()
len(data)
list(data)
assert len(data) == 128 * 128
assert data[0] == (20, 20, 70) assert data[0] == (20, 20, 70)
def test_mode() -> None: def test_mode() -> None:
def getdata(mode: str) -> tuple[float | tuple[int, ...] | None, int, int]: def getdata(mode: str) -> tuple[float | tuple[int, ...] | None, int, int]:
im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST) im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST)
data = im.getdata() data = im.get_flattened_data()
return data[0], len(data), len(list(data)) return data[0], len(data), len(list(data))
assert getdata("1") == (0, 960, 960) assert getdata("1") == (0, 960, 960)
@ -28,3 +28,13 @@ def test_mode() -> None:
assert getdata("RGBA") == ((11, 13, 52, 255), 960, 960) assert getdata("RGBA") == ((11, 13, 52, 255), 960, 960)
assert getdata("CMYK") == ((244, 242, 203, 0), 960, 960) assert getdata("CMYK") == ((244, 242, 203, 0), 960, 960)
assert getdata("YCbCr") == ((16, 147, 123), 960, 960) assert getdata("YCbCr") == ((16, 147, 123), 960, 960)
def test_deprecation() -> None:
im = hopper()
with pytest.warns(DeprecationWarning, match="getdata"):
data = im.getdata()
assert len(data) == 128 * 128
assert data[0] == (20, 20, 70)
assert list(data)[0] == (20, 20, 70)

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import sys import sys
from array import array from array import array
from typing import cast
import pytest import pytest
@ -12,21 +13,19 @@ from .helper import assert_image_equal, hopper
def test_sanity() -> None: def test_sanity() -> None:
im1 = hopper() im1 = hopper()
for data in (im1.get_flattened_data(), im1.im):
im2 = Image.new(im1.mode, im1.size, 0)
im2.putdata(data)
data = list(im1.getdata()) assert_image_equal(im1, im2)
im2 = Image.new(im1.mode, im1.size, 0) # readonly
im2.putdata(data) im2 = Image.new(im1.mode, im2.size, 0)
im2.readonly = 1
im2.putdata(data)
assert_image_equal(im1, im2) assert not im2.readonly
assert_image_equal(im1, im2)
# readonly
im2 = Image.new(im1.mode, im2.size, 0)
im2.readonly = 1
im2.putdata(data)
assert not im2.readonly
assert_image_equal(im1, im2)
def test_long_integers() -> None: def test_long_integers() -> None:
@ -60,22 +59,22 @@ def test_mode_with_L_with_float() -> None:
@pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B")) @pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B"))
def test_mode_i(mode: str) -> None: def test_mode_i(mode: str) -> None:
src = hopper("L") src = hopper("L")
data = list(src.getdata()) data = src.get_flattened_data()
im = Image.new(mode, src.size, 0) im = Image.new(mode, src.size, 0)
im.putdata(data, 2, 256) im.putdata(data, 2, 256)
target = [2 * elt + 256 for elt in data] target = tuple(2 * elt + 256 for elt in cast(tuple[int, ...], data))
assert list(im.getdata()) == target assert im.get_flattened_data() == target
def test_mode_F() -> None: def test_mode_F() -> None:
src = hopper("L") src = hopper("L")
data = list(src.getdata()) data = src.get_flattened_data()
im = Image.new("F", src.size, 0) im = Image.new("F", src.size, 0)
im.putdata(data, 2.0, 256.0) im.putdata(data, 2.0, 256.0)
target = [2.0 * float(elt) + 256.0 for elt in data] target = tuple(2.0 * float(elt) + 256.0 for elt in cast(tuple[int, ...], data))
assert list(im.getdata()) == target assert im.get_flattened_data() == target
def test_array_B() -> None: def test_array_B() -> None:
@ -86,7 +85,7 @@ def test_array_B() -> None:
im = Image.new("L", (150, 100)) im = Image.new("L", (150, 100))
im.putdata(arr) im.putdata(arr)
assert len(im.getdata()) == len(arr) assert len(im.get_flattened_data()) == len(arr)
def test_array_F() -> None: def test_array_F() -> None:
@ -97,7 +96,7 @@ def test_array_F() -> None:
arr = array("f", [0.0]) * 15000 arr = array("f", [0.0]) * 15000
im.putdata(arr) im.putdata(arr)
assert len(im.getdata()) == len(arr) assert len(im.get_flattened_data()) == len(arr)
def test_not_flattened() -> None: def test_not_flattened() -> None:

View File

@ -160,7 +160,7 @@ class TestImagingCoreResize:
r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample) r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample)
assert r.mode == "RGB" assert r.mode == "RGB"
assert r.size == (212, 195) assert r.size == (212, 195)
assert r.getdata()[0] == (0, 0, 0) assert r.getpixel((0, 0)) == (0, 0, 0)
def test_unknown_filter(self) -> None: def test_unknown_filter(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):

View File

@ -274,13 +274,13 @@ def test_simple_lab() -> None:
# not a linear luminance map. so L != 128: # not a linear luminance map. so L != 128:
assert k == (137, 128, 128) assert k == (137, 128, 128)
l_data = i_lab.getdata(0) l_data = i_lab.get_flattened_data(0)
a_data = i_lab.getdata(1) a_data = i_lab.get_flattened_data(1)
b_data = i_lab.getdata(2) b_data = i_lab.get_flattened_data(2)
assert list(l_data) == [137] * 100 assert l_data == (137,) * 100
assert list(a_data) == [128] * 100 assert a_data == (128,) * 100
assert list(b_data) == [128] * 100 assert b_data == (128,) * 100
def test_lab_color() -> None: def test_lab_color() -> None:

View File

@ -20,21 +20,19 @@ TEST_IMAGE_SIZE = (10, 10)
def test_numpy_to_image() -> None: def test_numpy_to_image() -> None:
def to_image(dtype: npt.DTypeLike, bands: int = 1, boolean: int = 0) -> Image.Image: def to_image(dtype: npt.DTypeLike, bands: int = 1, boolean: int = 0) -> Image.Image:
data = tuple(range(100))
if bands == 1: if bands == 1:
if boolean: if boolean:
data = [0, 255] * 50 data = (0, 255) * 50
else:
data = list(range(100))
a = numpy.array(data, dtype=dtype) a = numpy.array(data, dtype=dtype)
a.shape = TEST_IMAGE_SIZE a.shape = TEST_IMAGE_SIZE
i = Image.fromarray(a) i = Image.fromarray(a)
assert list(i.getdata()) == data assert i.get_flattened_data() == data
else: else:
data = list(range(100))
a = numpy.array([[x] * bands for x in data], dtype=dtype) a = numpy.array([[x] * bands for x in data], dtype=dtype)
a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands
i = Image.fromarray(a) i = Image.fromarray(a)
assert list(i.getchannel(0).getdata()) == list(range(100)) assert i.get_flattened_data(0) == tuple(range(100))
return i return i
# Check supported 1-bit integer formats # Check supported 1-bit integer formats
@ -191,7 +189,7 @@ def test_putdata() -> None:
arr = numpy.zeros((15000,), numpy.float32) arr = numpy.zeros((15000,), numpy.float32)
im.putdata(arr) im.putdata(arr)
assert len(im.getdata()) == len(arr) assert len(im.get_flattened_data()) == len(arr)
def test_resize() -> None: def test_resize() -> None:
@ -248,7 +246,7 @@ def test_bool() -> None:
a[0][0] = True a[0][0] = True
im2 = Image.fromarray(a) im2 = Image.fromarray(a)
assert im2.getdata()[0] == 255 assert im2.getpixel((0, 0)) == 255
def test_no_resource_warning_for_numpy_array() -> None: def test_no_resource_warning_for_numpy_array() -> None:

View File

@ -73,6 +73,16 @@ Image._show
``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15). ``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15).
Use :py:meth:`~PIL.ImageShow.show` instead. Use :py:meth:`~PIL.ImageShow.show` instead.
Image getdata()
~~~~~~~~~~~~~~~
.. deprecated:: 12.1.0
:py:meth:`~PIL.Image.Image.getdata` has been deprecated.
:py:meth:`~PIL.Image.Image.get_flattened_data` can be used instead. This new method is
identical, except that it returns a tuple of pixel values, instead of an internal
Pillow data type.
Removed features Removed features
---------------- ----------------

View File

@ -191,6 +191,7 @@ This helps to get the bounding box coordinates of the input image::
.. automethod:: PIL.Image.Image.getchannel .. automethod:: PIL.Image.Image.getchannel
.. automethod:: PIL.Image.Image.getcolors .. automethod:: PIL.Image.Image.getcolors
.. automethod:: PIL.Image.Image.getdata .. automethod:: PIL.Image.Image.getdata
.. automethod:: PIL.Image.Image.get_flattened_data
.. automethod:: PIL.Image.Image.getexif .. automethod:: PIL.Image.Image.getexif
.. automethod:: PIL.Image.Image.getextrema .. automethod:: PIL.Image.Image.getextrema
.. automethod:: PIL.Image.Image.getpalette .. automethod:: PIL.Image.Image.getpalette

View File

@ -1,6 +1,17 @@
12.1.0 12.1.0
------ ------
Deprecations
============
Image getdata()
^^^^^^^^^^^^^^^
:py:meth:`~PIL.Image.Image.getdata` has been deprecated.
:py:meth:`~PIL.Image.Image.get_flattened_data` can be used instead. This new method is
identical, except that it returns a tuple of pixel values, instead of an internal
Pillow data type.
API changes API changes
=========== ===========
@ -13,6 +24,13 @@ To match the behaviour of :py:meth:`~PIL.ImageMorph.LutBuilder.build_lut`,
API additions API additions
============= =============
Image get_flattened_data()
^^^^^^^^^^^^^^^^^^^^^^^^^^
:py:meth:`~PIL.Image.Image.get_flattened_data` is identical to the deprecated
:py:meth:`~PIL.Image.Image.getdata`, except that the new method returns a tuple of
pixel values, instead of an internal Pillow data type.
Specify window in ImageGrab on macOS Specify window in ImageGrab on macOS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -753,7 +753,7 @@ def _write_multiple_frames(
if delta.mode == "P": if delta.mode == "P":
# Convert to L without considering palette # Convert to L without considering palette
delta_l = Image.new("L", delta.size) delta_l = Image.new("L", delta.size)
delta_l.putdata(delta.getdata()) delta_l.putdata(delta.get_flattened_data())
delta = delta_l delta = delta_l
mask = ImageMath.lambda_eval( mask = ImageMath.lambda_eval(
lambda args: args["convert"](args["im"] * 255, "1"), lambda args: args["convert"](args["im"] * 255, "1"),

View File

@ -1435,12 +1435,31 @@ class Image:
value (e.g. 0 to get the "R" band from an "RGB" image). value (e.g. 0 to get the "R" band from an "RGB" image).
:returns: A sequence-like object. :returns: A sequence-like object.
""" """
deprecate("Image.Image.getdata", 14, "get_flattened_data")
self.load() self.load()
if band is not None: if band is not None:
return self.im.getband(band) return self.im.getband(band)
return self.im # could be abused return self.im # could be abused
def get_flattened_data(
self, band: int | None = None
) -> tuple[tuple[int, ...], ...] | tuple[float, ...]:
"""
Returns the contents of this image as a tuple containing pixel values.
The sequence object is flattened, so that values for line one follow
directly after the values of line zero, and so on.
:param band: What band to return. The default is to return
all bands. To return a single band, pass in the index
value (e.g. 0 to get the "R" band from an "RGB" image).
:returns: A tuple containing pixel values.
"""
self.load()
if band is not None:
return tuple(self.im.getband(band))
return tuple(self.im)
def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]: def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]:
""" """
Gets the minimum and maximum pixel values for each band in Gets the minimum and maximum pixel values for each band in

View File

@ -48,6 +48,8 @@ def deprecate(
raise RuntimeError(msg) raise RuntimeError(msg)
elif when == 13: elif when == 13:
removed = "Pillow 13 (2026-10-15)" removed = "Pillow 13 (2026-10-15)"
elif when == 14:
removed = "Pillow 14 (2027-10-15)"
else: else:
msg = f"Unknown removal version: {when}. Update {__name__}?" msg = f"Unknown removal version: {when}. Update {__name__}?"
raise ValueError(msg) raise ValueError(msg)