diff --git a/.ci/install.sh b/.ci/install.sh index 2178c6646..52b821417 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -51,10 +51,10 @@ pushd depends && ./install_webp.sh && popd pushd depends && ./install_imagequant.sh && popd # raqm -pushd depends && ./install_raqm.sh && popd +pushd depends && sudo ./install_raqm.sh && popd # libavif -pushd depends && ./install_libavif.sh && popd +pushd depends && sudo ./install_libavif.sh && popd # extra test images pushd depends && ./install_extra_test_images.sh && popd diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index d87d7956f..56517374f 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==3.1.4 +cibuildwheel==3.2.1 diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 68d69c183..6ca35d286 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1,4 +1,6 @@ -mypy==1.18.1 +mypy==1.18.2 +arro3-compute +arro3-core IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PySide6 ipython diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index 94e3d5d08..b114d4a23 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -2,9 +2,6 @@ set -e -if [[ "$ImageOS" == "macos13" ]]; then - brew uninstall gradle maven -fi brew install \ aom \ dav1d \ diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index e12a5b1f7..f6a7dd46b 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -97,8 +97,8 @@ jobs: choco install nasm --no-progress echo "C:\Program Files\NASM" >> $env:GITHUB_PATH - choco install ghostscript --version=10.5.1 --no-progress - echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH + choco install ghostscript --version=10.6.0 --no-progress + echo "C:\Program Files\gs\gs10.06.0\bin" >> $env:GITHUB_PATH # Install extra test images xcopy /S /Y Tests\test-images\* Tests\images diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8504e5c1e..b52000a27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,7 @@ jobs: - { python-version: "3.14t", disable-gil: true } - { python-version: "3.13t", disable-gil: true } # Intel - - { os: "macos-13", python-version: "3.10" } + - { os: "macos-15-intel", python-version: "3.10" } exclude: - { os: "macos-latest", python-version: "3.10" } diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 1fa634096..7d6eb8681 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -93,14 +93,18 @@ ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds. Version numbers with "Patched" # annotations have a source code patch that is required for some platforms. If # you change those versions, ensure the patch is also updated. -FREETYPE_VERSION=2.13.3 -HARFBUZZ_VERSION=11.4.5 +if [[ -n "$IOS_SDK" ]]; then + FREETYPE_VERSION=2.13.3 +else + FREETYPE_VERSION=2.14.1 +fi +HARFBUZZ_VERSION=12.1.0 LIBPNG_VERSION=1.6.50 JPEGTURBO_VERSION=3.1.2 -OPENJPEG_VERSION=2.5.3 +OPENJPEG_VERSION=2.5.4 XZ_VERSION=5.8.1 ZSTD_VERSION=1.5.7 -TIFF_VERSION=4.7.0 +TIFF_VERSION=4.7.1 LCMS2_VERSION=2.17 ZLIB_NG_VERSION=2.2.5 LIBWEBP_VERSION=1.6.0 @@ -267,7 +271,11 @@ function build { if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then yum remove -y zlib-devel fi - build_zlib_ng + if [[ -n "$IS_MACOS" ]]; then + CFLAGS="$CFLAGS -headerpad_max_install_names" build_zlib_ng + else + build_zlib_ng + fi build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto if [[ -n "$IS_MACOS" ]]; then @@ -314,6 +322,10 @@ function build { if [[ -n "$IS_MACOS" ]]; then # Custom freetype build + if [[ -z "$IOS_SDK" ]]; then + build_simple sed 4.9 https://mirrors.middlendian.com/gnu/sed + fi + build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no else build_freetype diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 81a688135..21ea79553 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -39,6 +39,7 @@ concurrency: cancel-in-progress: true env: + EXPECTED_DISTS: 91 FORCE_COLOR: 1 jobs: @@ -52,21 +53,21 @@ jobs: include: - name: "macOS 10.10 x86_64" platform: macos - os: macos-13 + os: macos-15-intel cibw_arch: x86_64 - build: "cp3{9,10,11}*" + build: "cp3{10,11}*" macosx_deployment_target: "10.10" - name: "macOS 10.13 x86_64" platform: macos - os: macos-13 + os: macos-15-intel cibw_arch: x86_64 - build: "cp3{12,13,14}*" + build: "cp3{12,13}*" macosx_deployment_target: "10.13" - name: "macOS 10.15 x86_64" platform: macos - os: macos-13 + os: macos-15-intel cibw_arch: x86_64 - build: "pp3*" + build: "{cp314,pp3}*" macosx_deployment_target: "10.15" - name: "macOS arm64" platform: macos @@ -99,11 +100,11 @@ jobs: cibw_arch: arm64_iphoneos - name: "iOS arm64 simulator" platform: ios - os: macos-14 + os: macos-latest cibw_arch: arm64_iphonesimulator - name: "iOS x86_64 simulator" platform: ios - os: macos-13 + os: macos-15-intel cibw_arch: x86_64_iphonesimulator steps: - uses: actions/checkout@v5 @@ -231,7 +232,7 @@ jobs: path: winbuild\build\bin\fribidi* sdist: - if: github.event_name != 'schedule' + if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow' runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -250,15 +251,33 @@ jobs: name: dist-sdist path: dist/*.tar.gz + count-dists: + needs: [build-native-wheels, windows, sdist] + runs-on: ubuntu-latest + name: Count dists + steps: + - uses: actions/download-artifact@v5 + with: + pattern: dist-* + path: dist + merge-multiple: true + - name: "What did we get?" + run: | + ls -alR + echo "Number of dists, should be $EXPECTED_DISTS:" + files=$(ls dist 2>/dev/null | wc -l) + echo $files + [ "$files" -eq $EXPECTED_DISTS ] || exit 1 + scientific-python-nightly-wheels-publish: if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') - needs: [build-native-wheels, windows] + needs: count-dists runs-on: ubuntu-latest name: Upload wheels to scientific-python-nightly-wheels steps: - uses: actions/download-artifact@v5 with: - pattern: dist-* + pattern: dist-!(sdist)* path: dist merge-multiple: true - name: Upload wheels to scientific-python-nightly-wheels @@ -269,7 +288,7 @@ jobs: pypi-publish: if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - needs: [build-native-wheels, windows, sdist] + needs: count-dists runs-on: ubuntu-latest name: Upload release to PyPI environment: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 23bda1ec7..ab0153687 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.11 + rev: v0.13.3 hooks: - id: ruff-check args: [--exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.1.0 + rev: 25.9.0 hooks: - id: black @@ -24,7 +24,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$) - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.0 + rev: v21.1.2 hooks: - id: clang-format types: [c] @@ -51,14 +51,14 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.33.3 + rev: 0.34.0 hooks: - id: check-github-workflows - id: check-readthedocs - id: check-renovate - repo: https://github.com/zizmorcore/zizmor-pre-commit - rev: v1.12.1 + rev: v1.14.2 hooks: - id: zizmor @@ -68,7 +68,7 @@ repos: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.6.0 + rev: v2.7.0 hooks: - id: pyproject-fmt diff --git a/Tests/createfontdatachunk.py b/Tests/createfontdatachunk.py old mode 100755 new mode 100644 index 41c76f87e..0a3fdb809 --- a/Tests/createfontdatachunk.py +++ b/Tests/createfontdatachunk.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import annotations import base64 diff --git a/Tests/images/colr_bungee.png b/Tests/images/colr_bungee.png index b10a60be0..9ec6b1182 100644 Binary files a/Tests/images/colr_bungee.png and b/Tests/images/colr_bungee.png differ diff --git a/Tests/images/colr_bungee_mask.png b/Tests/images/colr_bungee_mask.png index f13e17677..79106b639 100644 Binary files a/Tests/images/colr_bungee_mask.png and b/Tests/images/colr_bungee_mask.png differ diff --git a/Tests/images/colr_bungee_older.png b/Tests/images/colr_bungee_older.png new file mode 100644 index 000000000..b10a60be0 Binary files /dev/null and b/Tests/images/colr_bungee_older.png differ diff --git a/Tests/images/crash-5762152299364352.fli b/Tests/images/crash-5762152299364352.fli index 944fe0b56..d7588eea8 100644 Binary files a/Tests/images/crash-5762152299364352.fli and b/Tests/images/crash-5762152299364352.fli differ diff --git a/Tests/images/frame_size.mpo b/Tests/images/frame_size.mpo new file mode 100644 index 000000000..ee5c6cdf7 Binary files /dev/null and b/Tests/images/frame_size.mpo differ diff --git a/Tests/images/sugarshack_frame_size.mpo b/Tests/images/sugarshack_frame_size.mpo deleted file mode 100644 index abff98ea5..000000000 Binary files a/Tests/images/sugarshack_frame_size.mpo and /dev/null differ diff --git a/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli b/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli index ce4607d2d..73da81dcb 100644 Binary files a/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli and b/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli differ diff --git a/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli b/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli index 77a94b87a..abe642e6a 100644 Binary files a/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli and b/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli differ diff --git a/Tests/test_arro3.py b/Tests/test_arro3.py new file mode 100644 index 000000000..672eedc9b --- /dev/null +++ b/Tests/test_arro3.py @@ -0,0 +1,275 @@ +from __future__ import annotations + +import json +from typing import Any, NamedTuple + +import pytest + +from PIL import Image + +from .helper import ( + assert_deep_equal, + assert_image_equal, + hopper, + is_big_endian, +) + +TYPE_CHECKING = False +if TYPE_CHECKING: + from arro3 import compute + from arro3.core import ( + Array, + DataType, + Field, + fixed_size_list_array, + ) +else: + arro3 = pytest.importorskip("arro3", reason="Arro3 not installed") + from arro3 import compute + from arro3.core import Array, DataType, Field, fixed_size_list_array + +TEST_IMAGE_SIZE = (10, 10) + + +def _test_img_equals_pyarray( + img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1 +) -> None: + assert img.height * img.width * elts_per_pixel == len(arr) + px = img.load() + assert px is not None + if elts_per_pixel > 1 and mask is None: + # have to do element-wise comparison when we're comparing + # flattened r,g,b,a to a pixel. + mask = list(range(elts_per_pixel)) + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): + if mask: + pixel = px[x, y] + assert isinstance(pixel, tuple) + for ix, elt in enumerate(mask): + if elts_per_pixel == 1: + assert pixel[ix] == arr[y * img.width + x].as_py()[elt] + else: + assert ( + pixel[ix] + == arr[(y * img.width + x) * elts_per_pixel + elt].as_py() + ) + else: + assert_deep_equal(px[x, y], arr[y * img.width + x].as_py()) + + +def _test_img_equals_int32_pyarray( + img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1 +) -> None: + assert img.height * img.width * elts_per_pixel == len(arr) + px = img.load() + assert px is not None + if mask is None: + # have to do element-wise comparison when we're comparing + # flattened rgba in an uint32 to a pixel. + mask = list(range(elts_per_pixel)) + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): + pixel = px[x, y] + assert isinstance(pixel, tuple) + arr_pixel_int = arr[y * img.width + x].as_py() + arr_pixel_tuple = ( + arr_pixel_int % 256, + (arr_pixel_int // 256) % 256, + (arr_pixel_int // 256**2) % 256, + (arr_pixel_int // 256**3), + ) + if is_big_endian(): + arr_pixel_tuple = arr_pixel_tuple[::-1] + + for ix, elt in enumerate(mask): + assert pixel[ix] == arr_pixel_tuple[elt] + + +fl_uint8_4_type = DataType.list(Field("_", DataType.uint8()).with_nullable(False), 4) + + +@pytest.mark.parametrize( + "mode, dtype, mask", + ( + ("L", DataType.uint8(), None), + ("I", DataType.int32(), None), + ("F", DataType.float32(), None), + ("LA", fl_uint8_4_type, [0, 3]), + ("RGB", fl_uint8_4_type, [0, 1, 2]), + ("RGBA", fl_uint8_4_type, None), + ("RGBX", fl_uint8_4_type, None), + ("CMYK", fl_uint8_4_type, None), + ("YCbCr", fl_uint8_4_type, [0, 1, 2]), + ("HSV", fl_uint8_4_type, [0, 1, 2]), + ), +) +def test_to_array(mode: str, dtype: DataType, mask: list[int] | None) -> None: + img = hopper(mode) + + # Resize to non-square + img = img.crop((3, 0, 124, 127)) + assert img.size == (121, 127) + + arr = Array(img) + _test_img_equals_pyarray(img, arr, mask) + assert arr.type == dtype + + reloaded = Image.fromarrow(arr, mode, img.size) + assert_image_equal(img, reloaded) + + +def test_lifetime() -> None: + # valgrind shouldn't error out here. + # arrays should be accessible after the image is deleted. + + img = hopper("L") + + arr_1 = Array(img) + arr_2 = Array(img) + + del img + + assert compute.sum(arr_1).as_py() > 0 + del arr_1 + + assert compute.sum(arr_2).as_py() > 0 + del arr_2 + + +def test_lifetime2() -> None: + # valgrind shouldn't error out here. + # img should remain after the arrays are collected. + + img = hopper("L") + + arr_1 = Array(img) + arr_2 = Array(img) + + assert compute.sum(arr_1).as_py() > 0 + del arr_1 + + assert compute.sum(arr_2).as_py() > 0 + del arr_2 + + img2 = img.copy() + px = img2.load() + assert px # make mypy happy + assert isinstance(px[0, 0], int) + + +class DataShape(NamedTuple): + dtype: DataType + # Strictly speaking, elt should be a pixel or pixel component, so + # list[uint8][4], float, int, uint32, uint8, etc. But more + # correctly, it should be exactly the dtype from the line above. + elt: Any + elts_per_pixel: int + + +UINT_ARR = DataShape( + dtype=fl_uint8_4_type, + elt=[1, 2, 3, 4], # array of 4 uint8 per pixel + elts_per_pixel=1, # only one array per pixel +) + +UINT = DataShape( + dtype=DataType.uint8(), + elt=3, # one uint8, + elts_per_pixel=4, # but repeated 4x per pixel +) + +UINT32 = DataShape( + dtype=DataType.uint32(), + elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000 + elts_per_pixel=1, # one per pixel +) + +INT32 = DataShape( + dtype=DataType.uint32(), + elt=0x12CDEF45, # one packed int + elts_per_pixel=1, # one per pixel +) + + +@pytest.mark.parametrize( + "mode, data_tp, mask", + ( + ("L", DataShape(DataType.uint8(), 3, 1), None), + ("I", DataShape(DataType.int32(), 1 << 24, 1), None), + ("F", DataShape(DataType.float32(), 3.14159, 1), None), + ("LA", UINT_ARR, [0, 3]), + ("LA", UINT, [0, 3]), + ("RGB", UINT_ARR, [0, 1, 2]), + ("RGBA", UINT_ARR, None), + ("CMYK", UINT_ARR, None), + ("YCbCr", UINT_ARR, [0, 1, 2]), + ("HSV", UINT_ARR, [0, 1, 2]), + ("RGB", UINT, [0, 1, 2]), + ("RGBA", UINT, None), + ("CMYK", UINT, None), + ("YCbCr", UINT, [0, 1, 2]), + ("HSV", UINT, [0, 1, 2]), + ), +) +def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None: + (dtype, elt, elts_per_pixel) = data_tp + + ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] + if dtype == fl_uint8_4_type: + tmp_arr = Array(elt * (ct_pixels * elts_per_pixel), type=DataType.uint8()) + arr = fixed_size_list_array(tmp_arr, 4) + else: + arr = Array([elt] * (ct_pixels * elts_per_pixel), type=dtype) + img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) + + _test_img_equals_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, mask", + ( + ("LA", [0, 3]), + ("RGB", [0, 1, 2]), + ("RGBA", None), + ("CMYK", None), + ("YCbCr", [0, 1, 2]), + ("HSV", [0, 1, 2]), + ), +) +@pytest.mark.parametrize("data_tp", (UINT32, INT32)) +def test_from_int32array(mode: str, mask: list[int] | None, data_tp: DataShape) -> None: + (dtype, elt, elts_per_pixel) = data_tp + + ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] + arr = Array([elt] * (ct_pixels * elts_per_pixel), type=dtype) + img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) + + _test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, metadata", + ( + ("LA", ["L", "X", "X", "A"]), + ("RGB", ["R", "G", "B", "X"]), + ("RGBX", ["R", "G", "B", "X"]), + ("RGBA", ["R", "G", "B", "A"]), + ("CMYK", ["C", "M", "Y", "K"]), + ("YCbCr", ["Y", "Cb", "Cr", "X"]), + ("HSV", ["H", "S", "V", "X"]), + ), +) +def test_image_metadata(mode: str, metadata: list[str]) -> None: + img = hopper(mode) + + arr = Array(img) + + assert arr.type.value_field + assert arr.type.value_field.metadata + assert arr.type.value_field.metadata[b"image"] + + parsed_metadata = json.loads(arr.type.value_field.metadata[b"image"].decode("utf8")) + + assert "bands" in parsed_metadata + assert parsed_metadata["bands"] == metadata diff --git a/Tests/test_file_avif.py b/Tests/test_file_avif.py index 3fac51ac6..727191153 100644 --- a/Tests/test_file_avif.py +++ b/Tests/test_file_avif.py @@ -14,6 +14,7 @@ import pytest from PIL import ( AvifImagePlugin, + GifImagePlugin, Image, ImageDraw, ImageFile, @@ -220,6 +221,7 @@ class TestFileAvif: def test_background_from_gif(self, tmp_path: Path) -> None: with Image.open("Tests/images/chi.gif") as im: original_value = im.convert("RGB").getpixel((1, 1)) + assert isinstance(original_value, tuple) # Save as AVIF out_avif = tmp_path / "temp.avif" @@ -232,6 +234,7 @@ class TestFileAvif: with Image.open(out_gif) as reread: reread_value = reread.convert("RGB").getpixel((1, 1)) + assert isinstance(reread_value, tuple) difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)]) assert difference <= 6 @@ -240,6 +243,7 @@ class TestFileAvif: with Image.open("Tests/images/chi.gif") as im: im.save(temp_file) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 1 def test_invalid_file(self) -> None: @@ -598,10 +602,12 @@ class TestAvifAnimation: """ with Image.open(TEST_AVIF_FILE) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 1 assert not im.is_animated with Image.open("Tests/images/avif/star.avifs") as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 5 assert im.is_animated @@ -612,11 +618,13 @@ class TestAvifAnimation: """ with Image.open("Tests/images/avif/star.gif") as original: + assert isinstance(original, GifImagePlugin.GifImageFile) assert original.n_frames > 1 temp_file = tmp_path / "temp.avif" original.save(temp_file, save_all=True) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == original.n_frames # Compare first frame in P mode to frame from original GIF @@ -636,6 +644,7 @@ class TestAvifAnimation: def check(temp_file: Path) -> None: with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 4 # Compare first frame to original @@ -708,6 +717,7 @@ class TestAvifAnimation: ) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 5 assert im.is_animated @@ -737,6 +747,7 @@ class TestAvifAnimation: ) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 5 assert im.is_animated diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 746b2e180..c1c430aa5 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -6,6 +6,8 @@ from pathlib import Path import pytest from PIL import BmpImagePlugin, Image, _binary +from PIL._binary import o16le as o16 +from PIL._binary import o32le as o32 from .helper import ( assert_image_equal, @@ -114,7 +116,7 @@ def test_save_float_dpi(tmp_path: Path) -> None: def test_load_dib() -> None: - # test for #1293, Imagegrab returning Unsupported Bitfields Format + # test for #1293, ImageGrab returning Unsupported Bitfields Format with Image.open("Tests/images/clipboard.dib") as im: assert im.format == "DIB" assert im.get_format_mimetype() == "image/bmp" @@ -219,6 +221,18 @@ def test_rle8_eof(file_name: str, length: int) -> None: im.load() +def test_unsupported_bmp_bitfields_layout() -> None: + fp = io.BytesIO( + o32(40) # header size + + b"\x00" * 10 + + o16(1) # bits + + o32(3) # BITFIELDS compression + + b"\x00" * 32 + ) + with pytest.raises(OSError, match="Unsupported BMP bitfields layout"): + Image.open(fp) + + def test_offset() -> None: # This image has been hexedited # to exclude the palette size from the pixel data offset diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index dbf1b866d..4b3e3afcb 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -1,8 +1,13 @@ from __future__ import annotations +from io import BytesIO + import pytest from PIL import CurImagePlugin, Image +from PIL._binary import o8 +from PIL._binary import o16le as o16 +from PIL._binary import o32le as o32 TEST_FILE = "Tests/images/deerstalker.cur" @@ -17,6 +22,24 @@ def test_sanity() -> None: assert im.getpixel((16, 16)) == (84, 87, 86, 255) +def test_largest_cursor() -> None: + magic = b"\x00\x00\x02\x00" + sizes = ((1, 1), (8, 8), (4, 4)) + data = magic + o16(len(sizes)) + for w, h in sizes: + image_offset = 6 + len(sizes) * 16 if (w, h) == max(sizes) else 0 + data += o8(w) + o8(h) + o8(0) * 10 + o32(image_offset) + data += ( + o32(12) # header size + + o16(8) # width + + o16(16) # height + + o16(0) # planes + + o16(1) # bits + ) + with Image.open(BytesIO(data)) as im: + assert im.size == (8, 8) + + def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" @@ -26,6 +49,7 @@ def test_invalid_file() -> None: no_cursors_file = "Tests/images/no_cursors.cur" cur = CurImagePlugin.CurImageFile(TEST_FILE) + assert cur.fp is not None cur.fp.close() with open(no_cursors_file, "rb") as cur.fp: with pytest.raises(TypeError): diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index d94de7287..b50915f28 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -197,6 +197,14 @@ def test_load_long_binary_data(prefix: bytes) -> None: assert img.format == "EPS" +def test_begin_binary() -> None: + with open("Tests/images/eps/binary_preview_map.eps", "rb") as fp: + data = bytearray(fp.read()) + data[76875 : 76875 + 11] = b"%" * 11 + with Image.open(io.BytesIO(data)) as img: + assert img.size == (399, 480) + + @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 0fadd01d0..13c6a4323 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -48,6 +48,7 @@ def test_sanity() -> None: def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) with Image.open(animated_test_file_with_prefix_chunk) as im: + assert isinstance(im, FliImagePlugin.FliImageFile) assert im.mode == "P" assert im.size == (320, 200) assert im.format == "FLI" @@ -55,6 +56,7 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: assert im.is_animated palette = im.getpalette() + assert palette is not None assert palette[3:6] == [255, 255, 255] assert palette[381:384] == [204, 204, 12] assert palette[765:] == [252, 0, 0] diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index 806532c17..8a49fd4fa 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -1,5 +1,7 @@ from __future__ import annotations +from io import BytesIO + import pytest from PIL import GdImageFile, UnidentifiedImageError @@ -16,6 +18,14 @@ def test_sanity() -> None: assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14) +def test_transparency() -> None: + with open(TEST_GD_FILE, "rb") as fp: + data = bytearray(fp.read()) + data[7:11] = b"\x00\x00\x00\x05" + with GdImageFile.open(BytesIO(data)) as im: + assert im.info["transparency"] == 5 + + def test_bad_mode() -> None: with pytest.raises(ValueError): GdImageFile.open(TEST_GD_FILE, "bad mode") diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index f5c2f360c..acf79374e 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -293,6 +293,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None: im.save(out, save_all=True) with Image.open(out) as reread: + assert isinstance(reread, GifImagePlugin.GifImageFile) assert reread.n_frames == 5 @@ -1374,6 +1375,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None: with Image.open(out) as im: # Assert that the frames are correct, and each frame has the same palette + assert isinstance(im, GifImagePlugin.GifImageFile) assert_image_equal(im.convert("RGB"), frames[0].convert("RGB")) assert im.palette is not None assert im.global_palette is not None diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 51d518ae5..96e7f4239 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -330,8 +330,10 @@ class TestFileJpeg: # Reading with Image.open("Tests/images/exif_gps.jpg") as im: - exif = im._getexif() - assert exif[gps_index] == expected_exif_gps + assert isinstance(im, JpegImagePlugin.JpegImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[gps_index] == expected_exif_gps # Writing f = tmp_path / "temp.jpg" @@ -340,8 +342,10 @@ class TestFileJpeg: hopper().save(f, exif=exif) with Image.open(f) as reloaded: - exif = reloaded._getexif() - assert exif[gps_index] == expected_exif_gps + assert isinstance(reloaded, JpegImagePlugin.JpegImageFile) + exif_data = reloaded._getexif() + assert exif_data is not None + assert exif_data[gps_index] == expected_exif_gps def test_empty_exif_gps(self) -> None: with Image.open("Tests/images/empty_gps_ifd.jpg") as im: @@ -368,6 +372,7 @@ class TestFileJpeg: exifs = [] for i in range(2): with Image.open("Tests/images/exif-200dpcm.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) exifs.append(im._getexif()) assert exifs[0] == exifs[1] @@ -401,13 +406,17 @@ class TestFileJpeg: } with Image.open("Tests/images/exif_gps.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) exif = im._getexif() + assert exif is not None for tag, value in expected_exif.items(): assert value == exif[tag] def test_exif_gps_typeerror(self) -> None: with Image.open("Tests/images/exif_gps_typeerror.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) + # Should not raise a TypeError im._getexif() @@ -487,7 +496,9 @@ class TestFileJpeg: def test_exif(self) -> None: with Image.open("Tests/images/pil_sample_rgb.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) info = im._getexif() + assert info is not None assert info[305] == "Adobe Photoshop CS Macintosh" def test_get_child_images(self) -> None: @@ -690,11 +701,13 @@ class TestFileJpeg: def test_save_multiple_16bit_qtables(self) -> None: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) im2 = self.roundtrip(im, qtables="keep") assert im.quantization == im2.quantization def test_save_single_16bit_qtable(self) -> None: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) im2 = self.roundtrip(im, qtables={0: im.quantization[0]}) assert len(im2.quantization) == 1 assert im2.quantization[0] == im.quantization[0] @@ -898,7 +911,10 @@ class TestFileJpeg: # in contrast to normal 8 with Image.open("Tests/images/exif-ifd-offset.jpg") as im: # Act / Assert - assert im._getexif()[306] == "2017:03:13 23:03:09" + assert isinstance(im, JpegImagePlugin.JpegImageFile) + exif = im._getexif() + assert exif is not None + assert exif[306] == "2017:03:13 23:03:09" def test_multiple_exif(self) -> None: with Image.open("Tests/images/multiple_exif.jpg") as im: diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index f61f79f17..4908496cf 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -355,6 +355,27 @@ class TestFileLibTiff(LibTiffTestCase): # Should not segfault im.save(outfile) + def test_whitepoint_tag( + self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ) -> None: + monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) + + out = tmp_path / "temp.tif" + hopper().save(out, tiffinfo={318: (0.3127, 0.3289)}) + + with Image.open(out) as reloaded: + assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) + assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289)) + + # Save tag by default + out = tmp_path / "temp2.tif" + with Image.open("Tests/images/rdf.tif") as im: + im.save(out) + + with Image.open(out) as reloaded: + assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) + assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289999)) + def test_xmlpacket_tag( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 9262e6ca7..f947d1419 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -104,25 +104,27 @@ def test_exif(test_file: str) -> None: def test_frame_size() -> None: - # This image has been hexedited to contain a different size - # in the SOF marker of the second frame - with Image.open("Tests/images/sugarshack_frame_size.mpo") as im: - assert im.size == (640, 480) + with Image.open("Tests/images/frame_size.mpo") as im: + assert im.size == (56, 70) + im.load() im.seek(1) - assert im.size == (680, 480) + assert im.size == (349, 434) + im.load() im.seek(0) - assert im.size == (640, 480) + assert im.size == (56, 70) def test_ignore_frame_size() -> None: # Ignore the different size of the second frame # since this is not a "Large Thumbnail" image with Image.open("Tests/images/ignore_frame_size.mpo") as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) assert im.size == (64, 64) im.seek(1) + assert im.mpinfo is not None assert ( im.mpinfo[0xB002][1]["Attribute"]["MPType"] == "Multi-Frame Image: (Disparity)" @@ -155,6 +157,7 @@ def test_reload_exif_after_seek() -> None: @pytest.mark.parametrize("test_file", test_files) def test_mp(test_file: str) -> None: with Image.open(test_file) as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) mpinfo = im._getmp() assert mpinfo is not None assert mpinfo[45056] == b"0100" @@ -165,6 +168,7 @@ def test_mp_offset() -> None: # This image has been manually hexedited to have an IFD offset of 10 # in APP2 data, in contrast to normal 8 with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) mpinfo = im._getmp() assert mpinfo is not None assert mpinfo[45056] == b"0100" @@ -182,6 +186,7 @@ def test_mp_no_data() -> None: @pytest.mark.parametrize("test_file", test_files) def test_mp_attribute(test_file: str) -> None: with Image.open(test_file) as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) mpinfo = im._getmp() assert mpinfo is not None for frame_number, mpentry in enumerate(mpinfo[0xB002]): diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py index 9bf1a75f0..15dd7f116 100644 --- a/Tests/test_file_pcd.py +++ b/Tests/test_file_pcd.py @@ -6,6 +6,8 @@ import pytest from PIL import Image +from .helper import assert_image_equal + def test_load_raw() -> None: with Image.open("Tests/images/hopper.pcd") as im: @@ -30,3 +32,8 @@ def test_rotated(orientation: int) -> None: f = BytesIO(data) with Image.open(f) as im: assert im.size == (512, 768) + + with Image.open("Tests/images/hopper.pcd") as expected: + assert_image_equal( + im, expected.rotate(90 if orientation == 1 else 270, expand=True) + ) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 0a51fd493..dc1077fed 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -229,7 +229,9 @@ class TestFilePng: assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - assert len(im.getchannel("A").getcolors()) == 124 + colors = im.getchannel("A").getcolors() + assert colors is not None + assert len(colors) == 124 def test_load_transparent_rgb(self) -> None: test_file = "Tests/images/rgb_trns.png" @@ -241,7 +243,9 @@ class TestFilePng: assert_image(im, "RGBA", (64, 64)) # image has 876 transparent pixels - assert im.getchannel("A").getcolors()[0][0] == 876 + colors = im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == 876 def test_save_p_transparent_palette(self, tmp_path: Path) -> None: in_file = "Tests/images/pil123p.png" @@ -262,7 +266,9 @@ class TestFilePng: assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - assert len(im.getchannel("A").getcolors()) == 124 + colors = im.getchannel("A").getcolors() + assert colors is not None + assert len(colors) == 124 def test_save_p_single_transparency(self, tmp_path: Path) -> None: in_file = "Tests/images/p_trns_single.png" @@ -285,7 +291,9 @@ class TestFilePng: assert im.getpixel((31, 31)) == (0, 255, 52, 0) # image has 876 transparent pixels - assert im.getchannel("A").getcolors()[0][0] == 876 + colors = im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == 876 def test_save_p_transparent_black(self, tmp_path: Path) -> None: # check if solid black image with full transparency @@ -313,7 +321,9 @@ class TestFilePng: assert im.info["transparency"] == 255 im_rgba = im.convert("RGBA") - assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent + colors = im_rgba.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent test_file = tmp_path / "temp.png" im.save(test_file) @@ -324,7 +334,9 @@ class TestFilePng: assert_image_equal(im, test_im) test_im_rgba = test_im.convert("RGBA") - assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent + colors = test_im_rgba.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent def test_save_rgb_single_transparency(self, tmp_path: Path) -> None: in_file = "Tests/images/caption_6_33_22.png" @@ -671,6 +683,9 @@ class TestFilePng: im.save(out, bits=4, save_all=save_all) with Image.open(out) as reloaded: + assert isinstance(reloaded, PngImagePlugin.PngImageFile) + assert reloaded.png is not None + assert reloaded.png.im_palette is not None assert len(reloaded.png.im_palette[1]) == 48 def test_plte_length(self, tmp_path: Path) -> None: @@ -681,6 +696,9 @@ class TestFilePng: im.save(out) with Image.open(out) as reloaded: + assert isinstance(reloaded, PngImagePlugin.PngImageFile) + assert reloaded.png is not None + assert reloaded.png.im_palette is not None assert len(reloaded.png.im_palette[1]) == 3 def test_getxmp(self) -> None: @@ -702,13 +720,17 @@ class TestFilePng: def test_exif(self) -> None: # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: - exif = im._getexif() - assert exif[274] == 1 + assert isinstance(im, PngImagePlugin.PngImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[274] == 1 # With an ImageMagick zTXt chunk with Image.open("Tests/images/exif_imagemagick.png") as im: - exif = im._getexif() - assert exif[274] == 1 + assert isinstance(im, PngImagePlugin.PngImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[274] == 1 # Assert that info still can be extracted # when the image is no longer a PngImageFile instance @@ -717,8 +739,10 @@ class TestFilePng: # With a tEXt chunk with Image.open("Tests/images/exif_text.png") as im: - exif = im._getexif() - assert exif[274] == 1 + assert isinstance(im, PngImagePlugin.PngImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[274] == 1 # With XMP tags with Image.open("Tests/images/xmp_tags_orientation.png") as im: @@ -740,8 +764,10 @@ class TestFilePng: im.save(test_file, exif=im.getexif()) with Image.open(test_file) as reloaded: - exif = reloaded._getexif() - assert exif[274] == 1 + assert isinstance(reloaded, PngImagePlugin.PngImageFile) + exif_data = reloaded._getexif() + assert exif_data is not None + assert exif_data[274] == 1 @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 68f2f9468..598e9a445 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -92,6 +92,13 @@ def test_16bit_pgm() -> None: assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff") +def test_p4_save(tmp_path: Path) -> None: + with Image.open("Tests/images/hopper_1bit.pbm") as im: + filename = tmp_path / "temp.pbm" + im.save(filename) + assert_image_equal_tofile(im, filename) + + def test_16bit_pgm_write(tmp_path: Path) -> None: with Image.open("Tests/images/16_bit_binary.pgm") as im: filename = tmp_path / "temp.pgm" @@ -134,6 +141,12 @@ def test_pfm_big_endian(tmp_path: Path) -> None: assert_image_equal_tofile(im, filename) +def test_save_unsupported_mode(tmp_path: Path) -> None: + im = hopper("P") + with pytest.raises(OSError, match="cannot write mode P as PPM"): + im.save(tmp_path / "out.ppm") + + @pytest.mark.parametrize( "data", [ diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index bd39de2e1..bb8d3eefc 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -274,13 +274,17 @@ def test_save_l_transparency(tmp_path: Path) -> None: in_file = "Tests/images/la.tga" with Image.open(in_file) as im: assert im.mode == "LA" - assert im.getchannel("A").getcolors()[0][0] == num_transparent + colors = im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent out = tmp_path / "temp.tga" im.save(out) with Image.open(out) as test_im: assert test_im.mode == "LA" - assert test_im.getchannel("A").getcolors()[0][0] == num_transparent + colors = test_im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent assert_image_equal(im, test_im) diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 7543d22da..3de412b83 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -22,11 +22,13 @@ except ImportError: def test_read_exif_metadata() -> None: file_path = "Tests/images/flower.webp" with Image.open(file_path) as image: + assert isinstance(image, WebPImagePlugin.WebPImageFile) assert image.format == "WEBP" exif_data = image.info.get("exif", None) assert exif_data exif = image._getexif() + assert exif is not None # Camera make assert exif[271] == "Canon" diff --git a/Tests/test_image.py b/Tests/test_image.py index eb3882ddc..ac30f785c 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -284,33 +284,6 @@ class TestImage: assert item is not None assert item != num - def test_expand_x(self) -> None: - # Arrange - im = hopper() - orig_size = im.size - xmargin = 5 - - # Act - im = im._expand(xmargin) - - # Assert - assert im.size[0] == orig_size[0] + 2 * xmargin - assert im.size[1] == orig_size[1] + 2 * xmargin - - def test_expand_xy(self) -> None: - # Arrange - im = hopper() - orig_size = im.size - xmargin = 5 - ymargin = 3 - - # Act - im = im._expand(xmargin, ymargin) - - # Assert - assert im.size[0] == orig_size[0] + 2 * xmargin - assert im.size[1] == orig_size[1] + 2 * ymargin - def test_getbands(self) -> None: # Assert assert hopper("RGB").getbands() == ("R", "G", "B") @@ -1126,6 +1099,12 @@ class TestImage: assert im.palette is not None assert im.palette.colors[(27, 35, 6, 214)] == 24 + def test_merge_pa(self) -> None: + p = hopper("P") + a = Image.new("L", p.size) + pa = Image.merge("PA", (p, a)) + assert p.getpalette() == pa.getpalette() + def test_constants(self) -> None: for enum in ( Image.Transpose, diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 33f844437..8d0ef4b22 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -97,6 +97,13 @@ def test_opaque() -> None: assert_image_equal(alpha, solid) +def test_rgba() -> None: + with Image.open("Tests/images/transparent.png") as im: + assert im.mode == "RGBA" + + assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5) + + def test_rgba_p() -> None: im = hopper("RGBA") im.putalpha(hopper("L")) @@ -107,11 +114,19 @@ def test_rgba_p() -> None: assert_image_similar(im, comparable, 20) -def test_rgba() -> None: - with Image.open("Tests/images/transparent.png") as im: - assert im.mode == "RGBA" +def test_rgba_pa() -> None: + im = hopper("RGBA").convert("PA").convert("RGB") + expected = hopper("RGB") - assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5) + assert_image_similar(im, expected, 9.3) + + +def test_pa() -> None: + im = hopper().convert("PA") + + palette = im.palette + assert palette is not None + assert palette.colors != {} def test_trns_p(tmp_path: Path) -> None: diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 2cff6c893..37e4df103 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -124,6 +124,21 @@ class TestImagingPaste: im = im.crop((12, 23, im2.width + 12, im2.height + 23)) assert_image_equal(im, im2) + @pytest.mark.parametrize("y", [10, -10]) + @pytest.mark.parametrize("mode", ["L", "RGB"]) + @pytest.mark.parametrize("mask_mode", ["", "1", "L", "LA", "RGBa"]) + def test_image_self(self, y: int, mode: str, mask_mode: str) -> None: + im = getattr(self, "gradient_" + mode) + mask = Image.new(mask_mode, im.size, 0xFFFFFFFF) if mask_mode else None + + im_self = im.copy() + im_self.paste(im_self, (0, y), mask) + + im_copy = im.copy() + im_copy.paste(im_copy.copy(), (0, y), mask) + + assert_image_equal(im_self, im_copy) + @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) def test_image_mask_1(self, mode: str) -> None: im = Image.new(mode, (200, 200), "white") diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index f2c447f71..661764b60 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -62,6 +62,7 @@ def test_putpalette_with_alpha_values() -> None: expected = im.convert("RGBA") palette = im.getpalette() + assert palette is not None transparency = im.info.pop("transparency") palette_with_alpha_values = [] diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index d847c7440..e8b783ff3 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -118,6 +118,15 @@ def test_quantize_kmeans(method: Image.Quantize) -> None: im.quantize(kmeans=-1, method=method) +@skip_unless_feature("libimagequant") +def test_resize() -> None: + im = hopper().resize((100, 100)) + converted = im.quantize(100, Image.Quantize.LIBIMAGEQUANT) + colors = converted.getcolors() + assert colors is not None + assert len(colors) == 100 + + def test_colors() -> None: im = hopper() colors = 2 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 406d965b4..790acee2a 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1494,7 +1494,9 @@ def test_default_font_size() -> None: def draw_text() -> None: draw.text((0, 0), text, font_size=16) - assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png") + assert_image_similar_tofile( + im, "Tests/images/imagedraw_default_font_size.png", 1 + ) check(draw_text) @@ -1513,7 +1515,9 @@ def test_default_font_size() -> None: def draw_multiline_text() -> None: draw.multiline_text((0, 0), text, font_size=16) - assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png") + assert_image_similar_tofile( + im, "Tests/images/imagedraw_default_font_size.png", 1 + ) check(draw_multiline_text) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index d4dfb1b6d..7dfb3abf9 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -164,6 +164,11 @@ class TestImageFile: with pytest.raises(OSError): p.close() + def test_negative_offset(self) -> None: + with Image.open("Tests/images/raw_negative_stride.bin") as im: + with pytest.raises(ValueError, match="Tile offset cannot be negative"): + im.load() + def test_no_format(self) -> None: buf = BytesIO(b"\x00" * 255) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 4565d35ba..39ee9b9c9 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -19,6 +19,7 @@ from .helper import ( assert_image_equal, assert_image_equal_tofile, assert_image_similar_tofile, + has_feature_version, is_win32, skip_unless_feature, skip_unless_feature_version, @@ -492,6 +493,11 @@ def test_stroke_mask() -> None: assert mask.getpixel((42, 5)) == 255 +def test_load_invalid_file() -> None: + with pytest.raises(SyntaxError, match="Not a PILfont file"): + ImageFont.load("Tests/images/1_trns.png") + + def test_load_when_image_not_found() -> None: with tempfile.NamedTemporaryFile(delete=False) as tmp: pass @@ -549,7 +555,7 @@ def test_default_font() -> None: draw.text((10, 60), txt, font=larger_default_font) # Assert - assert_image_equal_tofile(im, "Tests/images/default_font_freetype.png") + assert_image_similar_tofile(im, "Tests/images/default_font_freetype.png", 0.13) @pytest.mark.parametrize("mode", ("", "1", "RGBA")) @@ -1055,7 +1061,10 @@ def test_colr(layout_engine: ImageFont.Layout) -> None: d.text((15, 5), "Bungee", font=font, embedded_color=True) - assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21) + if has_feature_version("freetype2", "2.14.0"): + assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 6.1) + else: + assert_image_similar_tofile(im, "Tests/images/colr_bungee_older.png", 21) @skip_unless_feature_version("freetype2", "2.10.0") @@ -1071,7 +1080,7 @@ def test_colr_mask(layout_engine: ImageFont.Layout) -> None: d.text((15, 5), "Bungee", "black", font=font) - assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) + assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 14.1) def test_woff2(layout_engine: ImageFont.Layout) -> None: diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 95af3fda8..633f6756b 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -183,7 +183,7 @@ def test_x_max_and_y_offset() -> None: draw.text((0, 0), "لح", font=ttf, fill=500) target = "Tests/images/test_x_max_and_y_offset.png" - assert_image_similar_tofile(im, target, 0.5) + assert_image_similar_tofile(im, target, 3.8) def test_language() -> None: diff --git a/Tests/test_imagefontpil.py b/Tests/test_imagefontpil.py index 3eb98d379..8c1cb3f58 100644 --- a/Tests/test_imagefontpil.py +++ b/Tests/test_imagefontpil.py @@ -30,6 +30,14 @@ def test_default_font(font: ImageFont.ImageFont) -> None: assert_image_equal_tofile(im, "Tests/images/default_font.png") +def test_invalid_mode() -> None: + font = ImageFont.ImageFont() + fp = BytesIO() + with Image.open("Tests/images/hopper.png") as im: + with pytest.raises(TypeError, match="invalid font image mode"): + font._load_pilfont_data(fp, im) + + def test_without_freetype() -> None: original_core = ImageFont.core if features.check_module("freetype2"): diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 9f2fd5ba2..27ac6f308 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -186,6 +186,21 @@ def test_palette(mode: str) -> None: ) +def test_rgba_palette() -> None: + im = Image.new("P", (1, 1)) + + red = (255, 0, 0, 255) + translucent_black = (0, 0, 0, 127) + im.putpalette(red + translucent_black, "RGBA") + + expanded_im = ImageOps.expand(im, 1, 1) + + palette = expanded_im.palette + assert palette is not None + assert palette.mode == "RGBA" + assert expanded_im.convert("RGBA").getpixel((0, 0)) == translucent_black + + def test_pil163() -> None: # Division by zero in equalize if < 255 pixels in image (@PIL163) diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 7b9ac80bc..32da22e04 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -76,9 +76,14 @@ def test_consecutive() -> None: def test_palette_mmap() -> None: # Using mmap in ImageFile can require to reload the palette. with Image.open("Tests/images/multipage-mmap.tiff") as im: - color1 = im.getpalette()[:3] + palette = im.getpalette() + assert palette is not None + color1 = palette[:3] im.seek(0) - color2 = im.getpalette()[:3] + + palette = im.getpalette() + assert palette is not None + color2 = palette[:3] assert color1 == color2 diff --git a/Tests/test_imagetext.py b/Tests/test_imagetext.py new file mode 100644 index 000000000..7db229897 --- /dev/null +++ b/Tests/test_imagetext.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import pytest + +from PIL import Image, ImageDraw, ImageFont, ImageText + +from .helper import assert_image_similar_tofile, skip_unless_feature + +FONT_PATH = "Tests/fonts/FreeMono.ttf" + + +@pytest.fixture( + scope="module", + params=[ + pytest.param(ImageFont.Layout.BASIC), + pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")), + ], +) +def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout: + return request.param + + +@pytest.fixture(scope="module") +def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont: + return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine) + + +def test_get_length(font: ImageFont.FreeTypeFont) -> None: + assert ImageText.Text("A", font).get_length() == 12 + assert ImageText.Text("AB", font).get_length() == 24 + assert ImageText.Text("M", font).get_length() == 12 + assert ImageText.Text("y", font).get_length() == 12 + assert ImageText.Text("a", font).get_length() == 12 + + +def test_get_bbox(font: ImageFont.FreeTypeFont) -> None: + assert ImageText.Text("A", font).get_bbox() == (0, 4, 12, 16) + assert ImageText.Text("AB", font).get_bbox() == (0, 4, 24, 16) + assert ImageText.Text("M", font).get_bbox() == (0, 4, 12, 16) + assert ImageText.Text("y", font).get_bbox() == (0, 7, 12, 20) + assert ImageText.Text("a", font).get_bbox() == (0, 7, 12, 16) + + +def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None: + font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) + text = ImageText.Text("Hello World!", font) + text.embed_color() + + im = Image.new("RGB", (300, 64), "white") + draw = ImageDraw.Draw(im) + draw.text((10, 10), text, "#fa6") + + assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1) + + +@skip_unless_feature("freetype2") +def test_stroke() -> None: + for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items(): + # Arrange + im = Image.new("RGB", (120, 130)) + draw = ImageDraw.Draw(im) + font = ImageFont.truetype(FONT_PATH, 120) + text = ImageText.Text("A", font) + text.stroke(2, stroke_fill) + + # Act + draw.text((12, 12), text, "#f00") + + # Assert + assert_image_similar_tofile( + im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1 + ) diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py new file mode 100644 index 000000000..69980e719 --- /dev/null +++ b/Tests/test_nanoarrow.py @@ -0,0 +1,302 @@ +from __future__ import annotations + +import json +from typing import Any, NamedTuple + +import pytest + +from PIL import Image + +from .helper import ( + assert_deep_equal, + assert_image_equal, + hopper, + is_big_endian, +) + +TYPE_CHECKING = False +if TYPE_CHECKING: + import nanoarrow # type: ignore [import-not-found] +else: + nanoarrow = pytest.importorskip("nanoarrow", reason="Nanoarrow not installed") + +TEST_IMAGE_SIZE = (10, 10) + + +def _test_img_equals_pyarray( + img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1 +) -> None: + assert img.height * img.width * elts_per_pixel == len(arr) + px = img.load() + assert px is not None + if elts_per_pixel > 1 and mask is None: + # have to do element-wise comparison when we're comparing + # flattened r,g,b,a to a pixel. + mask = list(range(elts_per_pixel)) + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): + if mask: + pixel = px[x, y] + assert isinstance(pixel, tuple) + for ix, elt in enumerate(mask): + if elts_per_pixel == 1: + assert pixel[ix] == arr[y * img.width + x].as_py()[elt] + else: + assert ( + pixel[ix] + == arr[(y * img.width + x) * elts_per_pixel + elt].as_py() + ) + else: + assert_deep_equal(px[x, y], arr[y * img.width + x].as_py()) + + +def _test_img_equals_int32_pyarray( + img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1 +) -> None: + assert img.height * img.width * elts_per_pixel == len(arr) + px = img.load() + assert px is not None + if mask is None: + # have to do element-wise comparison when we're comparing + # flattened rgba in an uint32 to a pixel. + mask = list(range(elts_per_pixel)) + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): + pixel = px[x, y] + assert isinstance(pixel, tuple) + arr_pixel_int = arr[y * img.width + x].as_py() + arr_pixel_tuple = ( + arr_pixel_int % 256, + (arr_pixel_int // 256) % 256, + (arr_pixel_int // 256**2) % 256, + (arr_pixel_int // 256**3), + ) + if is_big_endian(): + arr_pixel_tuple = arr_pixel_tuple[::-1] + + for ix, elt in enumerate(mask): + assert pixel[ix] == arr_pixel_tuple[elt] + + +fl_uint8_4_type = nanoarrow.fixed_size_list( + value_type=nanoarrow.uint8(nullable=False), list_size=4, nullable=False +) + + +@pytest.mark.parametrize( + "mode, dtype, mask", + ( + ("L", nanoarrow.uint8(nullable=False), None), + ("I", nanoarrow.int32(nullable=False), None), + ("F", nanoarrow.float32(nullable=False), None), + ("LA", fl_uint8_4_type, [0, 3]), + ("RGB", fl_uint8_4_type, [0, 1, 2]), + ("RGBA", fl_uint8_4_type, None), + ("RGBX", fl_uint8_4_type, None), + ("CMYK", fl_uint8_4_type, None), + ("YCbCr", fl_uint8_4_type, [0, 1, 2]), + ("HSV", fl_uint8_4_type, [0, 1, 2]), + ), +) +def test_to_array(mode: str, dtype: nanoarrow, mask: list[int] | None) -> None: + img = hopper(mode) + + # Resize to non-square + img = img.crop((3, 0, 124, 127)) + assert img.size == (121, 127) + + arr = nanoarrow.Array(img) + _test_img_equals_pyarray(img, arr, mask) + assert arr.schema.type == dtype.type + assert arr.schema.nullable == dtype.nullable + + reloaded = Image.fromarrow(arr, mode, img.size) + assert_image_equal(img, reloaded) + + +def test_lifetime() -> None: + # valgrind shouldn't error out here. + # arrays should be accessible after the image is deleted. + + img = hopper("L") + + arr_1 = nanoarrow.Array(img) + arr_2 = nanoarrow.Array(img) + + del img + + assert sum(arr_1.iter_py()) > 0 + del arr_1 + + assert sum(arr_2.iter_py()) > 0 + del arr_2 + + +def test_lifetime2() -> None: + # valgrind shouldn't error out here. + # img should remain after the arrays are collected. + + img = hopper("L") + + arr_1 = nanoarrow.Array(img) + arr_2 = nanoarrow.Array(img) + + assert sum(arr_1.iter_py()) > 0 + del arr_1 + + assert sum(arr_2.iter_py()) > 0 + del arr_2 + + img2 = img.copy() + px = img2.load() + assert px # make mypy happy + assert isinstance(px[0, 0], int) + + +class DataShape(NamedTuple): + dtype: nanoarrow + # Strictly speaking, elt should be a pixel or pixel component, so + # list[uint8][4], float, int, uint32, uint8, etc. But more + # correctly, it should be exactly the dtype from the line above. + elt: Any + elts_per_pixel: int + + +UINT_ARR = DataShape( + dtype=fl_uint8_4_type, + elt=[1, 2, 3, 4], # array of 4 uint8 per pixel + elts_per_pixel=1, # only one array per pixel +) + +UINT = DataShape( + dtype=nanoarrow.uint8(), + elt=3, # one uint8, + elts_per_pixel=4, # but repeated 4x per pixel +) + +UINT32 = DataShape( + dtype=nanoarrow.uint32(), + elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000 + elts_per_pixel=1, # one per pixel +) + +INT32 = DataShape( + dtype=nanoarrow.uint32(), + elt=0x12CDEF45, # one packed int + elts_per_pixel=1, # one per pixel +) + + +@pytest.mark.parametrize( + "mode, data_tp, mask", + ( + ("L", DataShape(nanoarrow.uint8(), 3, 1), None), + ("I", DataShape(nanoarrow.int32(), 1 << 24, 1), None), + ("F", DataShape(nanoarrow.float32(), 3.14159, 1), None), + ("LA", UINT_ARR, [0, 3]), + ("LA", UINT, [0, 3]), + ("RGB", UINT_ARR, [0, 1, 2]), + ("RGBA", UINT_ARR, None), + ("CMYK", UINT_ARR, None), + ("YCbCr", UINT_ARR, [0, 1, 2]), + ("HSV", UINT_ARR, [0, 1, 2]), + ("RGB", UINT, [0, 1, 2]), + ("RGBA", UINT, None), + ("CMYK", UINT, None), + ("YCbCr", UINT, [0, 1, 2]), + ("HSV", UINT, [0, 1, 2]), + ), +) +def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None: + (dtype, elt, elts_per_pixel) = data_tp + + ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] + if dtype == fl_uint8_4_type: + tmp_arr = nanoarrow.Array( + elt * (ct_pixels * elts_per_pixel), schema=nanoarrow.uint8() + ) + c_array = nanoarrow.c_array_from_buffers( + dtype, ct_pixels, buffers=[], children=[tmp_arr] + ) + arr = nanoarrow.Array(c_array) + else: + arr = nanoarrow.Array( + nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype) + ) + img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) + + _test_img_equals_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, mask", + ( + ("LA", [0, 3]), + ("RGB", [0, 1, 2]), + ("RGBA", None), + ("CMYK", None), + ("YCbCr", [0, 1, 2]), + ("HSV", [0, 1, 2]), + ), +) +@pytest.mark.parametrize("data_tp", (UINT32, INT32)) +def test_from_int32array(mode: str, mask: list[int] | None, data_tp: DataShape) -> None: + (dtype, elt, elts_per_pixel) = data_tp + + ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1] + arr = nanoarrow.Array( + nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype) + ) + img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) + + _test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, metadata", + ( + ("LA", ["L", "X", "X", "A"]), + ("RGB", ["R", "G", "B", "X"]), + ("RGBX", ["R", "G", "B", "X"]), + ("RGBA", ["R", "G", "B", "A"]), + ("CMYK", ["C", "M", "Y", "K"]), + ("YCbCr", ["Y", "Cb", "Cr", "X"]), + ("HSV", ["H", "S", "V", "X"]), + ), +) +def test_image_nested_metadata(mode: str, metadata: list[str]) -> None: + img = hopper(mode) + + arr = nanoarrow.Array(img) + + assert arr.schema.value_type.metadata + assert arr.schema.value_type.metadata[b"image"] + + parsed_metadata = json.loads( + arr.schema.value_type.metadata[b"image"].decode("utf8") + ) + + assert "bands" in parsed_metadata + assert parsed_metadata["bands"] == metadata + + +@pytest.mark.parametrize( + "mode, metadata", + ( + ("L", ["L"]), + ("I", ["I"]), + ("F", ["F"]), + ), +) +def test_image_flat_metadata(mode: str, metadata: list[str]) -> None: + img = hopper(mode) + + arr = nanoarrow.Array(img) + + assert arr.schema.metadata + assert arr.schema.metadata[b"image"] + + parsed_metadata = json.loads(arr.schema.metadata[b"image"].decode("utf8")) + + assert "bands" in parsed_metadata + assert parsed_metadata["bands"] == metadata diff --git a/Tests/test_pyarrow.py b/Tests/test_pyarrow.py index 8dad94fe0..a69504e78 100644 --- a/Tests/test_pyarrow.py +++ b/Tests/test_pyarrow.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from typing import Any, NamedTuple import pytest @@ -244,3 +245,29 @@ def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE) _test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel) + + +@pytest.mark.parametrize( + "mode, metadata", + ( + ("LA", ["L", "X", "X", "A"]), + ("RGB", ["R", "G", "B", "X"]), + ("RGBX", ["R", "G", "B", "X"]), + ("RGBA", ["R", "G", "B", "A"]), + ("CMYK", ["C", "M", "Y", "K"]), + ("YCbCr", ["Y", "Cb", "Cr", "X"]), + ("HSV", ["H", "S", "V", "X"]), + ), +) +def test_image_metadata(mode: str, metadata: list[str]) -> None: + img = hopper(mode) + + arr = pyarrow.array(img) # type: ignore[call-overload] + + assert arr.type.field(0).metadata + assert arr.type.field(0).metadata[b"image"] + + parsed_metadata = json.loads(arr.type.field(0).metadata[b"image"].decode("utf8")) + + assert "bands" in parsed_metadata + assert parsed_metadata["bands"] == metadata diff --git a/checks/32bit_segfault_check.py b/checks/32bit_segfault_check.py old mode 100755 new mode 100644 index 06ed2ed2f..e277bc10a --- a/checks/32bit_segfault_check.py +++ b/checks/32bit_segfault_check.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import annotations import sys diff --git a/checks/check_imaging_leaks.py b/checks/check_imaging_leaks.py old mode 100755 new mode 100644 index e9f202f3d..65090b6b6 --- a/checks/check_imaging_leaks.py +++ b/checks/check_imaging_leaks.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import annotations import sys diff --git a/depends/install_libavif.sh b/depends/install_libavif.sh index 26af8a36c..50ba01755 100755 --- a/depends/install_libavif.sh +++ b/depends/install_libavif.sh @@ -59,6 +59,6 @@ cmake \ "${LIBAVIF_CMAKE_FLAGS[@]}" \ . -sudo make install +make install popd diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index 1f8d78193..bc7c7c634 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -1,7 +1,7 @@ #!/bin/bash # install openjpeg -archive=openjpeg-2.5.3 +archive=openjpeg-2.5.4 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index b5a05100b..33bb2d0a7 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -8,6 +8,6 @@ archive=libraqm-0.10.3 pushd $archive -meson build --prefix=/usr && sudo ninja -C build install +meson build --prefix=/usr && ninja -C build install popd diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index fc7ef7646..6080d29af 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -44,7 +44,7 @@ Many of Pillow's features require external libraries: * **libtiff** provides compressed TIFF functionality - * Pillow has been tested with libtiff versions **4.0-4.7.0** + * Pillow has been tested with libtiff versions **4.0-4.7.1** * **libfreetype** provides type related services @@ -58,7 +58,7 @@ Many of Pillow's features require external libraries: * **openjpeg** provides JPEG 2000 functionality. * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, - **2.4.0**, **2.5.0**, **2.5.2** and **2.5.3**. + **2.4.0**, **2.5.0**, **2.5.2**, **2.5.3** and **2.5.4**. * Pillow does **not** support the earlier **1.5** series which ships with Debian Jessie. diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 3c5e4cd51..7999504fb 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -39,9 +39,9 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Gentoo | 3.12 | x86-64 | +----------------------------------+----------------------------+---------------------+ -| macOS 13 Ventura | 3.10 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14 | arm64 | +| macOS 15 Sequoia | 3.10 | x86-64 | +| +----------------------------+---------------------+ +| | 3.11, 3.12, 3.13, 3.14, | arm64 | | | PyPy3 | | +----------------------------------+----------------------------+---------------------+ | Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 | @@ -75,6 +75,8 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+============================+==================+==============+ +| macOS 26 Tahoe | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm | ++----------------------------------+----------------------------+------------------+--------------+ | macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm | | +----------------------------+------------------+ | | | 3.8 | 10.4.0 | | diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 4a2223a40..4c9567593 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -57,6 +57,43 @@ Color names See :ref:`color-names` for the color names supported by Pillow. +Alpha channel +^^^^^^^^^^^^^ + +By default, when drawing onto an existing image, the image's pixel values are simply +replaced by the new color:: + + im = Image.new("RGBA", (1, 1), (255, 0, 0)) + d = ImageDraw.Draw(im) + d.rectangle((0, 0, 1, 1), (0, 255, 0, 127)) + assert im.getpixel((0, 0)) == (0, 255, 0, 127) + + # Alpha channel values have no effect when drawing with RGB mode + im = Image.new("RGB", (1, 1), (255, 0, 0)) + d = ImageDraw.Draw(im) + d.rectangle((0, 0, 1, 1), (0, 255, 0, 127)) + assert im.getpixel((0, 0)) == (0, 255, 0) + +If you would like to combine translucent color with an RGB image, then initialize the +ImageDraw instance with the RGBA mode:: + + from PIL import Image, ImageDraw + im = Image.new("RGB", (1, 1), (255, 0, 0)) + d = ImageDraw.Draw(im, "RGBA") + d.rectangle((0, 0, 1, 1), (0, 255, 0, 127)) + assert im.getpixel((0, 0)) == (128, 127, 0) + +If you would like to combine translucent color with an RGBA image underneath, you will +need to combine multiple images:: + + from PIL import Image, ImageDraw + im = Image.new("RGBA", (1, 1), (255, 0, 0, 255)) + im2 = Image.new("RGBA", (1, 1)) + d = ImageDraw.Draw(im2) + d.rectangle((0, 0, 1, 1), (0, 255, 0, 127)) + im.paste(im2.convert("RGB"), mask=im2) + assert im.getpixel((0, 0)) == (128, 127, 0, 255) + Fonts ^^^^^ @@ -545,6 +582,8 @@ Methods hello_world = hello + world # kerning is disabled, no need to adjust assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True + .. seealso:: :py:meth:`PIL.ImageText.Text.get_length` + .. versionadded:: 8.0.0 :param text: Text to be measured. May not contain any newline characters. @@ -646,6 +685,8 @@ Methods 1/64 pixel precision. The bounding box includes extra margins for some fonts, e.g. italics or accents. + .. seealso:: :py:meth:`PIL.ImageText.Text.get_bbox` + .. versionadded:: 8.0.0 :param xy: The anchor coordinates of the text. diff --git a/docs/reference/ImageText.rst b/docs/reference/ImageText.rst new file mode 100644 index 000000000..8744ad368 --- /dev/null +++ b/docs/reference/ImageText.rst @@ -0,0 +1,61 @@ +.. py:module:: PIL.ImageText +.. py:currentmodule:: PIL.ImageText + +:py:mod:`~PIL.ImageText` module +=============================== + +The :py:mod:`~PIL.ImageText` module defines a :py:class:`~PIL.ImageText.Text` class. +Instances of this class provide a way to use fonts with text strings or bytes. The +result is a simple API to apply styling to pieces of text and measure or draw them. + +Example +------- + +:: + + from PIL import Image, ImageDraw, ImageFont, ImageText + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 24) + + text = ImageText.Text("Hello world", font) + text.embed_color() + text.stroke(2, "#0f0") + + print(text.get_length()) # 154.0 + print(text.get_bbox()) # (-2, 3, 156, 22) + + im = Image.new("RGB", text.get_bbox()[2:]) + d = ImageDraw.Draw(im) + d.text((0, 0), text, "#f00") + +Comparison +---------- + +Without ``ImageText.Text``:: + + from PIL import Image, ImageDraw + im = Image.new(mode, size) + d = ImageDraw.Draw(im) + + d.textlength(text, font, direction, features, language, embedded_color) + d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color) + d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color) + +With ``ImageText.Text``:: + + from PIL import ImageText + text = ImageText.Text(text, font, mode, spacing, direction, features, language) + text.embed_color() + text.stroke(stroke_width, stroke_fill) + + text.get_length() + text.get_bbox(xy, anchor, align) + + im = Image.new(mode, size) + d = ImageDraw.Draw(im) + d.text(xy, text, fill, anchor=anchor, align=align) + +Methods +------- + +.. autoclass:: PIL.ImageText.Text + :members: diff --git a/docs/reference/index.rst b/docs/reference/index.rst index effcd3c46..1ce26c909 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -24,6 +24,7 @@ Reference ImageSequence ImageShow ImageStat + ImageText ImageTk ImageTransform ImageWin diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst index de9d6dffd..4c00d8c4c 100644 --- a/docs/releasenotes/12.0.0.rst +++ b/docs/releasenotes/12.0.0.rst @@ -1,19 +1,6 @@ 12.0.0 ------ -Security -======== - -TODO -^^^^ - -TODO - -:cve:`YYYY-XXXXX`: TODO -^^^^^^^^^^^^^^^^^^^^^^^ - -TODO - Backwards incompatible changes ============================== @@ -132,18 +119,43 @@ Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0. API changes =========== -TODO -^^^^ +Image.alpha_composite: LA images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +:py:meth:`~PIL.Image.alpha_composite` can now use LA images as well as RGBA. API additions ============= -TODO -^^^^ +Added ImageText.Text +^^^^^^^^^^^^^^^^^^^^ -TODO +:py:class:`PIL.ImageText.Text` has been added, as a simpler way to use fonts with text +strings or bytes. + +Without ``ImageText.Text``:: + + from PIL import Image, ImageDraw + im = Image.new(mode, size) + d = ImageDraw.Draw(im) + + d.textlength(text, font, direction, features, language, embedded_color) + d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color) + d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color) + +With ``ImageText.Text``:: + + from PIL import ImageText + text = ImageText.Text(text, font, mode, spacing, direction, features, language) + text.embed_color() + text.stroke(stroke_width, stroke_fill) + + text.get_length() + text.get_bbox(xy, anchor, align) + + im = Image.new(mode, size) + d = ImageDraw.Draw(im) + d.text(xy, text, fill, anchor=anchor, align=align) Other changes ============= diff --git a/pyproject.toml b/pyproject.toml index 137726a1c..0006ccd12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,9 @@ optional-dependencies.mic = [ "olefile", ] optional-dependencies.test-arrow = [ + "arro3-compute", + "arro3-core", + "nanoarrow", "pyarrow", ] diff --git a/setup.py b/setup.py index df584f8df..3a72a0742 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,10 @@ from pybind11.setup_helpers import ParallelCompile from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +TYPE_CHECKING = False +if TYPE_CHECKING: + from setuptools import _BuildInfo + configuration: dict[str, list[str]] = {} # parse configuration from _custom_build/backend.py @@ -1072,16 +1076,20 @@ def debug_build() -> bool: return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD +libraries: list[tuple[str, _BuildInfo]] = [ + ("pil_imaging_mode", {"sources": ["src/libImaging/Mode.c"]}), +] + files: list[str | os.PathLike[str]] = ["src/_imaging.c"] for src_file in _IMAGING: files.append("src/" + src_file + ".c") for src_file in _LIB_IMAGING: files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ - Extension("PIL._imaging", files), - Extension("PIL._imagingft", ["src/_imagingft.c"]), - Extension("PIL._imagingcms", ["src/_imagingcms.c"]), - Extension("PIL._webp", ["src/_webp.c"]), + Extension("PIL._imaging", files, libraries=["pil_imaging_mode"]), + Extension("PIL._imagingft", ["src/_imagingft.c"], libraries=["pil_imaging_mode"]), + Extension("PIL._imagingcms", ["src/_imagingcms.c"], libraries=["pil_imaging_mode"]), + Extension("PIL._webp", ["src/_webp.c"], libraries=["pil_imaging_mode"]), Extension("PIL._avif", ["src/_avif.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), Extension("PIL._imagingmath", ["src/_imagingmath.c"]), @@ -1093,6 +1101,7 @@ try: setup( cmdclass={"build_ext": pil_build_ext}, ext_modules=ext_modules, + libraries=libraries, zip_safe=not (debug_build() or PLATFORM_MINGW), ) except RequiredDependencyException as err: diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index b817dbc87..9c188e084 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -17,7 +17,7 @@ # from __future__ import annotations -from . import BmpImagePlugin, Image, ImageFile +from . import BmpImagePlugin, Image from ._binary import i16le as i16 from ._binary import i32le as i32 @@ -38,6 +38,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): format_description = "Windows Cursor" def _open(self) -> None: + assert self.fp is not None offset = self.fp.tell() # check magic @@ -63,8 +64,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): # patch up the bitmap height self._size = self.size[0], self.size[1] // 2 - d, e, o, a = self.tile[0] - self.tile[0] = ImageFile._Tile(d, (0, 0) + self.size, o, a) + self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)] # diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 5e2ddad99..69f3062b4 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -354,6 +354,9 @@ class EpsImageFile(ImageFile.ImageFile): read_comment(s) elif bytes_mv[:9] == b"%%Trailer": trailer_reached = True + elif bytes_mv[:14] == b"%%BeginBinary:": + bytecount = int(byte_arr[14:bytes_read]) + self.fp.seek(bytecount, os.SEEK_CUR) bytes_read = 0 # A "BoundingBox" is always required, diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index ccb8a5953..da1e8e95c 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -48,8 +48,14 @@ class FliImageFile(ImageFile.ImageFile): def _open(self) -> None: # HEAD + assert self.fp is not None s = self.fp.read(128) - if not (_accept(s) and s[20:22] == b"\x00\x00"): + if not ( + _accept(s) + and s[20:22] == b"\x00" * 2 + and s[42:80] == b"\x00" * 38 + and s[88:] == b"\x00" * 40 + ): msg = "not an FLI/FLC file" raise SyntaxError(msg) @@ -77,8 +83,7 @@ class FliImageFile(ImageFile.ImageFile): if i16(s, 4) == 0xF100: # prefix chunk; ignore it - self.__offset = self.__offset + i32(s) - self.fp.seek(self.__offset) + self.fp.seek(self.__offset + i32(s)) s = self.fp.read(16) if i16(s, 4) == 0xF1FA: @@ -111,6 +116,7 @@ class FliImageFile(ImageFile.ImageFile): # load palette i = 0 + assert self.fp is not None for e in range(i16(self.fp.read(2))): s = self.fp.read(2) i = i + s[0] diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b17fd131d..9d50812eb 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1009,8 +1009,14 @@ class Image: new_im.info["transparency"] = transparency return new_im - if mode == "P" and self.mode == "RGBA": - return self.quantize(colors) + if self.mode == "RGBA": + if mode == "P": + return self.quantize(colors) + elif mode == "PA": + r, g, b, a = self.split() + rgb = merge("RGB", (r, g, b)) + p = rgb.quantize(colors) + return merge("PA", (p, a)) trns = None delete_trns = False @@ -1142,7 +1148,7 @@ class Image: raise ValueError(msg) from e new_im = self._new(im) - if mode == "P" and palette != Palette.ADAPTIVE: + if mode in ("P", "PA") and palette != Palette.ADAPTIVE: from . import ImagePalette new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB")) @@ -1336,12 +1342,6 @@ class Image: """ pass - def _expand(self, xmargin: int, ymargin: int | None = None) -> Image: - if ymargin is None: - ymargin = xmargin - self.load() - return self._new(self.im.expand(xmargin, ymargin)) - def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image: """ Filters this image using the given filter. For a list of @@ -2070,9 +2070,7 @@ class Image: :param value: The pixel value. """ - if self.readonly: - self._copy() - self.load() + self._ensure_mutable() if ( self.mode in ("P", "PA") diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index ed46899b4..8bcf2d8ee 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -36,7 +36,7 @@ import struct from collections.abc import Sequence from typing import cast -from . import Image, ImageColor +from . import Image, ImageColor, ImageText TYPE_CHECKING = False if TYPE_CHECKING: @@ -45,13 +45,11 @@ if TYPE_CHECKING: from typing import Any, AnyStr from . import ImageDraw2, ImageFont - from ._typing import Coords + from ._typing import Coords, _Ink # experimental access to the outline API Outline: Callable[[], Image.core._Outline] = Image.core.outline -_Ink = float | tuple[int, ...] | str - """ A simple 2D drawing interface for PIL images.
@@ -76,9 +74,7 @@ class ImageDraw:
must be the same as the image mode. If omitted, the mode
defaults to the mode of the image.
"""
- im.load()
- if im.readonly:
- im._copy() # make it writeable
+ im._ensure_mutable()
blend = 0
if mode is None:
mode = im.mode
@@ -539,15 +535,10 @@ class ImageDraw:
right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 1)
- def _multiline_check(self, text: AnyStr) -> bool:
- split_character = "\n" if isinstance(text, str) else b"\n"
-
- return split_character in text
-
def text(
self,
xy: tuple[float, float],
- text: AnyStr,
+ text: AnyStr | ImageText.Text,
fill: _Ink | None = None,
font: (
ImageFont.ImageFont
@@ -568,29 +559,18 @@ class ImageDraw:
**kwargs: Any,
) -> None:
"""Draw text."""
- if embedded_color and self.mode not in ("RGB", "RGBA"):
- msg = "Embedded color supported only in RGB and RGBA modes"
- raise ValueError(msg)
-
- if font is None:
- font = self._getfont(kwargs.get("font_size"))
-
- if self._multiline_check(text):
- return self.multiline_text(
- xy,
- text,
- fill,
- font,
- anchor,
- spacing,
- align,
- direction,
- features,
- language,
- stroke_width,
- stroke_fill,
- embedded_color,
+ if isinstance(text, ImageText.Text):
+ image_text = text
+ else:
+ if font is None:
+ font = self._getfont(kwargs.get("font_size"))
+ image_text = ImageText.Text(
+ text, font, self.mode, spacing, direction, features, language
)
+ if embedded_color:
+ image_text.embed_color()
+ if stroke_width:
+ image_text.stroke(stroke_width, stroke_fill)
def getink(fill: _Ink | None) -> int:
ink, fill_ink = self._getink(fill)
@@ -599,70 +579,79 @@ class ImageDraw:
return fill_ink
return ink
- def draw_text(ink: int, stroke_width: float = 0) -> None:
- mode = self.fontmode
- if stroke_width == 0 and embedded_color:
- mode = "RGBA"
- coord = []
- for i in range(2):
- coord.append(int(xy[i]))
- start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
- try:
- mask, offset = font.getmask2( # type: ignore[union-attr,misc]
- text,
- mode,
- direction=direction,
- features=features,
- language=language,
- stroke_width=stroke_width,
- stroke_filled=True,
- anchor=anchor,
- ink=ink,
- start=start,
- *args,
- **kwargs,
- )
- coord = [coord[0] + offset[0], coord[1] + offset[1]]
- except AttributeError:
+ ink = getink(fill)
+ if ink is None:
+ return
+
+ stroke_ink = None
+ if image_text.stroke_width:
+ stroke_ink = (
+ getink(image_text.stroke_fill)
+ if image_text.stroke_fill is not None
+ else ink
+ )
+
+ for xy, anchor, line in image_text._split(xy, anchor, align):
+
+ def draw_text(ink: int, stroke_width: float = 0) -> None:
+ mode = self.fontmode
+ if stroke_width == 0 and embedded_color:
+ mode = "RGBA"
+ coord = []
+ for i in range(2):
+ coord.append(int(xy[i]))
+ start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
try:
- mask = font.getmask( # type: ignore[misc]
- text,
+ mask, offset = image_text.font.getmask2( # type: ignore[union-attr,misc]
+ line,
mode,
- direction,
- features,
- language,
- stroke_width,
- anchor,
- ink,
+ direction=direction,
+ features=features,
+ language=language,
+ stroke_width=stroke_width,
+ stroke_filled=True,
+ anchor=anchor,
+ ink=ink,
start=start,
*args,
**kwargs,
)
- except TypeError:
- mask = font.getmask(text)
- if mode == "RGBA":
- # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
- # extract mask and set text alpha
- color, mask = mask, mask.getband(3)
- ink_alpha = struct.pack("i", ink)[3]
- color.fillband(3, ink_alpha)
- x, y = coord
- if self.im is not None:
- self.im.paste(
- color, (x, y, x + mask.size[0], y + mask.size[1]), mask
- )
- else:
- self.draw.draw_bitmap(coord, mask, ink)
-
- ink = getink(fill)
- if ink is not None:
- stroke_ink = None
- if stroke_width:
- stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink
+ coord = [coord[0] + offset[0], coord[1] + offset[1]]
+ except AttributeError:
+ try:
+ mask = image_text.font.getmask( # type: ignore[misc]
+ line,
+ mode,
+ direction,
+ features,
+ language,
+ stroke_width,
+ anchor,
+ ink,
+ start=start,
+ *args,
+ **kwargs,
+ )
+ except TypeError:
+ mask = image_text.font.getmask(line)
+ if mode == "RGBA":
+ # image_text.font.getmask2(mode="RGBA")
+ # returns color in RGB bands and mask in A
+ # extract mask and set text alpha
+ color, mask = mask, mask.getband(3)
+ ink_alpha = struct.pack("i", ink)[3]
+ color.fillband(3, ink_alpha)
+ x, y = coord
+ if self.im is not None:
+ self.im.paste(
+ color, (x, y, x + mask.size[0], y + mask.size[1]), mask
+ )
+ else:
+ self.draw.draw_bitmap(coord, mask, ink)
if stroke_ink is not None:
# Draw stroked text
- draw_text(stroke_ink, stroke_width)
+ draw_text(stroke_ink, image_text.stroke_width)
# Draw normal text
if ink != stroke_ink:
@@ -671,132 +660,6 @@ 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,
- list[tuple[tuple[float, float], str, AnyStr]],
- ]:
- if anchor is None:
- anchor = "lt" if direction == "ttb" else "la"
- elif len(anchor) != 2:
- msg = "anchor must be a 2 character string"
- raise ValueError(msg)
- elif anchor[1] in "tb" and direction != "ttb":
- msg = "anchor not supported for multiline text"
- raise ValueError(msg)
-
- if font is None:
- font = self._getfont(font_size)
-
- 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
- )
-
- top = xy[1]
- parts = []
- if direction == "ttb":
- left = xy[0]
- for line in lines:
- parts.append(((left, top), anchor, line))
- left += line_spacing
- else:
- widths = []
- max_width: float = 0
- 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)
-
- 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]
-
- # 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
- and idx != len(lines) - 1
- ):
- words = line.split(" " if isinstance(text, str) else b" ")
- if len(words) > 1:
- # align left by anchor
- if anchor[0] == "m":
- left -= max_width / 2.0
- elif anchor[0] == "r":
- left -= max_width
-
- word_widths = [
- self.textlength(
- word,
- font,
- direction=direction,
- features=features,
- language=language,
- embedded_color=embedded_color,
- )
- for word in words
- ]
- word_anchor = "l" + anchor[1]
- width_difference = max_width - sum(word_widths)
- for i, word in enumerate(words):
- parts.append(((left, top), word_anchor, word))
- left += word_widths[i] + width_difference / (len(words) - 1)
- top += line_spacing
- continue
-
- # align left by anchor
- if anchor[0] == "m":
- left -= width_difference / 2.0
- elif anchor[0] == "r":
- left -= width_difference
- parts.append(((left, top), anchor, line))
- top += line_spacing
-
- return font, parts
-
def multiline_text(
self,
xy: tuple[float, float],
@@ -820,9 +683,10 @@ class ImageDraw:
*,
font_size: float | None = None,
) -> None:
- font, lines = self._prepare_multiline_text(
+ return self.text(
xy,
text,
+ fill,
font,
anchor,
spacing,
@@ -831,25 +695,11 @@ class ImageDraw:
features,
language,
stroke_width,
+ stroke_fill,
embedded_color,
- font_size,
+ font_size=font_size,
)
- for xy, anchor, line in lines:
- self.text(
- xy,
- line,
- fill,
- font,
- anchor,
- direction=direction,
- features=features,
- language=language,
- stroke_width=stroke_width,
- stroke_fill=stroke_fill,
- embedded_color=embedded_color,
- )
-
def textlength(
self,
text: AnyStr,
@@ -867,17 +717,19 @@ class ImageDraw:
font_size: float | None = None,
) -> float:
"""Get the length of a given string, in pixels with 1/64 precision."""
- if self._multiline_check(text):
- msg = "can't measure length of multiline text"
- raise ValueError(msg)
- if embedded_color and self.mode not in ("RGB", "RGBA"):
- msg = "Embedded color supported only in RGB and RGBA modes"
- raise ValueError(msg)
-
if font is None:
font = self._getfont(font_size)
- mode = "RGBA" if embedded_color else self.fontmode
- return font.getlength(text, mode, direction, features, language)
+ image_text = ImageText.Text(
+ text,
+ font,
+ self.mode,
+ direction=direction,
+ features=features,
+ language=language,
+ )
+ if embedded_color:
+ image_text.embed_color()
+ return image_text.get_length()
def textbbox(
self,
@@ -901,33 +753,16 @@ class ImageDraw:
font_size: float | None = None,
) -> tuple[float, float, float, float]:
"""Get the bounding box of a given string, in pixels."""
- if embedded_color and self.mode not in ("RGB", "RGBA"):
- msg = "Embedded color supported only in RGB and RGBA modes"
- raise ValueError(msg)
-
if font is None:
font = self._getfont(font_size)
-
- if self._multiline_check(text):
- return self.multiline_textbbox(
- xy,
- text,
- font,
- anchor,
- spacing,
- align,
- direction,
- features,
- language,
- stroke_width,
- embedded_color,
- )
-
- mode = "RGBA" if embedded_color else self.fontmode
- bbox = font.getbbox(
- text, mode, direction, features, language, stroke_width, anchor
+ image_text = ImageText.Text(
+ text, font, self.mode, spacing, direction, features, language
)
- return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]
+ if embedded_color:
+ image_text.embed_color()
+ if stroke_width:
+ image_text.stroke(stroke_width)
+ return image_text.get_bbox(xy, anchor, align)
def multiline_textbbox(
self,
@@ -950,7 +785,7 @@ class ImageDraw:
*,
font_size: float | None = None,
) -> tuple[float, float, float, float]:
- font, lines = self._prepare_multiline_text(
+ return self.textbbox(
xy,
text,
font,
@@ -962,37 +797,9 @@ class ImageDraw:
language,
stroke_width,
embedded_color,
- font_size,
+ font_size=font_size,
)
- bbox: tuple[float, float, float, float] | None = None
-
- for xy, anchor, line in lines:
- bbox_line = self.textbbox(
- xy,
- line,
- font,
- anchor,
- direction=direction,
- features=features,
- language=language,
- stroke_width=stroke_width,
- embedded_color=embedded_color,
- )
- if bbox is None:
- bbox = bbox_line
- else:
- bbox = (
- min(bbox[0], bbox_line[0]),
- min(bbox[1], bbox_line[1]),
- max(bbox[2], bbox_line[2]),
- max(bbox[3], bbox_line[3]),
- )
-
- if bbox is None:
- return xy[0], xy[1], xy[0], xy[1]
- return bbox
-
def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
"""
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index e33b846d4..a1d98bd51 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -313,6 +313,9 @@ class ImageFile(Image.Image):
and args[0] == self.mode
and args[0] in Image._MAPMODES
):
+ if offset < 0:
+ msg = "Tile offset cannot be negative"
+ raise ValueError(msg)
try:
# use mmap, if possible
import mmap
diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py
index a2bf9ccf9..92eb763a5 100644
--- a/src/PIL/ImageFont.py
+++ b/src/PIL/ImageFont.py
@@ -125,11 +125,16 @@ class ImageFont:
image.close()
def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
+ # check image
+ if image.mode not in ("1", "L"):
+ msg = "invalid font image mode"
+ raise TypeError(msg)
+
# read PILfont header
- if file.readline() != b"PILfont\n":
+ if file.read(8) != b"PILfont\n":
msg = "Not a PILfont file"
raise SyntaxError(msg)
- file.readline().split(b";")
+ file.readline()
self.info = [] # FIXME: should be a dictionary
while True:
s = file.readline()
@@ -140,11 +145,6 @@ class ImageFont:
# read PILfont metrics
data = file.read(256 * 20)
- # check image
- if image.mode not in ("1", "L"):
- msg = "invalid font image mode"
- raise TypeError(msg)
-
image.load()
self.font = Image.core.font(image.im, data)
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index da28854b5..42b10bd7b 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -499,14 +499,15 @@ def expand(
height = top + image.size[1] + bottom
color = _color(fill, image.mode)
if image.palette:
- palette = ImagePalette.ImagePalette(palette=image.getpalette())
+ mode = image.palette.mode
+ palette = ImagePalette.ImagePalette(mode, image.getpalette(mode))
if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4):
color = palette.getcolor(color)
else:
palette = None
out = Image.new(image.mode, (width, height), color)
if palette:
- out.putpalette(palette.palette)
+ out.putpalette(palette.palette, mode)
out.paste(image, (left, top))
return out
diff --git a/src/PIL/ImageText.py b/src/PIL/ImageText.py
new file mode 100644
index 000000000..c74570e69
--- /dev/null
+++ b/src/PIL/ImageText.py
@@ -0,0 +1,318 @@
+from __future__ import annotations
+
+from . import ImageFont
+from ._typing import _Ink
+
+
+class Text:
+ def __init__(
+ self,
+ text: str | bytes,
+ font: (
+ ImageFont.ImageFont
+ | ImageFont.FreeTypeFont
+ | ImageFont.TransposedFont
+ | None
+ ) = None,
+ mode: str = "RGB",
+ spacing: float = 4,
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
+ ) -> None:
+ """
+ :param text: String to be drawn.
+ :param font: Either an :py:class:`~PIL.ImageFont.ImageFont` instance,
+ :py:class:`~PIL.ImageFont.FreeTypeFont` instance,
+ :py:class:`~PIL.ImageFont.TransposedFont` instance or ``None``. If
+ ``None``, the default font from :py:meth:`.ImageFont.load_default`
+ will be used.
+ :param mode: The image mode this will be used with.
+ :param spacing: The number of pixels between lines.
+ :param direction: Direction of the text. It can be ``"rtl"`` (right to left),
+ ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
+ Requires libraqm.
+ :param features: A list of OpenType font features to be used during text
+ layout. This is usually used to turn on optional font features
+ that are not enabled by default, for example ``"dlig"`` or
+ ``"ss01"``, but can be also used to turn off default font
+ features, for example ``"-liga"`` to disable ligatures or
+ ``"-kern"`` to disable kerning. To get all supported
+ features, see `OpenType docs`_.
+ Requires libraqm.
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP 47 language code`_.
+ Requires libraqm.
+ """
+ self.text = text
+ self.font = font or ImageFont.load_default()
+
+ self.mode = mode
+ self.spacing = spacing
+ self.direction = direction
+ self.features = features
+ self.language = language
+
+ self.embedded_color = False
+
+ self.stroke_width: float = 0
+ self.stroke_fill: _Ink | None = None
+
+ def embed_color(self) -> None:
+ """
+ Use embedded color glyphs (COLR, CBDT, SBIX).
+ """
+ if self.mode not in ("RGB", "RGBA"):
+ msg = "Embedded color supported only in RGB and RGBA modes"
+ raise ValueError(msg)
+ self.embedded_color = True
+
+ def stroke(self, width: float = 0, fill: _Ink | None = None) -> None:
+ """
+ :param width: The width of the text stroke.
+ :param fill: Color to use for the text stroke when drawing. If not given, will
+ default to the ``fill`` parameter from
+ :py:meth:`.ImageDraw.ImageDraw.text`.
+ """
+ self.stroke_width = width
+ self.stroke_fill = fill
+
+ def _get_fontmode(self) -> str:
+ if self.mode in ("1", "P", "I", "F"):
+ return "1"
+ elif self.embedded_color:
+ return "RGBA"
+ else:
+ return "L"
+
+ def get_length(self):
+ """
+ Returns length (in pixels with 1/64 precision) of text.
+
+ This is the amount by which following text should be offset.
+ Text bounding box may extend past the length in some fonts,
+ e.g. when using italics or accents.
+
+ The result is returned as a float; it is a whole number if using basic layout.
+
+ Note that the sum of two lengths may not equal the length of a concatenated
+ string due to kerning. If you need to adjust for kerning, include the following
+ character and subtract its length.
+
+ For example, instead of::
+
+ hello = ImageText.Text("Hello", font).get_length()
+ world = ImageText.Text("World", font).get_length()
+ helloworld = ImageText.Text("HelloWorld", font).get_length()
+ assert hello + world == helloworld
+
+ use::
+
+ hello = (
+ ImageText.Text("HelloW", font).get_length() -
+ ImageText.Text("W", font).get_length()
+ ) # adjusted for kerning
+ world = ImageText.Text("World", font).get_length()
+ helloworld = ImageText.Text("HelloWorld", font).get_length()
+ assert hello + world == helloworld
+
+ or disable kerning with (requires libraqm)::
+
+ hello = ImageText.Text("Hello", font, features=["-kern"]).get_length()
+ world = ImageText.Text("World", font, features=["-kern"]).get_length()
+ helloworld = ImageText.Text(
+ "HelloWorld", font, features=["-kern"]
+ ).get_length()
+ assert hello + world == helloworld
+
+ :return: Either width for horizontal text, or height for vertical text.
+ """
+ split_character = "\n" if isinstance(self.text, str) else b"\n"
+ if split_character in self.text:
+ msg = "can't measure length of multiline text"
+ raise ValueError(msg)
+ return self.font.getlength(
+ self.text,
+ self._get_fontmode(),
+ self.direction,
+ self.features,
+ self.language,
+ )
+
+ def _split(
+ self, xy: tuple[float, float], anchor: str | None, align: str
+ ) -> list[tuple[tuple[float, float], str, str | bytes]]:
+ if anchor is None:
+ anchor = "lt" if self.direction == "ttb" else "la"
+ elif len(anchor) != 2:
+ msg = "anchor must be a 2 character string"
+ raise ValueError(msg)
+
+ lines = (
+ self.text.split("\n")
+ if isinstance(self.text, str)
+ else self.text.split(b"\n")
+ )
+ if len(lines) == 1:
+ return [(xy, anchor, self.text)]
+
+ if anchor[1] in "tb" and self.direction != "ttb":
+ msg = "anchor not supported for multiline text"
+ raise ValueError(msg)
+
+ fontmode = self._get_fontmode()
+ line_spacing = (
+ self.font.getbbox(
+ "A",
+ fontmode,
+ None,
+ self.features,
+ self.language,
+ self.stroke_width,
+ )[3]
+ + self.stroke_width
+ + self.spacing
+ )
+
+ top = xy[1]
+ parts = []
+ if self.direction == "ttb":
+ left = xy[0]
+ for line in lines:
+ parts.append(((left, top), anchor, line))
+ left += line_spacing
+ else:
+ widths = []
+ max_width: float = 0
+ for line in lines:
+ line_width = self.font.getlength(
+ line, fontmode, self.direction, self.features, self.language
+ )
+ widths.append(line_width)
+ max_width = max(max_width, line_width)
+
+ if anchor[1] == "m":
+ top -= (len(lines) - 1) * line_spacing / 2.0
+ elif anchor[1] == "d":
+ top -= (len(lines) - 1) * line_spacing
+
+ idx = -1
+ for line in lines:
+ left = xy[0]
+ idx += 1
+ width_difference = max_width - widths[idx]
+
+ # 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
+ and idx != len(lines) - 1
+ ):
+ words = (
+ line.split(" ") if isinstance(line, str) else line.split(b" ")
+ )
+ if len(words) > 1:
+ # align left by anchor
+ if anchor[0] == "m":
+ left -= max_width / 2.0
+ elif anchor[0] == "r":
+ left -= max_width
+
+ word_widths = [
+ self.font.getlength(
+ word,
+ fontmode,
+ self.direction,
+ self.features,
+ self.language,
+ )
+ for word in words
+ ]
+ word_anchor = "l" + anchor[1]
+ width_difference = max_width - sum(word_widths)
+ i = 0
+ for word in words:
+ parts.append(((left, top), word_anchor, word))
+ left += word_widths[i] + width_difference / (len(words) - 1)
+ i += 1
+ top += line_spacing
+ continue
+
+ # align left by anchor
+ if anchor[0] == "m":
+ left -= width_difference / 2.0
+ elif anchor[0] == "r":
+ left -= width_difference
+ parts.append(((left, top), anchor, line))
+ top += line_spacing
+
+ return parts
+
+ def get_bbox(
+ self,
+ xy: tuple[float, float] = (0, 0),
+ anchor: str | None = None,
+ align: str = "left",
+ ) -> tuple[float, float, float, float]:
+ """
+ Returns bounding box (in pixels) of text.
+
+ Use :py:meth:`get_length` to get the offset of following text with 1/64 pixel
+ precision. The bounding box includes extra margins for some fonts, e.g. italics
+ or accents.
+
+ :param xy: The anchor coordinates of the text.
+ :param anchor: The text anchor alignment. Determines the relative location of
+ the anchor to the text. The default alignment is top left,
+ specifically ``la`` for horizontal text and ``lt`` for
+ vertical text. See :ref:`text-anchors` for details.
+ :param align: For multiline text, ``"left"``, ``"center"``, ``"right"`` or
+ ``"justify"`` determines the relative alignment of lines. Use the
+ ``anchor`` parameter to specify the alignment to ``xy``.
+
+ :return: ``(left, top, right, bottom)`` bounding box
+ """
+ bbox: tuple[float, float, float, float] | None = None
+ fontmode = self._get_fontmode()
+ for xy, anchor, line in self._split(xy, anchor, align):
+ bbox_line = self.font.getbbox(
+ line,
+ fontmode,
+ self.direction,
+ self.features,
+ self.language,
+ self.stroke_width,
+ anchor,
+ )
+ bbox_line = (
+ bbox_line[0] + xy[0],
+ bbox_line[1] + xy[1],
+ bbox_line[2] + xy[0],
+ bbox_line[3] + xy[1],
+ )
+ if bbox is None:
+ bbox = bbox_line
+ else:
+ bbox = (
+ min(bbox[0], bbox_line[0]),
+ min(bbox[1], bbox_line[1]),
+ max(bbox[2], bbox_line[2]),
+ max(bbox[3], bbox_line[3]),
+ )
+
+ if bbox is None:
+ return xy[0], xy[1], xy[0], xy[1]
+ return bbox
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index 0d110035e..755ca648e 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -193,6 +193,8 @@ def SOF(self: JpegImageFile, marker: int) -> None:
n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n)
self._size = i16(s, 3), i16(s, 1)
+ if self._im is not None and self.size != self.im.size:
+ self._im = None
self.bits = s[0]
if self.bits != 8:
diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py
index 7f9ab525c..296f3775b 100644
--- a/src/PIL/PcdImagePlugin.py
+++ b/src/PIL/PcdImagePlugin.py
@@ -43,16 +43,21 @@ class PcdImageFile(ImageFile.ImageFile):
if orientation == 1:
self.tile_post_rotate = 90
elif orientation == 3:
- self.tile_post_rotate = -90
+ self.tile_post_rotate = 270
self._mode = "RGB"
self._size = (512, 768) if orientation in (1, 3) else (768, 512)
- self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
+ self.tile = [ImageFile._Tile("pcd", (0, 0, 768, 512), 96 * 2048)]
+
+ def load_prepare(self) -> None:
+ if self._im is None and self.tile_post_rotate:
+ self.im = Image.core.new(self.mode, (768, 512))
+ ImageFile.ImageFile.load_prepare(self)
def load_end(self) -> None:
if self.tile_post_rotate:
# Handle rotated PCDs
- self.im = self.im.rotate(self.tile_post_rotate)
+ self.im = self.rotate(self.tile_post_rotate, expand=True).im
#
diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py
index e9c20ddc1..5594c7e0f 100644
--- a/src/PIL/PdfImagePlugin.py
+++ b/src/PIL/PdfImagePlugin.py
@@ -27,7 +27,7 @@ import os
import time
from typing import IO, Any
-from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features
+from . import Image, ImageFile, ImageSequence, PdfParser, features
#
# --------------------------------------------------------------------
@@ -221,7 +221,7 @@ def _save(
existing_pdf.start_writing()
existing_pdf.write_header()
- existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver")
+ existing_pdf.write_comment("created by Pillow PDF driver")
#
# pages
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index c1741284b..de2ce066e 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -252,6 +252,7 @@ OPEN_INFO = {
(II, 3, (1,), 1, (8,), ()): ("P", "P"),
(MM, 3, (1,), 1, (8,), ()): ("P", "P"),
(II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"),
+ (MM, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"),
(II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
(MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
(II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
@@ -1177,6 +1178,7 @@ class TiffImageFile(ImageFile.ImageFile):
"""Open the first image in a TIFF file"""
# Header
+ assert self.fp is not None
ifh = self.fp.read(8)
if ifh[2] == 43:
ifh += self.fp.read(8)
@@ -1343,6 +1345,7 @@ class TiffImageFile(ImageFile.ImageFile):
# To be nice on memory footprint, if there's a
# file descriptor, use that instead of reading
# into a string in python.
+ assert self.fp is not None
try:
fp = hasattr(self.fp, "fileno") and self.fp.fileno()
# flush the file descriptor, prevents error on pypy 2.4+
@@ -1936,9 +1939,10 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
types[tag] = TiffTags.LONG8
elif tag in ifd.tagtype:
types[tag] = ifd.tagtype[tag]
- elif not (isinstance(value, (int, float, str, bytes))):
- continue
- else:
+ elif isinstance(value, (int, float, str, bytes)) or (
+ isinstance(value, tuple)
+ and all(isinstance(v, (int, float, IFDRational)) for v in value)
+ ):
type = TiffTags.lookup(tag).type
if type:
types[tag] = type
diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py
index 86adaa458..761aa3f6b 100644
--- a/src/PIL/TiffTags.py
+++ b/src/PIL/TiffTags.py
@@ -203,6 +203,11 @@ _tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]]
531: ("YCbCrPositioning", SHORT, 1),
532: ("ReferenceBlackWhite", RATIONAL, 6),
700: ("XMP", BYTE, 0),
+ # Four private SGI tags
+ 32995: ("Matteing", SHORT, 1),
+ 32996: ("DataType", SHORT, 0),
+ 32997: ("ImageDepth", LONG, 1),
+ 32998: ("TileDepth", LONG, 1),
33432: ("Copyright", ASCII, 1),
33723: ("IptcNaaInfo", UNDEFINED, 1),
34377: ("PhotoshopInfo", BYTE, 0),
diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py
index 979147e0c..a941f8980 100644
--- a/src/PIL/_typing.py
+++ b/src/PIL/_typing.py
@@ -27,6 +27,8 @@ else:
Buffer = Any
+_Ink = float | tuple[int, ...] | str
+
Coords = Sequence[float] | Sequence[Sequence[float]]
diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c
index a36c3e0bd..834634bd7 100644
--- a/src/Tk/tkImaging.c
+++ b/src/Tk/tkImaging.c
@@ -121,15 +121,16 @@ PyImagingPhotoPut(
/* Mode */
- if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
+ if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
block.pixelSize = 1;
block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0;
- } else if (strncmp(im->mode, "RGB", 3) == 0) {
+ } else if (im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA ||
+ im->mode == IMAGING_MODE_RGBX || im->mode == IMAGING_MODE_RGBa) {
block.pixelSize = 4;
block.offset[0] = 0;
block.offset[1] = 1;
block.offset[2] = 2;
- if (strcmp(im->mode, "RGBA") == 0) {
+ if (im->mode == IMAGING_MODE_RGBA) {
block.offset[3] = 3; /* alpha (or reserved, under Tk 8.2) */
} else {
block.offset[3] = 0; /* no alpha */
diff --git a/src/_imaging.c b/src/_imaging.c
index fbfc0e41a..41af72568 100644
--- a/src/_imaging.c
+++ b/src/_imaging.c
@@ -297,6 +297,7 @@ ExportArrowArrayPyCapsule(ImagingObject *self) {
static PyObject *
_new_arrow(PyObject *self, PyObject *args) {
char *mode;
+ ModeID mode_id;
int xsize, ysize;
PyObject *schema_capsule, *array_capsule;
PyObject *ret;
@@ -307,9 +308,11 @@ _new_arrow(PyObject *self, PyObject *args) {
return NULL;
}
+ mode_id = findModeID(mode);
+
// ImagingBorrowArrow is responsible for retaining the array_capsule
ret = PyImagingNew(
- ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule)
+ ImagingNewArrow(mode_id, xsize, ysize, schema_capsule, array_capsule)
);
if (!ret) {
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
@@ -368,7 +371,7 @@ ImagingError_ValueError(const char *message) {
/* -------------------------------------------------------------------- */
static int
-getbands(const char *mode) {
+getbands(const ModeID mode) {
Imaging im;
int bands;
@@ -662,7 +665,7 @@ getink(PyObject *color, Imaging im, char *ink) {
memcpy(ink, &ftmp, sizeof(ftmp));
return ink;
case IMAGING_TYPE_SPECIAL:
- if (strncmp(im->mode, "I;16", 4) == 0) {
+ if (isModeI16(im->mode)) {
ink[0] = (UINT8)r;
ink[1] = (UINT8)(r >> 8);
ink[2] = ink[3] = 0;
@@ -694,7 +697,7 @@ getink(PyObject *color, Imaging im, char *ink) {
static PyObject *
_fill(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int xsize, ysize;
PyObject *color;
char buffer[4];
@@ -703,10 +706,12 @@ _fill(PyObject *self, PyObject *args) {
xsize = ysize = 256;
color = NULL;
- if (!PyArg_ParseTuple(args, "s|(ii)O", &mode, &xsize, &ysize, &color)) {
+ if (!PyArg_ParseTuple(args, "s|(ii)O", &mode_name, &xsize, &ysize, &color)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
im = ImagingNewDirty(mode, xsize, ysize);
if (!im) {
return NULL;
@@ -727,47 +732,55 @@ _fill(PyObject *self, PyObject *args) {
static PyObject *
_new(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int xsize, ysize;
- if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
+ if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingNew(mode, xsize, ysize));
}
static PyObject *
_new_block(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int xsize, ysize;
- if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
+ if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingNewBlock(mode, xsize, ysize));
}
static PyObject *
_linear_gradient(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
- if (!PyArg_ParseTuple(args, "s", &mode)) {
+ if (!PyArg_ParseTuple(args, "s", &mode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingFillLinearGradient(mode));
}
static PyObject *
_radial_gradient(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
- if (!PyArg_ParseTuple(args, "s", &mode)) {
+ if (!PyArg_ParseTuple(args, "s", &mode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingFillRadialGradient(mode));
}
@@ -907,7 +920,7 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) {
static PyObject *
_color_lut_3d(ImagingObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int filter;
int table_channels;
int size1D, size2D, size3D;
@@ -919,7 +932,7 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
"sii(iii)O:color_lut_3d",
- &mode,
+ &mode_name,
&filter,
&table_channels,
&size1D,
@@ -930,6 +943,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
/* actually, it is trilinear */
if (filter != IMAGING_TRANSFORM_BILINEAR) {
PyErr_SetString(PyExc_ValueError, "Only LINEAR filter is supported.");
@@ -976,11 +991,11 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
static PyObject *
_convert(ImagingObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int dither = 0;
ImagingObject *paletteimage = NULL;
- if (!PyArg_ParseTuple(args, "s|iO", &mode, &dither, &paletteimage)) {
+ if (!PyArg_ParseTuple(args, "s|iO", &mode_name, &dither, &paletteimage)) {
return NULL;
}
if (paletteimage != NULL) {
@@ -997,6 +1012,8 @@ _convert(ImagingObject *self, PyObject *args) {
}
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingConvert(
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither
));
@@ -1021,14 +1038,14 @@ _convert2(ImagingObject *self, PyObject *args) {
static PyObject *
_convert_matrix(ImagingObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
float m[12];
- if (!PyArg_ParseTuple(args, "s(ffff)", &mode, m + 0, m + 1, m + 2, m + 3)) {
+ if (!PyArg_ParseTuple(args, "s(ffff)", &mode_name, m + 0, m + 1, m + 2, m + 3)) {
PyErr_Clear();
if (!PyArg_ParseTuple(
args,
"s(ffffffffffff)",
- &mode,
+ &mode_name,
m + 0,
m + 1,
m + 2,
@@ -1046,18 +1063,22 @@ _convert_matrix(ImagingObject *self, PyObject *args) {
}
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingConvertMatrix(self->image, mode, m));
}
static PyObject *
_convert_transparent(ImagingObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int r, g, b;
- if (PyArg_ParseTuple(args, "s(iii)", &mode, &r, &g, &b)) {
+ if (PyArg_ParseTuple(args, "s(iii)", &mode_name, &r, &g, &b)) {
+ const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b));
}
PyErr_Clear();
- if (PyArg_ParseTuple(args, "si", &mode, &r)) {
+ if (PyArg_ParseTuple(args, "si", &mode_name, &r)) {
+ const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, 0, 0));
}
return NULL;
@@ -1156,9 +1177,9 @@ _getpalette(ImagingObject *self, PyObject *args) {
int bits;
ImagingShuffler pack;
- char *mode = "RGB";
- char *rawmode = "RGB";
- if (!PyArg_ParseTuple(args, "|ss", &mode, &rawmode)) {
+ char *mode_name = "RGB";
+ char *rawmode_name = "RGB";
+ if (!PyArg_ParseTuple(args, "|ss", &mode_name, &rawmode_name)) {
return NULL;
}
@@ -1167,6 +1188,9 @@ _getpalette(ImagingObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
pack = ImagingFindPacker(mode, rawmode, &bits);
if (!pack) {
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
@@ -1193,7 +1217,7 @@ _getpalettemode(ImagingObject *self) {
return NULL;
}
- return PyUnicode_FromString(self->image->palette->mode);
+ return PyUnicode_FromString(getModeData(self->image->palette->mode)->name);
}
static inline int
@@ -1474,12 +1498,14 @@ _point(ImagingObject *self, PyObject *args) {
Imaging im;
PyObject *list;
- char *mode;
- if (!PyArg_ParseTuple(args, "Oz", &list, &mode)) {
+ char *mode_name;
+ if (!PyArg_ParseTuple(args, "Oz", &list, &mode_name)) {
return NULL;
}
- if (mode && !strcmp(mode, "F")) {
+ const ModeID mode = findModeID(mode_name);
+
+ if (mode == IMAGING_MODE_F) {
FLOAT32 *data;
/* map from 8-bit data to floating point */
@@ -1490,8 +1516,7 @@ _point(ImagingObject *self, PyObject *args) {
}
im = ImagingPoint(self->image, mode, (void *)data);
free(data);
-
- } else if (!strcmp(self->image->mode, "I") && mode && !strcmp(mode, "L")) {
+ } else if (self->image->mode == IMAGING_MODE_I && mode == IMAGING_MODE_L) {
UINT8 *data;
/* map from 16-bit subset of 32-bit data to 8-bit */
@@ -1503,7 +1528,6 @@ _point(ImagingObject *self, PyObject *args) {
}
im = ImagingPoint(self->image, mode, (void *)data);
free(data);
-
} else {
INT32 *data;
UINT8 lut[1024];
@@ -1524,7 +1548,7 @@ _point(ImagingObject *self, PyObject *args) {
return NULL;
}
- if (mode && !strcmp(mode, "I")) {
+ if (mode == IMAGING_MODE_I) {
im = ImagingPoint(self->image, mode, (void *)data);
} else if (mode && bands > 1) {
for (i = 0; i < 256; i++) {
@@ -1630,9 +1654,9 @@ _putdata(ImagingObject *self, PyObject *args) {
if (image->type == IMAGING_TYPE_SPECIAL) {
// I;16*
if (
- strcmp(image->mode, "I;16B") == 0
+ image->mode == IMAGING_MODE_I_16B
#ifdef WORDS_BIGENDIAN
- || strcmp(image->mode, "I;16N") == 0
+ || image->mode == IMAGING_MODE_I_16N
#endif
) {
bigendian = 1;
@@ -1729,7 +1753,9 @@ _quantize(ImagingObject *self, PyObject *args) {
if (!self->image->xsize || !self->image->ysize) {
/* no content; return an empty image */
- return PyImagingNew(ImagingNew("P", self->image->xsize, self->image->ysize));
+ return PyImagingNew(
+ ImagingNew(IMAGING_MODE_P, self->image->xsize, self->image->ysize)
+ );
}
return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans));
@@ -1740,21 +1766,33 @@ _putpalette(ImagingObject *self, PyObject *args) {
ImagingShuffler unpack;
int bits;
- char *palette_mode, *rawmode;
+ char *palette_mode_name, *rawmode_name;
UINT8 *palette;
Py_ssize_t palettesize;
if (!PyArg_ParseTuple(
- args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize
+ args, "ssy#", &palette_mode_name, &rawmode_name, &palette, &palettesize
)) {
return NULL;
}
- if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") &&
- strcmp(self->image->mode, "P") && strcmp(self->image->mode, "PA")) {
+ if (self->image->mode != IMAGING_MODE_L && self->image->mode != IMAGING_MODE_LA &&
+ self->image->mode != IMAGING_MODE_P && self->image->mode != IMAGING_MODE_PA) {
PyErr_SetString(PyExc_ValueError, wrong_mode);
return NULL;
}
+ const ModeID palette_mode = findModeID(palette_mode_name);
+ if (palette_mode == IMAGING_MODE_UNKNOWN) {
+ PyErr_SetString(PyExc_ValueError, wrong_mode);
+ return NULL;
+ }
+
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+ if (rawmode == IMAGING_RAWMODE_UNKNOWN) {
+ PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
+ return NULL;
+ }
+
unpack = ImagingFindUnpacker(palette_mode, rawmode, &bits);
if (!unpack) {
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
@@ -1768,7 +1806,13 @@ _putpalette(ImagingObject *self, PyObject *args) {
ImagingPaletteDelete(self->image->palette);
- strcpy(self->image->mode, strlen(self->image->mode) == 2 ? "PA" : "P");
+ if (self->image->mode == IMAGING_MODE_LA) {
+ self->image->mode = IMAGING_MODE_PA;
+ } else if (self->image->mode == IMAGING_MODE_L) {
+ self->image->mode = IMAGING_MODE_P;
+ } else {
+ // The image already has a palette mode so we don't need to change it.
+ }
self->image->palette = ImagingPaletteNew(palette_mode);
@@ -1796,7 +1840,7 @@ _putpalettealpha(ImagingObject *self, PyObject *args) {
return NULL;
}
- strcpy(self->image->palette->mode, "RGBA");
+ self->image->palette->mode = IMAGING_MODE_RGBA;
self->image->palette->palette[index * 4 + 3] = (UINT8)alpha;
Py_RETURN_NONE;
@@ -1821,7 +1865,7 @@ _putpalettealphas(ImagingObject *self, PyObject *args) {
return NULL;
}
- strcpy(self->image->palette->mode, "RGBA");
+ self->image->palette->mode = IMAGING_MODE_RGBA;
for (i = 0; i < length; i++) {
self->image->palette->palette[i * 4 + 3] = (UINT8)values[i];
}
@@ -1989,8 +2033,11 @@ _reduce(ImagingObject *self, PyObject *args) {
return PyImagingNew(imOut);
}
-#define IS_RGB(mode) \
- (!strcmp(mode, "RGB") || !strcmp(mode, "RGBA") || !strcmp(mode, "RGBX"))
+static int
+isRGB(const ModeID mode) {
+ return mode == IMAGING_MODE_RGB || mode == IMAGING_MODE_RGBA ||
+ mode == IMAGING_MODE_RGBX;
+}
static PyObject *
im_setmode(ImagingObject *self, PyObject *args) {
@@ -1998,23 +2045,25 @@ im_setmode(ImagingObject *self, PyObject *args) {
Imaging im;
- char *mode;
+ char *mode_name;
Py_ssize_t modelen;
- if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) {
+ if (!PyArg_ParseTuple(args, "s#:setmode", &mode_name, &modelen)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
im = self->image;
/* move all logic in here to the libImaging primitive */
- if (!strcmp(im->mode, mode)) {
+ if (im->mode == mode) {
; /* same mode; always succeeds */
- } else if (IS_RGB(im->mode) && IS_RGB(mode)) {
+ } else if (isRGB(im->mode) && isRGB(mode)) {
/* color to color */
- strcpy(im->mode, mode);
+ im->mode = mode;
im->bands = modelen;
- if (!strcmp(mode, "RGBA")) {
+ if (mode == IMAGING_MODE_RGBA) {
(void)ImagingFillBand(im, 3, 255);
}
} else {
@@ -2294,7 +2343,7 @@ _getextrema(ImagingObject *self) {
case IMAGING_TYPE_FLOAT32:
return Py_BuildValue("dd", extrema.f[0], extrema.f[1]);
case IMAGING_TYPE_SPECIAL:
- if (strcmp(self->image->mode, "I;16") == 0) {
+ if (self->image->mode == IMAGING_MODE_I_16) {
return Py_BuildValue("HH", extrema.s[0], extrema.s[1]);
}
}
@@ -2383,7 +2432,7 @@ _putband(ImagingObject *self, PyObject *args) {
static PyObject *
_merge(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
ImagingObject *band0 = NULL;
ImagingObject *band1 = NULL;
ImagingObject *band2 = NULL;
@@ -2393,7 +2442,7 @@ _merge(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
"sO!|O!O!O!",
- &mode,
+ &mode_name,
&Imaging_Type,
&band0,
&Imaging_Type,
@@ -2406,6 +2455,8 @@ _merge(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
if (band0) {
bands[0] = band0->image;
}
@@ -2419,7 +2470,12 @@ _merge(PyObject *self, PyObject *args) {
bands[3] = band3->image;
}
- return PyImagingNew(ImagingMerge(mode, bands));
+ Imaging imOut = ImagingMerge(mode, bands);
+ if (!imOut) {
+ return NULL;
+ }
+ ImagingCopyPalette(imOut, bands[0]);
+ return PyImagingNew(imOut);
}
static PyObject *
@@ -3711,7 +3767,7 @@ static struct PyMethodDef methods[] = {
static PyObject *
_getattr_mode(ImagingObject *self, void *closure) {
- return PyUnicode_FromString(self->image->mode);
+ return PyUnicode_FromString(getModeData(self->image->mode)->name);
}
static PyObject *
@@ -4255,8 +4311,6 @@ setup_module(PyObject *m) {
return -1;
}
- ImagingAccessInit();
-
#ifdef HAVE_LIBJPEG
{
extern const char *ImagingJpegVersion(void);
diff --git a/src/_imagingcms.c b/src/_imagingcms.c
index e2f29d1b7..ad3b27896 100644
--- a/src/_imagingcms.c
+++ b/src/_imagingcms.c
@@ -212,32 +212,44 @@ cms_transform_dealloc(CmsTransformObject *self) {
/* internal functions */
static cmsUInt32Number
-findLCMStype(char *PILmode) {
- if (strcmp(PILmode, "RGB") == 0 || strcmp(PILmode, "RGBA") == 0 ||
- strcmp(PILmode, "RGBX") == 0) {
- return TYPE_RGBA_8;
+findLCMStype(const char *const mode_name) {
+ const ModeID mode = findModeID(mode_name);
+ switch (mode) {
+ case IMAGING_MODE_RGB:
+ case IMAGING_MODE_RGBA:
+ case IMAGING_MODE_RGBX:
+ return TYPE_RGBA_8;
+ case IMAGING_MODE_CMYK:
+ return TYPE_CMYK_8;
+ case IMAGING_MODE_I_16:
+ case IMAGING_MODE_I_16L:
+ return TYPE_GRAY_16;
+ case IMAGING_MODE_I_16B:
+ return TYPE_GRAY_16_SE;
+ case IMAGING_MODE_YCbCr:
+ return TYPE_YCbCr_8;
+ case IMAGING_MODE_LAB:
+ // LabX equivalent like ALab, but not reversed -- no #define in lcms2
+ return (
+ COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1)
+ );
+ default:
+ // This function only accepts a subset of the imaging modes Pillow has.
+ break;
}
- if (strcmp(PILmode, "RGBA;16B") == 0) {
+ // The following modes are not valid PIL Image modes.
+ if (strcmp(mode_name, "RGBA;16B") == 0) {
return TYPE_RGBA_16;
}
- if (strcmp(PILmode, "CMYK") == 0) {
- return TYPE_CMYK_8;
- }
- if (strcmp(PILmode, "I;16") == 0 || strcmp(PILmode, "I;16L") == 0 ||
- strcmp(PILmode, "L;16") == 0) {
+ if (strcmp(mode_name, "L;16") == 0) {
return TYPE_GRAY_16;
}
- if (strcmp(PILmode, "I;16B") == 0 || strcmp(PILmode, "L;16B") == 0) {
+ if (strcmp(mode_name, "L;16B") == 0) {
return TYPE_GRAY_16_SE;
}
- if (strcmp(PILmode, "YCbCr") == 0 || strcmp(PILmode, "YCCA") == 0 ||
- strcmp(PILmode, "YCC") == 0) {
+ if (strcmp(mode_name, "YCCA") == 0 || strcmp(mode_name, "YCC") == 0) {
return TYPE_YCbCr_8;
}
- if (strcmp(PILmode, "LAB") == 0) {
- // LabX equivalent like ALab, but not reversed -- no #define in lcms2
- return (COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1));
- }
/* presume "1" or "L" by default */
return TYPE_GRAY_8;
}
diff --git a/src/_imagingft.c b/src/_imagingft.c
index c9938fd3e..d0af25b30 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -525,7 +525,7 @@ font_getlength(FontObject *self, PyObject *args) {
int horizontal_dir; /* is primary axis horizontal? */
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
int color = 0; /* is FT_LOAD_COLOR enabled? */
- const char *mode = NULL;
+ const char *mode_name = NULL;
const char *dir = NULL;
const char *lang = NULL;
PyObject *features = Py_None;
@@ -534,15 +534,16 @@ font_getlength(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */
if (!PyArg_ParseTuple(
- args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang
+ args, "O|zzOz:getlength", &string, &mode_name, &dir, &features, &lang
)) {
return NULL;
}
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
- mask = mode && strcmp(mode, "1") == 0;
- color = mode && strcmp(mode, "RGBA") == 0;
+ const ModeID mode = findModeID(mode_name);
+ mask = mode == IMAGING_MODE_1;
+ color = mode == IMAGING_MODE_RGBA;
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
if (PyErr_Occurred()) {
@@ -754,7 +755,7 @@ font_getsize(FontObject *self, PyObject *args) {
int horizontal_dir; /* is primary axis horizontal? */
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
int color = 0; /* is FT_LOAD_COLOR enabled? */
- const char *mode = NULL;
+ const char *mode_name = NULL;
const char *dir = NULL;
const char *lang = NULL;
const char *anchor = NULL;
@@ -764,15 +765,23 @@ font_getsize(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */
if (!PyArg_ParseTuple(
- args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor
+ args,
+ "O|zzOzz:getsize",
+ &string,
+ &mode_name,
+ &dir,
+ &features,
+ &lang,
+ &anchor
)) {
return NULL;
}
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
- mask = mode && strcmp(mode, "1") == 0;
- color = mode && strcmp(mode, "RGBA") == 0;
+ const ModeID mode = findModeID(mode_name);
+ mask = mode == IMAGING_MODE_1;
+ color = mode == IMAGING_MODE_RGBA;
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
if (PyErr_Occurred()) {
@@ -839,7 +848,7 @@ font_render(FontObject *self, PyObject *args) {
int stroke_filled = 0;
PY_LONG_LONG foreground_ink_long = 0;
unsigned int foreground_ink;
- const char *mode = NULL;
+ const char *mode_name = NULL;
const char *dir = NULL;
const char *lang = NULL;
const char *anchor = NULL;
@@ -859,7 +868,7 @@ font_render(FontObject *self, PyObject *args) {
"OO|zzOzfpzL(ff):render",
&string,
&fill,
- &mode,
+ &mode_name,
&dir,
&features,
&lang,
@@ -873,8 +882,9 @@ font_render(FontObject *self, PyObject *args) {
return NULL;
}
- mask = mode && strcmp(mode, "1") == 0;
- color = mode && strcmp(mode, "RGBA") == 0;
+ const ModeID mode = findModeID(mode_name);
+ mask = mode == IMAGING_MODE_1;
+ color = mode == IMAGING_MODE_RGBA;
foreground_ink = foreground_ink_long;
diff --git a/src/_webp.c b/src/_webp.c
index e84e786ed..d065e329c 100644
--- a/src/_webp.c
+++ b/src/_webp.c
@@ -89,8 +89,8 @@ HandleMuxError(WebPMuxError err, char *chunk) {
static int
import_frame_libwebp(WebPPicture *frame, Imaging im) {
- if (strcmp(im->mode, "RGBA") && strcmp(im->mode, "RGB") &&
- strcmp(im->mode, "RGBX")) {
+ if (im->mode != IMAGING_MODE_RGBA && im->mode != IMAGING_MODE_RGB &&
+ im->mode != IMAGING_MODE_RGBX) {
PyErr_SetString(PyExc_ValueError, "unsupported image mode");
return -1;
}
@@ -104,7 +104,7 @@ import_frame_libwebp(WebPPicture *frame, Imaging im) {
return -2;
}
- int ignore_fourth_channel = strcmp(im->mode, "RGBA");
+ int ignore_fourth_channel = im->mode != IMAGING_MODE_RGBA;
for (int y = 0; y < im->ysize; ++y) {
UINT8 *src = (UINT8 *)im->image32[y];
UINT32 *dst = frame->argb + frame->argb_stride * y;
@@ -143,7 +143,7 @@ typedef struct {
PyObject_HEAD WebPAnimDecoder *dec;
WebPAnimInfo info;
WebPData data;
- char *mode;
+ ModeID mode;
} WebPAnimDecoderObject;
static PyTypeObject WebPAnimDecoder_Type;
@@ -396,7 +396,7 @@ _anim_decoder_new(PyObject *self, PyObject *args) {
const uint8_t *webp;
Py_ssize_t size;
WebPData webp_src;
- char *mode;
+ ModeID mode;
WebPDecoderConfig config;
WebPAnimDecoderObject *decp = NULL;
WebPAnimDecoder *dec = NULL;
@@ -409,10 +409,10 @@ _anim_decoder_new(PyObject *self, PyObject *args) {
webp_src.size = size;
// Sniff the mode, since the decoder API doesn't tell us
- mode = "RGBA";
+ mode = IMAGING_MODE_RGBA;
if (WebPGetFeatures(webp, size, &config.input) == VP8_STATUS_OK) {
if (!config.input.has_alpha) {
- mode = "RGBX";
+ mode = IMAGING_MODE_RGBX;
}
}
@@ -455,7 +455,7 @@ _anim_decoder_get_info(PyObject *self) {
info->loop_count,
info->bgcolor,
info->frame_count,
- decp->mode
+ getModeData(decp->mode)->name
);
}
diff --git a/src/decode.c b/src/decode.c
index e7a6e6323..051623ed4 100644
--- a/src/decode.c
+++ b/src/decode.c
@@ -266,7 +266,9 @@ static PyTypeObject ImagingDecoderType = {
/* -------------------------------------------------------------------- */
int
-get_unpacker(ImagingDecoderObject *decoder, const char *mode, const char *rawmode) {
+get_unpacker(
+ ImagingDecoderObject *decoder, const ModeID mode, const RawModeID rawmode
+) {
int bits;
ImagingShuffler unpack;
@@ -291,17 +293,20 @@ PyObject *
PyImaging_BitDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
+ const char *mode_name;
int bits = 8;
int pad = 8;
int fill = 0;
int sign = 0;
int ystep = 1;
- if (!PyArg_ParseTuple(args, "s|iiiii", &mode, &bits, &pad, &fill, &sign, &ystep)) {
+ if (!PyArg_ParseTuple(
+ args, "s|iiiii", &mode_name, &bits, &pad, &fill, &sign, &ystep
+ )) {
return NULL;
}
- if (strcmp(mode, "F") != 0) {
+ const ModeID mode = findModeID(mode_name);
+ if (mode != IMAGING_MODE_F) {
PyErr_SetString(PyExc_ValueError, "bad image mode");
return NULL;
}
@@ -331,34 +336,36 @@ PyObject *
PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *actual;
+ char *mode_name;
int n = 0;
char *pixel_format = "";
- if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) {
+ if (!PyArg_ParseTuple(args, "si|s", &mode_name, &n, &pixel_format)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ ModeID actual;
+
switch (n) {
case 1: /* BC1: 565 color, 1-bit alpha */
case 2: /* BC2: 565 color, 4-bit alpha */
case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */
case 7: /* BC7: 4-channel 8-bit via everything */
- actual = "RGBA";
+ actual = IMAGING_MODE_RGBA;
break;
case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */
- actual = "L";
+ actual = IMAGING_MODE_L;
break;
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
case 6: /* BC6: 3-channel 16-bit float */
- actual = "RGB";
+ actual = IMAGING_MODE_RGB;
break;
default:
PyErr_SetString(PyExc_ValueError, "block compression type unknown");
return NULL;
}
- if (strcmp(mode, actual) != 0) {
+ if (mode != actual) {
PyErr_SetString(PyExc_ValueError, "bad image mode");
return NULL;
}
@@ -401,15 +408,18 @@ PyObject *
PyImaging_GifDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
+ const char *mode_name;
int bits = 8;
int interlace = 0;
int transparency = -1;
- if (!PyArg_ParseTuple(args, "s|iii", &mode, &bits, &interlace, &transparency)) {
+ if (!PyArg_ParseTuple(
+ args, "s|iii", &mode_name, &bits, &interlace, &transparency
+ )) {
return NULL;
}
- if (strcmp(mode, "L") != 0 && strcmp(mode, "P") != 0) {
+ const ModeID mode = findModeID(mode_name);
+ if (mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
PyErr_SetString(PyExc_ValueError, "bad image mode");
return NULL;
}
@@ -436,12 +446,14 @@ PyObject *
PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
- if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
+ char *mode_name, *rawmode_name;
+ if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -469,16 +481,21 @@ PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
PyObject *
PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
char *compname;
int fp;
uint32_t ifdoffset;
- if (!PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset)) {
+ if (!PyArg_ParseTuple(
+ args, "sssiI", &mode_name, &rawmode_name, &compname, &fp, &ifdoffset
+ )) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
TRACE(("new tiff decoder %s\n", compname));
decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE));
@@ -511,12 +528,15 @@ PyObject *
PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
- if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
+ char *mode_name;
+ char *rawmode_name;
+ if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -545,7 +565,7 @@ PyImaging_PcdDecoderNew(PyObject *self, PyObject *args) {
}
/* Unpack from PhotoYCC to RGB */
- if (get_unpacker(decoder, "RGB", "YCC;P") < 0) {
+ if (get_unpacker(decoder, IMAGING_MODE_RGB, IMAGING_RAWMODE_YCC_P) < 0) {
return NULL;
}
@@ -562,13 +582,15 @@ PyObject *
PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int stride;
- if (!PyArg_ParseTuple(args, "ssi", &mode, &rawmode, &stride)) {
+ if (!PyArg_ParseTuple(args, "ssi", &mode_name, &rawmode_name, &stride)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -593,14 +615,16 @@ PyObject *
PyImaging_RawDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int stride = 0;
int ystep = 1;
- if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) {
+ if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &stride, &ystep)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(sizeof(RAWSTATE));
if (decoder == NULL) {
return NULL;
@@ -627,14 +651,16 @@ PyObject *
PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int ystep = 1;
int bpc = 1;
- if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &bpc)) {
+ if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &ystep, &bpc)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(sizeof(SGISTATE));
if (decoder == NULL) {
return NULL;
@@ -661,12 +687,14 @@ PyObject *
PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
- if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
+ char *mode_name, *rawmode_name;
+ if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -689,14 +717,16 @@ PyObject *
PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int ystep = 1;
int depth = 8;
- if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &depth)) {
+ if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &ystep, &depth)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -727,7 +757,7 @@ PyImaging_XbmDecoderNew(PyObject *self, PyObject *args) {
return NULL;
}
- if (get_unpacker(decoder, "1", "1;R") < 0) {
+ if (get_unpacker(decoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) {
return NULL;
}
@@ -748,13 +778,15 @@ PyObject *
PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int interlaced = 0;
- if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &interlaced)) {
+ if (!PyArg_ParseTuple(args, "ss|i", &mode_name, &rawmode_name, &interlaced)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE));
if (decoder == NULL) {
return NULL;
@@ -798,19 +830,21 @@ PyObject *
PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode; /* what we want from the decoder */
- char *jpegmode; /* what's in the file */
+ char *mode_name;
+ char *rawmode_name; /* what we want from the decoder */
+ char *jpegmode_name; /* what's in the file */
int scale = 1;
int draft = 0;
- if (!PyArg_ParseTuple(args, "ssz|ii", &mode, &rawmode, &jpegmode, &scale, &draft)) {
+ if (!PyArg_ParseTuple(
+ args, "ssz|ii", &mode_name, &rawmode_name, &jpegmode_name, &scale, &draft
+ )) {
return NULL;
}
- if (!jpegmode) {
- jpegmode = "";
- }
+ const ModeID mode = findModeID(mode_name);
+ RawModeID rawmode = findRawModeID(rawmode_name);
+ const RawModeID jpegmode = findRawModeID(jpegmode_name);
decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE));
if (decoder == NULL) {
@@ -820,8 +854,8 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
// libjpeg-turbo supports different output formats.
// We are choosing Pillow's native format (3 color bytes + 1 padding)
// to avoid extra conversion in Unpack.c.
- if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) {
- rawmode = "RGBX";
+ if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) {
+ rawmode = IMAGING_RAWMODE_RGBX;
}
if (get_unpacker(decoder, mode, rawmode) < 0) {
@@ -831,11 +865,13 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
decoder->decode = ImagingJpegDecode;
decoder->cleanup = ImagingJpegDecodeCleanup;
- strncpy(((JPEGSTATE *)decoder->state.context)->rawmode, rawmode, 8);
- strncpy(((JPEGSTATE *)decoder->state.context)->jpegmode, jpegmode, 8);
+ JPEGSTATE *jpeg_decoder_state_context = (JPEGSTATE *)decoder->state.context;
- ((JPEGSTATE *)decoder->state.context)->scale = scale;
- ((JPEGSTATE *)decoder->state.context)->draft = draft;
+ jpeg_decoder_state_context->rawmode = rawmode;
+ jpeg_decoder_state_context->jpegmode = jpegmode;
+
+ jpeg_decoder_state_context->scale = scale;
+ jpeg_decoder_state_context->draft = draft;
return (PyObject *)decoder;
}
diff --git a/src/display.c b/src/display.c
index 3215f6691..5b5853a3c 100644
--- a/src/display.c
+++ b/src/display.c
@@ -47,7 +47,7 @@ typedef struct {
static PyTypeObject ImagingDisplayType;
static ImagingDisplayObject *
-_new(const char *mode, int xsize, int ysize) {
+_new(const ModeID mode, int xsize, int ysize) {
ImagingDisplayObject *display;
if (PyType_Ready(&ImagingDisplayType) < 0) {
@@ -235,7 +235,7 @@ static struct PyMethodDef methods[] = {
static PyObject *
_getattr_mode(ImagingDisplayObject *self, void *closure) {
- return Py_BuildValue("s", self->dib->mode);
+ return Py_BuildValue("s", getModeData(self->dib->mode)->name);
}
static PyObject *
@@ -258,13 +258,14 @@ static PyTypeObject ImagingDisplayType = {
PyObject *
PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
ImagingDisplayObject *display;
- char *mode;
+ char *mode_name;
int xsize, ysize;
- if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
+ if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
display = _new(mode, xsize, ysize);
if (display == NULL) {
return NULL;
@@ -275,12 +276,9 @@ PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
PyObject *
PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) {
- char *mode;
int size[2];
-
- mode = ImagingGetModeDIB(size);
-
- return Py_BuildValue("s(ii)", mode, size[0], size[1]);
+ const ModeID mode = ImagingGetModeDIB(size);
+ return Py_BuildValue("s(ii)", getModeData(mode)->name, size[0], size[1]);
}
/* -------------------------------------------------------------------- */
diff --git a/src/encode.c b/src/encode.c
index e56494036..b1d0181e0 100644
--- a/src/encode.c
+++ b/src/encode.c
@@ -334,14 +334,19 @@ static PyTypeObject ImagingEncoderType = {
/* -------------------------------------------------------------------- */
int
-get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode) {
+get_packer(ImagingEncoderObject *encoder, const ModeID mode, const RawModeID rawmode) {
int bits;
ImagingShuffler pack;
pack = ImagingFindPacker(mode, rawmode, &bits);
if (!pack) {
Py_DECREF(encoder);
- PyErr_Format(PyExc_ValueError, "No packer found from %s to %s", mode, rawmode);
+ PyErr_Format(
+ PyExc_ValueError,
+ "No packer found from %s to %s",
+ getModeData(mode)->name,
+ getRawModeData(rawmode)->name
+ );
return -1;
}
@@ -402,11 +407,13 @@ PyObject *
PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t bits = 8;
Py_ssize_t interlace = 0;
- if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace)) {
+ if (!PyArg_ParseTuple(
+ args, "ss|nn", &mode_name, &rawmode_name, &bits, &interlace
+ )) {
return NULL;
}
@@ -415,6 +422,9 @@ PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -435,11 +445,11 @@ PyObject *
PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t bits = 8;
- if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &bits)) {
+ if (!PyArg_ParseTuple(args, "ss|n", &mode_name, &rawmode_name, &bits)) {
return NULL;
}
@@ -448,6 +458,9 @@ PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -465,12 +478,12 @@ PyObject *
PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t stride = 0;
Py_ssize_t ystep = 1;
- if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep)) {
+ if (!PyArg_ParseTuple(args, "ss|nn", &mode_name, &rawmode_name, &stride, &ystep)) {
return NULL;
}
@@ -479,6 +492,9 @@ PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -499,11 +515,11 @@ PyObject *
PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t ystep = 1;
- if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep)) {
+ if (!PyArg_ParseTuple(args, "ss|n", &mode_name, &rawmode_name, &ystep)) {
return NULL;
}
@@ -512,6 +528,9 @@ PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -536,7 +555,7 @@ PyImaging_XbmEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
- if (get_packer(encoder, "1", "1;R") < 0) {
+ if (get_packer(encoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) {
return NULL;
}
@@ -557,8 +576,8 @@ PyObject *
PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t optimize = 0;
Py_ssize_t compress_level = -1;
Py_ssize_t compress_type = -1;
@@ -567,8 +586,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
"ss|nnny#",
- &mode,
- &rawmode,
+ &mode_name,
+ &rawmode_name,
&optimize,
&compress_level,
&compress_type,
@@ -597,6 +616,9 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
free(dictionary);
return NULL;
@@ -605,7 +627,7 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
encoder->encode = ImagingZipEncode;
encoder->cleanup = ImagingZipEncodeCleanup;
- if (rawmode[0] == 'P') {
+ if (rawmode == IMAGING_RAWMODE_P || rawmode == IMAGING_RAWMODE_PA) {
/* disable filtering */
((ZIPSTATE *)encoder->state.context)->mode = ZIP_PNG_PALETTE;
}
@@ -634,8 +656,8 @@ PyObject *
PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
char *compname;
char *filename;
Py_ssize_t fp;
@@ -655,7 +677,15 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
PyObject *item;
if (!PyArg_ParseTuple(
- args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types
+ args,
+ "sssnsOO",
+ &mode_name,
+ &rawmode_name,
+ &compname,
+ &fp,
+ &filename,
+ &tags,
+ &types
)) {
return NULL;
}
@@ -693,6 +723,9 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -922,6 +955,18 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
);
free(av);
}
+ } else if (type == TIFF_RATIONAL) {
+ FLOAT32 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(FLOAT32));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i));
+ }
+ status =
+ ImagingLibTiffSetField(&encoder->state, (ttag_t)key_int, av);
+ free(av);
+ }
}
} else {
if (type == TIFF_SHORT) {
@@ -1076,8 +1121,8 @@ PyObject *
PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t quality = 0;
Py_ssize_t progressive = 0;
Py_ssize_t smooth = 0;
@@ -1101,8 +1146,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
"ss|nnnnpn(nn)nnnOz#y#y#",
- &mode,
- &rawmode,
+ &mode_name,
+ &rawmode_name,
&quality,
&progressive,
&smooth,
@@ -1130,11 +1175,14 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ RawModeID rawmode = findRawModeID(rawmode_name);
+
// libjpeg-turbo supports different output formats.
// We are choosing Pillow's native format (3 color bytes + 1 padding)
// to avoid extra conversion in Pack.c.
- if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) {
- rawmode = "RGBX";
+ if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) {
+ rawmode = IMAGING_RAWMODE_RGBX;
}
if (get_packer(encoder, mode, rawmode) < 0) {
@@ -1192,7 +1240,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
encoder->encode = ImagingJpegEncode;
JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context;
- strncpy(jpeg_encoder_state->rawmode, rawmode, 8);
+ jpeg_encoder_state->rawmode = rawmode;
jpeg_encoder_state->keep_rgb = keep_rgb;
jpeg_encoder_state->quality = quality;
jpeg_encoder_state->qtables = qarrays;
diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c
index 3db52377e..c77a9c21c 100644
--- a/src/libImaging/Access.c
+++ b/src/libImaging/Access.c
@@ -11,39 +11,6 @@
#include "Imaging.h"
-/* use make_hash.py from the pillow-scripts repository to calculate these values */
-#define ACCESS_TABLE_SIZE 35
-#define ACCESS_TABLE_HASH 8940
-
-static struct ImagingAccessInstance access_table[ACCESS_TABLE_SIZE];
-
-static inline UINT32
-hash(const char *mode) {
- UINT32 i = ACCESS_TABLE_HASH;
- while (*mode) {
- i = ((i << 5) + i) ^ (UINT8)*mode++;
- }
- return i % ACCESS_TABLE_SIZE;
-}
-
-static ImagingAccess
-add_item(const char *mode) {
- UINT32 i = hash(mode);
- /* printf("hash %s => %d\n", mode, i); */
- if (access_table[i].mode && strcmp(access_table[i].mode, mode) != 0) {
- fprintf(
- stderr,
- "AccessInit: hash collision: %d for both %s and %s\n",
- i,
- mode,
- access_table[i].mode
- );
- exit(1);
- }
- access_table[i].mode = mode;
- return &access_table[i];
-}
-
/* fetch individual pixel */
static void
@@ -64,7 +31,7 @@ static void
get_pixel_16L(Imaging im, int x, int y, void *color) {
UINT8 *in = (UINT8 *)&im->image[y][x + x];
#ifdef WORDS_BIGENDIAN
- UINT16 out = in[0] + (in[1] << 8);
+ UINT16 out = in[0] + ((UINT16)in[1] << 8);
memcpy(color, &out, sizeof(out));
#else
memcpy(color, in, sizeof(UINT16));
@@ -77,7 +44,7 @@ get_pixel_16B(Imaging im, int x, int y, void *color) {
#ifdef WORDS_BIGENDIAN
memcpy(color, in, sizeof(UINT16));
#else
- UINT16 out = in[1] + (in[0] << 8);
+ UINT16 out = in[1] + ((UINT16)in[0] << 8);
memcpy(color, &out, sizeof(out));
#endif
}
@@ -87,28 +54,6 @@ get_pixel_32(Imaging im, int x, int y, void *color) {
memcpy(color, &im->image32[y][x], sizeof(INT32));
}
-static void
-get_pixel_32L(Imaging im, int x, int y, void *color) {
- UINT8 *in = (UINT8 *)&im->image[y][x * 4];
-#ifdef WORDS_BIGENDIAN
- INT32 out = in[0] + (in[1] << 8) + (in[2] << 16) + (in[3] << 24);
- memcpy(color, &out, sizeof(out));
-#else
- memcpy(color, in, sizeof(INT32));
-#endif
-}
-
-static void
-get_pixel_32B(Imaging im, int x, int y, void *color) {
- UINT8 *in = (UINT8 *)&im->image[y][x * 4];
-#ifdef WORDS_BIGENDIAN
- memcpy(color, in, sizeof(INT32));
-#else
- INT32 out = in[3] + (in[2] << 8) + (in[1] << 16) + (in[0] << 24);
- memcpy(color, &out, sizeof(out));
-#endif
-}
-
/* store individual pixel */
static void
@@ -129,71 +74,46 @@ put_pixel_16B(Imaging im, int x, int y, const void *color) {
out[1] = in[0];
}
-static void
-put_pixel_32L(Imaging im, int x, int y, const void *color) {
- memcpy(&im->image8[y][x * 4], color, 4);
-}
-
-static void
-put_pixel_32B(Imaging im, int x, int y, const void *color) {
- const char *in = color;
- UINT8 *out = (UINT8 *)&im->image8[y][x * 4];
- out[0] = in[3];
- out[1] = in[2];
- out[2] = in[1];
- out[3] = in[0];
-}
-
static void
put_pixel_32(Imaging im, int x, int y, const void *color) {
memcpy(&im->image32[y][x], color, sizeof(INT32));
}
-void
-ImagingAccessInit(void) {
-#define ADD(mode_, get_pixel_, put_pixel_) \
- { \
- ImagingAccess access = add_item(mode_); \
- access->get_pixel = get_pixel_; \
- access->put_pixel = put_pixel_; \
- }
-
- /* populate access table */
- ADD("1", get_pixel_8, put_pixel_8);
- ADD("L", get_pixel_8, put_pixel_8);
- ADD("LA", get_pixel_32_2bands, put_pixel_32);
- ADD("La", get_pixel_32_2bands, put_pixel_32);
- ADD("I", get_pixel_32, put_pixel_32);
- ADD("I;16", get_pixel_16L, put_pixel_16L);
- ADD("I;16L", get_pixel_16L, put_pixel_16L);
- ADD("I;16B", get_pixel_16B, put_pixel_16B);
+static struct ImagingAccessInstance accessors[] = {
+ {IMAGING_MODE_1, get_pixel_8, put_pixel_8},
+ {IMAGING_MODE_L, get_pixel_8, put_pixel_8},
+ {IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32},
+ {IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32},
+ {IMAGING_MODE_I, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_I_16, get_pixel_16L, put_pixel_16L},
+ {IMAGING_MODE_I_16L, get_pixel_16L, put_pixel_16L},
+ {IMAGING_MODE_I_16B, get_pixel_16B, put_pixel_16B},
#ifdef WORDS_BIGENDIAN
- ADD("I;16N", get_pixel_16B, put_pixel_16B);
+ {IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B},
#else
- ADD("I;16N", get_pixel_16L, put_pixel_16L);
+ {IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L},
#endif
- ADD("I;32L", get_pixel_32L, put_pixel_32L);
- ADD("I;32B", get_pixel_32B, put_pixel_32B);
- ADD("F", get_pixel_32, put_pixel_32);
- ADD("P", get_pixel_8, put_pixel_8);
- ADD("PA", get_pixel_32_2bands, put_pixel_32);
- ADD("RGB", get_pixel_32, put_pixel_32);
- ADD("RGBA", get_pixel_32, put_pixel_32);
- ADD("RGBa", get_pixel_32, put_pixel_32);
- ADD("RGBX", get_pixel_32, put_pixel_32);
- ADD("CMYK", get_pixel_32, put_pixel_32);
- ADD("YCbCr", get_pixel_32, put_pixel_32);
- ADD("LAB", get_pixel_32, put_pixel_32);
- ADD("HSV", get_pixel_32, put_pixel_32);
-}
+ {IMAGING_MODE_F, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_P, get_pixel_8, put_pixel_8},
+ {IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32},
+ {IMAGING_MODE_RGB, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_LAB, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_HSV, get_pixel_32, put_pixel_32},
+};
ImagingAccess
-ImagingAccessNew(Imaging im) {
- ImagingAccess access = &access_table[hash(im->mode)];
- if (im->mode[0] != access->mode[0] || strcmp(im->mode, access->mode) != 0) {
- return NULL;
+ImagingAccessNew(const Imaging im) {
+ for (size_t i = 0; i < sizeof(accessors) / sizeof(*accessors); i++) {
+ if (im->mode == accessors[i].mode) {
+ return &accessors[i];
+ }
}
- return access;
+ return NULL;
}
void
diff --git a/src/libImaging/AlphaComposite.c b/src/libImaging/AlphaComposite.c
index 44c451679..280277e83 100644
--- a/src/libImaging/AlphaComposite.c
+++ b/src/libImaging/AlphaComposite.c
@@ -26,11 +26,11 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) {
/* Check arguments */
if (!imDst || !imSrc ||
- (strcmp(imDst->mode, "RGBA") && strcmp(imDst->mode, "LA"))) {
+ (imDst->mode != IMAGING_MODE_RGBA && imDst->mode != IMAGING_MODE_LA)) {
return ImagingError_ModeError();
}
- if (strcmp(imDst->mode, imSrc->mode) || imDst->xsize != imSrc->xsize ||
+ if (imDst->mode != imSrc->mode || imDst->xsize != imSrc->xsize ||
imDst->ysize != imSrc->ysize) {
return ImagingError_Mismatch();
}
diff --git a/src/libImaging/Arrow.c b/src/libImaging/Arrow.c
index ccafe33b9..e353ab2e9 100644
--- a/src/libImaging/Arrow.c
+++ b/src/libImaging/Arrow.c
@@ -55,6 +55,98 @@ ReleaseExportedSchema(struct ArrowSchema *array) {
// Mark array released
array->release = NULL;
}
+char *
+image_band_json(Imaging im) {
+ char *format = "{\"bands\": [\"%s\", \"%s\", \"%s\", \"%s\"]}";
+ char *json;
+ // Bands can be 4 bands * 2 characters each
+ int len = strlen(format) + 8 + 1;
+ int err;
+
+ json = calloc(1, len);
+
+ if (!json) {
+ return NULL;
+ }
+
+ err = PyOS_snprintf(
+ json,
+ len,
+ format,
+ im->band_names[0],
+ im->band_names[1],
+ im->band_names[2],
+ im->band_names[3]
+ );
+ if (err < 0) {
+ return NULL;
+ }
+ return json;
+}
+
+char *
+single_band_json(Imaging im) {
+ char *format = "{\"bands\": [\"%s\"]}";
+ char *json;
+ // Bands can be 1 band * (maybe but probably not) 2 characters each
+ int len = strlen(format) + 2 + 1;
+ int err;
+
+ json = calloc(1, len);
+
+ if (!json) {
+ return NULL;
+ }
+
+ err = PyOS_snprintf(json, len, format, im->band_names[0]);
+ if (err < 0) {
+ return NULL;
+ }
+ return json;
+}
+
+char *
+assemble_metadata(const char *band_json) {
+ /* format is
+ int32: number of key/value pairs (noted N below)
+ int32: byte length of key 0
+ key 0 (not null-terminated)
+ int32: byte length of value 0
+ value 0 (not null-terminated)
+ ...
+ int32: byte length of key N - 1
+ key N - 1 (not null-terminated)
+ int32: byte length of value N - 1
+ value N - 1 (not null-terminated)
+ */
+ const char *key = "image";
+ INT32 key_len = strlen(key);
+ INT32 band_json_len = strlen(band_json);
+
+ char *buf;
+ INT32 *dest_int;
+ char *dest;
+
+ buf = calloc(1, key_len + band_json_len + 4 + 1 * 8);
+ if (!buf) {
+ return NULL;
+ }
+
+ dest_int = (void *)buf;
+
+ dest_int[0] = 1;
+ dest_int[1] = key_len;
+ dest_int += 2;
+ dest = (void *)dest_int;
+ memcpy(dest, key, key_len);
+ dest += key_len;
+ dest_int = (void *)dest;
+ dest_int[0] = band_json_len;
+ dest_int += 1;
+ memcpy(dest_int, band_json, band_json_len);
+
+ return buf;
+}
int
export_named_type(struct ArrowSchema *schema, char *format, char *name) {
@@ -95,6 +187,7 @@ export_named_type(struct ArrowSchema *schema, char *format, char *name) {
int
export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
int retval = 0;
+ char *band_json;
if (strcmp(im->arrow_band_format, "") == 0) {
return IMAGING_ARROW_INCOMPATIBLE_MODE;
@@ -106,7 +199,17 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
}
if (im->bands == 1) {
- return export_named_type(schema, im->arrow_band_format, im->band_names[0]);
+ retval = export_named_type(schema, im->arrow_band_format, im->band_names[0]);
+ if (retval != 0) {
+ return retval;
+ }
+ // band related metadata
+ band_json = single_band_json(im);
+ if (band_json) {
+ schema->metadata = assemble_metadata(band_json);
+ free(band_json);
+ }
+ return retval;
}
retval = export_named_type(schema, "+w:4", "");
@@ -117,13 +220,26 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
schema->n_children = 1;
schema->children = calloc(1, sizeof(struct ArrowSchema *));
schema->children[0] = (struct ArrowSchema *)calloc(1, sizeof(struct ArrowSchema));
- retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel");
+ retval = export_named_type(
+ schema->children[0], im->arrow_band_format, getModeData(im->mode)->name
+ );
if (retval != 0) {
free(schema->children[0]);
free(schema->children);
schema->release(schema);
return retval;
}
+
+ // band related metadata
+ band_json = image_band_json(im);
+ if (band_json) {
+ // adding the metadata to the child array.
+ // Accessible in pyarrow via pa.array(img).type.field(0).metadata
+ // adding it to the top level is not accessible.
+ schema->children[0]->metadata = assemble_metadata(band_json);
+ free(band_json);
+ }
+
return 0;
}
diff --git a/src/libImaging/Bands.c b/src/libImaging/Bands.c
index e1b16b34a..d1b0ebc4e 100644
--- a/src/libImaging/Bands.c
+++ b/src/libImaging/Bands.c
@@ -41,7 +41,7 @@ ImagingGetBand(Imaging imIn, int band) {
band = 3;
}
- imOut = ImagingNewDirty("L", imIn->xsize, imIn->ysize);
+ imOut = ImagingNewDirty(IMAGING_MODE_L, imIn->xsize, imIn->ysize);
if (!imOut) {
return NULL;
}
@@ -82,7 +82,7 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) {
}
for (i = 0; i < imIn->bands; i++) {
- bands[i] = ImagingNewDirty("L", imIn->xsize, imIn->ysize);
+ bands[i] = ImagingNewDirty(IMAGING_MODE_L, imIn->xsize, imIn->ysize);
if (!bands[i]) {
for (j = 0; j < i; ++j) {
ImagingDelete(bands[j]);
@@ -240,7 +240,7 @@ ImagingFillBand(Imaging imOut, int band, int color) {
}
Imaging
-ImagingMerge(const char *mode, Imaging bands[4]) {
+ImagingMerge(const ModeID mode, Imaging bands[4]) {
int i, x, y;
int bandsCount = 0;
Imaging imOut;
diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c
index 7b3d8f908..ac81ed6df 100644
--- a/src/libImaging/BcnDecode.c
+++ b/src/libImaging/BcnDecode.c
@@ -603,7 +603,7 @@ static void
bc6_sign_extend(UINT16 *v, int prec) {
int x = *v;
if (x & (1 << (prec - 1))) {
- x |= -1 << prec;
+ x |= -(1 << prec);
}
*v = (UINT16)x;
}
diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c
index 7a5072dde..973a7a2fa 100644
--- a/src/libImaging/BcnEncode.c
+++ b/src/libImaging/BcnEncode.c
@@ -36,10 +36,9 @@ decode_565(UINT16 x) {
static UINT16
encode_565(rgba item) {
- UINT8 r, g, b;
- r = item.color[0] >> (8 - 5);
- g = item.color[1] >> (8 - 6);
- b = item.color[2] >> (8 - 5);
+ UINT16 r = item.color[0] >> (8 - 5);
+ UINT8 g = item.color[1] >> (8 - 6);
+ UINT8 b = item.color[2] >> (8 - 5);
return (r << (5 + 6)) | (g << 5) | b;
}
@@ -157,7 +156,8 @@ encode_bc1_color(Imaging im, ImagingCodecState state, UINT8 *dst, int separate_a
static void
encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) {
int i, j;
- UINT8 block[16], current_alpha;
+ UINT8 block[16];
+ UINT32 current_alpha;
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
int x = state->x + i * im->pixelsize;
@@ -253,7 +253,7 @@ int
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
int n = state->state;
int has_alpha_channel =
- strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "LA") == 0;
+ im->mode == IMAGING_MODE_RGBA || im->mode == IMAGING_MODE_LA;
UINT8 *dst = buf;
diff --git a/src/libImaging/Blend.c b/src/libImaging/Blend.c
index a53ae0fad..df94920f6 100644
--- a/src/libImaging/Blend.c
+++ b/src/libImaging/Blend.c
@@ -24,8 +24,8 @@ ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha) {
/* Check arguments */
if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 || imIn1->palette ||
- strcmp(imIn1->mode, "1") == 0 || imIn2->palette ||
- strcmp(imIn2->mode, "1") == 0) {
+ imIn1->mode == IMAGING_MODE_1 || imIn2->palette ||
+ imIn2->mode == IMAGING_MODE_1) {
return ImagingError_ModeError();
}
diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c
index ed91541fe..4fea4fe44 100644
--- a/src/libImaging/BoxBlur.c
+++ b/src/libImaging/BoxBlur.c
@@ -248,7 +248,7 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
return ImagingError_ValueError("radius must be >= 0");
}
- if (strcmp(imIn->mode, imOut->mode) || imIn->type != imOut->type ||
+ if (imIn->mode != imOut->mode || imIn->type != imOut->type ||
imIn->bands != imOut->bands || imIn->xsize != imOut->xsize ||
imIn->ysize != imOut->ysize) {
return ImagingError_Mismatch();
@@ -258,10 +258,10 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
return ImagingError_ModeError();
}
- if (!(strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "RGBA") == 0 ||
- strcmp(imIn->mode, "RGBa") == 0 || strcmp(imIn->mode, "RGBX") == 0 ||
- strcmp(imIn->mode, "CMYK") == 0 || strcmp(imIn->mode, "L") == 0 ||
- strcmp(imIn->mode, "LA") == 0 || strcmp(imIn->mode, "La") == 0)) {
+ if (imIn->mode != IMAGING_MODE_RGB && imIn->mode != IMAGING_MODE_RGBA &&
+ imIn->mode != IMAGING_MODE_RGBa && imIn->mode != IMAGING_MODE_RGBX &&
+ imIn->mode != IMAGING_MODE_CMYK && imIn->mode != IMAGING_MODE_L &&
+ imIn->mode != IMAGING_MODE_LA && imIn->mode != IMAGING_MODE_La) {
return ImagingError_ModeError();
}
diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c
index f326d402f..3ce8a0903 100644
--- a/src/libImaging/Chops.c
+++ b/src/libImaging/Chops.c
@@ -18,28 +18,28 @@
#include "Imaging.h"
-#define CHOP(operation) \
- int x, y; \
- Imaging imOut; \
- imOut = create(imIn1, imIn2, NULL); \
- if (!imOut) { \
- return NULL; \
- } \
- for (y = 0; y < imOut->ysize; y++) { \
- UINT8 *out = (UINT8 *)imOut->image[y]; \
- UINT8 *in1 = (UINT8 *)imIn1->image[y]; \
- UINT8 *in2 = (UINT8 *)imIn2->image[y]; \
- for (x = 0; x < imOut->linesize; x++) { \
- int temp = operation; \
- if (temp <= 0) { \
- out[x] = 0; \
- } else if (temp >= 255) { \
- out[x] = 255; \
- } else { \
- out[x] = temp; \
- } \
- } \
- } \
+#define CHOP(operation) \
+ int x, y; \
+ Imaging imOut; \
+ imOut = create(imIn1, imIn2, IMAGING_MODE_UNKNOWN); \
+ if (!imOut) { \
+ return NULL; \
+ } \
+ for (y = 0; y < imOut->ysize; y++) { \
+ UINT8 *out = (UINT8 *)imOut->image[y]; \
+ UINT8 *in1 = (UINT8 *)imIn1->image[y]; \
+ UINT8 *in2 = (UINT8 *)imIn2->image[y]; \
+ for (x = 0; x < imOut->linesize; x++) { \
+ int temp = operation; \
+ if (temp <= 0) { \
+ out[x] = 0; \
+ } else if (temp >= 255) { \
+ out[x] = 255; \
+ } else { \
+ out[x] = temp; \
+ } \
+ } \
+ } \
return imOut;
#define CHOP2(operation, mode) \
@@ -60,11 +60,12 @@
return imOut;
static Imaging
-create(Imaging im1, Imaging im2, char *mode) {
+create(Imaging im1, Imaging im2, const ModeID mode) {
int xsize, ysize;
if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 ||
- (mode != NULL && (strcmp(im1->mode, "1") || strcmp(im2->mode, "1")))) {
+ (mode != IMAGING_MODE_UNKNOWN &&
+ (im1->mode != IMAGING_MODE_1 || im2->mode != IMAGING_MODE_1))) {
return (Imaging)ImagingError_ModeError();
}
if (im1->type != im2->type || im1->bands != im2->bands) {
@@ -114,27 +115,27 @@ ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset) {
Imaging
ImagingChopAnd(Imaging imIn1, Imaging imIn2) {
- CHOP2((in1[x] && in2[x]) ? 255 : 0, "1");
+ CHOP2((in1[x] && in2[x]) ? 255 : 0, IMAGING_MODE_1);
}
Imaging
ImagingChopOr(Imaging imIn1, Imaging imIn2) {
- CHOP2((in1[x] || in2[x]) ? 255 : 0, "1");
+ CHOP2((in1[x] || in2[x]) ? 255 : 0, IMAGING_MODE_1);
}
Imaging
ImagingChopXor(Imaging imIn1, Imaging imIn2) {
- CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, "1");
+ CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, IMAGING_MODE_1);
}
Imaging
ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) {
- CHOP2(in1[x] + in2[x], NULL);
+ CHOP2(in1[x] + in2[x], IMAGING_MODE_UNKNOWN);
}
Imaging
ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) {
- CHOP2(in1[x] - in2[x], NULL);
+ CHOP2(in1[x] - in2[x], IMAGING_MODE_UNKNOWN);
}
Imaging
@@ -142,7 +143,7 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) {
CHOP2(
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
- NULL
+ IMAGING_MODE_UNKNOWN
);
}
@@ -151,7 +152,7 @@ ImagingChopHardLight(Imaging imIn1, Imaging imIn2) {
CHOP2(
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
- NULL
+ IMAGING_MODE_UNKNOWN
);
}
@@ -160,6 +161,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) {
CHOP2(
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
- NULL
+ IMAGING_MODE_UNKNOWN
);
}
diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c
index 9a2c9ff16..330e5325c 100644
--- a/src/libImaging/Convert.c
+++ b/src/libImaging/Convert.c
@@ -877,147 +877,12 @@ I16_RGB(UINT8 *out, const UINT8 *in, int xsize) {
}
}
-static struct {
- const char *from;
- const char *to;
- ImagingShuffler convert;
-} converters[] = {
-
- {"1", "L", bit2l},
- {"1", "I", bit2i},
- {"1", "F", bit2f},
- {"1", "RGB", bit2rgb},
- {"1", "RGBA", bit2rgb},
- {"1", "RGBX", bit2rgb},
- {"1", "CMYK", bit2cmyk},
- {"1", "YCbCr", bit2ycbcr},
- {"1", "HSV", bit2hsv},
-
- {"L", "1", l2bit},
- {"L", "LA", l2la},
- {"L", "I", l2i},
- {"L", "F", l2f},
- {"L", "RGB", l2rgb},
- {"L", "RGBA", l2rgb},
- {"L", "RGBX", l2rgb},
- {"L", "CMYK", l2cmyk},
- {"L", "YCbCr", l2ycbcr},
- {"L", "HSV", l2hsv},
-
- {"LA", "L", la2l},
- {"LA", "La", lA2la},
- {"LA", "RGB", la2rgb},
- {"LA", "RGBA", la2rgb},
- {"LA", "RGBX", la2rgb},
- {"LA", "CMYK", la2cmyk},
- {"LA", "YCbCr", la2ycbcr},
- {"LA", "HSV", la2hsv},
-
- {"La", "LA", la2lA},
-
- {"I", "L", i2l},
- {"I", "F", i2f},
- {"I", "RGB", i2rgb},
- {"I", "RGBA", i2rgb},
- {"I", "RGBX", i2rgb},
- {"I", "HSV", i2hsv},
-
- {"F", "L", f2l},
- {"F", "I", f2i},
-
- {"RGB", "1", rgb2bit},
- {"RGB", "L", rgb2l},
- {"RGB", "LA", rgb2la},
- {"RGB", "La", rgb2la},
- {"RGB", "I", rgb2i},
- {"RGB", "I;16", rgb2i16l},
- {"RGB", "I;16L", rgb2i16l},
- {"RGB", "I;16B", rgb2i16b},
-#ifdef WORDS_BIGENDIAN
- {"RGB", "I;16N", rgb2i16b},
-#else
- {"RGB", "I;16N", rgb2i16l},
-#endif
- {"RGB", "F", rgb2f},
- {"RGB", "RGBA", rgb2rgba},
- {"RGB", "RGBa", rgb2rgba},
- {"RGB", "RGBX", rgb2rgba},
- {"RGB", "CMYK", rgb2cmyk},
- {"RGB", "YCbCr", ImagingConvertRGB2YCbCr},
- {"RGB", "HSV", rgb2hsv},
-
- {"RGBA", "1", rgb2bit},
- {"RGBA", "L", rgb2l},
- {"RGBA", "LA", rgba2la},
- {"RGBA", "I", rgb2i},
- {"RGBA", "F", rgb2f},
- {"RGBA", "RGB", rgba2rgb},
- {"RGBA", "RGBa", rgbA2rgba},
- {"RGBA", "RGBX", rgb2rgba},
- {"RGBA", "CMYK", rgb2cmyk},
- {"RGBA", "YCbCr", ImagingConvertRGB2YCbCr},
- {"RGBA", "HSV", rgb2hsv},
-
- {"RGBa", "RGBA", rgba2rgbA},
- {"RGBa", "RGB", rgba2rgb_},
-
- {"RGBX", "1", rgb2bit},
- {"RGBX", "L", rgb2l},
- {"RGBX", "LA", rgb2la},
- {"RGBX", "I", rgb2i},
- {"RGBX", "F", rgb2f},
- {"RGBX", "RGB", rgba2rgb},
- {"RGBX", "CMYK", rgb2cmyk},
- {"RGBX", "YCbCr", ImagingConvertRGB2YCbCr},
- {"RGBX", "HSV", rgb2hsv},
-
- {"CMYK", "RGB", cmyk2rgb},
- {"CMYK", "RGBA", cmyk2rgb},
- {"CMYK", "RGBX", cmyk2rgb},
- {"CMYK", "HSV", cmyk2hsv},
-
- {"YCbCr", "L", ycbcr2l},
- {"YCbCr", "LA", ycbcr2la},
- {"YCbCr", "RGB", ImagingConvertYCbCr2RGB},
-
- {"HSV", "RGB", hsv2rgb},
-
- {"I", "I;16", I_I16L},
- {"I;16", "I", I16L_I},
- {"I;16", "RGB", I16_RGB},
- {"L", "I;16", L_I16L},
- {"I;16", "L", I16L_L},
-
- {"I", "I;16L", I_I16L},
- {"I;16L", "I", I16L_I},
- {"I", "I;16B", I_I16B},
- {"I;16B", "I", I16B_I},
-
- {"L", "I;16L", L_I16L},
- {"I;16L", "L", I16L_L},
- {"L", "I;16B", L_I16B},
- {"I;16B", "L", I16B_L},
-#ifdef WORDS_BIGENDIAN
- {"L", "I;16N", L_I16B},
- {"I;16N", "L", I16B_L},
-#else
- {"L", "I;16N", L_I16L},
- {"I;16N", "L", I16L_L},
-#endif
-
- {"I;16", "F", I16L_F},
- {"I;16L", "F", I16L_F},
- {"I;16B", "F", I16B_F},
-
- {NULL}
-};
-
-/* FIXME: translate indexed versions to pointer versions below this line */
-
/* ------------------- */
/* Palette conversions */
/* ------------------- */
+/* FIXME: translate indexed versions to pointer versions below this line */
+
static void
p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
int x;
@@ -1065,13 +930,13 @@ pa2p(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
static void
p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
int x;
- int rgb = strcmp(palette->mode, "RGB");
+ const int rgb = palette->mode == IMAGING_MODE_RGB;
for (x = 0; x < xsize; x++, in++) {
const UINT8 *rgba = &palette->palette[in[0] * 4];
*out++ = in[0];
*out++ = in[0];
*out++ = in[0];
- *out++ = rgb == 0 ? 255 : rgba[3];
+ *out++ = rgb ? 255 : rgba[3];
}
}
@@ -1225,7 +1090,7 @@ pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
}
static Imaging
-frompalette(Imaging imOut, Imaging imIn, const char *mode) {
+frompalette(Imaging imOut, Imaging imIn, const ModeID mode) {
ImagingSectionCookie cookie;
int alpha;
int y;
@@ -1237,31 +1102,31 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
return (Imaging)ImagingError_ValueError("no palette");
}
- alpha = !strcmp(imIn->mode, "PA");
+ alpha = imIn->mode == IMAGING_MODE_PA;
- if (strcmp(mode, "1") == 0) {
+ if (mode == IMAGING_MODE_1) {
convert = alpha ? pa2bit : p2bit;
- } else if (strcmp(mode, "L") == 0) {
+ } else if (mode == IMAGING_MODE_L) {
convert = alpha ? pa2l : p2l;
- } else if (strcmp(mode, "LA") == 0) {
+ } else if (mode == IMAGING_MODE_LA) {
convert = alpha ? pa2la : p2la;
- } else if (strcmp(mode, "P") == 0) {
+ } else if (mode == IMAGING_MODE_P) {
convert = pa2p;
- } else if (strcmp(mode, "PA") == 0) {
+ } else if (mode == IMAGING_MODE_PA) {
convert = p2pa;
- } else if (strcmp(mode, "I") == 0) {
+ } else if (mode == IMAGING_MODE_I) {
convert = alpha ? pa2i : p2i;
- } else if (strcmp(mode, "F") == 0) {
+ } else if (mode == IMAGING_MODE_F) {
convert = alpha ? pa2f : p2f;
- } else if (strcmp(mode, "RGB") == 0) {
+ } else if (mode == IMAGING_MODE_RGB) {
convert = alpha ? pa2rgb : p2rgb;
- } else if (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBX") == 0) {
+ } else if (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBX) {
convert = alpha ? pa2rgba : p2rgba;
- } else if (strcmp(mode, "CMYK") == 0) {
+ } else if (mode == IMAGING_MODE_CMYK) {
convert = alpha ? pa2cmyk : p2cmyk;
- } else if (strcmp(mode, "YCbCr") == 0) {
+ } else if (mode == IMAGING_MODE_YCbCr) {
convert = alpha ? pa2ycbcr : p2ycbcr;
- } else if (strcmp(mode, "HSV") == 0) {
+ } else if (mode == IMAGING_MODE_HSV) {
convert = alpha ? pa2hsv : p2hsv;
} else {
return (Imaging)ImagingError_ValueError("conversion not supported");
@@ -1271,7 +1136,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
if (!imOut) {
return NULL;
}
- if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
+ if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) {
ImagingPaletteDelete(imOut->palette);
imOut->palette = ImagingPaletteDuplicate(imIn->palette);
}
@@ -1295,24 +1160,26 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
#endif
static Imaging
topalette(
- Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither
+ Imaging imOut, Imaging imIn, const ModeID mode, ImagingPalette inpalette, int dither
) {
ImagingSectionCookie cookie;
int alpha;
int x, y;
ImagingPalette palette = inpalette;
- /* Map L or RGB/RGBX/RGBA to palette image */
- if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) {
+ /* Map L or RGB/RGBX/RGBA/RGBa to palette image */
+ if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB &&
+ imIn->mode != IMAGING_MODE_RGBX && imIn->mode != IMAGING_MODE_RGBA &&
+ imIn->mode != IMAGING_MODE_RGBa) {
return (Imaging)ImagingError_ValueError("conversion not supported");
}
- alpha = !strcmp(mode, "PA");
+ alpha = mode == IMAGING_MODE_PA;
if (palette == NULL) {
/* FIXME: make user configurable */
if (imIn->bands == 1) {
- palette = ImagingPaletteNew("RGB");
+ palette = ImagingPaletteNew(IMAGING_MODE_RGB);
palette->size = 256;
int i;
@@ -1499,11 +1366,11 @@ tobilevel(Imaging imOut, Imaging imIn) {
int *errors;
/* Map L or RGB to dithered 1 image */
- if (strcmp(imIn->mode, "L") != 0 && strcmp(imIn->mode, "RGB") != 0) {
+ if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB) {
return (Imaging)ImagingError_ValueError("conversion not supported");
}
- imOut = ImagingNew2Dirty("1", imOut, imIn);
+ imOut = ImagingNew2Dirty(IMAGING_MODE_1, imOut, imIn);
if (!imOut) {
return NULL;
}
@@ -1585,19 +1452,152 @@ tobilevel(Imaging imOut, Imaging imIn) {
#pragma optimize("", on)
#endif
+/* ------------------- */
+/* Conversion handlers */
+/* ------------------- */
+
+static struct {
+ const ModeID from;
+ const ModeID to;
+ ImagingShuffler convert;
+} converters[] = {
+ {IMAGING_MODE_1, IMAGING_MODE_L, bit2l},
+ {IMAGING_MODE_1, IMAGING_MODE_I, bit2i},
+ {IMAGING_MODE_1, IMAGING_MODE_F, bit2f},
+ {IMAGING_MODE_1, IMAGING_MODE_RGB, bit2rgb},
+ {IMAGING_MODE_1, IMAGING_MODE_RGBA, bit2rgb},
+ {IMAGING_MODE_1, IMAGING_MODE_RGBX, bit2rgb},
+ {IMAGING_MODE_1, IMAGING_MODE_CMYK, bit2cmyk},
+ {IMAGING_MODE_1, IMAGING_MODE_YCbCr, bit2ycbcr},
+ {IMAGING_MODE_1, IMAGING_MODE_HSV, bit2hsv},
+
+ {IMAGING_MODE_L, IMAGING_MODE_1, l2bit},
+ {IMAGING_MODE_L, IMAGING_MODE_LA, l2la},
+ {IMAGING_MODE_L, IMAGING_MODE_I, l2i},
+ {IMAGING_MODE_L, IMAGING_MODE_F, l2f},
+ {IMAGING_MODE_L, IMAGING_MODE_RGB, l2rgb},
+ {IMAGING_MODE_L, IMAGING_MODE_RGBA, l2rgb},
+ {IMAGING_MODE_L, IMAGING_MODE_RGBX, l2rgb},
+ {IMAGING_MODE_L, IMAGING_MODE_CMYK, l2cmyk},
+ {IMAGING_MODE_L, IMAGING_MODE_YCbCr, l2ycbcr},
+ {IMAGING_MODE_L, IMAGING_MODE_HSV, l2hsv},
+
+ {IMAGING_MODE_LA, IMAGING_MODE_L, la2l},
+ {IMAGING_MODE_LA, IMAGING_MODE_La, lA2la},
+ {IMAGING_MODE_LA, IMAGING_MODE_RGB, la2rgb},
+ {IMAGING_MODE_LA, IMAGING_MODE_RGBA, la2rgb},
+ {IMAGING_MODE_LA, IMAGING_MODE_RGBX, la2rgb},
+ {IMAGING_MODE_LA, IMAGING_MODE_CMYK, la2cmyk},
+ {IMAGING_MODE_LA, IMAGING_MODE_YCbCr, la2ycbcr},
+ {IMAGING_MODE_LA, IMAGING_MODE_HSV, la2hsv},
+
+ {IMAGING_MODE_La, IMAGING_MODE_LA, la2lA},
+
+ {IMAGING_MODE_I, IMAGING_MODE_L, i2l},
+ {IMAGING_MODE_I, IMAGING_MODE_F, i2f},
+ {IMAGING_MODE_I, IMAGING_MODE_RGB, i2rgb},
+ {IMAGING_MODE_I, IMAGING_MODE_RGBA, i2rgb},
+ {IMAGING_MODE_I, IMAGING_MODE_RGBX, i2rgb},
+ {IMAGING_MODE_I, IMAGING_MODE_HSV, i2hsv},
+
+ {IMAGING_MODE_F, IMAGING_MODE_L, f2l},
+ {IMAGING_MODE_F, IMAGING_MODE_I, f2i},
+
+ {IMAGING_MODE_RGB, IMAGING_MODE_1, rgb2bit},
+ {IMAGING_MODE_RGB, IMAGING_MODE_L, rgb2l},
+ {IMAGING_MODE_RGB, IMAGING_MODE_LA, rgb2la},
+ {IMAGING_MODE_RGB, IMAGING_MODE_La, rgb2la},
+ {IMAGING_MODE_RGB, IMAGING_MODE_I, rgb2i},
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16, rgb2i16l},
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16L, rgb2i16l},
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16B, rgb2i16b},
+#ifdef WORDS_BIGENDIAN
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16b},
+#else
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16l},
+#endif
+ {IMAGING_MODE_RGB, IMAGING_MODE_F, rgb2f},
+ {IMAGING_MODE_RGB, IMAGING_MODE_RGBA, rgb2rgba},
+ {IMAGING_MODE_RGB, IMAGING_MODE_RGBa, rgb2rgba},
+ {IMAGING_MODE_RGB, IMAGING_MODE_RGBX, rgb2rgba},
+ {IMAGING_MODE_RGB, IMAGING_MODE_CMYK, rgb2cmyk},
+ {IMAGING_MODE_RGB, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
+ {IMAGING_MODE_RGB, IMAGING_MODE_HSV, rgb2hsv},
+
+ {IMAGING_MODE_RGBA, IMAGING_MODE_1, rgb2bit},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_L, rgb2l},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_LA, rgba2la},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_I, rgb2i},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_F, rgb2f},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_RGB, rgba2rgb},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_RGBa, rgbA2rgba},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_RGBX, rgb2rgba},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_CMYK, rgb2cmyk},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_HSV, rgb2hsv},
+
+ {IMAGING_MODE_RGBa, IMAGING_MODE_RGBA, rgba2rgbA},
+ {IMAGING_MODE_RGBa, IMAGING_MODE_RGB, rgba2rgb_},
+
+ {IMAGING_MODE_RGBX, IMAGING_MODE_1, rgb2bit},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_L, rgb2l},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_LA, rgb2la},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_I, rgb2i},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_F, rgb2f},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_RGB, rgba2rgb},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_CMYK, rgb2cmyk},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_HSV, rgb2hsv},
+
+ {IMAGING_MODE_CMYK, IMAGING_MODE_RGB, cmyk2rgb},
+ {IMAGING_MODE_CMYK, IMAGING_MODE_RGBA, cmyk2rgb},
+ {IMAGING_MODE_CMYK, IMAGING_MODE_RGBX, cmyk2rgb},
+ {IMAGING_MODE_CMYK, IMAGING_MODE_HSV, cmyk2hsv},
+
+ {IMAGING_MODE_YCbCr, IMAGING_MODE_L, ycbcr2l},
+ {IMAGING_MODE_YCbCr, IMAGING_MODE_LA, ycbcr2la},
+ {IMAGING_MODE_YCbCr, IMAGING_MODE_RGB, ImagingConvertYCbCr2RGB},
+
+ {IMAGING_MODE_HSV, IMAGING_MODE_RGB, hsv2rgb},
+
+ {IMAGING_MODE_I, IMAGING_MODE_I_16, I_I16L},
+ {IMAGING_MODE_I_16, IMAGING_MODE_I, I16L_I},
+ {IMAGING_MODE_I_16, IMAGING_MODE_RGB, I16_RGB},
+ {IMAGING_MODE_L, IMAGING_MODE_I_16, L_I16L},
+ {IMAGING_MODE_I_16, IMAGING_MODE_L, I16L_L},
+
+ {IMAGING_MODE_I, IMAGING_MODE_I_16L, I_I16L},
+ {IMAGING_MODE_I_16L, IMAGING_MODE_I, I16L_I},
+ {IMAGING_MODE_I, IMAGING_MODE_I_16B, I_I16B},
+ {IMAGING_MODE_I_16B, IMAGING_MODE_I, I16B_I},
+
+ {IMAGING_MODE_L, IMAGING_MODE_I_16L, L_I16L},
+ {IMAGING_MODE_I_16L, IMAGING_MODE_L, I16L_L},
+ {IMAGING_MODE_L, IMAGING_MODE_I_16B, L_I16B},
+ {IMAGING_MODE_I_16B, IMAGING_MODE_L, I16B_L},
+#ifdef WORDS_BIGENDIAN
+ {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16B},
+ {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16B_L},
+#else
+ {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16L},
+ {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16L_L},
+#endif
+
+ {IMAGING_MODE_I_16, IMAGING_MODE_F, I16L_F},
+ {IMAGING_MODE_I_16L, IMAGING_MODE_F, I16L_F},
+ {IMAGING_MODE_I_16B, IMAGING_MODE_F, I16B_F}
+};
+
static Imaging
-convert(
- Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither
-) {
+convert(Imaging imOut, Imaging imIn, ModeID mode, ImagingPalette palette, int dither) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
- int y;
if (!imIn) {
return (Imaging)ImagingError_ModeError();
}
- if (!mode) {
+ if (mode == IMAGING_MODE_UNKNOWN) {
/* Map palette image to full depth */
if (!imIn->palette) {
return (Imaging)ImagingError_ModeError();
@@ -1605,33 +1605,31 @@ convert(
mode = imIn->palette->mode;
} else {
/* Same mode? */
- if (!strcmp(imIn->mode, mode)) {
+ if (imIn->mode == mode) {
return ImagingCopy2(imOut, imIn);
}
}
/* test for special conversions */
- if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) {
+ if (imIn->mode == IMAGING_MODE_P || imIn->mode == IMAGING_MODE_PA) {
return frompalette(imOut, imIn, mode);
}
- if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
+ if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) {
return topalette(imOut, imIn, mode, palette, dither);
}
- if (dither && strcmp(mode, "1") == 0) {
+ if (dither && mode == IMAGING_MODE_1) {
return tobilevel(imOut, imIn);
}
/* standard conversion machinery */
convert = NULL;
-
- for (y = 0; converters[y].from; y++) {
- if (!strcmp(imIn->mode, converters[y].from) &&
- !strcmp(mode, converters[y].to)) {
- convert = converters[y].convert;
+ for (size_t i = 0; i < sizeof(converters) / sizeof(*converters); i++) {
+ if (imIn->mode == converters[i].from && mode == converters[i].to) {
+ convert = converters[i].convert;
break;
}
}
@@ -1642,7 +1640,11 @@ convert(
#else
static char buf[100];
snprintf(
- buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode
+ buf,
+ 100,
+ "conversion from %.10s to %.10s not supported",
+ getModeData(imIn->mode)->name,
+ getModeData(mode)->name
);
return (Imaging)ImagingError_ValueError(buf);
#endif
@@ -1654,7 +1656,7 @@ convert(
}
ImagingSectionEnter(&cookie);
- for (y = 0; y < imIn->ysize; y++) {
+ for (int y = 0; y < imIn->ysize; y++) {
(*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
}
ImagingSectionLeave(&cookie);
@@ -1663,7 +1665,7 @@ convert(
}
Imaging
-ImagingConvert(Imaging imIn, const char *mode, ImagingPalette palette, int dither) {
+ImagingConvert(Imaging imIn, const ModeID mode, ImagingPalette palette, int dither) {
return convert(NULL, imIn, mode, palette, dither);
}
@@ -1673,7 +1675,7 @@ ImagingConvert2(Imaging imOut, Imaging imIn) {
}
Imaging
-ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
+ImagingConvertTransparent(Imaging imIn, const ModeID mode, int r, int g, int b) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
Imaging imOut = NULL;
@@ -1687,27 +1689,27 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
return (Imaging)ImagingError_ModeError();
}
- if (strcmp(imIn->mode, "RGB") == 0 &&
- (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) {
+ if (imIn->mode == IMAGING_MODE_RGB &&
+ (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBa)) {
convert = rgb2rgba;
- if (strcmp(mode, "RGBa") == 0) {
+ if (mode == IMAGING_MODE_RGBa) {
premultiplied = 1;
}
- } else if (strcmp(imIn->mode, "RGB") == 0 &&
- (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) {
+ } else if (imIn->mode == IMAGING_MODE_RGB &&
+ (mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)) {
convert = rgb2la;
source_transparency = 1;
- if (strcmp(mode, "La") == 0) {
+ if (mode == IMAGING_MODE_La) {
premultiplied = 1;
}
- } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 ||
- strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) &&
- (strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) {
- if (strcmp(imIn->mode, "1") == 0) {
+ } else if ((imIn->mode == IMAGING_MODE_1 || imIn->mode == IMAGING_MODE_I ||
+ imIn->mode == IMAGING_MODE_I_16 || imIn->mode == IMAGING_MODE_L) &&
+ (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_LA)) {
+ if (imIn->mode == IMAGING_MODE_1) {
convert = bit2rgb;
- } else if (strcmp(imIn->mode, "I") == 0) {
+ } else if (imIn->mode == IMAGING_MODE_I) {
convert = i2rgb;
- } else if (strcmp(imIn->mode, "I;16") == 0) {
+ } else if (imIn->mode == IMAGING_MODE_I_16) {
convert = I16_RGB;
} else {
convert = l2rgb;
@@ -1719,8 +1721,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
buf,
100,
"conversion from %.10s to %.10s not supported in convert_transparent",
- imIn->mode,
- mode
+ getModeData(imIn->mode)->name,
+ getModeData(mode)->name
);
return (Imaging)ImagingError_ValueError(buf);
}
@@ -1743,15 +1745,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
}
Imaging
-ImagingConvertInPlace(Imaging imIn, const char *mode) {
+ImagingConvertInPlace(Imaging imIn, const ModeID mode) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
int y;
/* limited support for inplace conversion */
- if (strcmp(imIn->mode, "L") == 0 && strcmp(mode, "1") == 0) {
+ if (imIn->mode == IMAGING_MODE_L && mode == IMAGING_MODE_1) {
convert = l2bit;
- } else if (strcmp(imIn->mode, "1") == 0 && strcmp(mode, "L") == 0) {
+ } else if (imIn->mode == IMAGING_MODE_1 && mode == IMAGING_MODE_L) {
convert = bit2l;
} else {
return ImagingError_ModeError();
diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c
index c69e9e552..2afe71d4a 100644
--- a/src/libImaging/Dib.c
+++ b/src/libImaging/Dib.c
@@ -25,20 +25,17 @@
#include "ImDib.h"
-char *
+ModeID
ImagingGetModeDIB(int size_out[2]) {
/* Get device characteristics */
- HDC dc;
- char *mode;
+ const HDC dc = CreateCompatibleDC(NULL);
- dc = CreateCompatibleDC(NULL);
-
- mode = "P";
+ ModeID mode = IMAGING_MODE_P;
if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) {
- mode = "RGB";
+ mode = IMAGING_MODE_RGB;
if (GetDeviceCaps(dc, BITSPIXEL) == 1) {
- mode = "1";
+ mode = IMAGING_MODE_1;
}
}
@@ -53,7 +50,7 @@ ImagingGetModeDIB(int size_out[2]) {
}
ImagingDIB
-ImagingNewDIB(const char *mode, int xsize, int ysize) {
+ImagingNewDIB(const ModeID mode, int xsize, int ysize) {
/* Create a Windows bitmap */
ImagingDIB dib;
@@ -61,10 +58,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
int i;
/* Check mode */
- if (strcmp(mode, "1") != 0 && strcmp(mode, "L") != 0 && strcmp(mode, "RGB") != 0) {
+ if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_L && mode != IMAGING_MODE_RGB) {
return (ImagingDIB)ImagingError_ModeError();
}
+ const int pixelsize = mode == IMAGING_MODE_RGB ? 3 : 1;
+
/* Create DIB context and info header */
/* malloc check ok, small constant allocation */
dib = (ImagingDIB)malloc(sizeof(*dib));
@@ -83,7 +82,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
dib->info->bmiHeader.biWidth = xsize;
dib->info->bmiHeader.biHeight = ysize;
dib->info->bmiHeader.biPlanes = 1;
- dib->info->bmiHeader.biBitCount = strlen(mode) * 8;
+ dib->info->bmiHeader.biBitCount = pixelsize * 8;
dib->info->bmiHeader.biCompression = BI_RGB;
/* Create DIB */
@@ -103,12 +102,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
return (ImagingDIB)ImagingError_MemoryError();
}
- strcpy(dib->mode, mode);
+ dib->mode = mode;
dib->xsize = xsize;
dib->ysize = ysize;
- dib->pixelsize = strlen(mode);
- dib->linesize = (xsize * dib->pixelsize + 3) & -4;
+ dib->pixelsize = pixelsize;
+ dib->linesize = (xsize * pixelsize + 3) & -4;
if (dib->pixelsize == 1) {
dib->pack = dib->unpack = (ImagingShuffler)memcpy;
@@ -132,7 +131,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
}
/* Create an associated palette (for 8-bit displays only) */
- if (strcmp(ImagingGetModeDIB(NULL), "P") == 0) {
+ if (ImagingGetModeDIB(NULL) == IMAGING_MODE_P) {
char palbuf[sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)];
LPLOGPALETTE pal = (LPLOGPALETTE)palbuf;
int i, r, g, b;
@@ -142,7 +141,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
pal->palNumEntries = 256;
GetSystemPaletteEntries(dib->dc, 0, 256, pal->palPalEntry);
- if (strcmp(mode, "L") == 0) {
+ if (mode == IMAGING_MODE_L) {
/* Grayscale DIB. Fill all 236 slots with a grayscale ramp
* (this is usually overkill on Windows since VGA only offers
* 6 bits grayscale resolution). Ignore the slots already
@@ -156,8 +155,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
}
dib->palette = CreatePalette(pal);
-
- } else if (strcmp(mode, "RGB") == 0) {
+ } else if (mode == IMAGING_MODE_RGB) {
#ifdef CUBE216
/* Colour DIB. Create a 6x6x6 colour cube (216 entries) and
diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c
index 27cac687e..d28980432 100644
--- a/src/libImaging/Draw.c
+++ b/src/libImaging/Draw.c
@@ -68,7 +68,7 @@ typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging);
static inline void
point8(Imaging im, int x, int y, int ink) {
if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) {
- if (strncmp(im->mode, "I;16", 4) == 0) {
+ if (isModeI16(im->mode)) {
#ifdef WORDS_BIGENDIAN
im->image8[y][x * 2] = (UINT8)(ink >> 8);
im->image8[y][x * 2 + 1] = (UINT8)ink;
@@ -117,13 +117,13 @@ hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
}
if (x0 <= x1) {
int bigendian = -1;
- if (strncmp(im->mode, "I;16", 4) == 0) {
+ if (isModeI16(im->mode)) {
bigendian =
(
#ifdef WORDS_BIGENDIAN
- strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16L") == 0
+ im->mode == IMAGING_MODE_I_16 || im->mode == IMAGING_MODE_I_16L
#else
- strcmp(im->mode, "I;16B") == 0
+ im->mode == IMAGING_MODE_I_16B
#endif
)
? 1
@@ -672,17 +672,17 @@ DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba};
/* Interface */
/* -------------------------------------------------------------------- */
-#define DRAWINIT() \
- if (im->image8) { \
- draw = &draw8; \
- if (strncmp(im->mode, "I;16", 4) == 0) { \
- ink = INK16(ink_); \
- } else { \
- ink = INK8(ink_); \
- } \
- } else { \
- draw = (op) ? &draw32rgba : &draw32; \
- memcpy(&ink, ink_, sizeof(ink)); \
+#define DRAWINIT() \
+ if (im->image8) { \
+ draw = &draw8; \
+ if (isModeI16(im->mode)) { \
+ ink = INK16(ink_); \
+ } else { \
+ ink = INK8(ink_); \
+ } \
+ } else { \
+ draw = (op) ? &draw32rgba : &draw32; \
+ memcpy(&ink, ink_, sizeof(ink)); \
}
int
diff --git a/src/libImaging/Effects.c b/src/libImaging/Effects.c
index 93e7af0bc..c05c5764e 100644
--- a/src/libImaging/Effects.c
+++ b/src/libImaging/Effects.c
@@ -36,7 +36,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) {
return (Imaging)ImagingError_ValueError(NULL);
}
- im = ImagingNewDirty("L", xsize, ysize);
+ im = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize);
if (!im) {
return NULL;
}
@@ -80,7 +80,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) {
int nextok;
double this, next;
- imOut = ImagingNewDirty("L", xsize, ysize);
+ imOut = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize);
if (!imOut) {
return NULL;
}
diff --git a/src/libImaging/File.c b/src/libImaging/File.c
index 901fe83ad..435dbeca0 100644
--- a/src/libImaging/File.c
+++ b/src/libImaging/File.c
@@ -23,14 +23,13 @@ int
ImagingSaveRaw(Imaging im, FILE *fp) {
int x, y, i;
- if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
+ if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
/* @PIL227: FIXME: for mode "1", map != 0 to 255 */
/* PGM "L" */
for (y = 0; y < im->ysize; y++) {
fwrite(im->image[y], 1, im->xsize, fp);
}
-
} else {
/* PPM "RGB" or other internal format */
for (y = 0; y < im->ysize; y++) {
@@ -58,10 +57,10 @@ ImagingSavePPM(Imaging im, const char *outfile) {
return 0;
}
- if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
+ if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
/* Write "PGM" */
fprintf(fp, "P5\n%d %d\n255\n", im->xsize, im->ysize);
- } else if (strcmp(im->mode, "RGB") == 0) {
+ } else if (im->mode == IMAGING_MODE_RGB) {
/* Write "PPM" */
fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize);
} else {
diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c
index 28f427370..cbd303204 100644
--- a/src/libImaging/Fill.c
+++ b/src/libImaging/Fill.c
@@ -68,11 +68,12 @@ ImagingFill(Imaging im, const void *colour) {
}
Imaging
-ImagingFillLinearGradient(const char *mode) {
+ImagingFillLinearGradient(const ModeID mode) {
Imaging im;
int y;
- if (strlen(mode) != 1) {
+ if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
+ mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
return (Imaging)ImagingError_ModeError();
}
@@ -102,12 +103,13 @@ ImagingFillLinearGradient(const char *mode) {
}
Imaging
-ImagingFillRadialGradient(const char *mode) {
+ImagingFillRadialGradient(const ModeID mode) {
Imaging im;
int x, y;
int d;
- if (strlen(mode) != 1) {
+ if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
+ mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
return (Imaging)ImagingError_ModeError();
}
diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c
index c46dd3cd1..cefb8fcdc 100644
--- a/src/libImaging/Filter.c
+++ b/src/libImaging/Filter.c
@@ -156,9 +156,9 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
int bigendian = 0;
if (im->type == IMAGING_TYPE_SPECIAL) {
if (
- strcmp(im->mode, "I;16B") == 0
+ im->mode == IMAGING_MODE_I_16B
#ifdef WORDS_BIGENDIAN
- || strcmp(im->mode, "I;16N") == 0
+ || im->mode == IMAGING_MODE_I_16N
#endif
) {
bigendian = 1;
@@ -310,9 +310,9 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
int bigendian = 0;
if (im->type == IMAGING_TYPE_SPECIAL) {
if (
- strcmp(im->mode, "I;16B") == 0
+ im->mode == IMAGING_MODE_I_16B
#ifdef WORDS_BIGENDIAN
- || strcmp(im->mode, "I;16N") == 0
+ || im->mode == IMAGING_MODE_I_16N
#endif
) {
bigendian = 1;
diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c
index 130ecb7f7..44994823e 100644
--- a/src/libImaging/FliDecode.c
+++ b/src/libImaging/FliDecode.c
@@ -16,9 +16,11 @@
#include "Imaging.h"
-#define I16(ptr) ((ptr)[0] + ((ptr)[1] << 8))
+#define I16(ptr) ((ptr)[0] + ((int)(ptr)[1] << 8))
-#define I32(ptr) ((ptr)[0] + ((ptr)[1] << 8) + ((ptr)[2] << 16) + ((ptr)[3] << 24))
+#define I32(ptr) \
+ ((ptr)[0] + ((INT32)(ptr)[1] << 8) + ((INT32)(ptr)[2] << 16) + \
+ ((INT32)(ptr)[3] << 24))
#define ERR_IF_DATA_OOB(offset) \
if ((data + (offset)) > ptr + bytes) { \
diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c
index 1e2abd7e7..80ecd7cb6 100644
--- a/src/libImaging/Geometry.c
+++ b/src/libImaging/Geometry.c
@@ -19,7 +19,7 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) {
ImagingSectionCookie cookie;
int x, y, xr;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) {
@@ -41,7 +41,7 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
FLIP_LEFT_RIGHT(UINT16, image8)
} else {
FLIP_LEFT_RIGHT(UINT8, image8)
@@ -62,7 +62,7 @@ ImagingFlipTopBottom(Imaging imOut, Imaging imIn) {
ImagingSectionCookie cookie;
int y, yr;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) {
@@ -89,7 +89,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn) {
int x, y, xx, yy, xr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
@@ -127,7 +127,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
ROTATE_90(UINT16, image8);
} else {
ROTATE_90(UINT8, image8);
@@ -149,7 +149,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn) {
int x, y, xx, yy, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
@@ -186,7 +186,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
TRANSPOSE(UINT16, image8);
} else {
TRANSPOSE(UINT8, image8);
@@ -208,7 +208,7 @@ ImagingTransverse(Imaging imOut, Imaging imIn) {
int x, y, xr, yr, xx, yy, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
@@ -247,7 +247,7 @@ ImagingTransverse(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
TRANSVERSE(UINT16, image8);
} else {
TRANSVERSE(UINT8, image8);
@@ -268,7 +268,7 @@ ImagingRotate180(Imaging imOut, Imaging imIn) {
ImagingSectionCookie cookie;
int x, y, xr, yr;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) {
@@ -291,7 +291,7 @@ ImagingRotate180(Imaging imOut, Imaging imIn) {
yr = imIn->ysize - 1;
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
ROTATE_180(UINT16, image8)
} else {
ROTATE_180(UINT8, image8)
@@ -313,7 +313,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn) {
int x, y, xx, yy, yr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
@@ -351,7 +351,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
ROTATE_270(UINT16, image8);
} else {
ROTATE_270(UINT8, image8);
@@ -791,7 +791,7 @@ ImagingGenericTransform(
char *out;
double xx, yy;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
@@ -848,7 +848,7 @@ ImagingScaleAffine(
int xmin, xmax;
int *xintab;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
@@ -1035,7 +1035,7 @@ ImagingTransformAffine(
double xx, yy;
double xo, yo;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c
index d430893dd..d336121d5 100644
--- a/src/libImaging/GetBBox.c
+++ b/src/libImaging/GetBBox.c
@@ -90,9 +90,9 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) {
if (im->bands == 3) {
((UINT8 *)&mask)[3] = 0;
} else if (alpha_only &&
- (strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 ||
- strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 ||
- strcmp(im->mode, "PA") == 0)) {
+ (im->mode == IMAGING_MODE_RGBa || im->mode == IMAGING_MODE_RGBA ||
+ im->mode == IMAGING_MODE_La || im->mode == IMAGING_MODE_LA ||
+ im->mode == IMAGING_MODE_PA)) {
#ifdef WORDS_BIGENDIAN
mask = 0x000000ff;
#else
@@ -208,11 +208,11 @@ ImagingGetExtrema(Imaging im, void *extrema) {
memcpy(((char *)extrema) + sizeof(fmin), &fmax, sizeof(fmax));
break;
case IMAGING_TYPE_SPECIAL:
- if (strcmp(im->mode, "I;16") == 0) {
+ if (im->mode == IMAGING_MODE_I_16) {
UINT16 v;
UINT8 *pixel = *im->image8;
#ifdef WORDS_BIGENDIAN
- v = pixel[0] + (pixel[1] << 8);
+ v = pixel[0] + ((UINT16)pixel[1] << 8);
#else
memcpy(&v, pixel, sizeof(v));
#endif
@@ -221,7 +221,7 @@ ImagingGetExtrema(Imaging im, void *extrema) {
for (x = 0; x < im->xsize; x++) {
pixel = (UINT8 *)im->image[y] + x * sizeof(v);
#ifdef WORDS_BIGENDIAN
- v = pixel[0] + (pixel[1] << 8);
+ v = pixel[0] + ((UINT16)pixel[1] << 8);
#else
memcpy(&v, pixel, sizeof(v));
#endif
diff --git a/src/libImaging/Histo.c b/src/libImaging/Histo.c
index 87c09d3d4..7af600035 100644
--- a/src/libImaging/Histo.c
+++ b/src/libImaging/Histo.c
@@ -43,10 +43,10 @@ ImagingHistogramNew(Imaging im) {
if (!h) {
return (ImagingHistogram)ImagingError_MemoryError();
}
- strncpy(h->mode, im->mode, IMAGING_MODE_LENGTH - 1);
- h->mode[IMAGING_MODE_LENGTH - 1] = 0;
+ h->mode = im->mode;
h->bands = im->bands;
+
h->histogram = calloc(im->pixelsize, 256 * sizeof(long));
if (!h->histogram) {
free(h);
@@ -73,7 +73,7 @@ ImagingGetHistogram(Imaging im, Imaging imMask, void *minmax) {
if (im->xsize != imMask->xsize || im->ysize != imMask->ysize) {
return ImagingError_Mismatch();
}
- if (strcmp(imMask->mode, "1") != 0 && strcmp(imMask->mode, "L") != 0) {
+ if (imMask->mode != IMAGING_MODE_1 && imMask->mode != IMAGING_MODE_L) {
return ImagingError_ValueError("bad transparency mask");
}
}
diff --git a/src/libImaging/ImDib.h b/src/libImaging/ImDib.h
index 91ff3f322..65f090f92 100644
--- a/src/libImaging/ImDib.h
+++ b/src/libImaging/ImDib.h
@@ -27,7 +27,7 @@ struct ImagingDIBInstance {
UINT8 *bits;
HPALETTE palette;
/* Used by cut and paste */
- char mode[4];
+ ModeID mode;
int xsize, ysize;
int pixelsize;
int linesize;
@@ -37,11 +37,11 @@ struct ImagingDIBInstance {
typedef struct ImagingDIBInstance *ImagingDIB;
-extern char *
+extern ModeID
ImagingGetModeDIB(int size_out[2]);
extern ImagingDIB
-ImagingNewDIB(const char *mode, int xsize, int ysize);
+ImagingNewDIB(ModeID mode, int xsize, int ysize);
extern void
ImagingDeleteDIB(ImagingDIB im);
diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h
index bfe67d462..f7049c892 100644
--- a/src/libImaging/Imaging.h
+++ b/src/libImaging/Imaging.h
@@ -11,6 +11,7 @@
*/
#include "ImPlatform.h"
+#include "Mode.h"
#if defined(__cplusplus)
extern "C" {
@@ -71,9 +72,6 @@ typedef struct ImagingPaletteInstance *ImagingPalette;
#define IMAGING_TYPE_FLOAT32 2
#define IMAGING_TYPE_SPECIAL 3 /* check mode for details */
-#define IMAGING_MODE_LENGTH \
- 6 + 1 /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */
-
typedef struct {
char *ptr;
int size;
@@ -81,12 +79,11 @@ typedef struct {
struct ImagingMemoryInstance {
/* Format */
- char mode[IMAGING_MODE_LENGTH]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK",
- "YCbCr", "BGR;xy") */
- int type; /* Data type (IMAGING_TYPE_*) */
- int depth; /* Depth (ignored in this version) */
- int bands; /* Number of bands (1, 2, 3, or 4) */
- int xsize; /* Image dimension. */
+ ModeID mode; /* Image mode (IMAGING_MODE_*) */
+ int type; /* Data type (IMAGING_TYPE_*) */
+ int depth; /* Depth (ignored in this version) */
+ int bands; /* Number of bands (1, 2, 3, or 4) */
+ int xsize; /* Image dimension. */
int ysize;
/* Colour palette (for "P" images only) */
@@ -140,15 +137,15 @@ struct ImagingMemoryInstance {
#define IMAGING_PIXEL_FLOAT32(im, x, y) (((FLOAT32 *)(im)->image32[y])[x])
struct ImagingAccessInstance {
- const char *mode;
+ ModeID mode;
void (*get_pixel)(Imaging im, int x, int y, void *pixel);
void (*put_pixel)(Imaging im, int x, int y, const void *pixel);
};
struct ImagingHistogramInstance {
/* Format */
- char mode[IMAGING_MODE_LENGTH]; /* Band names (of corresponding source image) */
- int bands; /* Number of bands (1, 3, or 4) */
+ ModeID mode; /* Mode ID of corresponding source image */
+ int bands; /* Number of bands (1, 2, 3, or 4) */
/* Data */
long *histogram; /* Histogram (bands*256 longs) */
@@ -156,7 +153,7 @@ struct ImagingHistogramInstance {
struct ImagingPaletteInstance {
/* Format */
- char mode[IMAGING_MODE_LENGTH]; /* Band names */
+ ModeID mode;
/* Data */
int size;
@@ -196,20 +193,20 @@ extern void
ImagingMemorySetBlockAllocator(ImagingMemoryArena arena, int use_block_allocator);
extern Imaging
-ImagingNew(const char *mode, int xsize, int ysize);
+ImagingNew(ModeID mode, int xsize, int ysize);
extern Imaging
-ImagingNewDirty(const char *mode, int xsize, int ysize);
+ImagingNewDirty(ModeID mode, int xsize, int ysize);
extern Imaging
-ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn);
+ImagingNew2Dirty(ModeID mode, Imaging imOut, Imaging imIn);
extern void
ImagingDelete(Imaging im);
extern Imaging
-ImagingNewBlock(const char *mode, int xsize, int ysize);
+ImagingNewBlock(ModeID mode, int xsize, int ysize);
extern Imaging
ImagingNewArrow(
- const char *mode,
+ const ModeID mode,
int xsize,
int ysize,
PyObject *schema_capsule,
@@ -217,9 +214,9 @@ ImagingNewArrow(
);
extern Imaging
-ImagingNewPrologue(const char *mode, int xsize, int ysize);
+ImagingNewPrologue(ModeID mode, int xsize, int ysize);
extern Imaging
-ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int structure_size);
+ImagingNewPrologueSubtype(ModeID mode, int xsize, int ysize, int structure_size);
extern void
ImagingCopyPalette(Imaging destination, Imaging source);
@@ -227,8 +224,6 @@ ImagingCopyPalette(Imaging destination, Imaging source);
extern void
ImagingHistogramDelete(ImagingHistogram histogram);
-extern void
-ImagingAccessInit(void);
extern ImagingAccess
ImagingAccessNew(Imaging im);
extern void
@@ -236,7 +231,7 @@ _ImagingAccessDelete(Imaging im, ImagingAccess access);
#define ImagingAccessDelete(im, access) /* nop, for now */
extern ImagingPalette
-ImagingPaletteNew(const char *mode);
+ImagingPaletteNew(ModeID mode);
extern ImagingPalette
ImagingPaletteNewBrowser(void);
extern ImagingPalette
@@ -308,13 +303,13 @@ ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha);
extern Imaging
ImagingCopy(Imaging im);
extern Imaging
-ImagingConvert(Imaging im, const char *mode, ImagingPalette palette, int dither);
+ImagingConvert(Imaging im, ModeID mode, ImagingPalette palette, int dither);
extern Imaging
-ImagingConvertInPlace(Imaging im, const char *mode);
+ImagingConvertInPlace(Imaging im, ModeID mode);
extern Imaging
-ImagingConvertMatrix(Imaging im, const char *mode, float m[]);
+ImagingConvertMatrix(Imaging im, ModeID mode, float m[]);
extern Imaging
-ImagingConvertTransparent(Imaging im, const char *mode, int r, int g, int b);
+ImagingConvertTransparent(Imaging im, ModeID mode, int r, int g, int b);
extern Imaging
ImagingCrop(Imaging im, int x0, int y0, int x1, int y1);
extern Imaging
@@ -328,9 +323,9 @@ ImagingFill2(
extern Imaging
ImagingFillBand(Imaging im, int band, int color);
extern Imaging
-ImagingFillLinearGradient(const char *mode);
+ImagingFillLinearGradient(ModeID mode);
extern Imaging
-ImagingFillRadialGradient(const char *mode);
+ImagingFillRadialGradient(ModeID mode);
extern Imaging
ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 offset);
extern Imaging
@@ -344,7 +339,7 @@ ImagingGaussianBlur(
extern Imaging
ImagingGetBand(Imaging im, int band);
extern Imaging
-ImagingMerge(const char *mode, Imaging bands[4]);
+ImagingMerge(ModeID mode, Imaging bands[4]);
extern int
ImagingSplit(Imaging im, Imaging bands[4]);
extern int
@@ -371,7 +366,7 @@ ImagingOffset(Imaging im, int xoffset, int yoffset);
extern int
ImagingPaste(Imaging into, Imaging im, Imaging mask, int x0, int y0, int x1, int y1);
extern Imaging
-ImagingPoint(Imaging im, const char *tablemode, const void *table);
+ImagingPoint(Imaging im, ModeID tablemode, const void *table);
extern Imaging
ImagingPointTransform(Imaging imIn, double scale, double offset);
extern Imaging
@@ -712,9 +707,9 @@ extern void
ImagingConvertYCbCr2RGB(UINT8 *out, const UINT8 *in, int pixels);
extern ImagingShuffler
-ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out);
+ImagingFindUnpacker(ModeID mode, RawModeID rawmode, int *bits_out);
extern ImagingShuffler
-ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out);
+ImagingFindPacker(ModeID mode, RawModeID rawmode, int *bits_out);
struct ImagingCodecStateInstance {
int count;
diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h
index 7cdba9022..e07904fc7 100644
--- a/src/libImaging/Jpeg.h
+++ b/src/libImaging/Jpeg.h
@@ -28,12 +28,12 @@ typedef struct {
typedef struct {
/* CONFIGURATION */
- /* Jpeg file mode (empty if not known) */
- char jpegmode[8 + 1];
+ /* Jpeg file mode */
+ RawModeID jpegmode;
- /* Converter output mode (input to the shuffler). If empty,
- convert conversions are disabled */
- char rawmode[8 + 1];
+ /* Converter output mode (input to the shuffler) */
+ /* If not a valid mode, convert conversions are disabled */
+ RawModeID rawmode;
/* If set, trade quality for speed */
int draft;
@@ -91,7 +91,7 @@ typedef struct {
unsigned int restart_marker_rows;
/* Converter input mode (input to the shuffler) */
- char rawmode[8 + 1];
+ RawModeID rawmode;
/* Custom quantization tables () */
unsigned int *qtables;
diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c
index cc6955ca5..1b496f45e 100644
--- a/src/libImaging/Jpeg2KDecode.c
+++ b/src/libImaging/Jpeg2KDecode.c
@@ -71,7 +71,7 @@ typedef void (*j2k_unpacker_t)(
);
struct j2k_decode_unpacker {
- const char *mode;
+ const ModeID mode;
OPJ_COLOR_SPACE color_space;
unsigned components;
/* bool indicating if unpacker supports subsampling */
@@ -599,26 +599,26 @@ j2ku_sycca_rgba(
}
static const struct j2k_decode_unpacker j2k_unpackers[] = {
- {"L", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l},
- {"P", OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l},
- {"PA", OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la},
- {"I;16", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
- {"I;16B", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
- {"LA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
- {"RGB", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
- {"RGB", OPJ_CLRSPC_GRAY, 2, 0, j2ku_gray_rgb},
- {"RGB", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
- {"RGB", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
- {"RGB", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgb_rgb},
- {"RGB", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycc_rgb},
- {"RGBA", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
- {"RGBA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
- {"RGBA", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
- {"RGBA", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
- {"RGBA", OPJ_CLRSPC_GRAY, 4, 1, j2ku_srgba_rgba},
- {"RGBA", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba},
- {"RGBA", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba},
- {"CMYK", OPJ_CLRSPC_CMYK, 4, 1, j2ku_srgba_rgba},
+ {IMAGING_MODE_L, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l},
+ {IMAGING_MODE_P, OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l},
+ {IMAGING_MODE_PA, OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la},
+ {IMAGING_MODE_I_16, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
+ {IMAGING_MODE_I_16B, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
+ {IMAGING_MODE_LA, OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_GRAY, 2, 0, j2ku_gray_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgb_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycc_rgb},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_GRAY, 4, 1, j2ku_srgba_rgba},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba},
+ {IMAGING_MODE_CMYK, OPJ_CLRSPC_CMYK, 4, 1, j2ku_srgba_rgba},
};
/* -------------------------------------------------------------------- */
@@ -771,7 +771,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
if (color_space == j2k_unpackers[n].color_space &&
image->numcomps == j2k_unpackers[n].components &&
(j2k_unpackers[n].subsampling || (subsampling == -1)) &&
- strcmp(im->mode, j2k_unpackers[n].mode) == 0) {
+ im->mode == j2k_unpackers[n].mode) {
unpack = j2k_unpackers[n].unpacker;
break;
}
diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c
index 61e095ad6..fdfbde2d7 100644
--- a/src/libImaging/Jpeg2KEncode.c
+++ b/src/libImaging/Jpeg2KEncode.c
@@ -305,34 +305,34 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
#endif
/* Setup an opj_image */
- if (strcmp(im->mode, "L") == 0) {
+ if (im->mode == IMAGING_MODE_L) {
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_l;
- } else if (strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16B") == 0) {
+ } else if (im->mode == IMAGING_MODE_I_16 || im->mode == IMAGING_MODE_I_16B) {
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_i16;
prec = 16;
- } else if (strcmp(im->mode, "LA") == 0) {
+ } else if (im->mode == IMAGING_MODE_LA) {
components = 2;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_la;
- } else if (strcmp(im->mode, "RGB") == 0) {
+ } else if (im->mode == IMAGING_MODE_RGB) {
components = 3;
color_space = OPJ_CLRSPC_SRGB;
pack = j2k_pack_rgb;
- } else if (strcmp(im->mode, "YCbCr") == 0) {
+ } else if (im->mode == IMAGING_MODE_YCbCr) {
components = 3;
color_space = OPJ_CLRSPC_SYCC;
pack = j2k_pack_rgb;
- } else if (strcmp(im->mode, "RGBA") == 0) {
+ } else if (im->mode == IMAGING_MODE_RGBA) {
components = 4;
color_space = OPJ_CLRSPC_SRGB;
pack = j2k_pack_rgba;
#if ((OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 5 && OPJ_VERSION_BUILD >= 3) || \
(OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR > 5) || OPJ_VERSION_MAJOR > 2)
- } else if (strcmp(im->mode, "CMYK") == 0) {
+ } else if (im->mode == IMAGING_MODE_CMYK) {
components = 4;
color_space = OPJ_CLRSPC_CMYK;
pack = j2k_pack_rgba;
@@ -497,9 +497,9 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
goto quick_exit;
}
- if (strcmp(im->mode, "RGBA") == 0) {
+ if (im->mode == IMAGING_MODE_RGBA) {
image->comps[3].alpha = 1;
- } else if (strcmp(im->mode, "LA") == 0) {
+ } else if (im->mode == IMAGING_MODE_LA) {
image->comps[1].alpha = 1;
}
diff --git a/src/libImaging/JpegDecode.c b/src/libImaging/JpegDecode.c
index 2970f56d1..ae3274456 100644
--- a/src/libImaging/JpegDecode.c
+++ b/src/libImaging/JpegDecode.c
@@ -180,41 +180,41 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by
/* Decoder settings */
- /* jpegmode indicates what's in the file; if not set, we'll
- trust the decoder */
- if (strcmp(context->jpegmode, "L") == 0) {
+ /* jpegmode indicates what's in the file. */
+ /* If not valid, we'll trust the decoder. */
+ if (context->jpegmode == IMAGING_RAWMODE_L) {
context->cinfo.jpeg_color_space = JCS_GRAYSCALE;
- } else if (strcmp(context->jpegmode, "RGB") == 0) {
+ } else if (context->jpegmode == IMAGING_RAWMODE_RGB) {
context->cinfo.jpeg_color_space = JCS_RGB;
- } else if (strcmp(context->jpegmode, "CMYK") == 0) {
+ } else if (context->jpegmode == IMAGING_RAWMODE_CMYK) {
context->cinfo.jpeg_color_space = JCS_CMYK;
- } else if (strcmp(context->jpegmode, "YCbCr") == 0) {
+ } else if (context->jpegmode == IMAGING_RAWMODE_YCbCr) {
context->cinfo.jpeg_color_space = JCS_YCbCr;
- } else if (strcmp(context->jpegmode, "YCbCrK") == 0) {
+ } else if (context->jpegmode == IMAGING_RAWMODE_YCbCrK) {
context->cinfo.jpeg_color_space = JCS_YCCK;
}
- /* rawmode indicates what we want from the decoder. if not
- set, conversions are disabled */
- if (strcmp(context->rawmode, "L") == 0) {
+ /* rawmode indicates what we want from the decoder. */
+ /* If not valid, conversions are disabled. */
+ if (context->rawmode == IMAGING_RAWMODE_L) {
context->cinfo.out_color_space = JCS_GRAYSCALE;
- } else if (strcmp(context->rawmode, "RGB") == 0) {
+ } else if (context->rawmode == IMAGING_RAWMODE_RGB) {
context->cinfo.out_color_space = JCS_RGB;
}
#ifdef JCS_EXTENSIONS
- else if (strcmp(context->rawmode, "RGBX") == 0) {
+ else if (context->rawmode == IMAGING_RAWMODE_RGBX) {
context->cinfo.out_color_space = JCS_EXT_RGBX;
}
#endif
- else if (strcmp(context->rawmode, "CMYK") == 0 ||
- strcmp(context->rawmode, "CMYK;I") == 0) {
+ else if (context->rawmode == IMAGING_RAWMODE_CMYK ||
+ context->rawmode == IMAGING_RAWMODE_CMYK_I) {
context->cinfo.out_color_space = JCS_CMYK;
- } else if (strcmp(context->rawmode, "YCbCr") == 0) {
+ } else if (context->rawmode == IMAGING_RAWMODE_YCbCr) {
context->cinfo.out_color_space = JCS_YCbCr;
- } else if (strcmp(context->rawmode, "YCbCrK") == 0) {
+ } else if (context->rawmode == IMAGING_RAWMODE_YCbCrK) {
context->cinfo.out_color_space = JCS_YCCK;
} else {
- /* Disable decoder conversions */
+ /* Disable decoder conversions. */
context->cinfo.jpeg_color_space = JCS_UNKNOWN;
context->cinfo.out_color_space = JCS_UNKNOWN;
}
diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c
index 972435ee1..098e431fc 100644
--- a/src/libImaging/JpegEncode.c
+++ b/src/libImaging/JpegEncode.c
@@ -114,7 +114,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
break;
case 24:
context->cinfo.input_components = 3;
- if (strcmp(im->mode, "YCbCr") == 0) {
+ if (im->mode == IMAGING_MODE_YCbCr) {
context->cinfo.in_color_space = JCS_YCbCr;
} else {
context->cinfo.in_color_space = JCS_RGB;
@@ -124,7 +124,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
context->cinfo.input_components = 4;
context->cinfo.in_color_space = JCS_CMYK;
#ifdef JCS_EXTENSIONS
- if (strcmp(context->rawmode, "RGBX") == 0) {
+ if (context->rawmode == IMAGING_RAWMODE_RGBX) {
context->cinfo.in_color_space = JCS_EXT_RGBX;
}
#endif
diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c
index ec7f4d93e..d28e04edf 100644
--- a/src/libImaging/Matrix.c
+++ b/src/libImaging/Matrix.c
@@ -18,7 +18,7 @@
#define CLIPF(v) ((v <= 0.0) ? 0 : (v >= 255.0F) ? 255 : (UINT8)v)
Imaging
-ImagingConvertMatrix(Imaging im, const char *mode, float m[]) {
+ImagingConvertMatrix(Imaging im, const ModeID mode, float m[]) {
Imaging imOut;
int x, y;
ImagingSectionCookie cookie;
@@ -28,8 +28,8 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) {
return (Imaging)ImagingError_ModeError();
}
- if (strcmp(mode, "L") == 0) {
- imOut = ImagingNewDirty("L", im->xsize, im->ysize);
+ if (mode == IMAGING_MODE_L) {
+ imOut = ImagingNewDirty(IMAGING_MODE_L, im->xsize, im->ysize);
if (!imOut) {
return NULL;
}
@@ -46,8 +46,8 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) {
}
}
ImagingSectionLeave(&cookie);
-
- } else if (strlen(mode) == 3) {
+ } else if (mode == IMAGING_MODE_HSV || mode == IMAGING_MODE_LAB ||
+ mode == IMAGING_MODE_RGB) {
imOut = ImagingNewDirty(mode, im->xsize, im->ysize);
if (!imOut) {
return NULL;
diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c
new file mode 100644
index 000000000..7521f4cda
--- /dev/null
+++ b/src/libImaging/Mode.c
@@ -0,0 +1,259 @@
+#include "Mode.h"
+#include