diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh
index 2301a3a7e..6aa59a4ac 100755
--- a/.github/workflows/macos-install.sh
+++ b/.github/workflows/macos-install.sh
@@ -10,15 +10,11 @@ brew install \
     ghostscript \
     jpeg-turbo \
     libimagequant \
+    libraqm \
     libtiff \
     little-cms2 \
     openjpeg \
     webp
-if [[ "$ImageOS" == "macos13" ]]; then
-    brew install --ignore-dependencies libraqm
-else
-    brew install libraqm
-fi
 export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
 
 python3 -m pip install coverage
diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml
index bb6d7dc37..045926482 100644
--- a/.github/workflows/test-mingw.yml
+++ b/.github/workflows/test-mingw.yml
@@ -60,7 +60,6 @@ jobs:
               mingw-w64-x86_64-gcc \
               mingw-w64-x86_64-ghostscript \
               mingw-w64-x86_64-lcms2 \
-              mingw-w64-x86_64-libimagequant \
               mingw-w64-x86_64-libjpeg-turbo \
               mingw-w64-x86_64-libraqm \
               mingw-w64-x86_64-libtiff \
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 20fa7d04f..a8c8cee15 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,17 +1,17 @@
 repos:
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.8.6
+    rev: v0.9.4
     hooks:
       - id: ruff
         args: [--exit-non-zero-on-fix]
 
   - repo: https://github.com/psf/black-pre-commit-mirror
-    rev: 24.10.0
+    rev: 25.1.0
     hooks:
       - id: black
 
   - repo: https://github.com/PyCQA/bandit
-    rev: 1.8.0
+    rev: 1.8.2
     hooks:
     - id: bandit
       args: [--severity-level=high]
@@ -24,7 +24,7 @@ repos:
         exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
 
   - repo: https://github.com/pre-commit/mirrors-clang-format
-    rev: v19.1.6
+    rev: v19.1.7
     hooks:
       - id: clang-format
         types: [c]
@@ -50,14 +50,14 @@ repos:
         exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
 
   - repo: https://github.com/python-jsonschema/check-jsonschema
-    rev: 0.30.0
+    rev: 0.31.1
     hooks:
       - id: check-github-workflows
       - id: check-readthedocs
       - id: check-renovate
 
   - repo: https://github.com/woodruffw/zizmor-pre-commit
-    rev: v1.0.0
+    rev: v1.3.0
     hooks:
       - id: zizmor
 
@@ -78,7 +78,7 @@ repos:
         additional_dependencies: [trove-classifiers>=2024.10.12]
 
   - repo: https://github.com/tox-dev/tox-ini-fmt
-    rev: 1.4.1
+    rev: 1.5.0
     hooks:
       - id: tox-ini-fmt
 
diff --git a/Tests/images/multiline_text_justify.png b/Tests/images/multiline_text_justify.png
new file mode 100644
index 000000000..32eed34cd
Binary files /dev/null and b/Tests/images/multiline_text_justify.png differ
diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py
index baa899df5..26945ae1a 100644
--- a/Tests/test_color_lut.py
+++ b/Tests/test_color_lut.py
@@ -19,7 +19,7 @@ except ImportError:
 class TestColorLut3DCoreAPI:
     def generate_identity_table(
         self, channels: int, size: int | tuple[int, int, int]
-    ) -> tuple[int, int, int, int, list[float]]:
+    ) -> tuple[int, tuple[int, int, int], list[float]]:
         if isinstance(size, tuple):
             size_1d, size_2d, size_3d = size
         else:
@@ -39,9 +39,7 @@ class TestColorLut3DCoreAPI:
         ]
         return (
             channels,
-            size_1d,
-            size_2d,
-            size_3d,
+            (size_1d, size_2d, size_3d),
             [item for sublist in table for item in sublist],
         )
 
@@ -89,21 +87,21 @@ class TestColorLut3DCoreAPI:
 
         with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
             im.im.color_lut_3d(
-                "RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 7
+                "RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), [0, 0, 0] * 7
             )
 
         with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
             im.im.color_lut_3d(
-                "RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 9
+                "RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), [0, 0, 0] * 9
             )
 
         with pytest.raises(TypeError):
             im.im.color_lut_3d(
-                "RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8
+                "RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), [0, 0, "0"] * 8
             )
 
         with pytest.raises(TypeError):
-            im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, 16)
+            im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), 16)
 
     @pytest.mark.parametrize(
         "lut_mode, table_channels, table_size",
@@ -264,7 +262,7 @@ class TestColorLut3DCoreAPI:
         assert_image_equal(
             Image.merge('RGB', im.split()[::-1]),
             im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
-                    3, 2, 2, 2, [
+                    3, (2, 2, 2), [
                         0, 0, 0,  0, 0, 1,
                         0, 1, 0,  0, 1, 1,
 
@@ -286,7 +284,7 @@ class TestColorLut3DCoreAPI:
 
         # fmt: off
         transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
-                              3, 2, 2, 2,
+                              3, (2, 2, 2),
                               [
                                   -1, -1, -1,   2, -1, -1,
                                   -1,  2, -1,   2,  2, -1,
@@ -307,7 +305,7 @@ class TestColorLut3DCoreAPI:
 
         # fmt: off
         transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
-                              3, 2, 2, 2,
+                              3, (2, 2, 2),
                               [
                                   -3, -3, -3,   5, -3, -3,
                                   -3,  5, -3,   5,  5, -3,
diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py
index baaf67305..653ab7b2c 100644
--- a/Tests/test_file_gif.py
+++ b/Tests/test_file_gif.py
@@ -86,16 +86,12 @@ def test_invalid_file() -> None:
 def test_l_mode_transparency() -> None:
     with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
         assert im.mode == "L"
-        px = im.load()
-        assert px is not None
-        assert px[0, 0] == 128
+        assert im.getpixel((0, 0)) == 128
         assert im.info["transparency"] == 255
 
         im.seek(1)
         assert im.mode == "L"
-        px = im.load()
-        assert px is not None
-        assert px[0, 0] == 128
+        assert im.getpixel((0, 0)) == 128
 
 
 def test_l_mode_after_rgb() -> None:
@@ -319,9 +315,7 @@ def test_loading_multiple_palettes(path: str, mode: str) -> None:
         assert im.mode == "P"
         assert im.palette is not None
         first_frame_colors = im.palette.colors.keys()
-        px = im.convert("RGB").load()
-        assert px is not None
-        original_color = px[0, 0]
+        original_color = im.convert("RGB").getpixel((0, 0))
 
         im.seek(1)
         assert im.mode == mode
@@ -329,14 +323,10 @@ def test_loading_multiple_palettes(path: str, mode: str) -> None:
             im = im.convert("RGB")
 
         # Check a color only from the old palette
-        px = im.load()
-        assert px is not None
-        assert px[0, 0] == original_color
+        assert im.getpixel((0, 0)) == original_color
 
         # Check a color from the new palette
-        px = im.load()
-        assert px is not None
-        assert px[24, 24] not in first_frame_colors
+        assert im.getpixel((24, 24)) not in first_frame_colors
 
 
 def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
@@ -510,9 +500,7 @@ def test_eoferror() -> None:
 
 def test_first_frame_transparency() -> None:
     with Image.open("Tests/images/first_frame_transparency.gif") as im:
-        px = im.load()
-        assert px is not None
-        assert px[0, 0] == im.info["transparency"]
+        assert im.getpixel((0, 0)) == im.info["transparency"]
 
 
 def test_dispose_none() -> None:
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
index 4b6c09751..e5deb1c7a 100644
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -964,7 +964,7 @@ class TestFileJpeg:
 
     def test_jpeg_magic_number(self, monkeypatch: pytest.MonkeyPatch) -> None:
         size = 4097
-        buffer = BytesIO(b"\xFF" * size)  # Many xFF bytes
+        buffer = BytesIO(b"\xff" * size)  # Many xff bytes
         max_pos = 0
         orig_read = buffer.read
 
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index 47f6bd9d0..73d73ec43 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -321,7 +321,7 @@ class TestFileLibTiff(LibTiffTestCase):
         }
 
         def check_tags(
-            tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str]
+            tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str],
         ) -> None:
             im = hopper()
 
diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py
index 1d5001b1a..815686a52 100644
--- a/Tests/test_file_pdf.py
+++ b/Tests/test_file_pdf.py
@@ -264,7 +264,7 @@ def test_pdf_append(tmp_path: Path) -> None:
         # append some info
         pdf.info.Title = "abc"
         pdf.info.Author = "def"
-        pdf.info.Subject = "ghi\uABCD"
+        pdf.info.Subject = "ghi\uabcd"
         pdf.info.Keywords = "qw)e\\r(ty"
         pdf.info.Creator = "hopper()"
         pdf.start_writing()
@@ -292,7 +292,7 @@ def test_pdf_append(tmp_path: Path) -> None:
         assert pdf.info.Title == "abc"
         assert pdf.info.Producer == "PdfParser"
         assert pdf.info.Keywords == "qw)e\\r(ty"
-        assert pdf.info.Subject == "ghi\uABCD"
+        assert pdf.info.Subject == "ghi\uabcd"
         assert b"CreationDate" in pdf.info
         assert b"ModDate" in pdf.info
         check_pdf_pages_consistency(pdf)
diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py
index 1f39afc19..98ed08411 100644
--- a/Tests/test_file_ppm.py
+++ b/Tests/test_file_ppm.py
@@ -49,7 +49,7 @@ def test_sanity() -> None:
         (b"P5 3 1 257 \x00\x00\x00\x80\x01\x01", "I", (0, 32640, 65535)),
         # P6 with maxval < 255
         (
-            b"P6 3 1 17 \x00\x01\x02\x08\x09\x0A\x0F\x10\x11",
+            b"P6 3 1 17 \x00\x01\x02\x08\x09\x0a\x0f\x10\x11",
             "RGB",
             (
                 (0, 15, 30),
@@ -60,7 +60,7 @@ def test_sanity() -> None:
         # P6 with maxval > 255
         (
             b"P6 3 1 257 \x00\x00\x00\x01\x00\x02"
-            b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xFF\xFF",
+            b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xff\xff",
             "RGB",
             (
                 (0, 1, 2),
diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py
index 4fde86450..5fe704d23 100644
--- a/Tests/test_file_tiff.py
+++ b/Tests/test_file_tiff.py
@@ -783,7 +783,7 @@ class TestFileTiff:
             assert reread.n_frames == 3
 
     def test_fixoffsets(self) -> None:
-        b = BytesIO(b"II\x2A\x00\x00\x00\x00\x00")
+        b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
         with TiffImagePlugin.AppendingTiffWriter(b) as a:
             b.seek(0)
             a.fixOffsets(1, isShort=True)
@@ -796,14 +796,14 @@ class TestFileTiff:
             with pytest.raises(RuntimeError):
                 a.fixOffsets(1)
 
-        b = BytesIO(b"II\x2A\x00\x00\x00\x00\x00")
+        b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
         with TiffImagePlugin.AppendingTiffWriter(b) as a:
             a.offsetOfNewPage = 2**16
 
             b.seek(0)
             a.fixOffsets(1, isShort=True)
 
-        b = BytesIO(b"II\x2B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
+        b = BytesIO(b"II\x2b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
         with TiffImagePlugin.AppendingTiffWriter(b) as a:
             a.offsetOfNewPage = 2**32
 
@@ -814,18 +814,20 @@ class TestFileTiff:
             a.fixOffsets(1, isLong=True)
 
     def test_appending_tiff_writer_writelong(self) -> None:
-        data = b"II\x2A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        data = b"II\x2a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
         b = BytesIO(data)
         with TiffImagePlugin.AppendingTiffWriter(b) as a:
+            a.seek(-4, os.SEEK_CUR)
             a.writeLong(2**32 - 1)
-            assert b.getvalue() == data + b"\xff\xff\xff\xff"
+            assert b.getvalue() == data[:-4] + b"\xff\xff\xff\xff"
 
     def test_appending_tiff_writer_rewritelastshorttolong(self) -> None:
-        data = b"II\x2A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        data = b"II\x2a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
         b = BytesIO(data)
         with TiffImagePlugin.AppendingTiffWriter(b) as a:
+            a.seek(-2, os.SEEK_CUR)
             a.rewriteLastShortToLong(2**32 - 1)
-            assert b.getvalue() == data[:-2] + b"\xff\xff\xff\xff"
+            assert b.getvalue() == data[:-4] + b"\xff\xff\xff\xff"
 
     def test_saving_icc_profile(self, tmp_path: Path) -> None:
         # Tests saving TIFF with icc_profile set.
diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py
index e52543c03..6a5cb69dd 100644
--- a/Tests/test_file_wmf.py
+++ b/Tests/test_file_wmf.py
@@ -73,7 +73,7 @@ def test_load_float_dpi() -> None:
 
     with open("Tests/images/drawing.emf", "rb") as fp:
         data = fp.read()
-    b = BytesIO(data[:8] + b"\x06\xFA" + data[10:])
+    b = BytesIO(data[:8] + b"\x06\xfa" + data[10:])
     with Image.open(b) as im:
         assert im.info["dpi"][0] == 2540
 
diff --git a/Tests/test_image.py b/Tests/test_image.py
index d1ac6326f..6fbdcff2f 100644
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -578,9 +578,7 @@ class TestImage:
     def test_one_item_tuple(self) -> None:
         for mode in ("I", "F", "L"):
             im = Image.new(mode, (100, 100), (5,))
-            px = im.load()
-            assert px is not None
-            assert px[0, 0] == 5
+            assert im.getpixel((0, 0)) == 5
 
     def test_linear_gradient_wrong_mode(self) -> None:
         # Arrange
diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py
index 3ed5784b5..ab2bb7625 100644
--- a/Tests/test_image_convert.py
+++ b/Tests/test_image_convert.py
@@ -222,9 +222,7 @@ def test_l_macro_rounding(convert_mode: str) -> None:
         im.palette.getcolor((0, 1, 2))
 
         converted_im = im.convert(convert_mode)
-        px = converted_im.load()
-        assert px is not None
-        converted_color = px[0, 0]
+        converted_color = converted_im.getpixel((0, 0))
         if convert_mode == "LA":
             assert isinstance(converted_color, tuple)
             converted_color = converted_color[0]
diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py
index 432ec8707..b9b2cbb0d 100644
--- a/Tests/test_image_quantize.py
+++ b/Tests/test_image_quantize.py
@@ -149,10 +149,8 @@ def test_palette(method: Image.Quantize, color: tuple[int, ...]) -> None:
     im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color)
 
     converted = im.quantize(method=method)
-    converted_px = converted.load()
-    assert converted_px is not None
     assert converted.palette is not None
-    assert converted_px[0, 0] == converted.palette.colors[color]
+    assert converted.getpixel((0, 0)) == converted.palette.colors[color]
 
 
 def test_small_palette() -> None:
diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py
index 0c2737bc1..b943764d5 100644
--- a/Tests/test_imagedraw.py
+++ b/Tests/test_imagedraw.py
@@ -813,7 +813,7 @@ def test_rounded_rectangle(
         tuple[int, int, int, int]
         | tuple[list[int]]
         | tuple[tuple[int, int], tuple[int, int]]
-    )
+    ),
 ) -> None:
     # Arrange
     im = Image.new("RGB", (200, 200))
diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py
index f110cc1d0..4cce8f180 100644
--- a/Tests/test_imagefont.py
+++ b/Tests/test_imagefont.py
@@ -254,7 +254,8 @@ def test_render_multiline_text(font: ImageFont.FreeTypeFont) -> None:
 
 
 @pytest.mark.parametrize(
-    "align, ext", (("left", ""), ("center", "_center"), ("right", "_right"))
+    "align, ext",
+    (("left", ""), ("center", "_center"), ("right", "_right"), ("justify", "_justify")),
 )
 def test_render_multiline_text_align(
     font: ImageFont.FreeTypeFont, align: str, ext: str
@@ -557,7 +558,7 @@ def test_render_empty(font: ImageFont.FreeTypeFont) -> None:
 
 def test_unicode_extended(layout_engine: ImageFont.Layout) -> None:
     # issue #3777
-    text = "A\u278A\U0001F12B"
+    text = "A\u278a\U0001f12b"
     target = "Tests/images/unicode_extended.png"
 
     ttf = ImageFont.truetype(
@@ -1026,7 +1027,7 @@ def test_sbix(layout_engine: ImageFont.Layout) -> None:
         im = Image.new("RGB", (400, 400), "white")
         d = ImageDraw.Draw(im)
 
-        d.text((50, 50), "\uE901", font=font, embedded_color=True)
+        d.text((50, 50), "\ue901", font=font, embedded_color=True)
 
         assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1)
     except OSError as e:  # pragma: no cover
@@ -1043,7 +1044,7 @@ def test_sbix_mask(layout_engine: ImageFont.Layout) -> None:
         im = Image.new("RGB", (400, 400), "white")
         d = ImageDraw.Draw(im)
 
-        d.text((50, 50), "\uE901", (100, 0, 0), font=font)
+        d.text((50, 50), "\ue901", (100, 0, 0), font=font)
 
         assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1)
     except OSError as e:  # pragma: no cover
diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py
index 24c7b871a..c85eb499c 100644
--- a/Tests/test_imagefontctl.py
+++ b/Tests/test_imagefontctl.py
@@ -229,7 +229,7 @@ def test_getlength(
 @pytest.mark.parametrize("direction", ("ltr", "ttb"))
 @pytest.mark.parametrize(
     "text",
-    ("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"),
+    ("i" + ("\u030c" * 15) + "i", "i" + "\u032c" * 15 + "i", "\u035cii", "i\u0305i"),
     ids=("caron-above", "caron-below", "double-breve", "overline"),
 )
 def test_getlength_combine(mode: str, direction: str, text: str) -> None:
@@ -272,27 +272,27 @@ def test_anchor_ttb(anchor: str) -> None:
 
 combine_tests = (
     # extends above (e.g. issue #4553)
-    ("caron", "a\u030C\u030C\u030C\u030C\u030Cb", None, None, 0.08),
-    ("caron_la", "a\u030C\u030C\u030C\u030C\u030Cb", "la", None, 0.08),
-    ("caron_lt", "a\u030C\u030C\u030C\u030C\u030Cb", "lt", None, 0.08),
-    ("caron_ls", "a\u030C\u030C\u030C\u030C\u030Cb", "ls", None, 0.08),
-    ("caron_ttb", "ca" + ("\u030C" * 15) + "b", None, "ttb", 0.3),
-    ("caron_ttb_lt", "ca" + ("\u030C" * 15) + "b", "lt", "ttb", 0.3),
+    ("caron", "a\u030c\u030c\u030c\u030c\u030cb", None, None, 0.08),
+    ("caron_la", "a\u030c\u030c\u030c\u030c\u030cb", "la", None, 0.08),
+    ("caron_lt", "a\u030c\u030c\u030c\u030c\u030cb", "lt", None, 0.08),
+    ("caron_ls", "a\u030c\u030c\u030c\u030c\u030cb", "ls", None, 0.08),
+    ("caron_ttb", "ca" + ("\u030c" * 15) + "b", None, "ttb", 0.3),
+    ("caron_ttb_lt", "ca" + ("\u030c" * 15) + "b", "lt", "ttb", 0.3),
     # extends below
-    ("caron_below", "a\u032C\u032C\u032C\u032C\u032Cb", None, None, 0.02),
-    ("caron_below_ld", "a\u032C\u032C\u032C\u032C\u032Cb", "ld", None, 0.02),
-    ("caron_below_lb", "a\u032C\u032C\u032C\u032C\u032Cb", "lb", None, 0.02),
-    ("caron_below_ls", "a\u032C\u032C\u032C\u032C\u032Cb", "ls", None, 0.02),
-    ("caron_below_ttb", "a" + ("\u032C" * 15) + "b", None, "ttb", 0.03),
-    ("caron_below_ttb_lb", "a" + ("\u032C" * 15) + "b", "lb", "ttb", 0.03),
+    ("caron_below", "a\u032c\u032c\u032c\u032c\u032cb", None, None, 0.02),
+    ("caron_below_ld", "a\u032c\u032c\u032c\u032c\u032cb", "ld", None, 0.02),
+    ("caron_below_lb", "a\u032c\u032c\u032c\u032c\u032cb", "lb", None, 0.02),
+    ("caron_below_ls", "a\u032c\u032c\u032c\u032c\u032cb", "ls", None, 0.02),
+    ("caron_below_ttb", "a" + ("\u032c" * 15) + "b", None, "ttb", 0.03),
+    ("caron_below_ttb_lb", "a" + ("\u032c" * 15) + "b", "lb", "ttb", 0.03),
     # extends to the right (e.g. issue #3745)
-    ("double_breve_below", "a\u035Ci", None, None, 0.02),
-    ("double_breve_below_ma", "a\u035Ci", "ma", None, 0.02),
-    ("double_breve_below_ra", "a\u035Ci", "ra", None, 0.02),
-    ("double_breve_below_ttb", "a\u035Cb", None, "ttb", 0.02),
-    ("double_breve_below_ttb_rt", "a\u035Cb", "rt", "ttb", 0.02),
-    ("double_breve_below_ttb_mt", "a\u035Cb", "mt", "ttb", 0.02),
-    ("double_breve_below_ttb_st", "a\u035Cb", "st", "ttb", 0.02),
+    ("double_breve_below", "a\u035ci", None, None, 0.02),
+    ("double_breve_below_ma", "a\u035ci", "ma", None, 0.02),
+    ("double_breve_below_ra", "a\u035ci", "ra", None, 0.02),
+    ("double_breve_below_ttb", "a\u035cb", None, "ttb", 0.02),
+    ("double_breve_below_ttb_rt", "a\u035cb", "rt", "ttb", 0.02),
+    ("double_breve_below_ttb_mt", "a\u035cb", "mt", "ttb", 0.02),
+    ("double_breve_below_ttb_st", "a\u035cb", "st", "ttb", 0.02),
     # extends to the left (fail=0.064)
     ("overline", "i\u0305", None, None, 0.02),
     ("overline_la", "i\u0305", "la", None, 0.02),
@@ -346,7 +346,7 @@ def test_combine_multiline(anchor: str, align: str) -> None:
 
     path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png"
     f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
-    text = "i\u0305\u035C\ntext"  # i with overline and double breve, and a word
+    text = "i\u0305\u035c\ntext"  # i with overline and double breve, and a word
 
     im = Image.new("RGB", (400, 400), "white")
     d = ImageDraw.Draw(im)
diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py
index 0a3975c54..d17d0c680 100644
--- a/Tests/test_imageops.py
+++ b/Tests/test_imageops.py
@@ -165,14 +165,10 @@ def test_pad() -> None:
 def test_pad_round() -> None:
     im = Image.new("1", (1, 1), 1)
     new_im = ImageOps.pad(im, (4, 1))
-    px = new_im.load()
-    assert px is not None
-    assert px[2, 0] == 1
+    assert new_im.getpixel((2, 0)) == 1
 
     new_im = ImageOps.pad(im, (1, 4))
-    px = new_im.load()
-    assert px is not None
-    assert px[0, 2] == 1
+    assert new_im.getpixel((0, 2)) == 1
 
 
 @pytest.mark.parametrize("mode", ("P", "PA"))
diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py
index e2f8308ea..e0b6359b0 100644
--- a/Tests/test_imagepalette.py
+++ b/Tests/test_imagepalette.py
@@ -190,7 +190,7 @@ def test_2bit_palette(tmp_path: Path) -> None:
 
     rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2
     img = Image.frombytes("P", (6, 1), rgb)
-    img.putpalette(b"\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF")  # RGB
+    img.putpalette(b"\xff\x00\x00\x00\xff\x00\x00\x00\xff")  # RGB
     img.save(outfile, format="PNG")
 
     assert_image_equal_tofile(img, outfile)
diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py
index 76bdf1e5f..1b1ee6bac 100644
--- a/Tests/test_imagepath.py
+++ b/Tests/test_imagepath.py
@@ -79,7 +79,7 @@ def test_path_constructors(
     ),
 )
 def test_invalid_path_constructors(
-    coords: tuple[str, str] | Sequence[Sequence[int]]
+    coords: tuple[str, str] | Sequence[Sequence[int]],
 ) -> None:
     # Act
     with pytest.raises(ValueError) as e:
diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py
index 79cd14b66..c4ad19d23 100644
--- a/Tests/test_numpy.py
+++ b/Tests/test_numpy.py
@@ -141,9 +141,7 @@ def test_save_tiff_uint16() -> None:
     a.shape = TEST_IMAGE_SIZE
     img = Image.fromarray(a)
 
-    img_px = img.load()
-    assert img_px is not None
-    assert img_px[0, 0] == pixel_value
+    assert img.getpixel((0, 0)) == pixel_value
 
 
 @pytest.mark.parametrize(
diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py
index f6b12cb20..d85fb1212 100644
--- a/Tests/test_pdfparser.py
+++ b/Tests/test_pdfparser.py
@@ -20,10 +20,10 @@ from PIL.PdfParser import (
 
 
 def test_text_encode_decode() -> None:
-    assert encode_text("abc") == b"\xFE\xFF\x00a\x00b\x00c"
-    assert decode_text(b"\xFE\xFF\x00a\x00b\x00c") == "abc"
+    assert encode_text("abc") == b"\xfe\xff\x00a\x00b\x00c"
+    assert decode_text(b"\xfe\xff\x00a\x00b\x00c") == "abc"
     assert decode_text(b"abc") == "abc"
-    assert decode_text(b"\x1B a \x1C") == "\u02D9 a \u02DD"
+    assert decode_text(b"\x1b a \x1c") == "\u02d9 a \u02dd"
 
 
 def test_indirect_refs() -> None:
@@ -45,8 +45,8 @@ def test_parsing() -> None:
     assert PdfParser.get_value(b"false%", 0) == (False, 5)
     assert PdfParser.get_value(b"null<", 0) == (None, 4)
     assert PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0) == (123, 15)
-    assert PdfParser.get_value(b"<901FA3>", 0) == (b"\x90\x1F\xA3", 8)
-    assert PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3) == (b"\x90\x1F\xA0", 17)
+    assert PdfParser.get_value(b"<901FA3>", 0) == (b"\x90\x1f\xa3", 8)
+    assert PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3) == (b"\x90\x1f\xa0", 17)
     assert PdfParser.get_value(b"(asd)", 0) == (b"asd", 5)
     assert PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0) == (b"asd(qwe)zxc", 13)
     assert PdfParser.get_value(b"(Two \\\nwords.)", 0) == (b"Two words.", 14)
@@ -56,9 +56,9 @@ def test_parsing() -> None:
     assert PdfParser.get_value(b"(One\\(paren).", 0) == (b"One(paren", 12)
     assert PdfParser.get_value(b"(One\\)paren).", 0) == (b"One)paren", 12)
     assert PdfParser.get_value(b"(\\0053)", 0) == (b"\x053", 7)
-    assert PdfParser.get_value(b"(\\053)", 0) == (b"\x2B", 6)
-    assert PdfParser.get_value(b"(\\53)", 0) == (b"\x2B", 5)
-    assert PdfParser.get_value(b"(\\53a)", 0) == (b"\x2Ba", 6)
+    assert PdfParser.get_value(b"(\\053)", 0) == (b"\x2b", 6)
+    assert PdfParser.get_value(b"(\\53)", 0) == (b"\x2b", 5)
+    assert PdfParser.get_value(b"(\\53a)", 0) == (b"\x2ba", 6)
     assert PdfParser.get_value(b"(\\1111)", 0) == (b"\x491", 7)
     assert PdfParser.get_value(b" 123 (", 0) == (123, 4)
     assert round(abs(PdfParser.get_value(b" 123.4 %", 0)[0] - 123.4), 7) == 0
@@ -118,7 +118,7 @@ def test_pdf_repr() -> None:
     assert pdf_repr(None) == b"null"
     assert pdf_repr(b"a)/b\\(c") == rb"(a\)/b\\\(c)"
     assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]"
-    assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>"
+    assert pdf_repr(PdfBinary(b"\x90\x1f\xa0")) == b"<901FA0>"
 
 
 def test_duplicate_xref_entry() -> None:
diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst
index 3e9aa73f8..b2f1bdc93 100644
--- a/docs/reference/ImageDraw.rst
+++ b/docs/reference/ImageDraw.rst
@@ -387,8 +387,11 @@ Methods
                     the number of pixels between lines.
     :param align: If the text is passed on to
                   :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
-                  ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
-                  Use the ``anchor`` parameter to specify the alignment to ``xy``.
+                  ``"left"``, ``"center"``, ``"right"`` or ``"justify"``. Determines
+                  the relative alignment of lines. Use the ``anchor`` parameter to
+                  specify the alignment to ``xy``.
+
+                  .. versionadded:: 11.2.0 ``"justify"``
     :param direction: Direction of the text. It can be ``"rtl"`` (right to
                       left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
                       Requires libraqm.
@@ -455,8 +458,11 @@ Methods
                               of Pillow, but implemented only in version 8.0.0.
 
     :param spacing: The number of pixels between lines.
-    :param align: ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
-                  Use the ``anchor`` parameter to specify the alignment to ``xy``.
+    :param align: ``"left"``, ``"center"``, ``"right"`` or ``"justify"``. Determines
+                  the relative alignment of lines. Use the ``anchor`` parameter to
+                  specify the alignment to ``xy``.
+
+                  .. versionadded:: 11.2.0 ``"justify"``
     :param direction: Direction of the text. It can be ``"rtl"`` (right to
                       left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
                       Requires libraqm.
@@ -599,8 +605,11 @@ Methods
                     the number of pixels between lines.
     :param align: If the text is passed on to
                   :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`,
-                  ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
-                  Use the ``anchor`` parameter to specify the alignment to ``xy``.
+                  ``"left"``, ``"center"``, ``"right"`` or ``"justify"``. Determines
+                  the relative alignment of lines. Use the ``anchor`` parameter to
+                  specify the alignment to ``xy``.
+
+                  .. versionadded:: 11.2.0 ``"justify"``
     :param direction: Direction of the text. It can be ``"rtl"`` (right to
                       left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
                       Requires libraqm.
@@ -650,8 +659,11 @@ Methods
                    vertical text. See :ref:`text-anchors` for details.
                    This parameter is ignored for non-TrueType fonts.
     :param spacing: The number of pixels between lines.
-    :param align: ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
-                  Use the ``anchor`` parameter to specify the alignment to ``xy``.
+    :param align: ``"left"``, ``"center"``, ``"right"`` or ``"justify"``. Determines
+                  the relative alignment of lines. Use the ``anchor`` parameter to
+                  specify the alignment to ``xy``.
+
+                  .. versionadded:: 11.2.0 ``"justify"``
     :param direction: Direction of the text. It can be ``"rtl"`` (right to
                       left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
                       Requires libraqm.
diff --git a/docs/releasenotes/11.2.0.rst b/docs/releasenotes/11.2.0.rst
index df28d05af..f7e644cf3 100644
--- a/docs/releasenotes/11.2.0.rst
+++ b/docs/releasenotes/11.2.0.rst
@@ -44,6 +44,18 @@ TODO
 API Additions
 =============
 
+``"justify"`` multiline text alignment
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In addition to ``"left"``, ``"center"`` and ``"right"``, multiline text can also be
+aligned using ``"justify"`` in :py:mod:`~PIL.ImageDraw`::
+
+    from PIL import Image, ImageDraw
+    im = Image.new("RGB", (50, 25))
+    draw = ImageDraw.Draw(im)
+    draw.multiline_text((0, 0), "Multiline\ntext 1", align="justify")
+    draw.multiline_textbbox((0, 0), "Multiline\ntext 2", align="justify")
+
 Check for MozJPEG
 ^^^^^^^^^^^^^^^^^
 
diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py
index f0fba5836..92611e106 100644
--- a/src/PIL/ImImagePlugin.py
+++ b/src/PIL/ImImagePlugin.py
@@ -147,7 +147,7 @@ class ImImageFile(ImageFile.ImageFile):
             if s == b"\r":
                 continue
 
-            if not s or s == b"\0" or s == b"\x1A":
+            if not s or s == b"\0" or s == b"\x1a":
                 break
 
             # FIXME: this may read whole file if not a text file
@@ -211,7 +211,7 @@ class ImImageFile(ImageFile.ImageFile):
         self._mode = self.info[MODE]
 
         # Skip forward to start of image data
-        while s and s[:1] != b"\x1A":
+        while s and s[:1] != b"\x1a":
             s = self.fp.read(1)
         if not s:
             msg = "File truncated"
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index 3d1f84f53..0444569a3 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -514,7 +514,7 @@ class ImagePointTransform:
 
 
 def _getscaleoffset(
-    expr: Callable[[ImagePointTransform], ImagePointTransform | float]
+    expr: Callable[[ImagePointTransform], ImagePointTransform | float],
 ) -> tuple[float, float]:
     a = expr(ImagePointTransform(1, 0))
     return (a.scale, a.offset) if isinstance(a, ImagePointTransform) else (0, a)
@@ -3883,7 +3883,7 @@ class Exif(_ExifBase):
             return self._fixup_dict(dict(info))
 
     def _get_head(self) -> bytes:
-        version = b"\x2B" if self.bigtiff else b"\x2A"
+        version = b"\x2b" if self.bigtiff else b"\x2a"
         if self.endian == "<":
             head = b"II" + version + b"\x00" + o32le(8)
         else:
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index 81f8fbce0..742b5f587 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -557,21 +557,6 @@ class ImageDraw:
 
         return split_character in text
 
-    def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
-        return text.split("\n" if isinstance(text, str) else b"\n")
-
-    def _multiline_spacing(
-        self,
-        font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
-        spacing: float,
-        stroke_width: float,
-    ) -> float:
-        return (
-            self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
-            + stroke_width
-            + spacing
-        )
-
     def text(
         self,
         xy: tuple[float, float],
@@ -699,6 +684,119 @@ class ImageDraw:
                 # Only draw normal text
                 draw_text(ink)
 
+    def _prepare_multiline_text(
+        self,
+        xy: tuple[float, float],
+        text: AnyStr,
+        font: (
+            ImageFont.ImageFont
+            | ImageFont.FreeTypeFont
+            | ImageFont.TransposedFont
+            | None
+        ),
+        anchor: str | None,
+        spacing: float,
+        align: str,
+        direction: str | None,
+        features: list[str] | None,
+        language: str | None,
+        stroke_width: float,
+        embedded_color: bool,
+        font_size: float | None,
+    ) -> tuple[
+        ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
+        str,
+        list[tuple[tuple[float, float], AnyStr]],
+    ]:
+        if direction == "ttb":
+            msg = "ttb direction is unsupported for multiline text"
+            raise ValueError(msg)
+
+        if anchor is None:
+            anchor = "la"
+        elif len(anchor) != 2:
+            msg = "anchor must be a 2 character string"
+            raise ValueError(msg)
+        elif anchor[1] in "tb":
+            msg = "anchor not supported for multiline text"
+            raise ValueError(msg)
+
+        if font is None:
+            font = self._getfont(font_size)
+
+        widths = []
+        max_width: float = 0
+        lines = text.split("\n" if isinstance(text, str) else b"\n")
+        line_spacing = (
+            self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
+            + stroke_width
+            + spacing
+        )
+
+        for line in lines:
+            line_width = self.textlength(
+                line,
+                font,
+                direction=direction,
+                features=features,
+                language=language,
+                embedded_color=embedded_color,
+            )
+            widths.append(line_width)
+            max_width = max(max_width, line_width)
+
+        top = xy[1]
+        if anchor[1] == "m":
+            top -= (len(lines) - 1) * line_spacing / 2.0
+        elif anchor[1] == "d":
+            top -= (len(lines) - 1) * line_spacing
+
+        parts = []
+        for idx, line in enumerate(lines):
+            left = xy[0]
+            width_difference = max_width - widths[idx]
+
+            # first align left by anchor
+            if anchor[0] == "m":
+                left -= width_difference / 2.0
+            elif anchor[0] == "r":
+                left -= width_difference
+
+            # then align by align parameter
+            if align in ("left", "justify"):
+                pass
+            elif align == "center":
+                left += width_difference / 2.0
+            elif align == "right":
+                left += width_difference
+            else:
+                msg = 'align must be "left", "center", "right" or "justify"'
+                raise ValueError(msg)
+
+            if align == "justify" and width_difference != 0:
+                words = line.split(" " if isinstance(text, str) else b" ")
+                word_widths = [
+                    self.textlength(
+                        word,
+                        font,
+                        direction=direction,
+                        features=features,
+                        language=language,
+                        embedded_color=embedded_color,
+                    )
+                    for word in words
+                ]
+                width_difference = max_width - sum(word_widths)
+                for i, word in enumerate(words):
+                    parts.append(((left, top), word))
+                    left += word_widths[i] + width_difference / (len(words) - 1)
+            else:
+                parts.append(((left, top), line))
+
+            top += line_spacing
+
+        return font, anchor, parts
+
     def multiline_text(
         self,
         xy: tuple[float, float],
@@ -722,62 +820,24 @@ class ImageDraw:
         *,
         font_size: float | None = None,
     ) -> None:
-        if direction == "ttb":
-            msg = "ttb direction is unsupported for multiline text"
-            raise ValueError(msg)
-
-        if anchor is None:
-            anchor = "la"
-        elif len(anchor) != 2:
-            msg = "anchor must be a 2 character string"
-            raise ValueError(msg)
-        elif anchor[1] in "tb":
-            msg = "anchor not supported for multiline text"
-            raise ValueError(msg)
-
-        if font is None:
-            font = self._getfont(font_size)
-
-        widths = []
-        max_width: float = 0
-        lines = self._multiline_split(text)
-        line_spacing = self._multiline_spacing(font, spacing, stroke_width)
-        for line in lines:
-            line_width = self.textlength(
-                line, font, direction=direction, features=features, language=language
-            )
-            widths.append(line_width)
-            max_width = max(max_width, line_width)
-
-        top = xy[1]
-        if anchor[1] == "m":
-            top -= (len(lines) - 1) * line_spacing / 2.0
-        elif anchor[1] == "d":
-            top -= (len(lines) - 1) * line_spacing
-
-        for idx, line in enumerate(lines):
-            left = xy[0]
-            width_difference = max_width - widths[idx]
-
-            # first align left by anchor
-            if anchor[0] == "m":
-                left -= width_difference / 2.0
-            elif anchor[0] == "r":
-                left -= width_difference
-
-            # then align by align parameter
-            if align == "left":
-                pass
-            elif align == "center":
-                left += width_difference / 2.0
-            elif align == "right":
-                left += width_difference
-            else:
-                msg = 'align must be "left", "center" or "right"'
-                raise ValueError(msg)
+        font, anchor, lines = self._prepare_multiline_text(
+            xy,
+            text,
+            font,
+            anchor,
+            spacing,
+            align,
+            direction,
+            features,
+            language,
+            stroke_width,
+            embedded_color,
+            font_size,
+        )
 
+        for xy, line in lines:
             self.text(
-                (left, top),
+                xy,
                 line,
                 fill,
                 font,
@@ -789,7 +849,6 @@ class ImageDraw:
                 stroke_fill=stroke_fill,
                 embedded_color=embedded_color,
             )
-            top += line_spacing
 
     def textlength(
         self,
@@ -891,69 +950,26 @@ class ImageDraw:
         *,
         font_size: float | None = None,
     ) -> tuple[float, float, float, float]:
-        if direction == "ttb":
-            msg = "ttb direction is unsupported for multiline text"
-            raise ValueError(msg)
-
-        if anchor is None:
-            anchor = "la"
-        elif len(anchor) != 2:
-            msg = "anchor must be a 2 character string"
-            raise ValueError(msg)
-        elif anchor[1] in "tb":
-            msg = "anchor not supported for multiline text"
-            raise ValueError(msg)
-
-        if font is None:
-            font = self._getfont(font_size)
-
-        widths = []
-        max_width: float = 0
-        lines = self._multiline_split(text)
-        line_spacing = self._multiline_spacing(font, spacing, stroke_width)
-        for line in lines:
-            line_width = self.textlength(
-                line,
-                font,
-                direction=direction,
-                features=features,
-                language=language,
-                embedded_color=embedded_color,
-            )
-            widths.append(line_width)
-            max_width = max(max_width, line_width)
-
-        top = xy[1]
-        if anchor[1] == "m":
-            top -= (len(lines) - 1) * line_spacing / 2.0
-        elif anchor[1] == "d":
-            top -= (len(lines) - 1) * line_spacing
+        font, anchor, lines = self._prepare_multiline_text(
+            xy,
+            text,
+            font,
+            anchor,
+            spacing,
+            align,
+            direction,
+            features,
+            language,
+            stroke_width,
+            embedded_color,
+            font_size,
+        )
 
         bbox: tuple[float, float, float, float] | None = None
 
-        for idx, line in enumerate(lines):
-            left = xy[0]
-            width_difference = max_width - widths[idx]
-
-            # first align left by anchor
-            if anchor[0] == "m":
-                left -= width_difference / 2.0
-            elif anchor[0] == "r":
-                left -= width_difference
-
-            # then align by align parameter
-            if align == "left":
-                pass
-            elif align == "center":
-                left += width_difference / 2.0
-            elif align == "right":
-                left += width_difference
-            else:
-                msg = 'align must be "left", "center" or "right"'
-                raise ValueError(msg)
-
+        for xy, line in lines:
             bbox_line = self.textbbox(
-                (left, top),
+                xy,
                 line,
                 font,
                 anchor,
@@ -973,8 +989,6 @@ class ImageDraw:
                     max(bbox[3], bbox_line[3]),
                 )
 
-            top += line_spacing
-
         if bbox is None:
             return xy[0], xy[1], xy[0], xy[1]
         return bbox
diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py
index b350e56f4..1c8b29b11 100644
--- a/src/PIL/ImageFilter.py
+++ b/src/PIL/ImageFilter.py
@@ -598,8 +598,6 @@ class Color3DLUT(MultibandFilter):
             self.mode or image.mode,
             Image.Resampling.BILINEAR,
             self.channels,
-            self.size[0],
-            self.size[1],
-            self.size[2],
+            self.size,
             self.table,
         )
diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py
index a4986aa8c..c8f05fbb7 100644
--- a/src/PIL/ImageFont.py
+++ b/src/PIL/ImageFont.py
@@ -647,8 +647,7 @@ class FreeTypeFont:
             kwargs.get("stroke_filled", False),
             anchor,
             ink,
-            start[0],
-            start[1],
+            start,
         )
 
     def font_variant(
diff --git a/src/PIL/ImageTransform.py b/src/PIL/ImageTransform.py
index a3d8f441a..fb144ff38 100644
--- a/src/PIL/ImageTransform.py
+++ b/src/PIL/ImageTransform.py
@@ -48,9 +48,9 @@ class AffineTransform(Transform):
     Define an affine image transform.
 
     This function takes a 6-tuple (a, b, c, d, e, f) which contain the first
-    two rows from an affine transform matrix. For each pixel (x, y) in the
-    output image, the new value is taken from a position (a x + b y + c,
-    d x + e y + f) in the input image, rounded to nearest pixel.
+    two rows from the inverse of an affine transform matrix. For each pixel
+    (x, y) in the output image, the new value is taken from a position (a x +
+    b y + c, d x + e y + f) in the input image, rounded to nearest pixel.
 
     This function can be used to scale, translate, rotate, and shear the
     original image.
@@ -58,7 +58,7 @@ class AffineTransform(Transform):
     See :py:meth:`.Image.transform`
 
     :param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows
-        from an affine transform matrix.
+        from the inverse of an affine transform matrix.
     """
 
     method = Image.Transform.AFFINE
diff --git a/src/PIL/ImtImagePlugin.py b/src/PIL/ImtImagePlugin.py
index 068cd5c33..c4eccee34 100644
--- a/src/PIL/ImtImagePlugin.py
+++ b/src/PIL/ImtImagePlugin.py
@@ -55,7 +55,7 @@ class ImtImageFile(ImageFile.ImageFile):
             if not s:
                 break
 
-            if s == b"\x0C":
+            if s == b"\x0c":
                 # image data begins
                 self.tile = [
                     ImageFile._Tile(
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index af2b13557..044767bd4 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -330,7 +330,7 @@ MARKER = {
 
 def _accept(prefix: bytes) -> bool:
     # Magic number was taken from https://en.wikipedia.org/wiki/JPEG
-    return prefix[:3] == b"\xFF\xD8\xFF"
+    return prefix[:3] == b"\xff\xd8\xff"
 
 
 ##
@@ -348,7 +348,7 @@ class JpegImageFile(ImageFile.ImageFile):
         if not _accept(s):
             msg = "not a JPEG file"
             raise SyntaxError(msg)
-        s = b"\xFF"
+        s = b"\xff"
 
         # Create attributes
         self.bits = self.layers = 0
@@ -424,7 +424,7 @@ class JpegImageFile(ImageFile.ImageFile):
             # Premature EOF.
             # Pretend file is finished adding EOI marker
             self._ended = True
-            return b"\xFF\xD9"
+            return b"\xff\xd9"
 
         return s
 
@@ -719,7 +719,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
     def validate_qtables(
         qtables: (
             str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None
-        )
+        ),
     ) -> list[list[int]] | None:
         if qtables is None:
             return qtables
@@ -776,7 +776,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
             msg = "XMP data is too long"
             raise ValueError(msg)
         size = o16(2 + overhead_len + len(xmp))
-        extra += b"\xFF\xE1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
+        extra += b"\xff\xe1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
 
     icc_profile = info.get("icc_profile")
     if icc_profile:
@@ -790,7 +790,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
         for marker in markers:
             size = o16(2 + overhead_len + len(marker))
             extra += (
-                b"\xFF\xE2"
+                b"\xff\xe2"
                 + size
                 + b"ICC_PROFILE\0"
                 + o8(i)
@@ -823,8 +823,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
         optimize,
         info.get("keep_rgb", False),
         info.get("streamtype", 0),
-        dpi[0],
-        dpi[1],
+        dpi,
         subsampling,
         info.get("restart_marker_blocks", 0),
         info.get("restart_marker_rows", 0),
diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py
index 02db52af4..3d04f9b95 100644
--- a/src/PIL/MpoImagePlugin.py
+++ b/src/PIL/MpoImagePlugin.py
@@ -52,7 +52,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
             if not offsets:
                 # APP2 marker
                 im_frame.encoderinfo["extra"] = (
-                    b"\xFF\xE2" + struct.pack(">H", 6 + 82) + b"MPF\0" + b" " * 82
+                    b"\xff\xe2" + struct.pack(">H", 6 + 82) + b"MPF\0" + b" " * 82
                 )
                 exif = im_frame.encoderinfo.get("exif")
                 if isinstance(exif, Image.Exif):
@@ -85,7 +85,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
     ifd[0xB002] = mpentries
 
     fp.seek(mpf_offset)
-    fp.write(b"II\x2A\x00" + o32le(8) + ifd.tobytes(8))
+    fp.write(b"II\x2a\x00" + o32le(8) + ifd.tobytes(8))
     fp.seek(0, os.SEEK_END)
 
 
diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py
index 32436cea3..299405ae0 100644
--- a/src/PIL/PcxImagePlugin.py
+++ b/src/PIL/PcxImagePlugin.py
@@ -188,7 +188,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
         + o16(dpi[0])
         + o16(dpi[1])
         + b"\0" * 24
-        + b"\xFF" * 24
+        + b"\xff" * 24
         + b"\0"
         + o8(planes)
         + o16(stride)
diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py
index 7cb2d241b..41b38ebbf 100644
--- a/src/PIL/PdfParser.py
+++ b/src/PIL/PdfParser.py
@@ -19,14 +19,14 @@ def encode_text(s: str) -> bytes:
 
 PDFDocEncoding = {
     0x16: "\u0017",
-    0x18: "\u02D8",
-    0x19: "\u02C7",
-    0x1A: "\u02C6",
-    0x1B: "\u02D9",
-    0x1C: "\u02DD",
-    0x1D: "\u02DB",
-    0x1E: "\u02DA",
-    0x1F: "\u02DC",
+    0x18: "\u02d8",
+    0x19: "\u02c7",
+    0x1A: "\u02c6",
+    0x1B: "\u02d9",
+    0x1C: "\u02dd",
+    0x1D: "\u02db",
+    0x1E: "\u02da",
+    0x1F: "\u02dc",
     0x80: "\u2022",
     0x81: "\u2020",
     0x82: "\u2021",
@@ -36,29 +36,29 @@ PDFDocEncoding = {
     0x86: "\u0192",
     0x87: "\u2044",
     0x88: "\u2039",
-    0x89: "\u203A",
+    0x89: "\u203a",
     0x8A: "\u2212",
     0x8B: "\u2030",
-    0x8C: "\u201E",
-    0x8D: "\u201C",
-    0x8E: "\u201D",
+    0x8C: "\u201e",
+    0x8D: "\u201c",
+    0x8E: "\u201d",
     0x8F: "\u2018",
     0x90: "\u2019",
-    0x91: "\u201A",
+    0x91: "\u201a",
     0x92: "\u2122",
-    0x93: "\uFB01",
-    0x94: "\uFB02",
+    0x93: "\ufb01",
+    0x94: "\ufb02",
     0x95: "\u0141",
     0x96: "\u0152",
     0x97: "\u0160",
     0x98: "\u0178",
-    0x99: "\u017D",
+    0x99: "\u017d",
     0x9A: "\u0131",
     0x9B: "\u0142",
     0x9C: "\u0153",
     0x9D: "\u0161",
-    0x9E: "\u017E",
-    0xA0: "\u20AC",
+    0x9E: "\u017e",
+    0xA0: "\u20ac",
 }
 
 
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index f0e4a2961..7136ed7ad 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -1439,7 +1439,7 @@ def _save(
                 chunk(fp, b"tRNS", transparency[:alpha_bytes])
             else:
                 transparency = max(0, min(255, transparency))
-                alpha = b"\xFF" * transparency + b"\0"
+                alpha = b"\xff" * transparency + b"\0"
                 chunk(fp, b"tRNS", alpha[:alpha_bytes])
         elif im.mode in ("1", "L", "I", "I;16"):
             transparency = max(0, min(65535, transparency))
diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py
index 4e779df17..fb228f572 100644
--- a/src/PIL/PpmImagePlugin.py
+++ b/src/PIL/PpmImagePlugin.py
@@ -230,7 +230,7 @@ class PpmPlainDecoder(ImageFile.PyDecoder):
                     msg = b"Invalid token for this mode: %s" % bytes([token])
                     raise ValueError(msg)
             data = (data + tokens)[:total_bytes]
-        invert = bytes.maketrans(b"01", b"\xFF\x00")
+        invert = bytes.maketrans(b"01", b"\xff\x00")
         return data.translate(invert)
 
     def _decode_blocks(self, maxval: int) -> bytearray:
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 9de575ce8..9f44fb213 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -275,12 +275,12 @@ OPEN_INFO = {
 MAX_SAMPLESPERPIXEL = max(len(key_tp[4]) for key_tp in OPEN_INFO)
 
 PREFIXES = [
-    b"MM\x00\x2A",  # Valid TIFF header with big-endian byte order
-    b"II\x2A\x00",  # Valid TIFF header with little-endian byte order
-    b"MM\x2A\x00",  # Invalid TIFF header, assume big-endian
-    b"II\x00\x2A",  # Invalid TIFF header, assume little-endian
-    b"MM\x00\x2B",  # BigTIFF with big-endian byte order
-    b"II\x2B\x00",  # BigTIFF with little-endian byte order
+    b"MM\x00\x2a",  # Valid TIFF header with big-endian byte order
+    b"II\x2a\x00",  # Valid TIFF header with little-endian byte order
+    b"MM\x2a\x00",  # Invalid TIFF header, assume big-endian
+    b"II\x00\x2a",  # Invalid TIFF header, assume little-endian
+    b"MM\x00\x2b",  # BigTIFF with big-endian byte order
+    b"II\x2b\x00",  # BigTIFF with little-endian byte order
 ]
 
 if not getattr(Image.core, "libtiff_support_custom_tags", True):
@@ -582,7 +582,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
 
     def __init__(
         self,
-        ifh: bytes = b"II\x2A\x00\x00\x00\x00\x00",
+        ifh: bytes = b"II\x2a\x00\x00\x00\x00\x00",
         prefix: bytes | None = None,
         group: int | None = None,
     ) -> None:
@@ -2051,7 +2051,7 @@ class AppendingTiffWriter(io.BytesIO):
         self.offsetOfNewPage = 0
 
         self.IIMM = iimm = self.f.read(4)
-        self._bigtiff = b"\x2B" in iimm
+        self._bigtiff = b"\x2b" in iimm
         if not iimm:
             # empty file - first page
             self.isFirst = True
diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py
index 1e360d044..f8ef4c35d 100644
--- a/src/PIL/WebPImagePlugin.py
+++ b/src/PIL/WebPImagePlugin.py
@@ -224,8 +224,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 
     # Setup the WebP animation encoder
     enc = _webp.WebPAnimEncoder(
-        im.size[0],
-        im.size[1],
+        im.size,
         background,
         loop,
         minimize_size,
diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi
index 813294747..1cb1429d6 100644
--- a/src/PIL/_imagingft.pyi
+++ b/src/PIL/_imagingft.pyi
@@ -31,8 +31,7 @@ class Font:
         stroke_filled: bool,
         anchor: str | None,
         foreground_ink_long: int,
-        x_start: float,
-        y_start: float,
+        start: tuple[float, float],
         /,
     ) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
     def getsize(
diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py
index beddfb062..9c0143003 100644
--- a/src/PIL/_tkinter_finder.py
+++ b/src/PIL/_tkinter_finder.py
@@ -1,5 +1,4 @@
-""" Find compiled module linking to Tcl / Tk libraries
-"""
+"""Find compiled module linking to Tcl / Tk libraries"""
 
 from __future__ import annotations
 
diff --git a/src/_imaging.c b/src/_imaging.c
index 2fd2deffb..ee373e964 100644
--- a/src/_imaging.c
+++ b/src/_imaging.c
@@ -866,7 +866,7 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
 
     if (!PyArg_ParseTuple(
             args,
-            "siiiiiO:color_lut_3d",
+            "sii(iii)O:color_lut_3d",
             &mode,
             &filter,
             &table_channels,
@@ -1013,10 +1013,6 @@ _convert_transparent(ImagingObject *self, PyObject *args) {
 
 static PyObject *
 _copy(ImagingObject *self, PyObject *args) {
-    if (!PyArg_ParseTuple(args, "")) {
-        return NULL;
-    }
-
     return PyImagingNew(ImagingCopy(self->image));
 }
 
@@ -4439,10 +4435,9 @@ PyInit__imaging(void) {
 
     static PyModuleDef module_def = {
         PyModuleDef_HEAD_INIT,
-        "_imaging", /* m_name */
-        NULL,       /* m_doc */
-        -1,         /* m_size */
-        functions,  /* m_methods */
+        .m_name = "_imaging",
+        .m_size = -1,
+        .m_methods = functions,
     };
 
     m = PyModule_Create(&module_def);
diff --git a/src/_imagingcms.c b/src/_imagingcms.c
index 14cf2acd2..6037e8bc4 100644
--- a/src/_imagingcms.c
+++ b/src/_imagingcms.c
@@ -1520,10 +1520,9 @@ PyInit__imagingcms(void) {
 
     static PyModuleDef module_def = {
         PyModuleDef_HEAD_INIT,
-        "_imagingcms",    /* m_name */
-        NULL,             /* m_doc */
-        -1,               /* m_size */
-        pyCMSdll_methods, /* m_methods */
+        .m_name = "_imagingcms",
+        .m_size = -1,
+        .m_methods = pyCMSdll_methods,
     };
 
     m = PyModule_Create(&module_def);
diff --git a/src/_imagingft.c b/src/_imagingft.c
index c202a8059..7d754e168 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -854,7 +854,7 @@ font_render(FontObject *self, PyObject *args) {
 
     if (!PyArg_ParseTuple(
             args,
-            "OO|zzOzfpzLffO:render",
+            "OO|zzOzfpzL(ff):render",
             &string,
             &fill,
             &mode,
@@ -1630,10 +1630,9 @@ PyInit__imagingft(void) {
 
     static PyModuleDef module_def = {
         PyModuleDef_HEAD_INIT,
-        "_imagingft", /* m_name */
-        NULL,         /* m_doc */
-        -1,           /* m_size */
-        _functions,   /* m_methods */
+        .m_name = "_imagingft",
+        .m_size = -1,
+        .m_methods = _functions,
     };
 
     m = PyModule_Create(&module_def);
diff --git a/src/_imagingmath.c b/src/_imagingmath.c
index 75b3716b5..4b9bf08ba 100644
--- a/src/_imagingmath.c
+++ b/src/_imagingmath.c
@@ -308,10 +308,9 @@ PyInit__imagingmath(void) {
 
     static PyModuleDef module_def = {
         PyModuleDef_HEAD_INIT,
-        "_imagingmath", /* m_name */
-        NULL,           /* m_doc */
-        -1,             /* m_size */
-        _functions,     /* m_methods */
+        .m_name = "_imagingmath",
+        .m_size = -1,
+        .m_methods = _functions,
     };
 
     m = PyModule_Create(&module_def);
diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c
index b763e3a6f..a20888294 100644
--- a/src/_imagingmorph.c
+++ b/src/_imagingmorph.c
@@ -252,10 +252,10 @@ PyInit__imagingmorph(void) {
 
     static PyModuleDef module_def = {
         PyModuleDef_HEAD_INIT,
-        "_imagingmorph",                       /* m_name */
-        "A module for doing image morphology", /* m_doc */
-        -1,                                    /* m_size */
-        functions,                             /* m_methods */
+        .m_name = "_imagingmorph",
+        .m_doc = "A module for doing image morphology",
+        .m_size = -1,
+        .m_methods = functions,
     };
 
     m = PyModule_Create(&module_def);
diff --git a/src/_imagingtk.c b/src/_imagingtk.c
index c44482651..4e06fe9b8 100644
--- a/src/_imagingtk.c
+++ b/src/_imagingtk.c
@@ -50,10 +50,9 @@ PyMODINIT_FUNC
 PyInit__imagingtk(void) {
     static PyModuleDef module_def = {
         PyModuleDef_HEAD_INIT,
-        "_imagingtk", /* m_name */
-        NULL,         /* m_doc */
-        -1,           /* m_size */
-        functions,    /* m_methods */
+        .m_name = "_imagingtk",
+        .m_size = -1,
+        .m_methods = functions,
     };
     PyObject *m;
     m = PyModule_Create(&module_def);
diff --git a/src/_webp.c b/src/_webp.c
index dfda7048d..26a5ebbc6 100644
--- a/src/_webp.c
+++ b/src/_webp.c
@@ -164,7 +164,7 @@ _anim_encoder_new(PyObject *self, PyObject *args) {
 
     if (!PyArg_ParseTuple(
             args,
-            "iiIiiiiii",
+            "(ii)Iiiiiii",
             &width,
             &height,
             &bgcolor,
@@ -835,10 +835,9 @@ PyInit__webp(void) {
 
     static PyModuleDef module_def = {
         PyModuleDef_HEAD_INIT,
-        "_webp",     /* m_name */
-        NULL,        /* m_doc */
-        -1,          /* m_size */
-        webpMethods, /* m_methods */
+        .m_name = "_webp",
+        .m_size = -1,
+        .m_methods = webpMethods,
     };
 
     m = PyModule_Create(&module_def);
diff --git a/src/encode.c b/src/encode.c
index 0bf5e63c5..74dd4a3fd 100644
--- a/src/encode.c
+++ b/src/encode.c
@@ -1097,7 +1097,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
 
     if (!PyArg_ParseTuple(
             args,
-            "ss|nnnnpnnnnnnOz#y#y#",
+            "ss|nnnnpn(nn)nnnOz#y#y#",
             &mode,
             &rawmode,
             &quality,