Merge branch 'main' into main

This commit is contained in:
Andrew Murray 2025-10-15 22:31:20 +11:00 committed by GitHub
commit c680ff029f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
130 changed files with 3582 additions and 1674 deletions

View File

@ -51,10 +51,10 @@ pushd depends && ./install_webp.sh && popd
pushd depends && ./install_imagequant.sh && popd pushd depends && ./install_imagequant.sh && popd
# raqm # raqm
pushd depends && ./install_raqm.sh && popd pushd depends && sudo ./install_raqm.sh && popd
# libavif # libavif
pushd depends && ./install_libavif.sh && popd pushd depends && sudo ./install_libavif.sh && popd
# extra test images # extra test images
pushd depends && ./install_extra_test_images.sh && popd pushd depends && ./install_extra_test_images.sh && popd

View File

@ -1 +1 @@
cibuildwheel==3.1.4 cibuildwheel==3.2.1

View File

@ -1,4 +1,6 @@
mypy==1.18.1 mypy==1.18.2
arro3-compute
arro3-core
IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6 IceSpringPySideStubs-PySide6
ipython ipython

View File

@ -2,9 +2,6 @@
set -e set -e
if [[ "$ImageOS" == "macos13" ]]; then
brew uninstall gradle maven
fi
brew install \ brew install \
aom \ aom \
dav1d \ dav1d \

View File

@ -97,8 +97,8 @@ jobs:
choco install nasm --no-progress choco install nasm --no-progress
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
choco install ghostscript --version=10.5.1 --no-progress choco install ghostscript --version=10.6.0 --no-progress
echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH echo "C:\Program Files\gs\gs10.06.0\bin" >> $env:GITHUB_PATH
# Install extra test images # Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images xcopy /S /Y Tests\test-images\* Tests\images

View File

@ -57,7 +57,7 @@ jobs:
- { python-version: "3.14t", disable-gil: true } - { python-version: "3.14t", disable-gil: true }
- { python-version: "3.13t", disable-gil: true } - { python-version: "3.13t", disable-gil: true }
# Intel # Intel
- { os: "macos-13", python-version: "3.10" } - { os: "macos-15-intel", python-version: "3.10" }
exclude: exclude:
- { os: "macos-latest", python-version: "3.10" } - { os: "macos-latest", python-version: "3.10" }

View File

@ -93,14 +93,18 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds. Version numbers with "Patched" # Package versions for fresh source builds. Version numbers with "Patched"
# annotations have a source code patch that is required for some platforms. If # annotations have a source code patch that is required for some platforms. If
# you change those versions, ensure the patch is also updated. # you change those versions, ensure the patch is also updated.
if [[ -n "$IOS_SDK" ]]; then
FREETYPE_VERSION=2.13.3 FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=11.4.5 else
FREETYPE_VERSION=2.14.1
fi
HARFBUZZ_VERSION=12.1.0
LIBPNG_VERSION=1.6.50 LIBPNG_VERSION=1.6.50
JPEGTURBO_VERSION=3.1.2 JPEGTURBO_VERSION=3.1.2
OPENJPEG_VERSION=2.5.3 OPENJPEG_VERSION=2.5.4
XZ_VERSION=5.8.1 XZ_VERSION=5.8.1
ZSTD_VERSION=1.5.7 ZSTD_VERSION=1.5.7
TIFF_VERSION=4.7.0 TIFF_VERSION=4.7.1
LCMS2_VERSION=2.17 LCMS2_VERSION=2.17
ZLIB_NG_VERSION=2.2.5 ZLIB_NG_VERSION=2.2.5
LIBWEBP_VERSION=1.6.0 LIBWEBP_VERSION=1.6.0
@ -267,7 +271,11 @@ function build {
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
yum remove -y zlib-devel yum remove -y zlib-devel
fi fi
if [[ -n "$IS_MACOS" ]]; then
CFLAGS="$CFLAGS -headerpad_max_install_names" build_zlib_ng
else
build_zlib_ng build_zlib_ng
fi
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
if [[ -n "$IS_MACOS" ]]; then if [[ -n "$IS_MACOS" ]]; then
@ -314,6 +322,10 @@ function build {
if [[ -n "$IS_MACOS" ]]; then if [[ -n "$IS_MACOS" ]]; then
# Custom freetype build # 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 build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
else else
build_freetype build_freetype

View File

@ -39,6 +39,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
env: env:
EXPECTED_DISTS: 91
FORCE_COLOR: 1 FORCE_COLOR: 1
jobs: jobs:
@ -52,21 +53,21 @@ jobs:
include: include:
- name: "macOS 10.10 x86_64" - name: "macOS 10.10 x86_64"
platform: macos platform: macos
os: macos-13 os: macos-15-intel
cibw_arch: x86_64 cibw_arch: x86_64
build: "cp3{9,10,11}*" build: "cp3{10,11}*"
macosx_deployment_target: "10.10" macosx_deployment_target: "10.10"
- name: "macOS 10.13 x86_64" - name: "macOS 10.13 x86_64"
platform: macos platform: macos
os: macos-13 os: macos-15-intel
cibw_arch: x86_64 cibw_arch: x86_64
build: "cp3{12,13,14}*" build: "cp3{12,13}*"
macosx_deployment_target: "10.13" macosx_deployment_target: "10.13"
- name: "macOS 10.15 x86_64" - name: "macOS 10.15 x86_64"
platform: macos platform: macos
os: macos-13 os: macos-15-intel
cibw_arch: x86_64 cibw_arch: x86_64
build: "pp3*" build: "{cp314,pp3}*"
macosx_deployment_target: "10.15" macosx_deployment_target: "10.15"
- name: "macOS arm64" - name: "macOS arm64"
platform: macos platform: macos
@ -99,11 +100,11 @@ jobs:
cibw_arch: arm64_iphoneos cibw_arch: arm64_iphoneos
- name: "iOS arm64 simulator" - name: "iOS arm64 simulator"
platform: ios platform: ios
os: macos-14 os: macos-latest
cibw_arch: arm64_iphonesimulator cibw_arch: arm64_iphonesimulator
- name: "iOS x86_64 simulator" - name: "iOS x86_64 simulator"
platform: ios platform: ios
os: macos-13 os: macos-15-intel
cibw_arch: x86_64_iphonesimulator cibw_arch: x86_64_iphonesimulator
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
@ -231,7 +232,7 @@ jobs:
path: winbuild\build\bin\fribidi* path: winbuild\build\bin\fribidi*
sdist: sdist:
if: github.event_name != 'schedule' if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
@ -250,15 +251,33 @@ jobs:
name: dist-sdist name: dist-sdist
path: dist/*.tar.gz 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: scientific-python-nightly-wheels-publish:
if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') 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 runs-on: ubuntu-latest
name: Upload wheels to scientific-python-nightly-wheels name: Upload wheels to scientific-python-nightly-wheels
steps: steps:
- uses: actions/download-artifact@v5 - uses: actions/download-artifact@v5
with: with:
pattern: dist-* pattern: dist-!(sdist)*
path: dist path: dist
merge-multiple: true merge-multiple: true
- name: Upload wheels to scientific-python-nightly-wheels - name: Upload wheels to scientific-python-nightly-wheels
@ -269,7 +288,7 @@ jobs:
pypi-publish: pypi-publish:
if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 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 runs-on: ubuntu-latest
name: Upload release to PyPI name: Upload release to PyPI
environment: environment:

View File

@ -1,12 +1,12 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.11 rev: v0.13.3
hooks: hooks:
- id: ruff-check - id: ruff-check
args: [--exit-non-zero-on-fix] args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
rev: 25.1.0 rev: 25.9.0
hooks: hooks:
- id: black - id: black
@ -24,7 +24,7 @@ repos:
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$) exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: v21.1.0 rev: v21.1.2
hooks: hooks:
- id: clang-format - id: clang-format
types: [c] types: [c]
@ -51,14 +51,14 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
- repo: https://github.com/python-jsonschema/check-jsonschema - repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.3 rev: 0.34.0
hooks: hooks:
- id: check-github-workflows - id: check-github-workflows
- id: check-readthedocs - id: check-readthedocs
- id: check-renovate - id: check-renovate
- repo: https://github.com/zizmorcore/zizmor-pre-commit - repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.12.1 rev: v1.14.2
hooks: hooks:
- id: zizmor - id: zizmor
@ -68,7 +68,7 @@ repos:
- id: sphinx-lint - id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt - repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.6.0 rev: v2.7.0
hooks: hooks:
- id: pyproject-fmt - id: pyproject-fmt

1
Tests/createfontdatachunk.py Executable file → Normal file
View File

@ -1,4 +1,3 @@
#!/usr/bin/env python3
from __future__ import annotations from __future__ import annotations
import base64 import base64

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
Tests/images/frame_size.mpo Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

275
Tests/test_arro3.py Normal file
View File

@ -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

View File

@ -14,6 +14,7 @@ import pytest
from PIL import ( from PIL import (
AvifImagePlugin, AvifImagePlugin,
GifImagePlugin,
Image, Image,
ImageDraw, ImageDraw,
ImageFile, ImageFile,
@ -220,6 +221,7 @@ class TestFileAvif:
def test_background_from_gif(self, tmp_path: Path) -> None: def test_background_from_gif(self, tmp_path: Path) -> None:
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
original_value = im.convert("RGB").getpixel((1, 1)) original_value = im.convert("RGB").getpixel((1, 1))
assert isinstance(original_value, tuple)
# Save as AVIF # Save as AVIF
out_avif = tmp_path / "temp.avif" out_avif = tmp_path / "temp.avif"
@ -232,6 +234,7 @@ class TestFileAvif:
with Image.open(out_gif) as reread: with Image.open(out_gif) as reread:
reread_value = reread.convert("RGB").getpixel((1, 1)) 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)]) difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)])
assert difference <= 6 assert difference <= 6
@ -240,6 +243,7 @@ class TestFileAvif:
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
im.save(temp_file) im.save(temp_file)
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
def test_invalid_file(self) -> None: def test_invalid_file(self) -> None:
@ -598,10 +602,12 @@ class TestAvifAnimation:
""" """
with Image.open(TEST_AVIF_FILE) as im: with Image.open(TEST_AVIF_FILE) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
assert not im.is_animated assert not im.is_animated
with Image.open("Tests/images/avif/star.avifs") as im: with Image.open("Tests/images/avif/star.avifs") as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 5 assert im.n_frames == 5
assert im.is_animated assert im.is_animated
@ -612,11 +618,13 @@ class TestAvifAnimation:
""" """
with Image.open("Tests/images/avif/star.gif") as original: with Image.open("Tests/images/avif/star.gif") as original:
assert isinstance(original, GifImagePlugin.GifImageFile)
assert original.n_frames > 1 assert original.n_frames > 1
temp_file = tmp_path / "temp.avif" temp_file = tmp_path / "temp.avif"
original.save(temp_file, save_all=True) original.save(temp_file, save_all=True)
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == original.n_frames assert im.n_frames == original.n_frames
# Compare first frame in P mode to frame from original GIF # Compare first frame in P mode to frame from original GIF
@ -636,6 +644,7 @@ class TestAvifAnimation:
def check(temp_file: Path) -> None: def check(temp_file: Path) -> None:
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 4 assert im.n_frames == 4
# Compare first frame to original # Compare first frame to original
@ -708,6 +717,7 @@ class TestAvifAnimation:
) )
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 5 assert im.n_frames == 5
assert im.is_animated assert im.is_animated
@ -737,6 +747,7 @@ class TestAvifAnimation:
) )
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 5 assert im.n_frames == 5
assert im.is_animated assert im.is_animated

View File

@ -6,6 +6,8 @@ from pathlib import Path
import pytest import pytest
from PIL import BmpImagePlugin, Image, _binary from PIL import BmpImagePlugin, Image, _binary
from PIL._binary import o16le as o16
from PIL._binary import o32le as o32
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -114,7 +116,7 @@ def test_save_float_dpi(tmp_path: Path) -> None:
def test_load_dib() -> 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: with Image.open("Tests/images/clipboard.dib") as im:
assert im.format == "DIB" assert im.format == "DIB"
assert im.get_format_mimetype() == "image/bmp" assert im.get_format_mimetype() == "image/bmp"
@ -219,6 +221,18 @@ def test_rle8_eof(file_name: str, length: int) -> None:
im.load() 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: def test_offset() -> None:
# This image has been hexedited # This image has been hexedited
# to exclude the palette size from the pixel data offset # to exclude the palette size from the pixel data offset

View File

@ -1,8 +1,13 @@
from __future__ import annotations from __future__ import annotations
from io import BytesIO
import pytest import pytest
from PIL import CurImagePlugin, Image 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" TEST_FILE = "Tests/images/deerstalker.cur"
@ -17,6 +22,24 @@ def test_sanity() -> None:
assert im.getpixel((16, 16)) == (84, 87, 86, 255) 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: def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
@ -26,6 +49,7 @@ def test_invalid_file() -> None:
no_cursors_file = "Tests/images/no_cursors.cur" no_cursors_file = "Tests/images/no_cursors.cur"
cur = CurImagePlugin.CurImageFile(TEST_FILE) cur = CurImagePlugin.CurImageFile(TEST_FILE)
assert cur.fp is not None
cur.fp.close() cur.fp.close()
with open(no_cursors_file, "rb") as cur.fp: with open(no_cursors_file, "rb") as cur.fp:
with pytest.raises(TypeError): with pytest.raises(TypeError):

View File

@ -197,6 +197,14 @@ def test_load_long_binary_data(prefix: bytes) -> None:
assert img.format == "EPS" 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( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )

View File

@ -48,6 +48,7 @@ def test_sanity() -> None:
def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
with Image.open(animated_test_file_with_prefix_chunk) as im: with Image.open(animated_test_file_with_prefix_chunk) as im:
assert isinstance(im, FliImagePlugin.FliImageFile)
assert im.mode == "P" assert im.mode == "P"
assert im.size == (320, 200) assert im.size == (320, 200)
assert im.format == "FLI" assert im.format == "FLI"
@ -55,6 +56,7 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
assert im.is_animated assert im.is_animated
palette = im.getpalette() palette = im.getpalette()
assert palette is not None
assert palette[3:6] == [255, 255, 255] assert palette[3:6] == [255, 255, 255]
assert palette[381:384] == [204, 204, 12] assert palette[381:384] == [204, 204, 12]
assert palette[765:] == [252, 0, 0] assert palette[765:] == [252, 0, 0]

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from io import BytesIO
import pytest import pytest
from PIL import GdImageFile, UnidentifiedImageError 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) 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: def test_bad_mode() -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
GdImageFile.open(TEST_GD_FILE, "bad mode") GdImageFile.open(TEST_GD_FILE, "bad mode")

View File

@ -293,6 +293,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None:
im.save(out, save_all=True) im.save(out, save_all=True)
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, GifImagePlugin.GifImageFile)
assert reread.n_frames == 5 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: with Image.open(out) as im:
# Assert that the frames are correct, and each frame has the same palette # 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_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
assert im.palette is not None assert im.palette is not None
assert im.global_palette is not None assert im.global_palette is not None

View File

@ -330,8 +330,10 @@ class TestFileJpeg:
# Reading # Reading
with Image.open("Tests/images/exif_gps.jpg") as im: with Image.open("Tests/images/exif_gps.jpg") as im:
exif = im._getexif() assert isinstance(im, JpegImagePlugin.JpegImageFile)
assert exif[gps_index] == expected_exif_gps exif_data = im._getexif()
assert exif_data is not None
assert exif_data[gps_index] == expected_exif_gps
# Writing # Writing
f = tmp_path / "temp.jpg" f = tmp_path / "temp.jpg"
@ -340,8 +342,10 @@ class TestFileJpeg:
hopper().save(f, exif=exif) hopper().save(f, exif=exif)
with Image.open(f) as reloaded: with Image.open(f) as reloaded:
exif = reloaded._getexif() assert isinstance(reloaded, JpegImagePlugin.JpegImageFile)
assert exif[gps_index] == expected_exif_gps 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: def test_empty_exif_gps(self) -> None:
with Image.open("Tests/images/empty_gps_ifd.jpg") as im: with Image.open("Tests/images/empty_gps_ifd.jpg") as im:
@ -368,6 +372,7 @@ class TestFileJpeg:
exifs = [] exifs = []
for i in range(2): for i in range(2):
with Image.open("Tests/images/exif-200dpcm.jpg") as im: with Image.open("Tests/images/exif-200dpcm.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
exifs.append(im._getexif()) exifs.append(im._getexif())
assert exifs[0] == exifs[1] assert exifs[0] == exifs[1]
@ -401,13 +406,17 @@ class TestFileJpeg:
} }
with Image.open("Tests/images/exif_gps.jpg") as im: with Image.open("Tests/images/exif_gps.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
exif = im._getexif() exif = im._getexif()
assert exif is not None
for tag, value in expected_exif.items(): for tag, value in expected_exif.items():
assert value == exif[tag] assert value == exif[tag]
def test_exif_gps_typeerror(self) -> None: def test_exif_gps_typeerror(self) -> None:
with Image.open("Tests/images/exif_gps_typeerror.jpg") as im: with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
# Should not raise a TypeError # Should not raise a TypeError
im._getexif() im._getexif()
@ -487,7 +496,9 @@ class TestFileJpeg:
def test_exif(self) -> None: def test_exif(self) -> None:
with Image.open("Tests/images/pil_sample_rgb.jpg") as im: with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
info = im._getexif() info = im._getexif()
assert info is not None
assert info[305] == "Adobe Photoshop CS Macintosh" assert info[305] == "Adobe Photoshop CS Macintosh"
def test_get_child_images(self) -> None: def test_get_child_images(self) -> None:
@ -690,11 +701,13 @@ class TestFileJpeg:
def test_save_multiple_16bit_qtables(self) -> None: def test_save_multiple_16bit_qtables(self) -> None:
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
im2 = self.roundtrip(im, qtables="keep") im2 = self.roundtrip(im, qtables="keep")
assert im.quantization == im2.quantization assert im.quantization == im2.quantization
def test_save_single_16bit_qtable(self) -> None: def test_save_single_16bit_qtable(self) -> None:
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: 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]}) im2 = self.roundtrip(im, qtables={0: im.quantization[0]})
assert len(im2.quantization) == 1 assert len(im2.quantization) == 1
assert im2.quantization[0] == im.quantization[0] assert im2.quantization[0] == im.quantization[0]
@ -898,7 +911,10 @@ class TestFileJpeg:
# in contrast to normal 8 # in contrast to normal 8
with Image.open("Tests/images/exif-ifd-offset.jpg") as im: with Image.open("Tests/images/exif-ifd-offset.jpg") as im:
# Act / Assert # 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: def test_multiple_exif(self) -> None:
with Image.open("Tests/images/multiple_exif.jpg") as im: with Image.open("Tests/images/multiple_exif.jpg") as im:

View File

@ -355,6 +355,27 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not segfault # Should not segfault
im.save(outfile) 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( def test_xmlpacket_tag(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None: ) -> None:

View File

@ -104,25 +104,27 @@ def test_exif(test_file: str) -> None:
def test_frame_size() -> None: def test_frame_size() -> None:
# This image has been hexedited to contain a different size with Image.open("Tests/images/frame_size.mpo") as im:
# in the SOF marker of the second frame assert im.size == (56, 70)
with Image.open("Tests/images/sugarshack_frame_size.mpo") as im: im.load()
assert im.size == (640, 480)
im.seek(1) im.seek(1)
assert im.size == (680, 480) assert im.size == (349, 434)
im.load()
im.seek(0) im.seek(0)
assert im.size == (640, 480) assert im.size == (56, 70)
def test_ignore_frame_size() -> None: def test_ignore_frame_size() -> None:
# Ignore the different size of the second frame # Ignore the different size of the second frame
# since this is not a "Large Thumbnail" image # since this is not a "Large Thumbnail" image
with Image.open("Tests/images/ignore_frame_size.mpo") as im: with Image.open("Tests/images/ignore_frame_size.mpo") as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
assert im.size == (64, 64) assert im.size == (64, 64)
im.seek(1) im.seek(1)
assert im.mpinfo is not None
assert ( assert (
im.mpinfo[0xB002][1]["Attribute"]["MPType"] im.mpinfo[0xB002][1]["Attribute"]["MPType"]
== "Multi-Frame Image: (Disparity)" == "Multi-Frame Image: (Disparity)"
@ -155,6 +157,7 @@ def test_reload_exif_after_seek() -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_mp(test_file: str) -> None: def test_mp(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None assert mpinfo is not None
assert mpinfo[45056] == b"0100" 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 # This image has been manually hexedited to have an IFD offset of 10
# in APP2 data, in contrast to normal 8 # in APP2 data, in contrast to normal 8
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None assert mpinfo is not None
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
@ -182,6 +186,7 @@ def test_mp_no_data() -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_mp_attribute(test_file: str) -> None: def test_mp_attribute(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None assert mpinfo is not None
for frame_number, mpentry in enumerate(mpinfo[0xB002]): for frame_number, mpentry in enumerate(mpinfo[0xB002]):

View File

@ -6,6 +6,8 @@ import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal
def test_load_raw() -> None: def test_load_raw() -> None:
with Image.open("Tests/images/hopper.pcd") as im: with Image.open("Tests/images/hopper.pcd") as im:
@ -30,3 +32,8 @@ def test_rotated(orientation: int) -> None:
f = BytesIO(data) f = BytesIO(data)
with Image.open(f) as im: with Image.open(f) as im:
assert im.size == (512, 768) 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)
)

View File

@ -229,7 +229,9 @@ class TestFilePng:
assert_image(im, "RGBA", (162, 150)) assert_image(im, "RGBA", (162, 150))
# image has 124 unique alpha values # 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: def test_load_transparent_rgb(self) -> None:
test_file = "Tests/images/rgb_trns.png" test_file = "Tests/images/rgb_trns.png"
@ -241,7 +243,9 @@ class TestFilePng:
assert_image(im, "RGBA", (64, 64)) assert_image(im, "RGBA", (64, 64))
# image has 876 transparent pixels # 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: def test_save_p_transparent_palette(self, tmp_path: Path) -> None:
in_file = "Tests/images/pil123p.png" in_file = "Tests/images/pil123p.png"
@ -262,7 +266,9 @@ class TestFilePng:
assert_image(im, "RGBA", (162, 150)) assert_image(im, "RGBA", (162, 150))
# image has 124 unique alpha values # 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: def test_save_p_single_transparency(self, tmp_path: Path) -> None:
in_file = "Tests/images/p_trns_single.png" in_file = "Tests/images/p_trns_single.png"
@ -285,7 +291,9 @@ class TestFilePng:
assert im.getpixel((31, 31)) == (0, 255, 52, 0) assert im.getpixel((31, 31)) == (0, 255, 52, 0)
# image has 876 transparent pixels # 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: def test_save_p_transparent_black(self, tmp_path: Path) -> None:
# check if solid black image with full transparency # check if solid black image with full transparency
@ -313,7 +321,9 @@ class TestFilePng:
assert im.info["transparency"] == 255 assert im.info["transparency"] == 255
im_rgba = im.convert("RGBA") 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" test_file = tmp_path / "temp.png"
im.save(test_file) im.save(test_file)
@ -324,7 +334,9 @@ class TestFilePng:
assert_image_equal(im, test_im) assert_image_equal(im, test_im)
test_im_rgba = test_im.convert("RGBA") 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: def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
in_file = "Tests/images/caption_6_33_22.png" in_file = "Tests/images/caption_6_33_22.png"
@ -671,6 +683,9 @@ class TestFilePng:
im.save(out, bits=4, save_all=save_all) im.save(out, bits=4, save_all=save_all)
with Image.open(out) as reloaded: 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 assert len(reloaded.png.im_palette[1]) == 48
def test_plte_length(self, tmp_path: Path) -> None: def test_plte_length(self, tmp_path: Path) -> None:
@ -681,6 +696,9 @@ class TestFilePng:
im.save(out) im.save(out)
with Image.open(out) as reloaded: 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 assert len(reloaded.png.im_palette[1]) == 3
def test_getxmp(self) -> None: def test_getxmp(self) -> None:
@ -702,13 +720,17 @@ class TestFilePng:
def test_exif(self) -> None: def test_exif(self) -> None:
# With an EXIF chunk # With an EXIF chunk
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
exif = im._getexif() assert isinstance(im, PngImagePlugin.PngImageFile)
assert exif[274] == 1 exif_data = im._getexif()
assert exif_data is not None
assert exif_data[274] == 1
# With an ImageMagick zTXt chunk # With an ImageMagick zTXt chunk
with Image.open("Tests/images/exif_imagemagick.png") as im: with Image.open("Tests/images/exif_imagemagick.png") as im:
exif = im._getexif() assert isinstance(im, PngImagePlugin.PngImageFile)
assert exif[274] == 1 exif_data = im._getexif()
assert exif_data is not None
assert exif_data[274] == 1
# Assert that info still can be extracted # Assert that info still can be extracted
# when the image is no longer a PngImageFile instance # when the image is no longer a PngImageFile instance
@ -717,8 +739,10 @@ class TestFilePng:
# With a tEXt chunk # With a tEXt chunk
with Image.open("Tests/images/exif_text.png") as im: with Image.open("Tests/images/exif_text.png") as im:
exif = im._getexif() assert isinstance(im, PngImagePlugin.PngImageFile)
assert exif[274] == 1 exif_data = im._getexif()
assert exif_data is not None
assert exif_data[274] == 1
# With XMP tags # With XMP tags
with Image.open("Tests/images/xmp_tags_orientation.png") as im: with Image.open("Tests/images/xmp_tags_orientation.png") as im:
@ -740,8 +764,10 @@ class TestFilePng:
im.save(test_file, exif=im.getexif()) im.save(test_file, exif=im.getexif())
with Image.open(test_file) as reloaded: with Image.open(test_file) as reloaded:
exif = reloaded._getexif() assert isinstance(reloaded, PngImagePlugin.PngImageFile)
assert exif[274] == 1 exif_data = reloaded._getexif()
assert exif_data is not None
assert exif_data[274] == 1
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"

View File

@ -92,6 +92,13 @@ def test_16bit_pgm() -> None:
assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff") 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: def test_16bit_pgm_write(tmp_path: Path) -> None:
with Image.open("Tests/images/16_bit_binary.pgm") as im: with Image.open("Tests/images/16_bit_binary.pgm") as im:
filename = tmp_path / "temp.pgm" filename = tmp_path / "temp.pgm"
@ -134,6 +141,12 @@ def test_pfm_big_endian(tmp_path: Path) -> None:
assert_image_equal_tofile(im, filename) 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( @pytest.mark.parametrize(
"data", "data",
[ [

View File

@ -274,13 +274,17 @@ def test_save_l_transparency(tmp_path: Path) -> None:
in_file = "Tests/images/la.tga" in_file = "Tests/images/la.tga"
with Image.open(in_file) as im: with Image.open(in_file) as im:
assert im.mode == "LA" 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" out = tmp_path / "temp.tga"
im.save(out) im.save(out)
with Image.open(out) as test_im: with Image.open(out) as test_im:
assert test_im.mode == "LA" 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) assert_image_equal(im, test_im)

View File

@ -22,11 +22,13 @@ except ImportError:
def test_read_exif_metadata() -> None: def test_read_exif_metadata() -> None:
file_path = "Tests/images/flower.webp" file_path = "Tests/images/flower.webp"
with Image.open(file_path) as image: with Image.open(file_path) as image:
assert isinstance(image, WebPImagePlugin.WebPImageFile)
assert image.format == "WEBP" assert image.format == "WEBP"
exif_data = image.info.get("exif", None) exif_data = image.info.get("exif", None)
assert exif_data assert exif_data
exif = image._getexif() exif = image._getexif()
assert exif is not None
# Camera make # Camera make
assert exif[271] == "Canon" assert exif[271] == "Canon"

View File

@ -284,33 +284,6 @@ class TestImage:
assert item is not None assert item is not None
assert item != num 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: def test_getbands(self) -> None:
# Assert # Assert
assert hopper("RGB").getbands() == ("R", "G", "B") assert hopper("RGB").getbands() == ("R", "G", "B")
@ -1126,6 +1099,12 @@ class TestImage:
assert im.palette is not None assert im.palette is not None
assert im.palette.colors[(27, 35, 6, 214)] == 24 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: def test_constants(self) -> None:
for enum in ( for enum in (
Image.Transpose, Image.Transpose,

View File

@ -97,6 +97,13 @@ def test_opaque() -> None:
assert_image_equal(alpha, solid) 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: def test_rgba_p() -> None:
im = hopper("RGBA") im = hopper("RGBA")
im.putalpha(hopper("L")) im.putalpha(hopper("L"))
@ -107,11 +114,19 @@ def test_rgba_p() -> None:
assert_image_similar(im, comparable, 20) assert_image_similar(im, comparable, 20)
def test_rgba() -> None: def test_rgba_pa() -> None:
with Image.open("Tests/images/transparent.png") as im: im = hopper("RGBA").convert("PA").convert("RGB")
assert im.mode == "RGBA" 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: def test_trns_p(tmp_path: Path) -> None:

View File

@ -124,6 +124,21 @@ class TestImagingPaste:
im = im.crop((12, 23, im2.width + 12, im2.height + 23)) im = im.crop((12, 23, im2.width + 12, im2.height + 23))
assert_image_equal(im, im2) 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"]) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_image_mask_1(self, mode: str) -> None: def test_image_mask_1(self, mode: str) -> None:
im = Image.new(mode, (200, 200), "white") im = Image.new(mode, (200, 200), "white")

View File

@ -62,6 +62,7 @@ def test_putpalette_with_alpha_values() -> None:
expected = im.convert("RGBA") expected = im.convert("RGBA")
palette = im.getpalette() palette = im.getpalette()
assert palette is not None
transparency = im.info.pop("transparency") transparency = im.info.pop("transparency")
palette_with_alpha_values = [] palette_with_alpha_values = []

View File

@ -118,6 +118,15 @@ def test_quantize_kmeans(method: Image.Quantize) -> None:
im.quantize(kmeans=-1, method=method) 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: def test_colors() -> None:
im = hopper() im = hopper()
colors = 2 colors = 2

View File

@ -1494,7 +1494,9 @@ def test_default_font_size() -> None:
def draw_text() -> None: def draw_text() -> None:
draw.text((0, 0), text, font_size=16) 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) check(draw_text)
@ -1513,7 +1515,9 @@ def test_default_font_size() -> None:
def draw_multiline_text() -> None: def draw_multiline_text() -> None:
draw.multiline_text((0, 0), text, font_size=16) 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) check(draw_multiline_text)

View File

@ -164,6 +164,11 @@ class TestImageFile:
with pytest.raises(OSError): with pytest.raises(OSError):
p.close() 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: def test_no_format(self) -> None:
buf = BytesIO(b"\x00" * 255) buf = BytesIO(b"\x00" * 255)

View File

@ -19,6 +19,7 @@ from .helper import (
assert_image_equal, assert_image_equal,
assert_image_equal_tofile, assert_image_equal_tofile,
assert_image_similar_tofile, assert_image_similar_tofile,
has_feature_version,
is_win32, is_win32,
skip_unless_feature, skip_unless_feature,
skip_unless_feature_version, skip_unless_feature_version,
@ -492,6 +493,11 @@ def test_stroke_mask() -> None:
assert mask.getpixel((42, 5)) == 255 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: def test_load_when_image_not_found() -> None:
with tempfile.NamedTemporaryFile(delete=False) as tmp: with tempfile.NamedTemporaryFile(delete=False) as tmp:
pass pass
@ -549,7 +555,7 @@ def test_default_font() -> None:
draw.text((10, 60), txt, font=larger_default_font) draw.text((10, 60), txt, font=larger_default_font)
# Assert # 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")) @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) 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") @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) 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: def test_woff2(layout_engine: ImageFont.Layout) -> None:

View File

@ -183,7 +183,7 @@ def test_x_max_and_y_offset() -> None:
draw.text((0, 0), "لح", font=ttf, fill=500) draw.text((0, 0), "لح", font=ttf, fill=500)
target = "Tests/images/test_x_max_and_y_offset.png" 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: def test_language() -> None:

View File

@ -30,6 +30,14 @@ def test_default_font(font: ImageFont.ImageFont) -> None:
assert_image_equal_tofile(im, "Tests/images/default_font.png") 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: def test_without_freetype() -> None:
original_core = ImageFont.core original_core = ImageFont.core
if features.check_module("freetype2"): if features.check_module("freetype2"):

View File

@ -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: def test_pil163() -> None:
# Division by zero in equalize if < 255 pixels in image (@PIL163) # Division by zero in equalize if < 255 pixels in image (@PIL163)

View File

@ -76,9 +76,14 @@ def test_consecutive() -> None:
def test_palette_mmap() -> None: def test_palette_mmap() -> None:
# Using mmap in ImageFile can require to reload the palette. # Using mmap in ImageFile can require to reload the palette.
with Image.open("Tests/images/multipage-mmap.tiff") as im: 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) im.seek(0)
color2 = im.getpalette()[:3]
palette = im.getpalette()
assert palette is not None
color2 = palette[:3]
assert color1 == color2 assert color1 == color2

72
Tests/test_imagetext.py Normal file
View File

@ -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
)

302
Tests/test_nanoarrow.py Normal file
View File

@ -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

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import json
from typing import Any, NamedTuple from typing import Any, NamedTuple
import pytest 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) img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel) _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

1
checks/32bit_segfault_check.py Executable file → Normal file
View File

@ -1,4 +1,3 @@
#!/usr/bin/env python3
from __future__ import annotations from __future__ import annotations
import sys import sys

1
checks/check_imaging_leaks.py Executable file → Normal file
View File

@ -1,4 +1,3 @@
#!/usr/bin/env python3
from __future__ import annotations from __future__ import annotations
import sys import sys

View File

@ -59,6 +59,6 @@ cmake \
"${LIBAVIF_CMAKE_FLAGS[@]}" \ "${LIBAVIF_CMAKE_FLAGS[@]}" \
. .
sudo make install make install
popd popd

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# install openjpeg # 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 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -8,6 +8,6 @@ archive=libraqm-0.10.3
pushd $archive pushd $archive
meson build --prefix=/usr && sudo ninja -C build install meson build --prefix=/usr && ninja -C build install
popd popd

View File

@ -44,7 +44,7 @@ Many of Pillow's features require external libraries:
* **libtiff** provides compressed TIFF functionality * **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 * **libfreetype** provides type related services
@ -58,7 +58,7 @@ Many of Pillow's features require external libraries:
* **openjpeg** provides JPEG 2000 functionality. * **openjpeg** provides JPEG 2000 functionality.
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, * 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 * Pillow does **not** support the earlier **1.5** series which ships
with Debian Jessie. with Debian Jessie.

View File

@ -39,9 +39,9 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Gentoo | 3.12 | x86-64 | | Gentoo | 3.12 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| macOS 13 Ventura | 3.10 | x86-64 | | macOS 15 Sequoia | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+ | +----------------------------+---------------------+
| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14 | arm64 | | | 3.11, 3.12, 3.13, 3.14, | arm64 |
| | PyPy3 | | | | PyPy3 | |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 | | 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 | | Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors | | | | 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 | | macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
| +----------------------------+------------------+ | | +----------------------------+------------------+ |
| | 3.8 | 10.4.0 | | | | 3.8 | 10.4.0 | |

View File

@ -57,6 +57,43 @@ Color names
See :ref:`color-names` for the color names supported by Pillow. 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 Fonts
^^^^^ ^^^^^
@ -545,6 +582,8 @@ Methods
hello_world = hello + world # kerning is disabled, no need to adjust hello_world = hello + world # kerning is disabled, no need to adjust
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True
.. seealso:: :py:meth:`PIL.ImageText.Text.get_length`
.. versionadded:: 8.0.0 .. versionadded:: 8.0.0
:param text: Text to be measured. May not contain any newline characters. :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 1/64 pixel precision. The bounding box includes extra margins for
some fonts, e.g. italics or accents. some fonts, e.g. italics or accents.
.. seealso:: :py:meth:`PIL.ImageText.Text.get_bbox`
.. versionadded:: 8.0.0 .. versionadded:: 8.0.0
:param xy: The anchor coordinates of the text. :param xy: The anchor coordinates of the text.

View File

@ -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:

View File

@ -24,6 +24,7 @@ Reference
ImageSequence ImageSequence
ImageShow ImageShow
ImageStat ImageStat
ImageText
ImageTk ImageTk
ImageTransform ImageTransform
ImageWin ImageWin

View File

@ -1,19 +1,6 @@
12.0.0 12.0.0
------ ------
Security
========
TODO
^^^^
TODO
:cve:`YYYY-XXXXX`: TODO
^^^^^^^^^^^^^^^^^^^^^^^
TODO
Backwards incompatible changes 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 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 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 Other changes
============= =============

View File

@ -57,6 +57,9 @@ optional-dependencies.mic = [
"olefile", "olefile",
] ]
optional-dependencies.test-arrow = [ optional-dependencies.test-arrow = [
"arro3-compute",
"arro3-core",
"nanoarrow",
"pyarrow", "pyarrow",
] ]

View File

@ -21,6 +21,10 @@ from pybind11.setup_helpers import ParallelCompile
from setuptools import Extension, setup from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext from setuptools.command.build_ext import build_ext
TYPE_CHECKING = False
if TYPE_CHECKING:
from setuptools import _BuildInfo
configuration: dict[str, list[str]] = {} configuration: dict[str, list[str]] = {}
# parse configuration from _custom_build/backend.py # parse configuration from _custom_build/backend.py
@ -1072,16 +1076,20 @@ def debug_build() -> bool:
return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD 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"] files: list[str | os.PathLike[str]] = ["src/_imaging.c"]
for src_file in _IMAGING: for src_file in _IMAGING:
files.append("src/" + src_file + ".c") files.append("src/" + src_file + ".c")
for src_file in _LIB_IMAGING: for src_file in _LIB_IMAGING:
files.append(os.path.join("src/libImaging", src_file + ".c")) files.append(os.path.join("src/libImaging", src_file + ".c"))
ext_modules = [ ext_modules = [
Extension("PIL._imaging", files), Extension("PIL._imaging", files, libraries=["pil_imaging_mode"]),
Extension("PIL._imagingft", ["src/_imagingft.c"]), Extension("PIL._imagingft", ["src/_imagingft.c"], libraries=["pil_imaging_mode"]),
Extension("PIL._imagingcms", ["src/_imagingcms.c"]), Extension("PIL._imagingcms", ["src/_imagingcms.c"], libraries=["pil_imaging_mode"]),
Extension("PIL._webp", ["src/_webp.c"]), Extension("PIL._webp", ["src/_webp.c"], libraries=["pil_imaging_mode"]),
Extension("PIL._avif", ["src/_avif.c"]), Extension("PIL._avif", ["src/_avif.c"]),
Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]),
Extension("PIL._imagingmath", ["src/_imagingmath.c"]), Extension("PIL._imagingmath", ["src/_imagingmath.c"]),
@ -1093,6 +1101,7 @@ try:
setup( setup(
cmdclass={"build_ext": pil_build_ext}, cmdclass={"build_ext": pil_build_ext},
ext_modules=ext_modules, ext_modules=ext_modules,
libraries=libraries,
zip_safe=not (debug_build() or PLATFORM_MINGW), zip_safe=not (debug_build() or PLATFORM_MINGW),
) )
except RequiredDependencyException as err: except RequiredDependencyException as err:

View File

@ -17,7 +17,7 @@
# #
from __future__ import annotations from __future__ import annotations
from . import BmpImagePlugin, Image, ImageFile from . import BmpImagePlugin, Image
from ._binary import i16le as i16 from ._binary import i16le as i16
from ._binary import i32le as i32 from ._binary import i32le as i32
@ -38,6 +38,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
format_description = "Windows Cursor" format_description = "Windows Cursor"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
offset = self.fp.tell() offset = self.fp.tell()
# check magic # check magic
@ -63,8 +64,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
# patch up the bitmap height # patch up the bitmap height
self._size = self.size[0], self.size[1] // 2 self._size = self.size[0], self.size[1] // 2
d, e, o, a = self.tile[0] self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)]
self.tile[0] = ImageFile._Tile(d, (0, 0) + self.size, o, a)
# #

View File

@ -354,6 +354,9 @@ class EpsImageFile(ImageFile.ImageFile):
read_comment(s) read_comment(s)
elif bytes_mv[:9] == b"%%Trailer": elif bytes_mv[:9] == b"%%Trailer":
trailer_reached = True 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 bytes_read = 0
# A "BoundingBox" is always required, # A "BoundingBox" is always required,

View File

@ -48,8 +48,14 @@ class FliImageFile(ImageFile.ImageFile):
def _open(self) -> None: def _open(self) -> None:
# HEAD # HEAD
assert self.fp is not None
s = self.fp.read(128) 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" msg = "not an FLI/FLC file"
raise SyntaxError(msg) raise SyntaxError(msg)
@ -77,8 +83,7 @@ class FliImageFile(ImageFile.ImageFile):
if i16(s, 4) == 0xF100: if i16(s, 4) == 0xF100:
# prefix chunk; ignore it # prefix chunk; ignore it
self.__offset = self.__offset + i32(s) self.fp.seek(self.__offset + i32(s))
self.fp.seek(self.__offset)
s = self.fp.read(16) s = self.fp.read(16)
if i16(s, 4) == 0xF1FA: if i16(s, 4) == 0xF1FA:
@ -111,6 +116,7 @@ class FliImageFile(ImageFile.ImageFile):
# load palette # load palette
i = 0 i = 0
assert self.fp is not None
for e in range(i16(self.fp.read(2))): for e in range(i16(self.fp.read(2))):
s = self.fp.read(2) s = self.fp.read(2)
i = i + s[0] i = i + s[0]

View File

@ -1009,8 +1009,14 @@ class Image:
new_im.info["transparency"] = transparency new_im.info["transparency"] = transparency
return new_im return new_im
if mode == "P" and self.mode == "RGBA": if self.mode == "RGBA":
if mode == "P":
return self.quantize(colors) 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 trns = None
delete_trns = False delete_trns = False
@ -1142,7 +1148,7 @@ class Image:
raise ValueError(msg) from e raise ValueError(msg) from e
new_im = self._new(im) new_im = self._new(im)
if mode == "P" and palette != Palette.ADAPTIVE: if mode in ("P", "PA") and palette != Palette.ADAPTIVE:
from . import ImagePalette from . import ImagePalette
new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB")) new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB"))
@ -1336,12 +1342,6 @@ class Image:
""" """
pass 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: def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image:
""" """
Filters this image using the given filter. For a list of Filters this image using the given filter. For a list of
@ -2070,9 +2070,7 @@ class Image:
:param value: The pixel value. :param value: The pixel value.
""" """
if self.readonly: self._ensure_mutable()
self._copy()
self.load()
if ( if (
self.mode in ("P", "PA") self.mode in ("P", "PA")

View File

@ -36,7 +36,7 @@ import struct
from collections.abc import Sequence from collections.abc import Sequence
from typing import cast from typing import cast
from . import Image, ImageColor from . import Image, ImageColor, ImageText
TYPE_CHECKING = False TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
@ -45,13 +45,11 @@ if TYPE_CHECKING:
from typing import Any, AnyStr from typing import Any, AnyStr
from . import ImageDraw2, ImageFont from . import ImageDraw2, ImageFont
from ._typing import Coords from ._typing import Coords, _Ink
# experimental access to the outline API # experimental access to the outline API
Outline: Callable[[], Image.core._Outline] = Image.core.outline Outline: Callable[[], Image.core._Outline] = Image.core.outline
_Ink = float | tuple[int, ...] | str
""" """
A simple 2D drawing interface for PIL images. A simple 2D drawing interface for PIL images.
<p> <p>
@ -76,9 +74,7 @@ class ImageDraw:
must be the same as the image mode. If omitted, the mode must be the same as the image mode. If omitted, the mode
defaults to the mode of the image. defaults to the mode of the image.
""" """
im.load() im._ensure_mutable()
if im.readonly:
im._copy() # make it writeable
blend = 0 blend = 0
if mode is None: if mode is None:
mode = im.mode mode = im.mode
@ -539,15 +535,10 @@ class ImageDraw:
right[3] -= r + 1 right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 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( def text(
self, self,
xy: tuple[float, float], xy: tuple[float, float],
text: AnyStr, text: AnyStr | ImageText.Text,
fill: _Ink | None = None, fill: _Ink | None = None,
font: ( font: (
ImageFont.ImageFont ImageFont.ImageFont
@ -568,29 +559,18 @@ class ImageDraw:
**kwargs: Any, **kwargs: Any,
) -> None: ) -> None:
"""Draw text.""" """Draw text."""
if embedded_color and self.mode not in ("RGB", "RGBA"): if isinstance(text, ImageText.Text):
msg = "Embedded color supported only in RGB and RGBA modes" image_text = text
raise ValueError(msg) else:
if font is None: if font is None:
font = self._getfont(kwargs.get("font_size")) font = self._getfont(kwargs.get("font_size"))
image_text = ImageText.Text(
if self._multiline_check(text): text, font, self.mode, spacing, direction, features, language
return self.multiline_text(
xy,
text,
fill,
font,
anchor,
spacing,
align,
direction,
features,
language,
stroke_width,
stroke_fill,
embedded_color,
) )
if embedded_color:
image_text.embed_color()
if stroke_width:
image_text.stroke(stroke_width, stroke_fill)
def getink(fill: _Ink | None) -> int: def getink(fill: _Ink | None) -> int:
ink, fill_ink = self._getink(fill) ink, fill_ink = self._getink(fill)
@ -599,6 +579,20 @@ class ImageDraw:
return fill_ink return fill_ink
return ink return ink
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: def draw_text(ink: int, stroke_width: float = 0) -> None:
mode = self.fontmode mode = self.fontmode
if stroke_width == 0 and embedded_color: if stroke_width == 0 and embedded_color:
@ -608,8 +602,8 @@ class ImageDraw:
coord.append(int(xy[i])) coord.append(int(xy[i]))
start = (math.modf(xy[0])[0], math.modf(xy[1])[0]) start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
try: try:
mask, offset = font.getmask2( # type: ignore[union-attr,misc] mask, offset = image_text.font.getmask2( # type: ignore[union-attr,misc]
text, line,
mode, mode,
direction=direction, direction=direction,
features=features, features=features,
@ -625,8 +619,8 @@ class ImageDraw:
coord = [coord[0] + offset[0], coord[1] + offset[1]] coord = [coord[0] + offset[0], coord[1] + offset[1]]
except AttributeError: except AttributeError:
try: try:
mask = font.getmask( # type: ignore[misc] mask = image_text.font.getmask( # type: ignore[misc]
text, line,
mode, mode,
direction, direction,
features, features,
@ -639,9 +633,10 @@ class ImageDraw:
**kwargs, **kwargs,
) )
except TypeError: except TypeError:
mask = font.getmask(text) mask = image_text.font.getmask(line)
if mode == "RGBA": if mode == "RGBA":
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A # image_text.font.getmask2(mode="RGBA")
# returns color in RGB bands and mask in A
# extract mask and set text alpha # extract mask and set text alpha
color, mask = mask, mask.getband(3) color, mask = mask, mask.getband(3)
ink_alpha = struct.pack("i", ink)[3] ink_alpha = struct.pack("i", ink)[3]
@ -654,15 +649,9 @@ class ImageDraw:
else: else:
self.draw.draw_bitmap(coord, mask, ink) 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
if stroke_ink is not None: if stroke_ink is not None:
# Draw stroked text # Draw stroked text
draw_text(stroke_ink, stroke_width) draw_text(stroke_ink, image_text.stroke_width)
# Draw normal text # Draw normal text
if ink != stroke_ink: if ink != stroke_ink:
@ -671,132 +660,6 @@ class ImageDraw:
# Only draw normal text # Only draw normal text
draw_text(ink) 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( def multiline_text(
self, self,
xy: tuple[float, float], xy: tuple[float, float],
@ -820,9 +683,10 @@ class ImageDraw:
*, *,
font_size: float | None = None, font_size: float | None = None,
) -> None: ) -> None:
font, lines = self._prepare_multiline_text( return self.text(
xy, xy,
text, text,
fill,
font, font,
anchor, anchor,
spacing, spacing,
@ -831,23 +695,9 @@ class ImageDraw:
features, features,
language, language,
stroke_width, stroke_width,
stroke_fill,
embedded_color, 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( def textlength(
@ -867,17 +717,19 @@ class ImageDraw:
font_size: float | None = None, font_size: float | None = None,
) -> float: ) -> float:
"""Get the length of a given string, in pixels with 1/64 precision.""" """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: if font is None:
font = self._getfont(font_size) font = self._getfont(font_size)
mode = "RGBA" if embedded_color else self.fontmode image_text = ImageText.Text(
return font.getlength(text, mode, direction, features, language) text,
font,
self.mode,
direction=direction,
features=features,
language=language,
)
if embedded_color:
image_text.embed_color()
return image_text.get_length()
def textbbox( def textbbox(
self, self,
@ -901,33 +753,16 @@ class ImageDraw:
font_size: float | None = None, font_size: float | None = None,
) -> tuple[float, float, float, float]: ) -> tuple[float, float, float, float]:
"""Get the bounding box of a given string, in pixels.""" """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: if font is None:
font = self._getfont(font_size) font = self._getfont(font_size)
image_text = ImageText.Text(
if self._multiline_check(text): text, font, self.mode, spacing, direction, features, language
return self.multiline_textbbox(
xy,
text,
font,
anchor,
spacing,
align,
direction,
features,
language,
stroke_width,
embedded_color,
) )
if embedded_color:
mode = "RGBA" if embedded_color else self.fontmode image_text.embed_color()
bbox = font.getbbox( if stroke_width:
text, mode, direction, features, language, stroke_width, anchor image_text.stroke(stroke_width)
) return image_text.get_bbox(xy, anchor, align)
return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]
def multiline_textbbox( def multiline_textbbox(
self, self,
@ -950,7 +785,7 @@ class ImageDraw:
*, *,
font_size: float | None = None, font_size: float | None = None,
) -> tuple[float, float, float, float]: ) -> tuple[float, float, float, float]:
font, lines = self._prepare_multiline_text( return self.textbbox(
xy, xy,
text, text,
font, font,
@ -962,37 +797,9 @@ class ImageDraw:
language, language,
stroke_width, stroke_width,
embedded_color, 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: def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
""" """

View File

@ -313,6 +313,9 @@ class ImageFile(Image.Image):
and args[0] == self.mode and args[0] == self.mode
and args[0] in Image._MAPMODES and args[0] in Image._MAPMODES
): ):
if offset < 0:
msg = "Tile offset cannot be negative"
raise ValueError(msg)
try: try:
# use mmap, if possible # use mmap, if possible
import mmap import mmap

View File

@ -125,11 +125,16 @@ class ImageFont:
image.close() image.close()
def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None: 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 # read PILfont header
if file.readline() != b"PILfont\n": if file.read(8) != b"PILfont\n":
msg = "Not a PILfont file" msg = "Not a PILfont file"
raise SyntaxError(msg) raise SyntaxError(msg)
file.readline().split(b";") file.readline()
self.info = [] # FIXME: should be a dictionary self.info = [] # FIXME: should be a dictionary
while True: while True:
s = file.readline() s = file.readline()
@ -140,11 +145,6 @@ class ImageFont:
# read PILfont metrics # read PILfont metrics
data = file.read(256 * 20) data = file.read(256 * 20)
# check image
if image.mode not in ("1", "L"):
msg = "invalid font image mode"
raise TypeError(msg)
image.load() image.load()
self.font = Image.core.font(image.im, data) self.font = Image.core.font(image.im, data)

View File

@ -499,14 +499,15 @@ def expand(
height = top + image.size[1] + bottom height = top + image.size[1] + bottom
color = _color(fill, image.mode) color = _color(fill, image.mode)
if image.palette: 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): if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4):
color = palette.getcolor(color) color = palette.getcolor(color)
else: else:
palette = None palette = None
out = Image.new(image.mode, (width, height), color) out = Image.new(image.mode, (width, height), color)
if palette: if palette:
out.putpalette(palette.palette) out.putpalette(palette.palette, mode)
out.paste(image, (left, top)) out.paste(image, (left, top))
return out return out

318
src/PIL/ImageText.py Normal file
View File

@ -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

View File

@ -193,6 +193,8 @@ def SOF(self: JpegImageFile, marker: int) -> None:
n = i16(self.fp.read(2)) - 2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
self._size = i16(s, 3), i16(s, 1) 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] self.bits = s[0]
if self.bits != 8: if self.bits != 8:

View File

@ -43,16 +43,21 @@ class PcdImageFile(ImageFile.ImageFile):
if orientation == 1: if orientation == 1:
self.tile_post_rotate = 90 self.tile_post_rotate = 90
elif orientation == 3: elif orientation == 3:
self.tile_post_rotate = -90 self.tile_post_rotate = 270
self._mode = "RGB" self._mode = "RGB"
self._size = (512, 768) if orientation in (1, 3) else (768, 512) 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: def load_end(self) -> None:
if self.tile_post_rotate: if self.tile_post_rotate:
# Handle rotated PCDs # Handle rotated PCDs
self.im = self.im.rotate(self.tile_post_rotate) self.im = self.rotate(self.tile_post_rotate, expand=True).im
# #

View File

@ -27,7 +27,7 @@ import os
import time import time
from typing import IO, Any 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.start_writing()
existing_pdf.write_header() 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 # pages

View File

@ -252,6 +252,7 @@ OPEN_INFO = {
(II, 3, (1,), 1, (8,), ()): ("P", "P"), (II, 3, (1,), 1, (8,), ()): ("P", "P"),
(MM, 3, (1,), 1, (8,), ()): ("P", "P"), (MM, 3, (1,), 1, (8,), ()): ("P", "P"),
(II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"), (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"), (II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
(MM, 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"), (II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
@ -1177,6 +1178,7 @@ class TiffImageFile(ImageFile.ImageFile):
"""Open the first image in a TIFF file""" """Open the first image in a TIFF file"""
# Header # Header
assert self.fp is not None
ifh = self.fp.read(8) ifh = self.fp.read(8)
if ifh[2] == 43: if ifh[2] == 43:
ifh += self.fp.read(8) ifh += self.fp.read(8)
@ -1343,6 +1345,7 @@ class TiffImageFile(ImageFile.ImageFile):
# To be nice on memory footprint, if there's a # To be nice on memory footprint, if there's a
# file descriptor, use that instead of reading # file descriptor, use that instead of reading
# into a string in python. # into a string in python.
assert self.fp is not None
try: try:
fp = hasattr(self.fp, "fileno") and self.fp.fileno() fp = hasattr(self.fp, "fileno") and self.fp.fileno()
# flush the file descriptor, prevents error on pypy 2.4+ # 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 types[tag] = TiffTags.LONG8
elif tag in ifd.tagtype: elif tag in ifd.tagtype:
types[tag] = ifd.tagtype[tag] types[tag] = ifd.tagtype[tag]
elif not (isinstance(value, (int, float, str, bytes))): elif isinstance(value, (int, float, str, bytes)) or (
continue isinstance(value, tuple)
else: and all(isinstance(v, (int, float, IFDRational)) for v in value)
):
type = TiffTags.lookup(tag).type type = TiffTags.lookup(tag).type
if type: if type:
types[tag] = type types[tag] = type

View File

@ -203,6 +203,11 @@ _tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]]
531: ("YCbCrPositioning", SHORT, 1), 531: ("YCbCrPositioning", SHORT, 1),
532: ("ReferenceBlackWhite", RATIONAL, 6), 532: ("ReferenceBlackWhite", RATIONAL, 6),
700: ("XMP", BYTE, 0), 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), 33432: ("Copyright", ASCII, 1),
33723: ("IptcNaaInfo", UNDEFINED, 1), 33723: ("IptcNaaInfo", UNDEFINED, 1),
34377: ("PhotoshopInfo", BYTE, 0), 34377: ("PhotoshopInfo", BYTE, 0),

View File

@ -27,6 +27,8 @@ else:
Buffer = Any Buffer = Any
_Ink = float | tuple[int, ...] | str
Coords = Sequence[float] | Sequence[Sequence[float]] Coords = Sequence[float] | Sequence[Sequence[float]]

View File

@ -121,15 +121,16 @@ PyImagingPhotoPut(
/* Mode */ /* 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.pixelSize = 1;
block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0; 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.pixelSize = 4;
block.offset[0] = 0; block.offset[0] = 0;
block.offset[1] = 1; block.offset[1] = 1;
block.offset[2] = 2; 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) */ block.offset[3] = 3; /* alpha (or reserved, under Tk 8.2) */
} else { } else {
block.offset[3] = 0; /* no alpha */ block.offset[3] = 0; /* no alpha */

View File

@ -297,6 +297,7 @@ ExportArrowArrayPyCapsule(ImagingObject *self) {
static PyObject * static PyObject *
_new_arrow(PyObject *self, PyObject *args) { _new_arrow(PyObject *self, PyObject *args) {
char *mode; char *mode;
ModeID mode_id;
int xsize, ysize; int xsize, ysize;
PyObject *schema_capsule, *array_capsule; PyObject *schema_capsule, *array_capsule;
PyObject *ret; PyObject *ret;
@ -307,9 +308,11 @@ _new_arrow(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
mode_id = findModeID(mode);
// ImagingBorrowArrow is responsible for retaining the array_capsule // ImagingBorrowArrow is responsible for retaining the array_capsule
ret = PyImagingNew( ret = PyImagingNew(
ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule) ImagingNewArrow(mode_id, xsize, ysize, schema_capsule, array_capsule)
); );
if (!ret) { if (!ret) {
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch"); return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
@ -368,7 +371,7 @@ ImagingError_ValueError(const char *message) {
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
static int static int
getbands(const char *mode) { getbands(const ModeID mode) {
Imaging im; Imaging im;
int bands; int bands;
@ -662,7 +665,7 @@ getink(PyObject *color, Imaging im, char *ink) {
memcpy(ink, &ftmp, sizeof(ftmp)); memcpy(ink, &ftmp, sizeof(ftmp));
return ink; return ink;
case IMAGING_TYPE_SPECIAL: case IMAGING_TYPE_SPECIAL:
if (strncmp(im->mode, "I;16", 4) == 0) { if (isModeI16(im->mode)) {
ink[0] = (UINT8)r; ink[0] = (UINT8)r;
ink[1] = (UINT8)(r >> 8); ink[1] = (UINT8)(r >> 8);
ink[2] = ink[3] = 0; ink[2] = ink[3] = 0;
@ -694,7 +697,7 @@ getink(PyObject *color, Imaging im, char *ink) {
static PyObject * static PyObject *
_fill(PyObject *self, PyObject *args) { _fill(PyObject *self, PyObject *args) {
char *mode; char *mode_name;
int xsize, ysize; int xsize, ysize;
PyObject *color; PyObject *color;
char buffer[4]; char buffer[4];
@ -703,10 +706,12 @@ _fill(PyObject *self, PyObject *args) {
xsize = ysize = 256; xsize = ysize = 256;
color = NULL; 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; return NULL;
} }
const ModeID mode = findModeID(mode_name);
im = ImagingNewDirty(mode, xsize, ysize); im = ImagingNewDirty(mode, xsize, ysize);
if (!im) { if (!im) {
return NULL; return NULL;
@ -727,47 +732,55 @@ _fill(PyObject *self, PyObject *args) {
static PyObject * static PyObject *
_new(PyObject *self, PyObject *args) { _new(PyObject *self, PyObject *args) {
char *mode; char *mode_name;
int xsize, ysize; int xsize, ysize;
if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingNew(mode, xsize, ysize)); return PyImagingNew(ImagingNew(mode, xsize, ysize));
} }
static PyObject * static PyObject *
_new_block(PyObject *self, PyObject *args) { _new_block(PyObject *self, PyObject *args) {
char *mode; char *mode_name;
int xsize, ysize; int xsize, ysize;
if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingNewBlock(mode, xsize, ysize)); return PyImagingNew(ImagingNewBlock(mode, xsize, ysize));
} }
static PyObject * static PyObject *
_linear_gradient(PyObject *self, PyObject *args) { _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; return NULL;
} }
const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingFillLinearGradient(mode)); return PyImagingNew(ImagingFillLinearGradient(mode));
} }
static PyObject * static PyObject *
_radial_gradient(PyObject *self, PyObject *args) { _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; return NULL;
} }
const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingFillRadialGradient(mode)); return PyImagingNew(ImagingFillRadialGradient(mode));
} }
@ -907,7 +920,7 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) {
static PyObject * static PyObject *
_color_lut_3d(ImagingObject *self, PyObject *args) { _color_lut_3d(ImagingObject *self, PyObject *args) {
char *mode; char *mode_name;
int filter; int filter;
int table_channels; int table_channels;
int size1D, size2D, size3D; int size1D, size2D, size3D;
@ -919,7 +932,7 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, args,
"sii(iii)O:color_lut_3d", "sii(iii)O:color_lut_3d",
&mode, &mode_name,
&filter, &filter,
&table_channels, &table_channels,
&size1D, &size1D,
@ -930,6 +943,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
/* actually, it is trilinear */ /* actually, it is trilinear */
if (filter != IMAGING_TRANSFORM_BILINEAR) { if (filter != IMAGING_TRANSFORM_BILINEAR) {
PyErr_SetString(PyExc_ValueError, "Only LINEAR filter is supported."); PyErr_SetString(PyExc_ValueError, "Only LINEAR filter is supported.");
@ -976,11 +991,11 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
static PyObject * static PyObject *
_convert(ImagingObject *self, PyObject *args) { _convert(ImagingObject *self, PyObject *args) {
char *mode; char *mode_name;
int dither = 0; int dither = 0;
ImagingObject *paletteimage = NULL; ImagingObject *paletteimage = NULL;
if (!PyArg_ParseTuple(args, "s|iO", &mode, &dither, &paletteimage)) { if (!PyArg_ParseTuple(args, "s|iO", &mode_name, &dither, &paletteimage)) {
return NULL; return NULL;
} }
if (paletteimage != NULL) { if (paletteimage != NULL) {
@ -997,6 +1012,8 @@ _convert(ImagingObject *self, PyObject *args) {
} }
} }
const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingConvert( return PyImagingNew(ImagingConvert(
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither
)); ));
@ -1021,14 +1038,14 @@ _convert2(ImagingObject *self, PyObject *args) {
static PyObject * static PyObject *
_convert_matrix(ImagingObject *self, PyObject *args) { _convert_matrix(ImagingObject *self, PyObject *args) {
char *mode; char *mode_name;
float m[12]; 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(); PyErr_Clear();
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, args,
"s(ffffffffffff)", "s(ffffffffffff)",
&mode, &mode_name,
m + 0, m + 0,
m + 1, m + 1,
m + 2, 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)); return PyImagingNew(ImagingConvertMatrix(self->image, mode, m));
} }
static PyObject * static PyObject *
_convert_transparent(ImagingObject *self, PyObject *args) { _convert_transparent(ImagingObject *self, PyObject *args) {
char *mode; char *mode_name;
int r, g, b; 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)); return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b));
} }
PyErr_Clear(); 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 PyImagingNew(ImagingConvertTransparent(self->image, mode, r, 0, 0));
} }
return NULL; return NULL;
@ -1156,9 +1177,9 @@ _getpalette(ImagingObject *self, PyObject *args) {
int bits; int bits;
ImagingShuffler pack; ImagingShuffler pack;
char *mode = "RGB"; char *mode_name = "RGB";
char *rawmode = "RGB"; char *rawmode_name = "RGB";
if (!PyArg_ParseTuple(args, "|ss", &mode, &rawmode)) { if (!PyArg_ParseTuple(args, "|ss", &mode_name, &rawmode_name)) {
return NULL; return NULL;
} }
@ -1167,6 +1188,9 @@ _getpalette(ImagingObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
pack = ImagingFindPacker(mode, rawmode, &bits); pack = ImagingFindPacker(mode, rawmode, &bits);
if (!pack) { if (!pack) {
PyErr_SetString(PyExc_ValueError, wrong_raw_mode); PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
@ -1193,7 +1217,7 @@ _getpalettemode(ImagingObject *self) {
return NULL; return NULL;
} }
return PyUnicode_FromString(self->image->palette->mode); return PyUnicode_FromString(getModeData(self->image->palette->mode)->name);
} }
static inline int static inline int
@ -1474,12 +1498,14 @@ _point(ImagingObject *self, PyObject *args) {
Imaging im; Imaging im;
PyObject *list; PyObject *list;
char *mode; char *mode_name;
if (!PyArg_ParseTuple(args, "Oz", &list, &mode)) { if (!PyArg_ParseTuple(args, "Oz", &list, &mode_name)) {
return NULL; return NULL;
} }
if (mode && !strcmp(mode, "F")) { const ModeID mode = findModeID(mode_name);
if (mode == IMAGING_MODE_F) {
FLOAT32 *data; FLOAT32 *data;
/* map from 8-bit data to floating point */ /* map from 8-bit data to floating point */
@ -1490,8 +1516,7 @@ _point(ImagingObject *self, PyObject *args) {
} }
im = ImagingPoint(self->image, mode, (void *)data); im = ImagingPoint(self->image, mode, (void *)data);
free(data); free(data);
} else if (self->image->mode == IMAGING_MODE_I && mode == IMAGING_MODE_L) {
} else if (!strcmp(self->image->mode, "I") && mode && !strcmp(mode, "L")) {
UINT8 *data; UINT8 *data;
/* map from 16-bit subset of 32-bit data to 8-bit */ /* 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); im = ImagingPoint(self->image, mode, (void *)data);
free(data); free(data);
} else { } else {
INT32 *data; INT32 *data;
UINT8 lut[1024]; UINT8 lut[1024];
@ -1524,7 +1548,7 @@ _point(ImagingObject *self, PyObject *args) {
return NULL; return NULL;
} }
if (mode && !strcmp(mode, "I")) { if (mode == IMAGING_MODE_I) {
im = ImagingPoint(self->image, mode, (void *)data); im = ImagingPoint(self->image, mode, (void *)data);
} else if (mode && bands > 1) { } else if (mode && bands > 1) {
for (i = 0; i < 256; i++) { for (i = 0; i < 256; i++) {
@ -1630,9 +1654,9 @@ _putdata(ImagingObject *self, PyObject *args) {
if (image->type == IMAGING_TYPE_SPECIAL) { if (image->type == IMAGING_TYPE_SPECIAL) {
// I;16* // I;16*
if ( if (
strcmp(image->mode, "I;16B") == 0 image->mode == IMAGING_MODE_I_16B
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
|| strcmp(image->mode, "I;16N") == 0 || image->mode == IMAGING_MODE_I_16N
#endif #endif
) { ) {
bigendian = 1; bigendian = 1;
@ -1729,7 +1753,9 @@ _quantize(ImagingObject *self, PyObject *args) {
if (!self->image->xsize || !self->image->ysize) { if (!self->image->xsize || !self->image->ysize) {
/* no content; return an empty image */ /* 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)); return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans));
@ -1740,21 +1766,33 @@ _putpalette(ImagingObject *self, PyObject *args) {
ImagingShuffler unpack; ImagingShuffler unpack;
int bits; int bits;
char *palette_mode, *rawmode; char *palette_mode_name, *rawmode_name;
UINT8 *palette; UINT8 *palette;
Py_ssize_t palettesize; Py_ssize_t palettesize;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize args, "ssy#", &palette_mode_name, &rawmode_name, &palette, &palettesize
)) { )) {
return NULL; return NULL;
} }
if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") && if (self->image->mode != IMAGING_MODE_L && self->image->mode != IMAGING_MODE_LA &&
strcmp(self->image->mode, "P") && strcmp(self->image->mode, "PA")) { self->image->mode != IMAGING_MODE_P && self->image->mode != IMAGING_MODE_PA) {
PyErr_SetString(PyExc_ValueError, wrong_mode); PyErr_SetString(PyExc_ValueError, wrong_mode);
return NULL; 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); unpack = ImagingFindUnpacker(palette_mode, rawmode, &bits);
if (!unpack) { if (!unpack) {
PyErr_SetString(PyExc_ValueError, wrong_raw_mode); PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
@ -1768,7 +1806,13 @@ _putpalette(ImagingObject *self, PyObject *args) {
ImagingPaletteDelete(self->image->palette); 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); self->image->palette = ImagingPaletteNew(palette_mode);
@ -1796,7 +1840,7 @@ _putpalettealpha(ImagingObject *self, PyObject *args) {
return NULL; return NULL;
} }
strcpy(self->image->palette->mode, "RGBA"); self->image->palette->mode = IMAGING_MODE_RGBA;
self->image->palette->palette[index * 4 + 3] = (UINT8)alpha; self->image->palette->palette[index * 4 + 3] = (UINT8)alpha;
Py_RETURN_NONE; Py_RETURN_NONE;
@ -1821,7 +1865,7 @@ _putpalettealphas(ImagingObject *self, PyObject *args) {
return NULL; return NULL;
} }
strcpy(self->image->palette->mode, "RGBA"); self->image->palette->mode = IMAGING_MODE_RGBA;
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
self->image->palette->palette[i * 4 + 3] = (UINT8)values[i]; self->image->palette->palette[i * 4 + 3] = (UINT8)values[i];
} }
@ -1989,8 +2033,11 @@ _reduce(ImagingObject *self, PyObject *args) {
return PyImagingNew(imOut); return PyImagingNew(imOut);
} }
#define IS_RGB(mode) \ static int
(!strcmp(mode, "RGB") || !strcmp(mode, "RGBA") || !strcmp(mode, "RGBX")) isRGB(const ModeID mode) {
return mode == IMAGING_MODE_RGB || mode == IMAGING_MODE_RGBA ||
mode == IMAGING_MODE_RGBX;
}
static PyObject * static PyObject *
im_setmode(ImagingObject *self, PyObject *args) { im_setmode(ImagingObject *self, PyObject *args) {
@ -1998,23 +2045,25 @@ im_setmode(ImagingObject *self, PyObject *args) {
Imaging im; Imaging im;
char *mode; char *mode_name;
Py_ssize_t modelen; Py_ssize_t modelen;
if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) { if (!PyArg_ParseTuple(args, "s#:setmode", &mode_name, &modelen)) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
im = self->image; im = self->image;
/* move all logic in here to the libImaging primitive */ /* move all logic in here to the libImaging primitive */
if (!strcmp(im->mode, mode)) { if (im->mode == mode) {
; /* same mode; always succeeds */ ; /* same mode; always succeeds */
} else if (IS_RGB(im->mode) && IS_RGB(mode)) { } else if (isRGB(im->mode) && isRGB(mode)) {
/* color to color */ /* color to color */
strcpy(im->mode, mode); im->mode = mode;
im->bands = modelen; im->bands = modelen;
if (!strcmp(mode, "RGBA")) { if (mode == IMAGING_MODE_RGBA) {
(void)ImagingFillBand(im, 3, 255); (void)ImagingFillBand(im, 3, 255);
} }
} else { } else {
@ -2294,7 +2343,7 @@ _getextrema(ImagingObject *self) {
case IMAGING_TYPE_FLOAT32: case IMAGING_TYPE_FLOAT32:
return Py_BuildValue("dd", extrema.f[0], extrema.f[1]); return Py_BuildValue("dd", extrema.f[0], extrema.f[1]);
case IMAGING_TYPE_SPECIAL: 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]); return Py_BuildValue("HH", extrema.s[0], extrema.s[1]);
} }
} }
@ -2383,7 +2432,7 @@ _putband(ImagingObject *self, PyObject *args) {
static PyObject * static PyObject *
_merge(PyObject *self, PyObject *args) { _merge(PyObject *self, PyObject *args) {
char *mode; char *mode_name;
ImagingObject *band0 = NULL; ImagingObject *band0 = NULL;
ImagingObject *band1 = NULL; ImagingObject *band1 = NULL;
ImagingObject *band2 = NULL; ImagingObject *band2 = NULL;
@ -2393,7 +2442,7 @@ _merge(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, args,
"sO!|O!O!O!", "sO!|O!O!O!",
&mode, &mode_name,
&Imaging_Type, &Imaging_Type,
&band0, &band0,
&Imaging_Type, &Imaging_Type,
@ -2406,6 +2455,8 @@ _merge(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
if (band0) { if (band0) {
bands[0] = band0->image; bands[0] = band0->image;
} }
@ -2419,7 +2470,12 @@ _merge(PyObject *self, PyObject *args) {
bands[3] = band3->image; 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 * static PyObject *
@ -3711,7 +3767,7 @@ static struct PyMethodDef methods[] = {
static PyObject * static PyObject *
_getattr_mode(ImagingObject *self, void *closure) { _getattr_mode(ImagingObject *self, void *closure) {
return PyUnicode_FromString(self->image->mode); return PyUnicode_FromString(getModeData(self->image->mode)->name);
} }
static PyObject * static PyObject *
@ -4255,8 +4311,6 @@ setup_module(PyObject *m) {
return -1; return -1;
} }
ImagingAccessInit();
#ifdef HAVE_LIBJPEG #ifdef HAVE_LIBJPEG
{ {
extern const char *ImagingJpegVersion(void); extern const char *ImagingJpegVersion(void);

View File

@ -212,32 +212,44 @@ cms_transform_dealloc(CmsTransformObject *self) {
/* internal functions */ /* internal functions */
static cmsUInt32Number static cmsUInt32Number
findLCMStype(char *PILmode) { findLCMStype(const char *const mode_name) {
if (strcmp(PILmode, "RGB") == 0 || strcmp(PILmode, "RGBA") == 0 || const ModeID mode = findModeID(mode_name);
strcmp(PILmode, "RGBX") == 0) { switch (mode) {
case IMAGING_MODE_RGB:
case IMAGING_MODE_RGBA:
case IMAGING_MODE_RGBX:
return TYPE_RGBA_8; 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; return TYPE_RGBA_16;
} }
if (strcmp(PILmode, "CMYK") == 0) { if (strcmp(mode_name, "L;16") == 0) {
return TYPE_CMYK_8;
}
if (strcmp(PILmode, "I;16") == 0 || strcmp(PILmode, "I;16L") == 0 ||
strcmp(PILmode, "L;16") == 0) {
return TYPE_GRAY_16; 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; return TYPE_GRAY_16_SE;
} }
if (strcmp(PILmode, "YCbCr") == 0 || strcmp(PILmode, "YCCA") == 0 || if (strcmp(mode_name, "YCCA") == 0 || strcmp(mode_name, "YCC") == 0) {
strcmp(PILmode, "YCC") == 0) {
return TYPE_YCbCr_8; 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 */ /* presume "1" or "L" by default */
return TYPE_GRAY_8; return TYPE_GRAY_8;
} }

View File

@ -525,7 +525,7 @@ font_getlength(FontObject *self, PyObject *args) {
int horizontal_dir; /* is primary axis horizontal? */ int horizontal_dir; /* is primary axis horizontal? */
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
int color = 0; /* is FT_LOAD_COLOR enabled? */ int color = 0; /* is FT_LOAD_COLOR enabled? */
const char *mode = NULL; const char *mode_name = NULL;
const char *dir = NULL; const char *dir = NULL;
const char *lang = NULL; const char *lang = NULL;
PyObject *features = Py_None; PyObject *features = Py_None;
@ -534,15 +534,16 @@ font_getlength(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */ /* calculate size and bearing for a given string */
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang args, "O|zzOz:getlength", &string, &mode_name, &dir, &features, &lang
)) { )) {
return NULL; return NULL;
} }
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
mask = mode && strcmp(mode, "1") == 0; const ModeID mode = findModeID(mode_name);
color = mode && strcmp(mode, "RGBA") == 0; mask = mode == IMAGING_MODE_1;
color = mode == IMAGING_MODE_RGBA;
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color); count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
@ -754,7 +755,7 @@ font_getsize(FontObject *self, PyObject *args) {
int horizontal_dir; /* is primary axis horizontal? */ int horizontal_dir; /* is primary axis horizontal? */
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
int color = 0; /* is FT_LOAD_COLOR enabled? */ int color = 0; /* is FT_LOAD_COLOR enabled? */
const char *mode = NULL; const char *mode_name = NULL;
const char *dir = NULL; const char *dir = NULL;
const char *lang = NULL; const char *lang = NULL;
const char *anchor = NULL; const char *anchor = NULL;
@ -764,15 +765,23 @@ font_getsize(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */ /* calculate size and bearing for a given string */
if (!PyArg_ParseTuple( 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; return NULL;
} }
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
mask = mode && strcmp(mode, "1") == 0; const ModeID mode = findModeID(mode_name);
color = mode && strcmp(mode, "RGBA") == 0; mask = mode == IMAGING_MODE_1;
color = mode == IMAGING_MODE_RGBA;
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color); count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
@ -839,7 +848,7 @@ font_render(FontObject *self, PyObject *args) {
int stroke_filled = 0; int stroke_filled = 0;
PY_LONG_LONG foreground_ink_long = 0; PY_LONG_LONG foreground_ink_long = 0;
unsigned int foreground_ink; unsigned int foreground_ink;
const char *mode = NULL; const char *mode_name = NULL;
const char *dir = NULL; const char *dir = NULL;
const char *lang = NULL; const char *lang = NULL;
const char *anchor = NULL; const char *anchor = NULL;
@ -859,7 +868,7 @@ font_render(FontObject *self, PyObject *args) {
"OO|zzOzfpzL(ff):render", "OO|zzOzfpzL(ff):render",
&string, &string,
&fill, &fill,
&mode, &mode_name,
&dir, &dir,
&features, &features,
&lang, &lang,
@ -873,8 +882,9 @@ font_render(FontObject *self, PyObject *args) {
return NULL; return NULL;
} }
mask = mode && strcmp(mode, "1") == 0; const ModeID mode = findModeID(mode_name);
color = mode && strcmp(mode, "RGBA") == 0; mask = mode == IMAGING_MODE_1;
color = mode == IMAGING_MODE_RGBA;
foreground_ink = foreground_ink_long; foreground_ink = foreground_ink_long;

View File

@ -89,8 +89,8 @@ HandleMuxError(WebPMuxError err, char *chunk) {
static int static int
import_frame_libwebp(WebPPicture *frame, Imaging im) { import_frame_libwebp(WebPPicture *frame, Imaging im) {
if (strcmp(im->mode, "RGBA") && strcmp(im->mode, "RGB") && if (im->mode != IMAGING_MODE_RGBA && im->mode != IMAGING_MODE_RGB &&
strcmp(im->mode, "RGBX")) { im->mode != IMAGING_MODE_RGBX) {
PyErr_SetString(PyExc_ValueError, "unsupported image mode"); PyErr_SetString(PyExc_ValueError, "unsupported image mode");
return -1; return -1;
} }
@ -104,7 +104,7 @@ import_frame_libwebp(WebPPicture *frame, Imaging im) {
return -2; 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) { for (int y = 0; y < im->ysize; ++y) {
UINT8 *src = (UINT8 *)im->image32[y]; UINT8 *src = (UINT8 *)im->image32[y];
UINT32 *dst = frame->argb + frame->argb_stride * y; UINT32 *dst = frame->argb + frame->argb_stride * y;
@ -143,7 +143,7 @@ typedef struct {
PyObject_HEAD WebPAnimDecoder *dec; PyObject_HEAD WebPAnimDecoder *dec;
WebPAnimInfo info; WebPAnimInfo info;
WebPData data; WebPData data;
char *mode; ModeID mode;
} WebPAnimDecoderObject; } WebPAnimDecoderObject;
static PyTypeObject WebPAnimDecoder_Type; static PyTypeObject WebPAnimDecoder_Type;
@ -396,7 +396,7 @@ _anim_decoder_new(PyObject *self, PyObject *args) {
const uint8_t *webp; const uint8_t *webp;
Py_ssize_t size; Py_ssize_t size;
WebPData webp_src; WebPData webp_src;
char *mode; ModeID mode;
WebPDecoderConfig config; WebPDecoderConfig config;
WebPAnimDecoderObject *decp = NULL; WebPAnimDecoderObject *decp = NULL;
WebPAnimDecoder *dec = NULL; WebPAnimDecoder *dec = NULL;
@ -409,10 +409,10 @@ _anim_decoder_new(PyObject *self, PyObject *args) {
webp_src.size = size; webp_src.size = size;
// Sniff the mode, since the decoder API doesn't tell us // 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 (WebPGetFeatures(webp, size, &config.input) == VP8_STATUS_OK) {
if (!config.input.has_alpha) { 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->loop_count,
info->bgcolor, info->bgcolor,
info->frame_count, info->frame_count,
decp->mode getModeData(decp->mode)->name
); );
} }

View File

@ -266,7 +266,9 @@ static PyTypeObject ImagingDecoderType = {
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
int int
get_unpacker(ImagingDecoderObject *decoder, const char *mode, const char *rawmode) { get_unpacker(
ImagingDecoderObject *decoder, const ModeID mode, const RawModeID rawmode
) {
int bits; int bits;
ImagingShuffler unpack; ImagingShuffler unpack;
@ -291,17 +293,20 @@ PyObject *
PyImaging_BitDecoderNew(PyObject *self, PyObject *args) { PyImaging_BitDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; const char *mode_name;
int bits = 8; int bits = 8;
int pad = 8; int pad = 8;
int fill = 0; int fill = 0;
int sign = 0; int sign = 0;
int ystep = 1; 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; 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"); PyErr_SetString(PyExc_ValueError, "bad image mode");
return NULL; return NULL;
} }
@ -331,34 +336,36 @@ PyObject *
PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) { PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name;
char *actual;
int n = 0; int n = 0;
char *pixel_format = ""; 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; return NULL;
} }
const ModeID mode = findModeID(mode_name);
ModeID actual;
switch (n) { switch (n) {
case 1: /* BC1: 565 color, 1-bit alpha */ case 1: /* BC1: 565 color, 1-bit alpha */
case 2: /* BC2: 565 color, 4-bit alpha */ case 2: /* BC2: 565 color, 4-bit alpha */
case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */ case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */
case 7: /* BC7: 4-channel 8-bit via everything */ case 7: /* BC7: 4-channel 8-bit via everything */
actual = "RGBA"; actual = IMAGING_MODE_RGBA;
break; break;
case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */ case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */
actual = "L"; actual = IMAGING_MODE_L;
break; break;
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */ case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
case 6: /* BC6: 3-channel 16-bit float */ case 6: /* BC6: 3-channel 16-bit float */
actual = "RGB"; actual = IMAGING_MODE_RGB;
break; break;
default: default:
PyErr_SetString(PyExc_ValueError, "block compression type unknown"); PyErr_SetString(PyExc_ValueError, "block compression type unknown");
return NULL; return NULL;
} }
if (strcmp(mode, actual) != 0) { if (mode != actual) {
PyErr_SetString(PyExc_ValueError, "bad image mode"); PyErr_SetString(PyExc_ValueError, "bad image mode");
return NULL; return NULL;
} }
@ -401,15 +408,18 @@ PyObject *
PyImaging_GifDecoderNew(PyObject *self, PyObject *args) { PyImaging_GifDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; const char *mode_name;
int bits = 8; int bits = 8;
int interlace = 0; int interlace = 0;
int transparency = -1; 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; 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"); PyErr_SetString(PyExc_ValueError, "bad image mode");
return NULL; return NULL;
} }
@ -436,12 +446,14 @@ PyObject *
PyImaging_HexDecoderNew(PyObject *self, PyObject *args) { PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name, *rawmode_name;
char *rawmode; if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
decoder = PyImaging_DecoderNew(0); decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) { if (decoder == NULL) {
return NULL; return NULL;
@ -469,16 +481,21 @@ PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
PyObject * PyObject *
PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) { PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name;
char *rawmode; char *rawmode_name;
char *compname; char *compname;
int fp; int fp;
uint32_t ifdoffset; 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; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
TRACE(("new tiff decoder %s\n", compname)); TRACE(("new tiff decoder %s\n", compname));
decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE)); decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE));
@ -511,12 +528,15 @@ PyObject *
PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) { PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name;
char *rawmode; char *rawmode_name;
if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) { if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
decoder = PyImaging_DecoderNew(0); decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) { if (decoder == NULL) {
return NULL; return NULL;
@ -545,7 +565,7 @@ PyImaging_PcdDecoderNew(PyObject *self, PyObject *args) {
} }
/* Unpack from PhotoYCC to RGB */ /* 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; return NULL;
} }
@ -562,13 +582,15 @@ PyObject *
PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) { PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name, *rawmode_name;
char *rawmode;
int stride; int stride;
if (!PyArg_ParseTuple(args, "ssi", &mode, &rawmode, &stride)) { if (!PyArg_ParseTuple(args, "ssi", &mode_name, &rawmode_name, &stride)) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
decoder = PyImaging_DecoderNew(0); decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) { if (decoder == NULL) {
return NULL; return NULL;
@ -593,14 +615,16 @@ PyObject *
PyImaging_RawDecoderNew(PyObject *self, PyObject *args) { PyImaging_RawDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name, *rawmode_name;
char *rawmode;
int stride = 0; int stride = 0;
int ystep = 1; 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; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
decoder = PyImaging_DecoderNew(sizeof(RAWSTATE)); decoder = PyImaging_DecoderNew(sizeof(RAWSTATE));
if (decoder == NULL) { if (decoder == NULL) {
return NULL; return NULL;
@ -627,14 +651,16 @@ PyObject *
PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) { PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name, *rawmode_name;
char *rawmode;
int ystep = 1; int ystep = 1;
int bpc = 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; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
decoder = PyImaging_DecoderNew(sizeof(SGISTATE)); decoder = PyImaging_DecoderNew(sizeof(SGISTATE));
if (decoder == NULL) { if (decoder == NULL) {
return NULL; return NULL;
@ -661,12 +687,14 @@ PyObject *
PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) { PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name, *rawmode_name;
char *rawmode; if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
decoder = PyImaging_DecoderNew(0); decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) { if (decoder == NULL) {
return NULL; return NULL;
@ -689,14 +717,16 @@ PyObject *
PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) { PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name, *rawmode_name;
char *rawmode;
int ystep = 1; int ystep = 1;
int depth = 8; 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; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
decoder = PyImaging_DecoderNew(0); decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) { if (decoder == NULL) {
return NULL; return NULL;
@ -727,7 +757,7 @@ PyImaging_XbmDecoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
if (get_unpacker(decoder, "1", "1;R") < 0) { if (get_unpacker(decoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) {
return NULL; return NULL;
} }
@ -748,13 +778,15 @@ PyObject *
PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) { PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name, *rawmode_name;
char *rawmode;
int interlaced = 0; 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; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE)); decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE));
if (decoder == NULL) { if (decoder == NULL) {
return NULL; return NULL;
@ -798,19 +830,21 @@ PyObject *
PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) { PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder; ImagingDecoderObject *decoder;
char *mode; char *mode_name;
char *rawmode; /* what we want from the decoder */ char *rawmode_name; /* what we want from the decoder */
char *jpegmode; /* what's in the file */ char *jpegmode_name; /* what's in the file */
int scale = 1; int scale = 1;
int draft = 0; 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; return NULL;
} }
if (!jpegmode) { const ModeID mode = findModeID(mode_name);
jpegmode = ""; RawModeID rawmode = findRawModeID(rawmode_name);
} const RawModeID jpegmode = findRawModeID(jpegmode_name);
decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE)); decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE));
if (decoder == NULL) { if (decoder == NULL) {
@ -820,8 +854,8 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
// libjpeg-turbo supports different output formats. // libjpeg-turbo supports different output formats.
// We are choosing Pillow's native format (3 color bytes + 1 padding) // We are choosing Pillow's native format (3 color bytes + 1 padding)
// to avoid extra conversion in Unpack.c. // to avoid extra conversion in Unpack.c.
if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) { if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) {
rawmode = "RGBX"; rawmode = IMAGING_RAWMODE_RGBX;
} }
if (get_unpacker(decoder, mode, rawmode) < 0) { if (get_unpacker(decoder, mode, rawmode) < 0) {
@ -831,11 +865,13 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
decoder->decode = ImagingJpegDecode; decoder->decode = ImagingJpegDecode;
decoder->cleanup = ImagingJpegDecodeCleanup; decoder->cleanup = ImagingJpegDecodeCleanup;
strncpy(((JPEGSTATE *)decoder->state.context)->rawmode, rawmode, 8); JPEGSTATE *jpeg_decoder_state_context = (JPEGSTATE *)decoder->state.context;
strncpy(((JPEGSTATE *)decoder->state.context)->jpegmode, jpegmode, 8);
((JPEGSTATE *)decoder->state.context)->scale = scale; jpeg_decoder_state_context->rawmode = rawmode;
((JPEGSTATE *)decoder->state.context)->draft = draft; jpeg_decoder_state_context->jpegmode = jpegmode;
jpeg_decoder_state_context->scale = scale;
jpeg_decoder_state_context->draft = draft;
return (PyObject *)decoder; return (PyObject *)decoder;
} }

View File

@ -47,7 +47,7 @@ typedef struct {
static PyTypeObject ImagingDisplayType; static PyTypeObject ImagingDisplayType;
static ImagingDisplayObject * static ImagingDisplayObject *
_new(const char *mode, int xsize, int ysize) { _new(const ModeID mode, int xsize, int ysize) {
ImagingDisplayObject *display; ImagingDisplayObject *display;
if (PyType_Ready(&ImagingDisplayType) < 0) { if (PyType_Ready(&ImagingDisplayType) < 0) {
@ -235,7 +235,7 @@ static struct PyMethodDef methods[] = {
static PyObject * static PyObject *
_getattr_mode(ImagingDisplayObject *self, void *closure) { _getattr_mode(ImagingDisplayObject *self, void *closure) {
return Py_BuildValue("s", self->dib->mode); return Py_BuildValue("s", getModeData(self->dib->mode)->name);
} }
static PyObject * static PyObject *
@ -258,13 +258,14 @@ static PyTypeObject ImagingDisplayType = {
PyObject * PyObject *
PyImaging_DisplayWin32(PyObject *self, PyObject *args) { PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
ImagingDisplayObject *display; ImagingDisplayObject *display;
char *mode; char *mode_name;
int xsize, ysize; int xsize, ysize;
if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
display = _new(mode, xsize, ysize); display = _new(mode, xsize, ysize);
if (display == NULL) { if (display == NULL) {
return NULL; return NULL;
@ -275,12 +276,9 @@ PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
PyObject * PyObject *
PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) { PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) {
char *mode;
int size[2]; int size[2];
const ModeID mode = ImagingGetModeDIB(size);
mode = ImagingGetModeDIB(size); return Py_BuildValue("s(ii)", getModeData(mode)->name, size[0], size[1]);
return Py_BuildValue("s(ii)", mode, size[0], size[1]);
} }
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */

View File

@ -334,14 +334,19 @@ static PyTypeObject ImagingEncoderType = {
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
int int
get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode) { get_packer(ImagingEncoderObject *encoder, const ModeID mode, const RawModeID rawmode) {
int bits; int bits;
ImagingShuffler pack; ImagingShuffler pack;
pack = ImagingFindPacker(mode, rawmode, &bits); pack = ImagingFindPacker(mode, rawmode, &bits);
if (!pack) { if (!pack) {
Py_DECREF(encoder); 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; return -1;
} }
@ -402,11 +407,13 @@ PyObject *
PyImaging_GifEncoderNew(PyObject *self, PyObject *args) { PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder; ImagingEncoderObject *encoder;
char *mode; char *mode_name;
char *rawmode; char *rawmode_name;
Py_ssize_t bits = 8; Py_ssize_t bits = 8;
Py_ssize_t interlace = 0; 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; return NULL;
} }
@ -415,6 +422,9 @@ PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
if (get_packer(encoder, mode, rawmode) < 0) { if (get_packer(encoder, mode, rawmode) < 0) {
return NULL; return NULL;
} }
@ -435,11 +445,11 @@ PyObject *
PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) { PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder; ImagingEncoderObject *encoder;
char *mode; char *mode_name;
char *rawmode; char *rawmode_name;
Py_ssize_t bits = 8; 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; return NULL;
} }
@ -448,6 +458,9 @@ PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
if (get_packer(encoder, mode, rawmode) < 0) { if (get_packer(encoder, mode, rawmode) < 0) {
return NULL; return NULL;
} }
@ -465,12 +478,12 @@ PyObject *
PyImaging_RawEncoderNew(PyObject *self, PyObject *args) { PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder; ImagingEncoderObject *encoder;
char *mode; char *mode_name;
char *rawmode; char *rawmode_name;
Py_ssize_t stride = 0; Py_ssize_t stride = 0;
Py_ssize_t ystep = 1; 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; return NULL;
} }
@ -479,6 +492,9 @@ PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
if (get_packer(encoder, mode, rawmode) < 0) { if (get_packer(encoder, mode, rawmode) < 0) {
return NULL; return NULL;
} }
@ -499,11 +515,11 @@ PyObject *
PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) { PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder; ImagingEncoderObject *encoder;
char *mode; char *mode_name;
char *rawmode; char *rawmode_name;
Py_ssize_t ystep = 1; 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; return NULL;
} }
@ -512,6 +528,9 @@ PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
if (get_packer(encoder, mode, rawmode) < 0) { if (get_packer(encoder, mode, rawmode) < 0) {
return NULL; return NULL;
} }
@ -536,7 +555,7 @@ PyImaging_XbmEncoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
if (get_packer(encoder, "1", "1;R") < 0) { if (get_packer(encoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) {
return NULL; return NULL;
} }
@ -557,8 +576,8 @@ PyObject *
PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) { PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder; ImagingEncoderObject *encoder;
char *mode; char *mode_name;
char *rawmode; char *rawmode_name;
Py_ssize_t optimize = 0; Py_ssize_t optimize = 0;
Py_ssize_t compress_level = -1; Py_ssize_t compress_level = -1;
Py_ssize_t compress_type = -1; Py_ssize_t compress_type = -1;
@ -567,8 +586,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, args,
"ss|nnny#", "ss|nnny#",
&mode, &mode_name,
&rawmode, &rawmode_name,
&optimize, &optimize,
&compress_level, &compress_level,
&compress_type, &compress_type,
@ -597,6 +616,9 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
if (get_packer(encoder, mode, rawmode) < 0) { if (get_packer(encoder, mode, rawmode) < 0) {
free(dictionary); free(dictionary);
return NULL; return NULL;
@ -605,7 +627,7 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
encoder->encode = ImagingZipEncode; encoder->encode = ImagingZipEncode;
encoder->cleanup = ImagingZipEncodeCleanup; encoder->cleanup = ImagingZipEncodeCleanup;
if (rawmode[0] == 'P') { if (rawmode == IMAGING_RAWMODE_P || rawmode == IMAGING_RAWMODE_PA) {
/* disable filtering */ /* disable filtering */
((ZIPSTATE *)encoder->state.context)->mode = ZIP_PNG_PALETTE; ((ZIPSTATE *)encoder->state.context)->mode = ZIP_PNG_PALETTE;
} }
@ -634,8 +656,8 @@ PyObject *
PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder; ImagingEncoderObject *encoder;
char *mode; char *mode_name;
char *rawmode; char *rawmode_name;
char *compname; char *compname;
char *filename; char *filename;
Py_ssize_t fp; Py_ssize_t fp;
@ -655,7 +677,15 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
PyObject *item; PyObject *item;
if (!PyArg_ParseTuple( 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; return NULL;
} }
@ -693,6 +723,9 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
const RawModeID rawmode = findRawModeID(rawmode_name);
if (get_packer(encoder, mode, rawmode) < 0) { if (get_packer(encoder, mode, rawmode) < 0) {
return NULL; return NULL;
} }
@ -922,6 +955,18 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
); );
free(av); 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 { } else {
if (type == TIFF_SHORT) { if (type == TIFF_SHORT) {
@ -1076,8 +1121,8 @@ PyObject *
PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder; ImagingEncoderObject *encoder;
char *mode; char *mode_name;
char *rawmode; char *rawmode_name;
Py_ssize_t quality = 0; Py_ssize_t quality = 0;
Py_ssize_t progressive = 0; Py_ssize_t progressive = 0;
Py_ssize_t smooth = 0; Py_ssize_t smooth = 0;
@ -1101,8 +1146,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, args,
"ss|nnnnpn(nn)nnnOz#y#y#", "ss|nnnnpn(nn)nnnOz#y#y#",
&mode, &mode_name,
&rawmode, &rawmode_name,
&quality, &quality,
&progressive, &progressive,
&smooth, &smooth,
@ -1130,11 +1175,14 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
const ModeID mode = findModeID(mode_name);
RawModeID rawmode = findRawModeID(rawmode_name);
// libjpeg-turbo supports different output formats. // libjpeg-turbo supports different output formats.
// We are choosing Pillow's native format (3 color bytes + 1 padding) // We are choosing Pillow's native format (3 color bytes + 1 padding)
// to avoid extra conversion in Pack.c. // to avoid extra conversion in Pack.c.
if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) { if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) {
rawmode = "RGBX"; rawmode = IMAGING_RAWMODE_RGBX;
} }
if (get_packer(encoder, mode, rawmode) < 0) { if (get_packer(encoder, mode, rawmode) < 0) {
@ -1192,7 +1240,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
encoder->encode = ImagingJpegEncode; encoder->encode = ImagingJpegEncode;
JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context; 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->keep_rgb = keep_rgb;
jpeg_encoder_state->quality = quality; jpeg_encoder_state->quality = quality;
jpeg_encoder_state->qtables = qarrays; jpeg_encoder_state->qtables = qarrays;

View File

@ -11,39 +11,6 @@
#include "Imaging.h" #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 */ /* fetch individual pixel */
static void static void
@ -64,7 +31,7 @@ static void
get_pixel_16L(Imaging im, int x, int y, void *color) { get_pixel_16L(Imaging im, int x, int y, void *color) {
UINT8 *in = (UINT8 *)&im->image[y][x + x]; UINT8 *in = (UINT8 *)&im->image[y][x + x];
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
UINT16 out = in[0] + (in[1] << 8); UINT16 out = in[0] + ((UINT16)in[1] << 8);
memcpy(color, &out, sizeof(out)); memcpy(color, &out, sizeof(out));
#else #else
memcpy(color, in, sizeof(UINT16)); memcpy(color, in, sizeof(UINT16));
@ -77,7 +44,7 @@ get_pixel_16B(Imaging im, int x, int y, void *color) {
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
memcpy(color, in, sizeof(UINT16)); memcpy(color, in, sizeof(UINT16));
#else #else
UINT16 out = in[1] + (in[0] << 8); UINT16 out = in[1] + ((UINT16)in[0] << 8);
memcpy(color, &out, sizeof(out)); memcpy(color, &out, sizeof(out));
#endif #endif
} }
@ -87,28 +54,6 @@ get_pixel_32(Imaging im, int x, int y, void *color) {
memcpy(color, &im->image32[y][x], sizeof(INT32)); 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 */ /* store individual pixel */
static void static void
@ -129,71 +74,46 @@ put_pixel_16B(Imaging im, int x, int y, const void *color) {
out[1] = in[0]; 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 static void
put_pixel_32(Imaging im, int x, int y, const void *color) { put_pixel_32(Imaging im, int x, int y, const void *color) {
memcpy(&im->image32[y][x], color, sizeof(INT32)); memcpy(&im->image32[y][x], color, sizeof(INT32));
} }
void static struct ImagingAccessInstance accessors[] = {
ImagingAccessInit(void) { {IMAGING_MODE_1, get_pixel_8, put_pixel_8},
#define ADD(mode_, get_pixel_, put_pixel_) \ {IMAGING_MODE_L, get_pixel_8, put_pixel_8},
{ \ {IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32},
ImagingAccess access = add_item(mode_); \ {IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32},
access->get_pixel = get_pixel_; \ {IMAGING_MODE_I, get_pixel_32, put_pixel_32},
access->put_pixel = put_pixel_; \ {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},
/* 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);
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
ADD("I;16N", get_pixel_16B, put_pixel_16B); {IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B},
#else #else
ADD("I;16N", get_pixel_16L, put_pixel_16L); {IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L},
#endif #endif
ADD("I;32L", get_pixel_32L, put_pixel_32L); {IMAGING_MODE_F, get_pixel_32, put_pixel_32},
ADD("I;32B", get_pixel_32B, put_pixel_32B); {IMAGING_MODE_P, get_pixel_8, put_pixel_8},
ADD("F", get_pixel_32, put_pixel_32); {IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32},
ADD("P", get_pixel_8, put_pixel_8); {IMAGING_MODE_RGB, get_pixel_32, put_pixel_32},
ADD("PA", get_pixel_32_2bands, put_pixel_32); {IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32},
ADD("RGB", get_pixel_32, put_pixel_32); {IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32},
ADD("RGBA", get_pixel_32, put_pixel_32); {IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32},
ADD("RGBa", get_pixel_32, put_pixel_32); {IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32},
ADD("RGBX", get_pixel_32, put_pixel_32); {IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32},
ADD("CMYK", get_pixel_32, put_pixel_32); {IMAGING_MODE_LAB, get_pixel_32, put_pixel_32},
ADD("YCbCr", get_pixel_32, put_pixel_32); {IMAGING_MODE_HSV, get_pixel_32, put_pixel_32},
ADD("LAB", get_pixel_32, put_pixel_32); };
ADD("HSV", get_pixel_32, put_pixel_32);
}
ImagingAccess ImagingAccess
ImagingAccessNew(Imaging im) { ImagingAccessNew(const Imaging im) {
ImagingAccess access = &access_table[hash(im->mode)]; for (size_t i = 0; i < sizeof(accessors) / sizeof(*accessors); i++) {
if (im->mode[0] != access->mode[0] || strcmp(im->mode, access->mode) != 0) { if (im->mode == accessors[i].mode) {
return NULL; return &accessors[i];
} }
return access; }
return NULL;
} }
void void

View File

@ -26,11 +26,11 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) {
/* Check arguments */ /* Check arguments */
if (!imDst || !imSrc || if (!imDst || !imSrc ||
(strcmp(imDst->mode, "RGBA") && strcmp(imDst->mode, "LA"))) { (imDst->mode != IMAGING_MODE_RGBA && imDst->mode != IMAGING_MODE_LA)) {
return ImagingError_ModeError(); 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) { imDst->ysize != imSrc->ysize) {
return ImagingError_Mismatch(); return ImagingError_Mismatch();
} }

View File

@ -55,6 +55,98 @@ ReleaseExportedSchema(struct ArrowSchema *array) {
// Mark array released // Mark array released
array->release = NULL; 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 int
export_named_type(struct ArrowSchema *schema, char *format, char *name) { 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 int
export_imaging_schema(Imaging im, struct ArrowSchema *schema) { export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
int retval = 0; int retval = 0;
char *band_json;
if (strcmp(im->arrow_band_format, "") == 0) { if (strcmp(im->arrow_band_format, "") == 0) {
return IMAGING_ARROW_INCOMPATIBLE_MODE; return IMAGING_ARROW_INCOMPATIBLE_MODE;
@ -106,7 +199,17 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
} }
if (im->bands == 1) { 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", ""); retval = export_named_type(schema, "+w:4", "");
@ -117,13 +220,26 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
schema->n_children = 1; schema->n_children = 1;
schema->children = calloc(1, sizeof(struct ArrowSchema *)); schema->children = calloc(1, sizeof(struct ArrowSchema *));
schema->children[0] = (struct ArrowSchema *)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) { if (retval != 0) {
free(schema->children[0]); free(schema->children[0]);
free(schema->children); free(schema->children);
schema->release(schema); schema->release(schema);
return retval; 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; return 0;
} }

View File

@ -41,7 +41,7 @@ ImagingGetBand(Imaging imIn, int band) {
band = 3; band = 3;
} }
imOut = ImagingNewDirty("L", imIn->xsize, imIn->ysize); imOut = ImagingNewDirty(IMAGING_MODE_L, imIn->xsize, imIn->ysize);
if (!imOut) { if (!imOut) {
return NULL; return NULL;
} }
@ -82,7 +82,7 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) {
} }
for (i = 0; i < imIn->bands; i++) { 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]) { if (!bands[i]) {
for (j = 0; j < i; ++j) { for (j = 0; j < i; ++j) {
ImagingDelete(bands[j]); ImagingDelete(bands[j]);
@ -240,7 +240,7 @@ ImagingFillBand(Imaging imOut, int band, int color) {
} }
Imaging Imaging
ImagingMerge(const char *mode, Imaging bands[4]) { ImagingMerge(const ModeID mode, Imaging bands[4]) {
int i, x, y; int i, x, y;
int bandsCount = 0; int bandsCount = 0;
Imaging imOut; Imaging imOut;

View File

@ -603,7 +603,7 @@ static void
bc6_sign_extend(UINT16 *v, int prec) { bc6_sign_extend(UINT16 *v, int prec) {
int x = *v; int x = *v;
if (x & (1 << (prec - 1))) { if (x & (1 << (prec - 1))) {
x |= -1 << prec; x |= -(1 << prec);
} }
*v = (UINT16)x; *v = (UINT16)x;
} }

View File

@ -36,10 +36,9 @@ decode_565(UINT16 x) {
static UINT16 static UINT16
encode_565(rgba item) { encode_565(rgba item) {
UINT8 r, g, b; UINT16 r = item.color[0] >> (8 - 5);
r = item.color[0] >> (8 - 5); UINT8 g = item.color[1] >> (8 - 6);
g = item.color[1] >> (8 - 6); UINT8 b = item.color[2] >> (8 - 5);
b = item.color[2] >> (8 - 5);
return (r << (5 + 6)) | (g << 5) | b; 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 static void
encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) { encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) {
int i, j; int i, j;
UINT8 block[16], current_alpha; UINT8 block[16];
UINT32 current_alpha;
for (i = 0; i < 4; i++) { for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) { for (j = 0; j < 4; j++) {
int x = state->x + i * im->pixelsize; int x = state->x + i * im->pixelsize;
@ -253,7 +253,7 @@ int
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
int n = state->state; int n = state->state;
int has_alpha_channel = 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; UINT8 *dst = buf;

View File

@ -24,8 +24,8 @@ ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha) {
/* Check arguments */ /* Check arguments */
if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 || imIn1->palette || if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 || imIn1->palette ||
strcmp(imIn1->mode, "1") == 0 || imIn2->palette || imIn1->mode == IMAGING_MODE_1 || imIn2->palette ||
strcmp(imIn2->mode, "1") == 0) { imIn2->mode == IMAGING_MODE_1) {
return ImagingError_ModeError(); return ImagingError_ModeError();
} }

View File

@ -248,7 +248,7 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
return ImagingError_ValueError("radius must be >= 0"); 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->bands != imOut->bands || imIn->xsize != imOut->xsize ||
imIn->ysize != imOut->ysize) { imIn->ysize != imOut->ysize) {
return ImagingError_Mismatch(); return ImagingError_Mismatch();
@ -258,10 +258,10 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
return ImagingError_ModeError(); return ImagingError_ModeError();
} }
if (!(strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "RGBA") == 0 || if (imIn->mode != IMAGING_MODE_RGB && imIn->mode != IMAGING_MODE_RGBA &&
strcmp(imIn->mode, "RGBa") == 0 || strcmp(imIn->mode, "RGBX") == 0 || imIn->mode != IMAGING_MODE_RGBa && imIn->mode != IMAGING_MODE_RGBX &&
strcmp(imIn->mode, "CMYK") == 0 || strcmp(imIn->mode, "L") == 0 || imIn->mode != IMAGING_MODE_CMYK && imIn->mode != IMAGING_MODE_L &&
strcmp(imIn->mode, "LA") == 0 || strcmp(imIn->mode, "La") == 0)) { imIn->mode != IMAGING_MODE_LA && imIn->mode != IMAGING_MODE_La) {
return ImagingError_ModeError(); return ImagingError_ModeError();
} }

View File

@ -21,7 +21,7 @@
#define CHOP(operation) \ #define CHOP(operation) \
int x, y; \ int x, y; \
Imaging imOut; \ Imaging imOut; \
imOut = create(imIn1, imIn2, NULL); \ imOut = create(imIn1, imIn2, IMAGING_MODE_UNKNOWN); \
if (!imOut) { \ if (!imOut) { \
return NULL; \ return NULL; \
} \ } \
@ -60,11 +60,12 @@
return imOut; return imOut;
static Imaging static Imaging
create(Imaging im1, Imaging im2, char *mode) { create(Imaging im1, Imaging im2, const ModeID mode) {
int xsize, ysize; int xsize, ysize;
if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 || 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(); return (Imaging)ImagingError_ModeError();
} }
if (im1->type != im2->type || im1->bands != im2->bands) { if (im1->type != im2->type || im1->bands != im2->bands) {
@ -114,27 +115,27 @@ ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset) {
Imaging Imaging
ImagingChopAnd(Imaging imIn1, Imaging imIn2) { ImagingChopAnd(Imaging imIn1, Imaging imIn2) {
CHOP2((in1[x] && in2[x]) ? 255 : 0, "1"); CHOP2((in1[x] && in2[x]) ? 255 : 0, IMAGING_MODE_1);
} }
Imaging Imaging
ImagingChopOr(Imaging imIn1, Imaging imIn2) { ImagingChopOr(Imaging imIn1, Imaging imIn2) {
CHOP2((in1[x] || in2[x]) ? 255 : 0, "1"); CHOP2((in1[x] || in2[x]) ? 255 : 0, IMAGING_MODE_1);
} }
Imaging Imaging
ImagingChopXor(Imaging imIn1, Imaging imIn2) { 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 Imaging
ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) { ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) {
CHOP2(in1[x] + in2[x], NULL); CHOP2(in1[x] + in2[x], IMAGING_MODE_UNKNOWN);
} }
Imaging Imaging
ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) { ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) {
CHOP2(in1[x] - in2[x], NULL); CHOP2(in1[x] - in2[x], IMAGING_MODE_UNKNOWN);
} }
Imaging Imaging
@ -142,7 +143,7 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) {
CHOP2( CHOP2(
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) + (((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255, (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( CHOP2(
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127) (in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127), : 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
NULL IMAGING_MODE_UNKNOWN
); );
} }
@ -160,6 +161,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) {
CHOP2( CHOP2(
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127) (in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127), : 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
NULL IMAGING_MODE_UNKNOWN
); );
} }

View File

@ -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 */ /* Palette conversions */
/* ------------------- */ /* ------------------- */
/* FIXME: translate indexed versions to pointer versions below this line */
static void static void
p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
int x; int x;
@ -1065,13 +930,13 @@ pa2p(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
static void static void
p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
int x; int x;
int rgb = strcmp(palette->mode, "RGB"); const int rgb = palette->mode == IMAGING_MODE_RGB;
for (x = 0; x < xsize; x++, in++) { for (x = 0; x < xsize; x++, in++) {
const UINT8 *rgba = &palette->palette[in[0] * 4]; const UINT8 *rgba = &palette->palette[in[0] * 4];
*out++ = in[0]; *out++ = in[0];
*out++ = in[0]; *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 static Imaging
frompalette(Imaging imOut, Imaging imIn, const char *mode) { frompalette(Imaging imOut, Imaging imIn, const ModeID mode) {
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
int alpha; int alpha;
int y; int y;
@ -1237,31 +1102,31 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
return (Imaging)ImagingError_ValueError("no palette"); 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; convert = alpha ? pa2bit : p2bit;
} else if (strcmp(mode, "L") == 0) { } else if (mode == IMAGING_MODE_L) {
convert = alpha ? pa2l : p2l; convert = alpha ? pa2l : p2l;
} else if (strcmp(mode, "LA") == 0) { } else if (mode == IMAGING_MODE_LA) {
convert = alpha ? pa2la : p2la; convert = alpha ? pa2la : p2la;
} else if (strcmp(mode, "P") == 0) { } else if (mode == IMAGING_MODE_P) {
convert = pa2p; convert = pa2p;
} else if (strcmp(mode, "PA") == 0) { } else if (mode == IMAGING_MODE_PA) {
convert = p2pa; convert = p2pa;
} else if (strcmp(mode, "I") == 0) { } else if (mode == IMAGING_MODE_I) {
convert = alpha ? pa2i : p2i; convert = alpha ? pa2i : p2i;
} else if (strcmp(mode, "F") == 0) { } else if (mode == IMAGING_MODE_F) {
convert = alpha ? pa2f : p2f; convert = alpha ? pa2f : p2f;
} else if (strcmp(mode, "RGB") == 0) { } else if (mode == IMAGING_MODE_RGB) {
convert = alpha ? pa2rgb : p2rgb; 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; convert = alpha ? pa2rgba : p2rgba;
} else if (strcmp(mode, "CMYK") == 0) { } else if (mode == IMAGING_MODE_CMYK) {
convert = alpha ? pa2cmyk : p2cmyk; convert = alpha ? pa2cmyk : p2cmyk;
} else if (strcmp(mode, "YCbCr") == 0) { } else if (mode == IMAGING_MODE_YCbCr) {
convert = alpha ? pa2ycbcr : p2ycbcr; convert = alpha ? pa2ycbcr : p2ycbcr;
} else if (strcmp(mode, "HSV") == 0) { } else if (mode == IMAGING_MODE_HSV) {
convert = alpha ? pa2hsv : p2hsv; convert = alpha ? pa2hsv : p2hsv;
} else { } else {
return (Imaging)ImagingError_ValueError("conversion not supported"); return (Imaging)ImagingError_ValueError("conversion not supported");
@ -1271,7 +1136,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
if (!imOut) { if (!imOut) {
return NULL; return NULL;
} }
if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) { if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) {
ImagingPaletteDelete(imOut->palette); ImagingPaletteDelete(imOut->palette);
imOut->palette = ImagingPaletteDuplicate(imIn->palette); imOut->palette = ImagingPaletteDuplicate(imIn->palette);
} }
@ -1295,24 +1160,26 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
#endif #endif
static Imaging static Imaging
topalette( 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; ImagingSectionCookie cookie;
int alpha; int alpha;
int x, y; int x, y;
ImagingPalette palette = inpalette; ImagingPalette palette = inpalette;
/* Map L or RGB/RGBX/RGBA to palette image */ /* Map L or RGB/RGBX/RGBA/RGBa to palette image */
if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) { 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"); return (Imaging)ImagingError_ValueError("conversion not supported");
} }
alpha = !strcmp(mode, "PA"); alpha = mode == IMAGING_MODE_PA;
if (palette == NULL) { if (palette == NULL) {
/* FIXME: make user configurable */ /* FIXME: make user configurable */
if (imIn->bands == 1) { if (imIn->bands == 1) {
palette = ImagingPaletteNew("RGB"); palette = ImagingPaletteNew(IMAGING_MODE_RGB);
palette->size = 256; palette->size = 256;
int i; int i;
@ -1499,11 +1366,11 @@ tobilevel(Imaging imOut, Imaging imIn) {
int *errors; int *errors;
/* Map L or RGB to dithered 1 image */ /* 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"); return (Imaging)ImagingError_ValueError("conversion not supported");
} }
imOut = ImagingNew2Dirty("1", imOut, imIn); imOut = ImagingNew2Dirty(IMAGING_MODE_1, imOut, imIn);
if (!imOut) { if (!imOut) {
return NULL; return NULL;
} }
@ -1585,19 +1452,152 @@ tobilevel(Imaging imOut, Imaging imIn) {
#pragma optimize("", on) #pragma optimize("", on)
#endif #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 static Imaging
convert( convert(Imaging imOut, Imaging imIn, ModeID mode, ImagingPalette palette, int dither) {
Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither
) {
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
ImagingShuffler convert; ImagingShuffler convert;
int y;
if (!imIn) { if (!imIn) {
return (Imaging)ImagingError_ModeError(); return (Imaging)ImagingError_ModeError();
} }
if (!mode) { if (mode == IMAGING_MODE_UNKNOWN) {
/* Map palette image to full depth */ /* Map palette image to full depth */
if (!imIn->palette) { if (!imIn->palette) {
return (Imaging)ImagingError_ModeError(); return (Imaging)ImagingError_ModeError();
@ -1605,33 +1605,31 @@ convert(
mode = imIn->palette->mode; mode = imIn->palette->mode;
} else { } else {
/* Same mode? */ /* Same mode? */
if (!strcmp(imIn->mode, mode)) { if (imIn->mode == mode) {
return ImagingCopy2(imOut, imIn); return ImagingCopy2(imOut, imIn);
} }
} }
/* test for special conversions */ /* 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); 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); return topalette(imOut, imIn, mode, palette, dither);
} }
if (dither && strcmp(mode, "1") == 0) { if (dither && mode == IMAGING_MODE_1) {
return tobilevel(imOut, imIn); return tobilevel(imOut, imIn);
} }
/* standard conversion machinery */ /* standard conversion machinery */
convert = NULL; convert = NULL;
for (size_t i = 0; i < sizeof(converters) / sizeof(*converters); i++) {
for (y = 0; converters[y].from; y++) { if (imIn->mode == converters[i].from && mode == converters[i].to) {
if (!strcmp(imIn->mode, converters[y].from) && convert = converters[i].convert;
!strcmp(mode, converters[y].to)) {
convert = converters[y].convert;
break; break;
} }
} }
@ -1642,7 +1640,11 @@ convert(
#else #else
static char buf[100]; static char buf[100];
snprintf( 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); return (Imaging)ImagingError_ValueError(buf);
#endif #endif
@ -1654,7 +1656,7 @@ convert(
} }
ImagingSectionEnter(&cookie); 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); (*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
} }
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
@ -1663,7 +1665,7 @@ convert(
} }
Imaging 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); return convert(NULL, imIn, mode, palette, dither);
} }
@ -1673,7 +1675,7 @@ ImagingConvert2(Imaging imOut, Imaging imIn) {
} }
Imaging 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; ImagingSectionCookie cookie;
ImagingShuffler convert; ImagingShuffler convert;
Imaging imOut = NULL; Imaging imOut = NULL;
@ -1687,27 +1689,27 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
return (Imaging)ImagingError_ModeError(); return (Imaging)ImagingError_ModeError();
} }
if (strcmp(imIn->mode, "RGB") == 0 && if (imIn->mode == IMAGING_MODE_RGB &&
(strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) { (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBa)) {
convert = rgb2rgba; convert = rgb2rgba;
if (strcmp(mode, "RGBa") == 0) { if (mode == IMAGING_MODE_RGBa) {
premultiplied = 1; premultiplied = 1;
} }
} else if (strcmp(imIn->mode, "RGB") == 0 && } else if (imIn->mode == IMAGING_MODE_RGB &&
(strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) { (mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)) {
convert = rgb2la; convert = rgb2la;
source_transparency = 1; source_transparency = 1;
if (strcmp(mode, "La") == 0) { if (mode == IMAGING_MODE_La) {
premultiplied = 1; premultiplied = 1;
} }
} else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || } else if ((imIn->mode == IMAGING_MODE_1 || imIn->mode == IMAGING_MODE_I ||
strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) && imIn->mode == IMAGING_MODE_I_16 || imIn->mode == IMAGING_MODE_L) &&
(strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) { (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_LA)) {
if (strcmp(imIn->mode, "1") == 0) { if (imIn->mode == IMAGING_MODE_1) {
convert = bit2rgb; convert = bit2rgb;
} else if (strcmp(imIn->mode, "I") == 0) { } else if (imIn->mode == IMAGING_MODE_I) {
convert = i2rgb; convert = i2rgb;
} else if (strcmp(imIn->mode, "I;16") == 0) { } else if (imIn->mode == IMAGING_MODE_I_16) {
convert = I16_RGB; convert = I16_RGB;
} else { } else {
convert = l2rgb; convert = l2rgb;
@ -1719,8 +1721,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
buf, buf,
100, 100,
"conversion from %.10s to %.10s not supported in convert_transparent", "conversion from %.10s to %.10s not supported in convert_transparent",
imIn->mode, getModeData(imIn->mode)->name,
mode getModeData(mode)->name
); );
return (Imaging)ImagingError_ValueError(buf); return (Imaging)ImagingError_ValueError(buf);
} }
@ -1743,15 +1745,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
} }
Imaging Imaging
ImagingConvertInPlace(Imaging imIn, const char *mode) { ImagingConvertInPlace(Imaging imIn, const ModeID mode) {
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
ImagingShuffler convert; ImagingShuffler convert;
int y; int y;
/* limited support for inplace conversion */ /* 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; 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; convert = bit2l;
} else { } else {
return ImagingError_ModeError(); return ImagingError_ModeError();

View File

@ -25,20 +25,17 @@
#include "ImDib.h" #include "ImDib.h"
char * ModeID
ImagingGetModeDIB(int size_out[2]) { ImagingGetModeDIB(int size_out[2]) {
/* Get device characteristics */ /* Get device characteristics */
HDC dc; const HDC dc = CreateCompatibleDC(NULL);
char *mode;
dc = CreateCompatibleDC(NULL); ModeID mode = IMAGING_MODE_P;
mode = "P";
if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) { if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) {
mode = "RGB"; mode = IMAGING_MODE_RGB;
if (GetDeviceCaps(dc, BITSPIXEL) == 1) { if (GetDeviceCaps(dc, BITSPIXEL) == 1) {
mode = "1"; mode = IMAGING_MODE_1;
} }
} }
@ -53,7 +50,7 @@ ImagingGetModeDIB(int size_out[2]) {
} }
ImagingDIB ImagingDIB
ImagingNewDIB(const char *mode, int xsize, int ysize) { ImagingNewDIB(const ModeID mode, int xsize, int ysize) {
/* Create a Windows bitmap */ /* Create a Windows bitmap */
ImagingDIB dib; ImagingDIB dib;
@ -61,10 +58,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
int i; int i;
/* Check mode */ /* 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(); return (ImagingDIB)ImagingError_ModeError();
} }
const int pixelsize = mode == IMAGING_MODE_RGB ? 3 : 1;
/* Create DIB context and info header */ /* Create DIB context and info header */
/* malloc check ok, small constant allocation */ /* malloc check ok, small constant allocation */
dib = (ImagingDIB)malloc(sizeof(*dib)); 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.biWidth = xsize;
dib->info->bmiHeader.biHeight = ysize; dib->info->bmiHeader.biHeight = ysize;
dib->info->bmiHeader.biPlanes = 1; dib->info->bmiHeader.biPlanes = 1;
dib->info->bmiHeader.biBitCount = strlen(mode) * 8; dib->info->bmiHeader.biBitCount = pixelsize * 8;
dib->info->bmiHeader.biCompression = BI_RGB; dib->info->bmiHeader.biCompression = BI_RGB;
/* Create DIB */ /* Create DIB */
@ -103,12 +102,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
return (ImagingDIB)ImagingError_MemoryError(); return (ImagingDIB)ImagingError_MemoryError();
} }
strcpy(dib->mode, mode); dib->mode = mode;
dib->xsize = xsize; dib->xsize = xsize;
dib->ysize = ysize; dib->ysize = ysize;
dib->pixelsize = strlen(mode); dib->pixelsize = pixelsize;
dib->linesize = (xsize * dib->pixelsize + 3) & -4; dib->linesize = (xsize * pixelsize + 3) & -4;
if (dib->pixelsize == 1) { if (dib->pixelsize == 1) {
dib->pack = dib->unpack = (ImagingShuffler)memcpy; 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) */ /* 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)]; char palbuf[sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)];
LPLOGPALETTE pal = (LPLOGPALETTE)palbuf; LPLOGPALETTE pal = (LPLOGPALETTE)palbuf;
int i, r, g, b; int i, r, g, b;
@ -142,7 +141,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
pal->palNumEntries = 256; pal->palNumEntries = 256;
GetSystemPaletteEntries(dib->dc, 0, 256, pal->palPalEntry); 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 /* Grayscale DIB. Fill all 236 slots with a grayscale ramp
* (this is usually overkill on Windows since VGA only offers * (this is usually overkill on Windows since VGA only offers
* 6 bits grayscale resolution). Ignore the slots already * 6 bits grayscale resolution). Ignore the slots already
@ -156,8 +155,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
} }
dib->palette = CreatePalette(pal); dib->palette = CreatePalette(pal);
} else if (mode == IMAGING_MODE_RGB) {
} else if (strcmp(mode, "RGB") == 0) {
#ifdef CUBE216 #ifdef CUBE216
/* Colour DIB. Create a 6x6x6 colour cube (216 entries) and /* Colour DIB. Create a 6x6x6 colour cube (216 entries) and

View File

@ -68,7 +68,7 @@ typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging);
static inline void static inline void
point8(Imaging im, int x, int y, int ink) { point8(Imaging im, int x, int y, int ink) {
if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { 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 #ifdef WORDS_BIGENDIAN
im->image8[y][x * 2] = (UINT8)(ink >> 8); im->image8[y][x * 2] = (UINT8)(ink >> 8);
im->image8[y][x * 2 + 1] = (UINT8)ink; 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) { if (x0 <= x1) {
int bigendian = -1; int bigendian = -1;
if (strncmp(im->mode, "I;16", 4) == 0) { if (isModeI16(im->mode)) {
bigendian = bigendian =
( (
#ifdef WORDS_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 #else
strcmp(im->mode, "I;16B") == 0 im->mode == IMAGING_MODE_I_16B
#endif #endif
) )
? 1 ? 1
@ -675,7 +675,7 @@ DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba};
#define DRAWINIT() \ #define DRAWINIT() \
if (im->image8) { \ if (im->image8) { \
draw = &draw8; \ draw = &draw8; \
if (strncmp(im->mode, "I;16", 4) == 0) { \ if (isModeI16(im->mode)) { \
ink = INK16(ink_); \ ink = INK16(ink_); \
} else { \ } else { \
ink = INK8(ink_); \ ink = INK8(ink_); \

View File

@ -36,7 +36,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) {
return (Imaging)ImagingError_ValueError(NULL); return (Imaging)ImagingError_ValueError(NULL);
} }
im = ImagingNewDirty("L", xsize, ysize); im = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize);
if (!im) { if (!im) {
return NULL; return NULL;
} }
@ -80,7 +80,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) {
int nextok; int nextok;
double this, next; double this, next;
imOut = ImagingNewDirty("L", xsize, ysize); imOut = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize);
if (!imOut) { if (!imOut) {
return NULL; return NULL;
} }

View File

@ -23,14 +23,13 @@ int
ImagingSaveRaw(Imaging im, FILE *fp) { ImagingSaveRaw(Imaging im, FILE *fp) {
int x, y, i; 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 */ /* @PIL227: FIXME: for mode "1", map != 0 to 255 */
/* PGM "L" */ /* PGM "L" */
for (y = 0; y < im->ysize; y++) { for (y = 0; y < im->ysize; y++) {
fwrite(im->image[y], 1, im->xsize, fp); fwrite(im->image[y], 1, im->xsize, fp);
} }
} else { } else {
/* PPM "RGB" or other internal format */ /* PPM "RGB" or other internal format */
for (y = 0; y < im->ysize; y++) { for (y = 0; y < im->ysize; y++) {
@ -58,10 +57,10 @@ ImagingSavePPM(Imaging im, const char *outfile) {
return 0; 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" */ /* Write "PGM" */
fprintf(fp, "P5\n%d %d\n255\n", im->xsize, im->ysize); 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" */ /* Write "PPM" */
fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize); fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize);
} else { } else {

View File

@ -68,11 +68,12 @@ ImagingFill(Imaging im, const void *colour) {
} }
Imaging Imaging
ImagingFillLinearGradient(const char *mode) { ImagingFillLinearGradient(const ModeID mode) {
Imaging im; Imaging im;
int y; 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(); return (Imaging)ImagingError_ModeError();
} }
@ -102,12 +103,13 @@ ImagingFillLinearGradient(const char *mode) {
} }
Imaging Imaging
ImagingFillRadialGradient(const char *mode) { ImagingFillRadialGradient(const ModeID mode) {
Imaging im; Imaging im;
int x, y; int x, y;
int d; 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(); return (Imaging)ImagingError_ModeError();
} }

Some files were not shown because too many files have changed in this diff Show More