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
# raqm
pushd depends && ./install_raqm.sh && popd
pushd depends && sudo ./install_raqm.sh && popd
# libavif
pushd depends && ./install_libavif.sh && popd
pushd depends && sudo ./install_libavif.sh && popd
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

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-PySide6
ipython

View File

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

View File

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

View File

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

View File

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

View File

@ -39,6 +39,7 @@ concurrency:
cancel-in-progress: true
env:
EXPECTED_DISTS: 91
FORCE_COLOR: 1
jobs:
@ -52,21 +53,21 @@ jobs:
include:
- name: "macOS 10.10 x86_64"
platform: macos
os: macos-13
os: macos-15-intel
cibw_arch: x86_64
build: "cp3{9,10,11}*"
build: "cp3{10,11}*"
macosx_deployment_target: "10.10"
- name: "macOS 10.13 x86_64"
platform: macos
os: macos-13
os: macos-15-intel
cibw_arch: x86_64
build: "cp3{12,13,14}*"
build: "cp3{12,13}*"
macosx_deployment_target: "10.13"
- name: "macOS 10.15 x86_64"
platform: macos
os: macos-13
os: macos-15-intel
cibw_arch: x86_64
build: "pp3*"
build: "{cp314,pp3}*"
macosx_deployment_target: "10.15"
- name: "macOS arm64"
platform: macos
@ -99,11 +100,11 @@ jobs:
cibw_arch: arm64_iphoneos
- name: "iOS arm64 simulator"
platform: ios
os: macos-14
os: macos-latest
cibw_arch: arm64_iphonesimulator
- name: "iOS x86_64 simulator"
platform: ios
os: macos-13
os: macos-15-intel
cibw_arch: x86_64_iphonesimulator
steps:
- uses: actions/checkout@v5
@ -231,7 +232,7 @@ jobs:
path: winbuild\build\bin\fribidi*
sdist:
if: github.event_name != 'schedule'
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
@ -250,15 +251,33 @@ jobs:
name: dist-sdist
path: dist/*.tar.gz
count-dists:
needs: [build-native-wheels, windows, sdist]
runs-on: ubuntu-latest
name: Count dists
steps:
- uses: actions/download-artifact@v5
with:
pattern: dist-*
path: dist
merge-multiple: true
- name: "What did we get?"
run: |
ls -alR
echo "Number of dists, should be $EXPECTED_DISTS:"
files=$(ls dist 2>/dev/null | wc -l)
echo $files
[ "$files" -eq $EXPECTED_DISTS ] || exit 1
scientific-python-nightly-wheels-publish:
if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
needs: [build-native-wheels, windows]
needs: count-dists
runs-on: ubuntu-latest
name: Upload wheels to scientific-python-nightly-wheels
steps:
- uses: actions/download-artifact@v5
with:
pattern: dist-*
pattern: dist-!(sdist)*
path: dist
merge-multiple: true
- name: Upload wheels to scientific-python-nightly-wheels
@ -269,7 +288,7 @@ jobs:
pypi-publish:
if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
needs: [build-native-wheels, windows, sdist]
needs: count-dists
runs-on: ubuntu-latest
name: Upload release to PyPI
environment:

View File

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

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

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

View File

@ -6,6 +6,8 @@ from pathlib import Path
import pytest
from PIL import BmpImagePlugin, Image, _binary
from PIL._binary import o16le as o16
from PIL._binary import o32le as o32
from .helper import (
assert_image_equal,
@ -114,7 +116,7 @@ def test_save_float_dpi(tmp_path: Path) -> None:
def test_load_dib() -> None:
# test for #1293, Imagegrab returning Unsupported Bitfields Format
# test for #1293, ImageGrab returning Unsupported Bitfields Format
with Image.open("Tests/images/clipboard.dib") as im:
assert im.format == "DIB"
assert im.get_format_mimetype() == "image/bmp"
@ -219,6 +221,18 @@ def test_rle8_eof(file_name: str, length: int) -> None:
im.load()
def test_unsupported_bmp_bitfields_layout() -> None:
fp = io.BytesIO(
o32(40) # header size
+ b"\x00" * 10
+ o16(1) # bits
+ o32(3) # BITFIELDS compression
+ b"\x00" * 32
)
with pytest.raises(OSError, match="Unsupported BMP bitfields layout"):
Image.open(fp)
def test_offset() -> None:
# This image has been hexedited
# to exclude the palette size from the pixel data offset

View File

@ -1,8 +1,13 @@
from __future__ import annotations
from io import BytesIO
import pytest
from PIL import CurImagePlugin, Image
from PIL._binary import o8
from PIL._binary import o16le as o16
from PIL._binary import o32le as o32
TEST_FILE = "Tests/images/deerstalker.cur"
@ -17,6 +22,24 @@ def test_sanity() -> None:
assert im.getpixel((16, 16)) == (84, 87, 86, 255)
def test_largest_cursor() -> None:
magic = b"\x00\x00\x02\x00"
sizes = ((1, 1), (8, 8), (4, 4))
data = magic + o16(len(sizes))
for w, h in sizes:
image_offset = 6 + len(sizes) * 16 if (w, h) == max(sizes) else 0
data += o8(w) + o8(h) + o8(0) * 10 + o32(image_offset)
data += (
o32(12) # header size
+ o16(8) # width
+ o16(16) # height
+ o16(0) # planes
+ o16(1) # bits
)
with Image.open(BytesIO(data)) as im:
assert im.size == (8, 8)
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
@ -26,6 +49,7 @@ def test_invalid_file() -> None:
no_cursors_file = "Tests/images/no_cursors.cur"
cur = CurImagePlugin.CurImageFile(TEST_FILE)
assert cur.fp is not None
cur.fp.close()
with open(no_cursors_file, "rb") as cur.fp:
with pytest.raises(TypeError):

View File

@ -197,6 +197,14 @@ def test_load_long_binary_data(prefix: bytes) -> None:
assert img.format == "EPS"
def test_begin_binary() -> None:
with open("Tests/images/eps/binary_preview_map.eps", "rb") as fp:
data = bytearray(fp.read())
data[76875 : 76875 + 11] = b"%" * 11
with Image.open(io.BytesIO(data)) as img:
assert img.size == (399, 480)
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)

View File

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

View File

@ -1,5 +1,7 @@
from __future__ import annotations
from io import BytesIO
import pytest
from PIL import GdImageFile, UnidentifiedImageError
@ -16,6 +18,14 @@ def test_sanity() -> None:
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14)
def test_transparency() -> None:
with open(TEST_GD_FILE, "rb") as fp:
data = bytearray(fp.read())
data[7:11] = b"\x00\x00\x00\x05"
with GdImageFile.open(BytesIO(data)) as im:
assert im.info["transparency"] == 5
def test_bad_mode() -> None:
with pytest.raises(ValueError):
GdImageFile.open(TEST_GD_FILE, "bad mode")

View File

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

View File

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

View File

@ -355,6 +355,27 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not segfault
im.save(outfile)
def test_whitepoint_tag(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
out = tmp_path / "temp.tif"
hopper().save(out, tiffinfo={318: (0.3127, 0.3289)})
with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289))
# Save tag by default
out = tmp_path / "temp2.tif"
with Image.open("Tests/images/rdf.tif") as im:
im.save(out)
with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289999))
def test_xmlpacket_tag(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:

View File

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

View File

@ -6,6 +6,8 @@ import pytest
from PIL import Image
from .helper import assert_image_equal
def test_load_raw() -> None:
with Image.open("Tests/images/hopper.pcd") as im:
@ -30,3 +32,8 @@ def test_rotated(orientation: int) -> None:
f = BytesIO(data)
with Image.open(f) as im:
assert im.size == (512, 768)
with Image.open("Tests/images/hopper.pcd") as expected:
assert_image_equal(
im, expected.rotate(90 if orientation == 1 else 270, expand=True)
)

View File

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

View File

@ -92,6 +92,13 @@ def test_16bit_pgm() -> None:
assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff")
def test_p4_save(tmp_path: Path) -> None:
with Image.open("Tests/images/hopper_1bit.pbm") as im:
filename = tmp_path / "temp.pbm"
im.save(filename)
assert_image_equal_tofile(im, filename)
def test_16bit_pgm_write(tmp_path: Path) -> None:
with Image.open("Tests/images/16_bit_binary.pgm") as im:
filename = tmp_path / "temp.pgm"
@ -134,6 +141,12 @@ def test_pfm_big_endian(tmp_path: Path) -> None:
assert_image_equal_tofile(im, filename)
def test_save_unsupported_mode(tmp_path: Path) -> None:
im = hopper("P")
with pytest.raises(OSError, match="cannot write mode P as PPM"):
im.save(tmp_path / "out.ppm")
@pytest.mark.parametrize(
"data",
[

View File

@ -274,13 +274,17 @@ def test_save_l_transparency(tmp_path: Path) -> None:
in_file = "Tests/images/la.tga"
with Image.open(in_file) as im:
assert im.mode == "LA"
assert im.getchannel("A").getcolors()[0][0] == num_transparent
colors = im.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == num_transparent
out = tmp_path / "temp.tga"
im.save(out)
with Image.open(out) as test_im:
assert test_im.mode == "LA"
assert test_im.getchannel("A").getcolors()[0][0] == num_transparent
colors = test_im.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == num_transparent
assert_image_equal(im, test_im)

View File

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

View File

@ -284,33 +284,6 @@ class TestImage:
assert item is not None
assert item != num
def test_expand_x(self) -> None:
# Arrange
im = hopper()
orig_size = im.size
xmargin = 5
# Act
im = im._expand(xmargin)
# Assert
assert im.size[0] == orig_size[0] + 2 * xmargin
assert im.size[1] == orig_size[1] + 2 * xmargin
def test_expand_xy(self) -> None:
# Arrange
im = hopper()
orig_size = im.size
xmargin = 5
ymargin = 3
# Act
im = im._expand(xmargin, ymargin)
# Assert
assert im.size[0] == orig_size[0] + 2 * xmargin
assert im.size[1] == orig_size[1] + 2 * ymargin
def test_getbands(self) -> None:
# Assert
assert hopper("RGB").getbands() == ("R", "G", "B")
@ -1126,6 +1099,12 @@ class TestImage:
assert im.palette is not None
assert im.palette.colors[(27, 35, 6, 214)] == 24
def test_merge_pa(self) -> None:
p = hopper("P")
a = Image.new("L", p.size)
pa = Image.merge("PA", (p, a))
assert p.getpalette() == pa.getpalette()
def test_constants(self) -> None:
for enum in (
Image.Transpose,

View File

@ -97,6 +97,13 @@ def test_opaque() -> None:
assert_image_equal(alpha, solid)
def test_rgba() -> None:
with Image.open("Tests/images/transparent.png") as im:
assert im.mode == "RGBA"
assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
def test_rgba_p() -> None:
im = hopper("RGBA")
im.putalpha(hopper("L"))
@ -107,11 +114,19 @@ def test_rgba_p() -> None:
assert_image_similar(im, comparable, 20)
def test_rgba() -> None:
with Image.open("Tests/images/transparent.png") as im:
assert im.mode == "RGBA"
def test_rgba_pa() -> None:
im = hopper("RGBA").convert("PA").convert("RGB")
expected = hopper("RGB")
assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
assert_image_similar(im, expected, 9.3)
def test_pa() -> None:
im = hopper().convert("PA")
palette = im.palette
assert palette is not None
assert palette.colors != {}
def test_trns_p(tmp_path: Path) -> None:

View File

@ -124,6 +124,21 @@ class TestImagingPaste:
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
assert_image_equal(im, im2)
@pytest.mark.parametrize("y", [10, -10])
@pytest.mark.parametrize("mode", ["L", "RGB"])
@pytest.mark.parametrize("mask_mode", ["", "1", "L", "LA", "RGBa"])
def test_image_self(self, y: int, mode: str, mask_mode: str) -> None:
im = getattr(self, "gradient_" + mode)
mask = Image.new(mask_mode, im.size, 0xFFFFFFFF) if mask_mode else None
im_self = im.copy()
im_self.paste(im_self, (0, y), mask)
im_copy = im.copy()
im_copy.paste(im_copy.copy(), (0, y), mask)
assert_image_equal(im_self, im_copy)
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_image_mask_1(self, mode: str) -> None:
im = Image.new(mode, (200, 200), "white")

View File

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

View File

@ -118,6 +118,15 @@ def test_quantize_kmeans(method: Image.Quantize) -> None:
im.quantize(kmeans=-1, method=method)
@skip_unless_feature("libimagequant")
def test_resize() -> None:
im = hopper().resize((100, 100))
converted = im.quantize(100, Image.Quantize.LIBIMAGEQUANT)
colors = converted.getcolors()
assert colors is not None
assert len(colors) == 100
def test_colors() -> None:
im = hopper()
colors = 2

View File

@ -1494,7 +1494,9 @@ def test_default_font_size() -> None:
def draw_text() -> None:
draw.text((0, 0), text, font_size=16)
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
assert_image_similar_tofile(
im, "Tests/images/imagedraw_default_font_size.png", 1
)
check(draw_text)
@ -1513,7 +1515,9 @@ def test_default_font_size() -> None:
def draw_multiline_text() -> None:
draw.multiline_text((0, 0), text, font_size=16)
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
assert_image_similar_tofile(
im, "Tests/images/imagedraw_default_font_size.png", 1
)
check(draw_multiline_text)

View File

@ -164,6 +164,11 @@ class TestImageFile:
with pytest.raises(OSError):
p.close()
def test_negative_offset(self) -> None:
with Image.open("Tests/images/raw_negative_stride.bin") as im:
with pytest.raises(ValueError, match="Tile offset cannot be negative"):
im.load()
def test_no_format(self) -> None:
buf = BytesIO(b"\x00" * 255)

View File

@ -19,6 +19,7 @@ from .helper import (
assert_image_equal,
assert_image_equal_tofile,
assert_image_similar_tofile,
has_feature_version,
is_win32,
skip_unless_feature,
skip_unless_feature_version,
@ -492,6 +493,11 @@ def test_stroke_mask() -> None:
assert mask.getpixel((42, 5)) == 255
def test_load_invalid_file() -> None:
with pytest.raises(SyntaxError, match="Not a PILfont file"):
ImageFont.load("Tests/images/1_trns.png")
def test_load_when_image_not_found() -> None:
with tempfile.NamedTemporaryFile(delete=False) as tmp:
pass
@ -549,7 +555,7 @@ def test_default_font() -> None:
draw.text((10, 60), txt, font=larger_default_font)
# Assert
assert_image_equal_tofile(im, "Tests/images/default_font_freetype.png")
assert_image_similar_tofile(im, "Tests/images/default_font_freetype.png", 0.13)
@pytest.mark.parametrize("mode", ("", "1", "RGBA"))
@ -1055,7 +1061,10 @@ def test_colr(layout_engine: ImageFont.Layout) -> None:
d.text((15, 5), "Bungee", font=font, embedded_color=True)
assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21)
if has_feature_version("freetype2", "2.14.0"):
assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 6.1)
else:
assert_image_similar_tofile(im, "Tests/images/colr_bungee_older.png", 21)
@skip_unless_feature_version("freetype2", "2.10.0")
@ -1071,7 +1080,7 @@ def test_colr_mask(layout_engine: ImageFont.Layout) -> None:
d.text((15, 5), "Bungee", "black", font=font)
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 14.1)
def test_woff2(layout_engine: ImageFont.Layout) -> None:

View File

@ -183,7 +183,7 @@ def test_x_max_and_y_offset() -> None:
draw.text((0, 0), "لح", font=ttf, fill=500)
target = "Tests/images/test_x_max_and_y_offset.png"
assert_image_similar_tofile(im, target, 0.5)
assert_image_similar_tofile(im, target, 3.8)
def test_language() -> None:

View File

@ -30,6 +30,14 @@ def test_default_font(font: ImageFont.ImageFont) -> None:
assert_image_equal_tofile(im, "Tests/images/default_font.png")
def test_invalid_mode() -> None:
font = ImageFont.ImageFont()
fp = BytesIO()
with Image.open("Tests/images/hopper.png") as im:
with pytest.raises(TypeError, match="invalid font image mode"):
font._load_pilfont_data(fp, im)
def test_without_freetype() -> None:
original_core = ImageFont.core
if features.check_module("freetype2"):

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

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
import json
from typing import Any, NamedTuple
import pytest
@ -244,3 +245,29 @@ def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None)
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)
@pytest.mark.parametrize(
"mode, metadata",
(
("LA", ["L", "X", "X", "A"]),
("RGB", ["R", "G", "B", "X"]),
("RGBX", ["R", "G", "B", "X"]),
("RGBA", ["R", "G", "B", "A"]),
("CMYK", ["C", "M", "Y", "K"]),
("YCbCr", ["Y", "Cb", "Cr", "X"]),
("HSV", ["H", "S", "V", "X"]),
),
)
def test_image_metadata(mode: str, metadata: list[str]) -> None:
img = hopper(mode)
arr = pyarrow.array(img) # type: ignore[call-overload]
assert arr.type.field(0).metadata
assert arr.type.field(0).metadata[b"image"]
parsed_metadata = json.loads(arr.type.field(0).metadata[b"image"].decode("utf8"))
assert "bands" in parsed_metadata
assert parsed_metadata["bands"] == metadata

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

@ -1,4 +1,3 @@
#!/usr/bin/env python3
from __future__ import annotations
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
import sys

View File

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

View File

@ -1,7 +1,7 @@
#!/bin/bash
# install openjpeg
archive=openjpeg-2.5.3
archive=openjpeg-2.5.4
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

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

View File

@ -44,7 +44,7 @@ Many of Pillow's features require external libraries:
* **libtiff** provides compressed TIFF functionality
* Pillow has been tested with libtiff versions **4.0-4.7.0**
* Pillow has been tested with libtiff versions **4.0-4.7.1**
* **libfreetype** provides type related services
@ -58,7 +58,7 @@ Many of Pillow's features require external libraries:
* **openjpeg** provides JPEG 2000 functionality.
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
**2.4.0**, **2.5.0**, **2.5.2** and **2.5.3**.
**2.4.0**, **2.5.0**, **2.5.2**, **2.5.3** and **2.5.4**.
* Pillow does **not** support the earlier **1.5** series which ships
with Debian Jessie.

View File

@ -39,9 +39,9 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.12 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 13 Ventura | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14 | arm64 |
| macOS 15 Sequoia | 3.10 | x86-64 |
| +----------------------------+---------------------+
| | 3.11, 3.12, 3.13, 3.14, | arm64 |
| | PyPy3 | |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
@ -75,6 +75,8 @@ These platforms have been reported to work at the versions mentioned.
| Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors |
+==================================+============================+==================+==============+
| macOS 26 Tahoe | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
| +----------------------------+------------------+ |
| | 3.8 | 10.4.0 | |

View File

@ -57,6 +57,43 @@ Color names
See :ref:`color-names` for the color names supported by Pillow.
Alpha channel
^^^^^^^^^^^^^
By default, when drawing onto an existing image, the image's pixel values are simply
replaced by the new color::
im = Image.new("RGBA", (1, 1), (255, 0, 0))
d = ImageDraw.Draw(im)
d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
assert im.getpixel((0, 0)) == (0, 255, 0, 127)
# Alpha channel values have no effect when drawing with RGB mode
im = Image.new("RGB", (1, 1), (255, 0, 0))
d = ImageDraw.Draw(im)
d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
assert im.getpixel((0, 0)) == (0, 255, 0)
If you would like to combine translucent color with an RGB image, then initialize the
ImageDraw instance with the RGBA mode::
from PIL import Image, ImageDraw
im = Image.new("RGB", (1, 1), (255, 0, 0))
d = ImageDraw.Draw(im, "RGBA")
d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
assert im.getpixel((0, 0)) == (128, 127, 0)
If you would like to combine translucent color with an RGBA image underneath, you will
need to combine multiple images::
from PIL import Image, ImageDraw
im = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
im2 = Image.new("RGBA", (1, 1))
d = ImageDraw.Draw(im2)
d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
im.paste(im2.convert("RGB"), mask=im2)
assert im.getpixel((0, 0)) == (128, 127, 0, 255)
Fonts
^^^^^
@ -545,6 +582,8 @@ Methods
hello_world = hello + world # kerning is disabled, no need to adjust
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True
.. seealso:: :py:meth:`PIL.ImageText.Text.get_length`
.. versionadded:: 8.0.0
:param text: Text to be measured. May not contain any newline characters.
@ -646,6 +685,8 @@ Methods
1/64 pixel precision. The bounding box includes extra margins for
some fonts, e.g. italics or accents.
.. seealso:: :py:meth:`PIL.ImageText.Text.get_bbox`
.. versionadded:: 8.0.0
:param xy: The anchor coordinates of the text.

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
ImageShow
ImageStat
ImageText
ImageTk
ImageTransform
ImageWin

View File

@ -1,19 +1,6 @@
12.0.0
------
Security
========
TODO
^^^^
TODO
:cve:`YYYY-XXXXX`: TODO
^^^^^^^^^^^^^^^^^^^^^^^
TODO
Backwards incompatible changes
==============================
@ -132,18 +119,43 @@ Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
API changes
===========
TODO
^^^^
Image.alpha_composite: LA images
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO
:py:meth:`~PIL.Image.alpha_composite` can now use LA images as well as RGBA.
API additions
=============
TODO
^^^^
Added ImageText.Text
^^^^^^^^^^^^^^^^^^^^
TODO
:py:class:`PIL.ImageText.Text` has been added, as a simpler way to use fonts with text
strings or bytes.
Without ``ImageText.Text``::
from PIL import Image, ImageDraw
im = Image.new(mode, size)
d = ImageDraw.Draw(im)
d.textlength(text, font, direction, features, language, embedded_color)
d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color)
d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color)
With ``ImageText.Text``::
from PIL import ImageText
text = ImageText.Text(text, font, mode, spacing, direction, features, language)
text.embed_color()
text.stroke(stroke_width, stroke_fill)
text.get_length()
text.get_bbox(xy, anchor, align)
im = Image.new(mode, size)
d = ImageDraw.Draw(im)
d.text(xy, text, fill, anchor=anchor, align=align)
Other changes
=============

View File

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

View File

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

View File

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

View File

@ -354,6 +354,9 @@ class EpsImageFile(ImageFile.ImageFile):
read_comment(s)
elif bytes_mv[:9] == b"%%Trailer":
trailer_reached = True
elif bytes_mv[:14] == b"%%BeginBinary:":
bytecount = int(byte_arr[14:bytes_read])
self.fp.seek(bytecount, os.SEEK_CUR)
bytes_read = 0
# A "BoundingBox" is always required,

View File

@ -48,8 +48,14 @@ class FliImageFile(ImageFile.ImageFile):
def _open(self) -> None:
# HEAD
assert self.fp is not None
s = self.fp.read(128)
if not (_accept(s) and s[20:22] == b"\x00\x00"):
if not (
_accept(s)
and s[20:22] == b"\x00" * 2
and s[42:80] == b"\x00" * 38
and s[88:] == b"\x00" * 40
):
msg = "not an FLI/FLC file"
raise SyntaxError(msg)
@ -77,8 +83,7 @@ class FliImageFile(ImageFile.ImageFile):
if i16(s, 4) == 0xF100:
# prefix chunk; ignore it
self.__offset = self.__offset + i32(s)
self.fp.seek(self.__offset)
self.fp.seek(self.__offset + i32(s))
s = self.fp.read(16)
if i16(s, 4) == 0xF1FA:
@ -111,6 +116,7 @@ class FliImageFile(ImageFile.ImageFile):
# load palette
i = 0
assert self.fp is not None
for e in range(i16(self.fp.read(2))):
s = self.fp.read(2)
i = i + s[0]

View File

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

View File

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

View File

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

View File

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

View File

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

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
s = ImageFile._safe_read(self.fp, n)
self._size = i16(s, 3), i16(s, 1)
if self._im is not None and self.size != self.im.size:
self._im = None
self.bits = s[0]
if self.bits != 8:

View File

@ -43,16 +43,21 @@ class PcdImageFile(ImageFile.ImageFile):
if orientation == 1:
self.tile_post_rotate = 90
elif orientation == 3:
self.tile_post_rotate = -90
self.tile_post_rotate = 270
self._mode = "RGB"
self._size = (512, 768) if orientation in (1, 3) else (768, 512)
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
self.tile = [ImageFile._Tile("pcd", (0, 0, 768, 512), 96 * 2048)]
def load_prepare(self) -> None:
if self._im is None and self.tile_post_rotate:
self.im = Image.core.new(self.mode, (768, 512))
ImageFile.ImageFile.load_prepare(self)
def load_end(self) -> None:
if self.tile_post_rotate:
# Handle rotated PCDs
self.im = self.im.rotate(self.tile_post_rotate)
self.im = self.rotate(self.tile_post_rotate, expand=True).im
#

View File

@ -27,7 +27,7 @@ import os
import time
from typing import IO, Any
from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features
from . import Image, ImageFile, ImageSequence, PdfParser, features
#
# --------------------------------------------------------------------
@ -221,7 +221,7 @@ def _save(
existing_pdf.start_writing()
existing_pdf.write_header()
existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver")
existing_pdf.write_comment("created by Pillow PDF driver")
#
# pages

View File

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

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),
532: ("ReferenceBlackWhite", RATIONAL, 6),
700: ("XMP", BYTE, 0),
# Four private SGI tags
32995: ("Matteing", SHORT, 1),
32996: ("DataType", SHORT, 0),
32997: ("ImageDepth", LONG, 1),
32998: ("TileDepth", LONG, 1),
33432: ("Copyright", ASCII, 1),
33723: ("IptcNaaInfo", UNDEFINED, 1),
34377: ("PhotoshopInfo", BYTE, 0),

View File

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

View File

@ -121,15 +121,16 @@ PyImagingPhotoPut(
/* Mode */
if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
block.pixelSize = 1;
block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0;
} else if (strncmp(im->mode, "RGB", 3) == 0) {
} else if (im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA ||
im->mode == IMAGING_MODE_RGBX || im->mode == IMAGING_MODE_RGBa) {
block.pixelSize = 4;
block.offset[0] = 0;
block.offset[1] = 1;
block.offset[2] = 2;
if (strcmp(im->mode, "RGBA") == 0) {
if (im->mode == IMAGING_MODE_RGBA) {
block.offset[3] = 3; /* alpha (or reserved, under Tk 8.2) */
} else {
block.offset[3] = 0; /* no alpha */

View File

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

View File

@ -212,32 +212,44 @@ cms_transform_dealloc(CmsTransformObject *self) {
/* internal functions */
static cmsUInt32Number
findLCMStype(char *PILmode) {
if (strcmp(PILmode, "RGB") == 0 || strcmp(PILmode, "RGBA") == 0 ||
strcmp(PILmode, "RGBX") == 0) {
findLCMStype(const char *const mode_name) {
const ModeID mode = findModeID(mode_name);
switch (mode) {
case IMAGING_MODE_RGB:
case IMAGING_MODE_RGBA:
case IMAGING_MODE_RGBX:
return TYPE_RGBA_8;
case IMAGING_MODE_CMYK:
return TYPE_CMYK_8;
case IMAGING_MODE_I_16:
case IMAGING_MODE_I_16L:
return TYPE_GRAY_16;
case IMAGING_MODE_I_16B:
return TYPE_GRAY_16_SE;
case IMAGING_MODE_YCbCr:
return TYPE_YCbCr_8;
case IMAGING_MODE_LAB:
// LabX equivalent like ALab, but not reversed -- no #define in lcms2
return (
COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1)
);
default:
// This function only accepts a subset of the imaging modes Pillow has.
break;
}
if (strcmp(PILmode, "RGBA;16B") == 0) {
// The following modes are not valid PIL Image modes.
if (strcmp(mode_name, "RGBA;16B") == 0) {
return TYPE_RGBA_16;
}
if (strcmp(PILmode, "CMYK") == 0) {
return TYPE_CMYK_8;
}
if (strcmp(PILmode, "I;16") == 0 || strcmp(PILmode, "I;16L") == 0 ||
strcmp(PILmode, "L;16") == 0) {
if (strcmp(mode_name, "L;16") == 0) {
return TYPE_GRAY_16;
}
if (strcmp(PILmode, "I;16B") == 0 || strcmp(PILmode, "L;16B") == 0) {
if (strcmp(mode_name, "L;16B") == 0) {
return TYPE_GRAY_16_SE;
}
if (strcmp(PILmode, "YCbCr") == 0 || strcmp(PILmode, "YCCA") == 0 ||
strcmp(PILmode, "YCC") == 0) {
if (strcmp(mode_name, "YCCA") == 0 || strcmp(mode_name, "YCC") == 0) {
return TYPE_YCbCr_8;
}
if (strcmp(PILmode, "LAB") == 0) {
// LabX equivalent like ALab, but not reversed -- no #define in lcms2
return (COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1));
}
/* presume "1" or "L" by default */
return TYPE_GRAY_8;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,39 +11,6 @@
#include "Imaging.h"
/* use make_hash.py from the pillow-scripts repository to calculate these values */
#define ACCESS_TABLE_SIZE 35
#define ACCESS_TABLE_HASH 8940
static struct ImagingAccessInstance access_table[ACCESS_TABLE_SIZE];
static inline UINT32
hash(const char *mode) {
UINT32 i = ACCESS_TABLE_HASH;
while (*mode) {
i = ((i << 5) + i) ^ (UINT8)*mode++;
}
return i % ACCESS_TABLE_SIZE;
}
static ImagingAccess
add_item(const char *mode) {
UINT32 i = hash(mode);
/* printf("hash %s => %d\n", mode, i); */
if (access_table[i].mode && strcmp(access_table[i].mode, mode) != 0) {
fprintf(
stderr,
"AccessInit: hash collision: %d for both %s and %s\n",
i,
mode,
access_table[i].mode
);
exit(1);
}
access_table[i].mode = mode;
return &access_table[i];
}
/* fetch individual pixel */
static void
@ -64,7 +31,7 @@ static void
get_pixel_16L(Imaging im, int x, int y, void *color) {
UINT8 *in = (UINT8 *)&im->image[y][x + x];
#ifdef WORDS_BIGENDIAN
UINT16 out = in[0] + (in[1] << 8);
UINT16 out = in[0] + ((UINT16)in[1] << 8);
memcpy(color, &out, sizeof(out));
#else
memcpy(color, in, sizeof(UINT16));
@ -77,7 +44,7 @@ get_pixel_16B(Imaging im, int x, int y, void *color) {
#ifdef WORDS_BIGENDIAN
memcpy(color, in, sizeof(UINT16));
#else
UINT16 out = in[1] + (in[0] << 8);
UINT16 out = in[1] + ((UINT16)in[0] << 8);
memcpy(color, &out, sizeof(out));
#endif
}
@ -87,28 +54,6 @@ get_pixel_32(Imaging im, int x, int y, void *color) {
memcpy(color, &im->image32[y][x], sizeof(INT32));
}
static void
get_pixel_32L(Imaging im, int x, int y, void *color) {
UINT8 *in = (UINT8 *)&im->image[y][x * 4];
#ifdef WORDS_BIGENDIAN
INT32 out = in[0] + (in[1] << 8) + (in[2] << 16) + (in[3] << 24);
memcpy(color, &out, sizeof(out));
#else
memcpy(color, in, sizeof(INT32));
#endif
}
static void
get_pixel_32B(Imaging im, int x, int y, void *color) {
UINT8 *in = (UINT8 *)&im->image[y][x * 4];
#ifdef WORDS_BIGENDIAN
memcpy(color, in, sizeof(INT32));
#else
INT32 out = in[3] + (in[2] << 8) + (in[1] << 16) + (in[0] << 24);
memcpy(color, &out, sizeof(out));
#endif
}
/* store individual pixel */
static void
@ -129,71 +74,46 @@ put_pixel_16B(Imaging im, int x, int y, const void *color) {
out[1] = in[0];
}
static void
put_pixel_32L(Imaging im, int x, int y, const void *color) {
memcpy(&im->image8[y][x * 4], color, 4);
}
static void
put_pixel_32B(Imaging im, int x, int y, const void *color) {
const char *in = color;
UINT8 *out = (UINT8 *)&im->image8[y][x * 4];
out[0] = in[3];
out[1] = in[2];
out[2] = in[1];
out[3] = in[0];
}
static void
put_pixel_32(Imaging im, int x, int y, const void *color) {
memcpy(&im->image32[y][x], color, sizeof(INT32));
}
void
ImagingAccessInit(void) {
#define ADD(mode_, get_pixel_, put_pixel_) \
{ \
ImagingAccess access = add_item(mode_); \
access->get_pixel = get_pixel_; \
access->put_pixel = put_pixel_; \
}
/* populate access table */
ADD("1", get_pixel_8, put_pixel_8);
ADD("L", get_pixel_8, put_pixel_8);
ADD("LA", get_pixel_32_2bands, put_pixel_32);
ADD("La", get_pixel_32_2bands, put_pixel_32);
ADD("I", get_pixel_32, put_pixel_32);
ADD("I;16", get_pixel_16L, put_pixel_16L);
ADD("I;16L", get_pixel_16L, put_pixel_16L);
ADD("I;16B", get_pixel_16B, put_pixel_16B);
static struct ImagingAccessInstance accessors[] = {
{IMAGING_MODE_1, get_pixel_8, put_pixel_8},
{IMAGING_MODE_L, get_pixel_8, put_pixel_8},
{IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32},
{IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32},
{IMAGING_MODE_I, get_pixel_32, put_pixel_32},
{IMAGING_MODE_I_16, get_pixel_16L, put_pixel_16L},
{IMAGING_MODE_I_16L, get_pixel_16L, put_pixel_16L},
{IMAGING_MODE_I_16B, get_pixel_16B, put_pixel_16B},
#ifdef WORDS_BIGENDIAN
ADD("I;16N", get_pixel_16B, put_pixel_16B);
{IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B},
#else
ADD("I;16N", get_pixel_16L, put_pixel_16L);
{IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L},
#endif
ADD("I;32L", get_pixel_32L, put_pixel_32L);
ADD("I;32B", get_pixel_32B, put_pixel_32B);
ADD("F", get_pixel_32, put_pixel_32);
ADD("P", get_pixel_8, put_pixel_8);
ADD("PA", get_pixel_32_2bands, put_pixel_32);
ADD("RGB", get_pixel_32, put_pixel_32);
ADD("RGBA", get_pixel_32, put_pixel_32);
ADD("RGBa", get_pixel_32, put_pixel_32);
ADD("RGBX", get_pixel_32, put_pixel_32);
ADD("CMYK", get_pixel_32, put_pixel_32);
ADD("YCbCr", get_pixel_32, put_pixel_32);
ADD("LAB", get_pixel_32, put_pixel_32);
ADD("HSV", get_pixel_32, put_pixel_32);
}
{IMAGING_MODE_F, get_pixel_32, put_pixel_32},
{IMAGING_MODE_P, get_pixel_8, put_pixel_8},
{IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32},
{IMAGING_MODE_RGB, get_pixel_32, put_pixel_32},
{IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32},
{IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32},
{IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32},
{IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32},
{IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32},
{IMAGING_MODE_LAB, get_pixel_32, put_pixel_32},
{IMAGING_MODE_HSV, get_pixel_32, put_pixel_32},
};
ImagingAccess
ImagingAccessNew(Imaging im) {
ImagingAccess access = &access_table[hash(im->mode)];
if (im->mode[0] != access->mode[0] || strcmp(im->mode, access->mode) != 0) {
return NULL;
ImagingAccessNew(const Imaging im) {
for (size_t i = 0; i < sizeof(accessors) / sizeof(*accessors); i++) {
if (im->mode == accessors[i].mode) {
return &accessors[i];
}
return access;
}
return NULL;
}
void

View File

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

View File

@ -55,6 +55,98 @@ ReleaseExportedSchema(struct ArrowSchema *array) {
// Mark array released
array->release = NULL;
}
char *
image_band_json(Imaging im) {
char *format = "{\"bands\": [\"%s\", \"%s\", \"%s\", \"%s\"]}";
char *json;
// Bands can be 4 bands * 2 characters each
int len = strlen(format) + 8 + 1;
int err;
json = calloc(1, len);
if (!json) {
return NULL;
}
err = PyOS_snprintf(
json,
len,
format,
im->band_names[0],
im->band_names[1],
im->band_names[2],
im->band_names[3]
);
if (err < 0) {
return NULL;
}
return json;
}
char *
single_band_json(Imaging im) {
char *format = "{\"bands\": [\"%s\"]}";
char *json;
// Bands can be 1 band * (maybe but probably not) 2 characters each
int len = strlen(format) + 2 + 1;
int err;
json = calloc(1, len);
if (!json) {
return NULL;
}
err = PyOS_snprintf(json, len, format, im->band_names[0]);
if (err < 0) {
return NULL;
}
return json;
}
char *
assemble_metadata(const char *band_json) {
/* format is
int32: number of key/value pairs (noted N below)
int32: byte length of key 0
key 0 (not null-terminated)
int32: byte length of value 0
value 0 (not null-terminated)
...
int32: byte length of key N - 1
key N - 1 (not null-terminated)
int32: byte length of value N - 1
value N - 1 (not null-terminated)
*/
const char *key = "image";
INT32 key_len = strlen(key);
INT32 band_json_len = strlen(band_json);
char *buf;
INT32 *dest_int;
char *dest;
buf = calloc(1, key_len + band_json_len + 4 + 1 * 8);
if (!buf) {
return NULL;
}
dest_int = (void *)buf;
dest_int[0] = 1;
dest_int[1] = key_len;
dest_int += 2;
dest = (void *)dest_int;
memcpy(dest, key, key_len);
dest += key_len;
dest_int = (void *)dest;
dest_int[0] = band_json_len;
dest_int += 1;
memcpy(dest_int, band_json, band_json_len);
return buf;
}
int
export_named_type(struct ArrowSchema *schema, char *format, char *name) {
@ -95,6 +187,7 @@ export_named_type(struct ArrowSchema *schema, char *format, char *name) {
int
export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
int retval = 0;
char *band_json;
if (strcmp(im->arrow_band_format, "") == 0) {
return IMAGING_ARROW_INCOMPATIBLE_MODE;
@ -106,7 +199,17 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
}
if (im->bands == 1) {
return export_named_type(schema, im->arrow_band_format, im->band_names[0]);
retval = export_named_type(schema, im->arrow_band_format, im->band_names[0]);
if (retval != 0) {
return retval;
}
// band related metadata
band_json = single_band_json(im);
if (band_json) {
schema->metadata = assemble_metadata(band_json);
free(band_json);
}
return retval;
}
retval = export_named_type(schema, "+w:4", "");
@ -117,13 +220,26 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
schema->n_children = 1;
schema->children = calloc(1, sizeof(struct ArrowSchema *));
schema->children[0] = (struct ArrowSchema *)calloc(1, sizeof(struct ArrowSchema));
retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel");
retval = export_named_type(
schema->children[0], im->arrow_band_format, getModeData(im->mode)->name
);
if (retval != 0) {
free(schema->children[0]);
free(schema->children);
schema->release(schema);
return retval;
}
// band related metadata
band_json = image_band_json(im);
if (band_json) {
// adding the metadata to the child array.
// Accessible in pyarrow via pa.array(img).type.field(0).metadata
// adding it to the top level is not accessible.
schema->children[0]->metadata = assemble_metadata(band_json);
free(band_json);
}
return 0;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 */
/* ------------------- */
/* FIXME: translate indexed versions to pointer versions below this line */
static void
p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
int x;
@ -1065,13 +930,13 @@ pa2p(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
static void
p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
int x;
int rgb = strcmp(palette->mode, "RGB");
const int rgb = palette->mode == IMAGING_MODE_RGB;
for (x = 0; x < xsize; x++, in++) {
const UINT8 *rgba = &palette->palette[in[0] * 4];
*out++ = in[0];
*out++ = in[0];
*out++ = in[0];
*out++ = rgb == 0 ? 255 : rgba[3];
*out++ = rgb ? 255 : rgba[3];
}
}
@ -1225,7 +1090,7 @@ pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
}
static Imaging
frompalette(Imaging imOut, Imaging imIn, const char *mode) {
frompalette(Imaging imOut, Imaging imIn, const ModeID mode) {
ImagingSectionCookie cookie;
int alpha;
int y;
@ -1237,31 +1102,31 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
return (Imaging)ImagingError_ValueError("no palette");
}
alpha = !strcmp(imIn->mode, "PA");
alpha = imIn->mode == IMAGING_MODE_PA;
if (strcmp(mode, "1") == 0) {
if (mode == IMAGING_MODE_1) {
convert = alpha ? pa2bit : p2bit;
} else if (strcmp(mode, "L") == 0) {
} else if (mode == IMAGING_MODE_L) {
convert = alpha ? pa2l : p2l;
} else if (strcmp(mode, "LA") == 0) {
} else if (mode == IMAGING_MODE_LA) {
convert = alpha ? pa2la : p2la;
} else if (strcmp(mode, "P") == 0) {
} else if (mode == IMAGING_MODE_P) {
convert = pa2p;
} else if (strcmp(mode, "PA") == 0) {
} else if (mode == IMAGING_MODE_PA) {
convert = p2pa;
} else if (strcmp(mode, "I") == 0) {
} else if (mode == IMAGING_MODE_I) {
convert = alpha ? pa2i : p2i;
} else if (strcmp(mode, "F") == 0) {
} else if (mode == IMAGING_MODE_F) {
convert = alpha ? pa2f : p2f;
} else if (strcmp(mode, "RGB") == 0) {
} else if (mode == IMAGING_MODE_RGB) {
convert = alpha ? pa2rgb : p2rgb;
} else if (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBX") == 0) {
} else if (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBX) {
convert = alpha ? pa2rgba : p2rgba;
} else if (strcmp(mode, "CMYK") == 0) {
} else if (mode == IMAGING_MODE_CMYK) {
convert = alpha ? pa2cmyk : p2cmyk;
} else if (strcmp(mode, "YCbCr") == 0) {
} else if (mode == IMAGING_MODE_YCbCr) {
convert = alpha ? pa2ycbcr : p2ycbcr;
} else if (strcmp(mode, "HSV") == 0) {
} else if (mode == IMAGING_MODE_HSV) {
convert = alpha ? pa2hsv : p2hsv;
} else {
return (Imaging)ImagingError_ValueError("conversion not supported");
@ -1271,7 +1136,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
if (!imOut) {
return NULL;
}
if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) {
ImagingPaletteDelete(imOut->palette);
imOut->palette = ImagingPaletteDuplicate(imIn->palette);
}
@ -1295,24 +1160,26 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
#endif
static Imaging
topalette(
Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither
Imaging imOut, Imaging imIn, const ModeID mode, ImagingPalette inpalette, int dither
) {
ImagingSectionCookie cookie;
int alpha;
int x, y;
ImagingPalette palette = inpalette;
/* Map L or RGB/RGBX/RGBA to palette image */
if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) {
/* Map L or RGB/RGBX/RGBA/RGBa to palette image */
if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB &&
imIn->mode != IMAGING_MODE_RGBX && imIn->mode != IMAGING_MODE_RGBA &&
imIn->mode != IMAGING_MODE_RGBa) {
return (Imaging)ImagingError_ValueError("conversion not supported");
}
alpha = !strcmp(mode, "PA");
alpha = mode == IMAGING_MODE_PA;
if (palette == NULL) {
/* FIXME: make user configurable */
if (imIn->bands == 1) {
palette = ImagingPaletteNew("RGB");
palette = ImagingPaletteNew(IMAGING_MODE_RGB);
palette->size = 256;
int i;
@ -1499,11 +1366,11 @@ tobilevel(Imaging imOut, Imaging imIn) {
int *errors;
/* Map L or RGB to dithered 1 image */
if (strcmp(imIn->mode, "L") != 0 && strcmp(imIn->mode, "RGB") != 0) {
if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB) {
return (Imaging)ImagingError_ValueError("conversion not supported");
}
imOut = ImagingNew2Dirty("1", imOut, imIn);
imOut = ImagingNew2Dirty(IMAGING_MODE_1, imOut, imIn);
if (!imOut) {
return NULL;
}
@ -1585,19 +1452,152 @@ tobilevel(Imaging imOut, Imaging imIn) {
#pragma optimize("", on)
#endif
/* ------------------- */
/* Conversion handlers */
/* ------------------- */
static struct {
const ModeID from;
const ModeID to;
ImagingShuffler convert;
} converters[] = {
{IMAGING_MODE_1, IMAGING_MODE_L, bit2l},
{IMAGING_MODE_1, IMAGING_MODE_I, bit2i},
{IMAGING_MODE_1, IMAGING_MODE_F, bit2f},
{IMAGING_MODE_1, IMAGING_MODE_RGB, bit2rgb},
{IMAGING_MODE_1, IMAGING_MODE_RGBA, bit2rgb},
{IMAGING_MODE_1, IMAGING_MODE_RGBX, bit2rgb},
{IMAGING_MODE_1, IMAGING_MODE_CMYK, bit2cmyk},
{IMAGING_MODE_1, IMAGING_MODE_YCbCr, bit2ycbcr},
{IMAGING_MODE_1, IMAGING_MODE_HSV, bit2hsv},
{IMAGING_MODE_L, IMAGING_MODE_1, l2bit},
{IMAGING_MODE_L, IMAGING_MODE_LA, l2la},
{IMAGING_MODE_L, IMAGING_MODE_I, l2i},
{IMAGING_MODE_L, IMAGING_MODE_F, l2f},
{IMAGING_MODE_L, IMAGING_MODE_RGB, l2rgb},
{IMAGING_MODE_L, IMAGING_MODE_RGBA, l2rgb},
{IMAGING_MODE_L, IMAGING_MODE_RGBX, l2rgb},
{IMAGING_MODE_L, IMAGING_MODE_CMYK, l2cmyk},
{IMAGING_MODE_L, IMAGING_MODE_YCbCr, l2ycbcr},
{IMAGING_MODE_L, IMAGING_MODE_HSV, l2hsv},
{IMAGING_MODE_LA, IMAGING_MODE_L, la2l},
{IMAGING_MODE_LA, IMAGING_MODE_La, lA2la},
{IMAGING_MODE_LA, IMAGING_MODE_RGB, la2rgb},
{IMAGING_MODE_LA, IMAGING_MODE_RGBA, la2rgb},
{IMAGING_MODE_LA, IMAGING_MODE_RGBX, la2rgb},
{IMAGING_MODE_LA, IMAGING_MODE_CMYK, la2cmyk},
{IMAGING_MODE_LA, IMAGING_MODE_YCbCr, la2ycbcr},
{IMAGING_MODE_LA, IMAGING_MODE_HSV, la2hsv},
{IMAGING_MODE_La, IMAGING_MODE_LA, la2lA},
{IMAGING_MODE_I, IMAGING_MODE_L, i2l},
{IMAGING_MODE_I, IMAGING_MODE_F, i2f},
{IMAGING_MODE_I, IMAGING_MODE_RGB, i2rgb},
{IMAGING_MODE_I, IMAGING_MODE_RGBA, i2rgb},
{IMAGING_MODE_I, IMAGING_MODE_RGBX, i2rgb},
{IMAGING_MODE_I, IMAGING_MODE_HSV, i2hsv},
{IMAGING_MODE_F, IMAGING_MODE_L, f2l},
{IMAGING_MODE_F, IMAGING_MODE_I, f2i},
{IMAGING_MODE_RGB, IMAGING_MODE_1, rgb2bit},
{IMAGING_MODE_RGB, IMAGING_MODE_L, rgb2l},
{IMAGING_MODE_RGB, IMAGING_MODE_LA, rgb2la},
{IMAGING_MODE_RGB, IMAGING_MODE_La, rgb2la},
{IMAGING_MODE_RGB, IMAGING_MODE_I, rgb2i},
{IMAGING_MODE_RGB, IMAGING_MODE_I_16, rgb2i16l},
{IMAGING_MODE_RGB, IMAGING_MODE_I_16L, rgb2i16l},
{IMAGING_MODE_RGB, IMAGING_MODE_I_16B, rgb2i16b},
#ifdef WORDS_BIGENDIAN
{IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16b},
#else
{IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16l},
#endif
{IMAGING_MODE_RGB, IMAGING_MODE_F, rgb2f},
{IMAGING_MODE_RGB, IMAGING_MODE_RGBA, rgb2rgba},
{IMAGING_MODE_RGB, IMAGING_MODE_RGBa, rgb2rgba},
{IMAGING_MODE_RGB, IMAGING_MODE_RGBX, rgb2rgba},
{IMAGING_MODE_RGB, IMAGING_MODE_CMYK, rgb2cmyk},
{IMAGING_MODE_RGB, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
{IMAGING_MODE_RGB, IMAGING_MODE_HSV, rgb2hsv},
{IMAGING_MODE_RGBA, IMAGING_MODE_1, rgb2bit},
{IMAGING_MODE_RGBA, IMAGING_MODE_L, rgb2l},
{IMAGING_MODE_RGBA, IMAGING_MODE_LA, rgba2la},
{IMAGING_MODE_RGBA, IMAGING_MODE_I, rgb2i},
{IMAGING_MODE_RGBA, IMAGING_MODE_F, rgb2f},
{IMAGING_MODE_RGBA, IMAGING_MODE_RGB, rgba2rgb},
{IMAGING_MODE_RGBA, IMAGING_MODE_RGBa, rgbA2rgba},
{IMAGING_MODE_RGBA, IMAGING_MODE_RGBX, rgb2rgba},
{IMAGING_MODE_RGBA, IMAGING_MODE_CMYK, rgb2cmyk},
{IMAGING_MODE_RGBA, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
{IMAGING_MODE_RGBA, IMAGING_MODE_HSV, rgb2hsv},
{IMAGING_MODE_RGBa, IMAGING_MODE_RGBA, rgba2rgbA},
{IMAGING_MODE_RGBa, IMAGING_MODE_RGB, rgba2rgb_},
{IMAGING_MODE_RGBX, IMAGING_MODE_1, rgb2bit},
{IMAGING_MODE_RGBX, IMAGING_MODE_L, rgb2l},
{IMAGING_MODE_RGBX, IMAGING_MODE_LA, rgb2la},
{IMAGING_MODE_RGBX, IMAGING_MODE_I, rgb2i},
{IMAGING_MODE_RGBX, IMAGING_MODE_F, rgb2f},
{IMAGING_MODE_RGBX, IMAGING_MODE_RGB, rgba2rgb},
{IMAGING_MODE_RGBX, IMAGING_MODE_CMYK, rgb2cmyk},
{IMAGING_MODE_RGBX, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
{IMAGING_MODE_RGBX, IMAGING_MODE_HSV, rgb2hsv},
{IMAGING_MODE_CMYK, IMAGING_MODE_RGB, cmyk2rgb},
{IMAGING_MODE_CMYK, IMAGING_MODE_RGBA, cmyk2rgb},
{IMAGING_MODE_CMYK, IMAGING_MODE_RGBX, cmyk2rgb},
{IMAGING_MODE_CMYK, IMAGING_MODE_HSV, cmyk2hsv},
{IMAGING_MODE_YCbCr, IMAGING_MODE_L, ycbcr2l},
{IMAGING_MODE_YCbCr, IMAGING_MODE_LA, ycbcr2la},
{IMAGING_MODE_YCbCr, IMAGING_MODE_RGB, ImagingConvertYCbCr2RGB},
{IMAGING_MODE_HSV, IMAGING_MODE_RGB, hsv2rgb},
{IMAGING_MODE_I, IMAGING_MODE_I_16, I_I16L},
{IMAGING_MODE_I_16, IMAGING_MODE_I, I16L_I},
{IMAGING_MODE_I_16, IMAGING_MODE_RGB, I16_RGB},
{IMAGING_MODE_L, IMAGING_MODE_I_16, L_I16L},
{IMAGING_MODE_I_16, IMAGING_MODE_L, I16L_L},
{IMAGING_MODE_I, IMAGING_MODE_I_16L, I_I16L},
{IMAGING_MODE_I_16L, IMAGING_MODE_I, I16L_I},
{IMAGING_MODE_I, IMAGING_MODE_I_16B, I_I16B},
{IMAGING_MODE_I_16B, IMAGING_MODE_I, I16B_I},
{IMAGING_MODE_L, IMAGING_MODE_I_16L, L_I16L},
{IMAGING_MODE_I_16L, IMAGING_MODE_L, I16L_L},
{IMAGING_MODE_L, IMAGING_MODE_I_16B, L_I16B},
{IMAGING_MODE_I_16B, IMAGING_MODE_L, I16B_L},
#ifdef WORDS_BIGENDIAN
{IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16B},
{IMAGING_MODE_I_16N, IMAGING_MODE_L, I16B_L},
#else
{IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16L},
{IMAGING_MODE_I_16N, IMAGING_MODE_L, I16L_L},
#endif
{IMAGING_MODE_I_16, IMAGING_MODE_F, I16L_F},
{IMAGING_MODE_I_16L, IMAGING_MODE_F, I16L_F},
{IMAGING_MODE_I_16B, IMAGING_MODE_F, I16B_F}
};
static Imaging
convert(
Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither
) {
convert(Imaging imOut, Imaging imIn, ModeID mode, ImagingPalette palette, int dither) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
int y;
if (!imIn) {
return (Imaging)ImagingError_ModeError();
}
if (!mode) {
if (mode == IMAGING_MODE_UNKNOWN) {
/* Map palette image to full depth */
if (!imIn->palette) {
return (Imaging)ImagingError_ModeError();
@ -1605,33 +1605,31 @@ convert(
mode = imIn->palette->mode;
} else {
/* Same mode? */
if (!strcmp(imIn->mode, mode)) {
if (imIn->mode == mode) {
return ImagingCopy2(imOut, imIn);
}
}
/* test for special conversions */
if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) {
if (imIn->mode == IMAGING_MODE_P || imIn->mode == IMAGING_MODE_PA) {
return frompalette(imOut, imIn, mode);
}
if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) {
return topalette(imOut, imIn, mode, palette, dither);
}
if (dither && strcmp(mode, "1") == 0) {
if (dither && mode == IMAGING_MODE_1) {
return tobilevel(imOut, imIn);
}
/* standard conversion machinery */
convert = NULL;
for (y = 0; converters[y].from; y++) {
if (!strcmp(imIn->mode, converters[y].from) &&
!strcmp(mode, converters[y].to)) {
convert = converters[y].convert;
for (size_t i = 0; i < sizeof(converters) / sizeof(*converters); i++) {
if (imIn->mode == converters[i].from && mode == converters[i].to) {
convert = converters[i].convert;
break;
}
}
@ -1642,7 +1640,11 @@ convert(
#else
static char buf[100];
snprintf(
buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode
buf,
100,
"conversion from %.10s to %.10s not supported",
getModeData(imIn->mode)->name,
getModeData(mode)->name
);
return (Imaging)ImagingError_ValueError(buf);
#endif
@ -1654,7 +1656,7 @@ convert(
}
ImagingSectionEnter(&cookie);
for (y = 0; y < imIn->ysize; y++) {
for (int y = 0; y < imIn->ysize; y++) {
(*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
}
ImagingSectionLeave(&cookie);
@ -1663,7 +1665,7 @@ convert(
}
Imaging
ImagingConvert(Imaging imIn, const char *mode, ImagingPalette palette, int dither) {
ImagingConvert(Imaging imIn, const ModeID mode, ImagingPalette palette, int dither) {
return convert(NULL, imIn, mode, palette, dither);
}
@ -1673,7 +1675,7 @@ ImagingConvert2(Imaging imOut, Imaging imIn) {
}
Imaging
ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
ImagingConvertTransparent(Imaging imIn, const ModeID mode, int r, int g, int b) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
Imaging imOut = NULL;
@ -1687,27 +1689,27 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
return (Imaging)ImagingError_ModeError();
}
if (strcmp(imIn->mode, "RGB") == 0 &&
(strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) {
if (imIn->mode == IMAGING_MODE_RGB &&
(mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBa)) {
convert = rgb2rgba;
if (strcmp(mode, "RGBa") == 0) {
if (mode == IMAGING_MODE_RGBa) {
premultiplied = 1;
}
} else if (strcmp(imIn->mode, "RGB") == 0 &&
(strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) {
} else if (imIn->mode == IMAGING_MODE_RGB &&
(mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)) {
convert = rgb2la;
source_transparency = 1;
if (strcmp(mode, "La") == 0) {
if (mode == IMAGING_MODE_La) {
premultiplied = 1;
}
} else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 ||
strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) &&
(strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) {
if (strcmp(imIn->mode, "1") == 0) {
} else if ((imIn->mode == IMAGING_MODE_1 || imIn->mode == IMAGING_MODE_I ||
imIn->mode == IMAGING_MODE_I_16 || imIn->mode == IMAGING_MODE_L) &&
(mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_LA)) {
if (imIn->mode == IMAGING_MODE_1) {
convert = bit2rgb;
} else if (strcmp(imIn->mode, "I") == 0) {
} else if (imIn->mode == IMAGING_MODE_I) {
convert = i2rgb;
} else if (strcmp(imIn->mode, "I;16") == 0) {
} else if (imIn->mode == IMAGING_MODE_I_16) {
convert = I16_RGB;
} else {
convert = l2rgb;
@ -1719,8 +1721,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
buf,
100,
"conversion from %.10s to %.10s not supported in convert_transparent",
imIn->mode,
mode
getModeData(imIn->mode)->name,
getModeData(mode)->name
);
return (Imaging)ImagingError_ValueError(buf);
}
@ -1743,15 +1745,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
}
Imaging
ImagingConvertInPlace(Imaging imIn, const char *mode) {
ImagingConvertInPlace(Imaging imIn, const ModeID mode) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
int y;
/* limited support for inplace conversion */
if (strcmp(imIn->mode, "L") == 0 && strcmp(mode, "1") == 0) {
if (imIn->mode == IMAGING_MODE_L && mode == IMAGING_MODE_1) {
convert = l2bit;
} else if (strcmp(imIn->mode, "1") == 0 && strcmp(mode, "L") == 0) {
} else if (imIn->mode == IMAGING_MODE_1 && mode == IMAGING_MODE_L) {
convert = bit2l;
} else {
return ImagingError_ModeError();

View File

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

View File

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

View File

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

View File

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

View File

@ -68,11 +68,12 @@ ImagingFill(Imaging im, const void *colour) {
}
Imaging
ImagingFillLinearGradient(const char *mode) {
ImagingFillLinearGradient(const ModeID mode) {
Imaging im;
int y;
if (strlen(mode) != 1) {
if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
return (Imaging)ImagingError_ModeError();
}
@ -102,12 +103,13 @@ ImagingFillLinearGradient(const char *mode) {
}
Imaging
ImagingFillRadialGradient(const char *mode) {
ImagingFillRadialGradient(const ModeID mode) {
Imaging im;
int x, y;
int d;
if (strlen(mode) != 1) {
if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
return (Imaging)ImagingError_ModeError();
}

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