Merge branch 'main' into main
|
|
@ -51,10 +51,10 @@ pushd depends && ./install_webp.sh && popd
|
||||||
pushd depends && ./install_imagequant.sh && popd
|
pushd depends && ./install_imagequant.sh && popd
|
||||||
|
|
||||||
# raqm
|
# raqm
|
||||||
pushd depends && ./install_raqm.sh && popd
|
pushd depends && sudo ./install_raqm.sh && popd
|
||||||
|
|
||||||
# libavif
|
# libavif
|
||||||
pushd depends && ./install_libavif.sh && popd
|
pushd depends && sudo ./install_libavif.sh && popd
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
cibuildwheel==3.1.4
|
cibuildwheel==3.2.1
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
mypy==1.18.1
|
mypy==1.18.2
|
||||||
|
arro3-compute
|
||||||
|
arro3-core
|
||||||
IceSpringPySideStubs-PyQt6
|
IceSpringPySideStubs-PyQt6
|
||||||
IceSpringPySideStubs-PySide6
|
IceSpringPySideStubs-PySide6
|
||||||
ipython
|
ipython
|
||||||
|
|
|
||||||
3
.github/workflows/macos-install.sh
vendored
|
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ "$ImageOS" == "macos13" ]]; then
|
|
||||||
brew uninstall gradle maven
|
|
||||||
fi
|
|
||||||
brew install \
|
brew install \
|
||||||
aom \
|
aom \
|
||||||
dav1d \
|
dav1d \
|
||||||
|
|
|
||||||
4
.github/workflows/test-windows.yml
vendored
|
|
@ -97,8 +97,8 @@ jobs:
|
||||||
choco install nasm --no-progress
|
choco install nasm --no-progress
|
||||||
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
choco install ghostscript --version=10.5.1 --no-progress
|
choco install ghostscript --version=10.6.0 --no-progress
|
||||||
echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files\gs\gs10.06.0\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
# Install extra test images
|
# Install extra test images
|
||||||
xcopy /S /Y Tests\test-images\* Tests\images
|
xcopy /S /Y Tests\test-images\* Tests\images
|
||||||
|
|
|
||||||
2
.github/workflows/test.yml
vendored
|
|
@ -57,7 +57,7 @@ jobs:
|
||||||
- { python-version: "3.14t", disable-gil: true }
|
- { python-version: "3.14t", disable-gil: true }
|
||||||
- { python-version: "3.13t", disable-gil: true }
|
- { python-version: "3.13t", disable-gil: true }
|
||||||
# Intel
|
# Intel
|
||||||
- { os: "macos-13", python-version: "3.10" }
|
- { os: "macos-15-intel", python-version: "3.10" }
|
||||||
exclude:
|
exclude:
|
||||||
- { os: "macos-latest", python-version: "3.10" }
|
- { os: "macos-latest", python-version: "3.10" }
|
||||||
|
|
||||||
|
|
|
||||||
22
.github/workflows/wheels-dependencies.sh
vendored
|
|
@ -93,14 +93,18 @@ ARCHIVE_SDIR=pillow-depends-main
|
||||||
# Package versions for fresh source builds. Version numbers with "Patched"
|
# Package versions for fresh source builds. Version numbers with "Patched"
|
||||||
# annotations have a source code patch that is required for some platforms. If
|
# annotations have a source code patch that is required for some platforms. If
|
||||||
# you change those versions, ensure the patch is also updated.
|
# you change those versions, ensure the patch is also updated.
|
||||||
FREETYPE_VERSION=2.13.3
|
if [[ -n "$IOS_SDK" ]]; then
|
||||||
HARFBUZZ_VERSION=11.4.5
|
FREETYPE_VERSION=2.13.3
|
||||||
|
else
|
||||||
|
FREETYPE_VERSION=2.14.1
|
||||||
|
fi
|
||||||
|
HARFBUZZ_VERSION=12.1.0
|
||||||
LIBPNG_VERSION=1.6.50
|
LIBPNG_VERSION=1.6.50
|
||||||
JPEGTURBO_VERSION=3.1.2
|
JPEGTURBO_VERSION=3.1.2
|
||||||
OPENJPEG_VERSION=2.5.3
|
OPENJPEG_VERSION=2.5.4
|
||||||
XZ_VERSION=5.8.1
|
XZ_VERSION=5.8.1
|
||||||
ZSTD_VERSION=1.5.7
|
ZSTD_VERSION=1.5.7
|
||||||
TIFF_VERSION=4.7.0
|
TIFF_VERSION=4.7.1
|
||||||
LCMS2_VERSION=2.17
|
LCMS2_VERSION=2.17
|
||||||
ZLIB_NG_VERSION=2.2.5
|
ZLIB_NG_VERSION=2.2.5
|
||||||
LIBWEBP_VERSION=1.6.0
|
LIBWEBP_VERSION=1.6.0
|
||||||
|
|
@ -267,7 +271,11 @@ function build {
|
||||||
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
|
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
|
||||||
yum remove -y zlib-devel
|
yum remove -y zlib-devel
|
||||||
fi
|
fi
|
||||||
build_zlib_ng
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
|
CFLAGS="$CFLAGS -headerpad_max_install_names" build_zlib_ng
|
||||||
|
else
|
||||||
|
build_zlib_ng
|
||||||
|
fi
|
||||||
|
|
||||||
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
|
|
@ -314,6 +322,10 @@ function build {
|
||||||
|
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
# Custom freetype build
|
# Custom freetype build
|
||||||
|
if [[ -z "$IOS_SDK" ]]; then
|
||||||
|
build_simple sed 4.9 https://mirrors.middlendian.com/gnu/sed
|
||||||
|
fi
|
||||||
|
|
||||||
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
|
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
|
||||||
else
|
else
|
||||||
build_freetype
|
build_freetype
|
||||||
|
|
|
||||||
43
.github/workflows/wheels.yml
vendored
|
|
@ -39,6 +39,7 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
EXPECTED_DISTS: 91
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -52,21 +53,21 @@ jobs:
|
||||||
include:
|
include:
|
||||||
- name: "macOS 10.10 x86_64"
|
- name: "macOS 10.10 x86_64"
|
||||||
platform: macos
|
platform: macos
|
||||||
os: macos-13
|
os: macos-15-intel
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "cp3{9,10,11}*"
|
build: "cp3{10,11}*"
|
||||||
macosx_deployment_target: "10.10"
|
macosx_deployment_target: "10.10"
|
||||||
- name: "macOS 10.13 x86_64"
|
- name: "macOS 10.13 x86_64"
|
||||||
platform: macos
|
platform: macos
|
||||||
os: macos-13
|
os: macos-15-intel
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "cp3{12,13,14}*"
|
build: "cp3{12,13}*"
|
||||||
macosx_deployment_target: "10.13"
|
macosx_deployment_target: "10.13"
|
||||||
- name: "macOS 10.15 x86_64"
|
- name: "macOS 10.15 x86_64"
|
||||||
platform: macos
|
platform: macos
|
||||||
os: macos-13
|
os: macos-15-intel
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "pp3*"
|
build: "{cp314,pp3}*"
|
||||||
macosx_deployment_target: "10.15"
|
macosx_deployment_target: "10.15"
|
||||||
- name: "macOS arm64"
|
- name: "macOS arm64"
|
||||||
platform: macos
|
platform: macos
|
||||||
|
|
@ -99,11 +100,11 @@ jobs:
|
||||||
cibw_arch: arm64_iphoneos
|
cibw_arch: arm64_iphoneos
|
||||||
- name: "iOS arm64 simulator"
|
- name: "iOS arm64 simulator"
|
||||||
platform: ios
|
platform: ios
|
||||||
os: macos-14
|
os: macos-latest
|
||||||
cibw_arch: arm64_iphonesimulator
|
cibw_arch: arm64_iphonesimulator
|
||||||
- name: "iOS x86_64 simulator"
|
- name: "iOS x86_64 simulator"
|
||||||
platform: ios
|
platform: ios
|
||||||
os: macos-13
|
os: macos-15-intel
|
||||||
cibw_arch: x86_64_iphonesimulator
|
cibw_arch: x86_64_iphonesimulator
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
|
|
@ -231,7 +232,7 @@ jobs:
|
||||||
path: winbuild\build\bin\fribidi*
|
path: winbuild\build\bin\fribidi*
|
||||||
|
|
||||||
sdist:
|
sdist:
|
||||||
if: github.event_name != 'schedule'
|
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
|
|
@ -250,15 +251,33 @@ jobs:
|
||||||
name: dist-sdist
|
name: dist-sdist
|
||||||
path: dist/*.tar.gz
|
path: dist/*.tar.gz
|
||||||
|
|
||||||
|
count-dists:
|
||||||
|
needs: [build-native-wheels, windows, sdist]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Count dists
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
pattern: dist-*
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: "What did we get?"
|
||||||
|
run: |
|
||||||
|
ls -alR
|
||||||
|
echo "Number of dists, should be $EXPECTED_DISTS:"
|
||||||
|
files=$(ls dist 2>/dev/null | wc -l)
|
||||||
|
echo $files
|
||||||
|
[ "$files" -eq $EXPECTED_DISTS ] || exit 1
|
||||||
|
|
||||||
scientific-python-nightly-wheels-publish:
|
scientific-python-nightly-wheels-publish:
|
||||||
if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||||
needs: [build-native-wheels, windows]
|
needs: count-dists
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Upload wheels to scientific-python-nightly-wheels
|
name: Upload wheels to scientific-python-nightly-wheels
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v5
|
- uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
pattern: dist-*
|
pattern: dist-!(sdist)*
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: Upload wheels to scientific-python-nightly-wheels
|
- name: Upload wheels to scientific-python-nightly-wheels
|
||||||
|
|
@ -269,7 +288,7 @@ jobs:
|
||||||
|
|
||||||
pypi-publish:
|
pypi-publish:
|
||||||
if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||||
needs: [build-native-wheels, windows, sdist]
|
needs: count-dists
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Upload release to PyPI
|
name: Upload release to PyPI
|
||||||
environment:
|
environment:
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.12.11
|
rev: v0.13.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
args: [--exit-non-zero-on-fix]
|
args: [--exit-non-zero-on-fix]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 25.1.0
|
rev: 25.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ repos:
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v21.1.0
|
rev: v21.1.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types: [c]
|
types: [c]
|
||||||
|
|
@ -51,14 +51,14 @@ repos:
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
|
||||||
|
|
||||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
rev: 0.33.3
|
rev: 0.34.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-github-workflows
|
- id: check-github-workflows
|
||||||
- id: check-readthedocs
|
- id: check-readthedocs
|
||||||
- id: check-renovate
|
- id: check-renovate
|
||||||
|
|
||||||
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||||
rev: v1.12.1
|
rev: v1.14.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ repos:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: v2.6.0
|
rev: v2.7.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
|
|
||||||
|
|
|
||||||
1
Tests/createfontdatachunk.py
Executable file → Normal file
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
Tests/images/colr_bungee_older.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
Tests/images/frame_size.mpo
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 117 KiB |
275
Tests/test_arro3.py
Normal 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
|
||||||
|
|
@ -14,6 +14,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import (
|
from PIL import (
|
||||||
AvifImagePlugin,
|
AvifImagePlugin,
|
||||||
|
GifImagePlugin,
|
||||||
Image,
|
Image,
|
||||||
ImageDraw,
|
ImageDraw,
|
||||||
ImageFile,
|
ImageFile,
|
||||||
|
|
@ -220,6 +221,7 @@ class TestFileAvif:
|
||||||
def test_background_from_gif(self, tmp_path: Path) -> None:
|
def test_background_from_gif(self, tmp_path: Path) -> None:
|
||||||
with Image.open("Tests/images/chi.gif") as im:
|
with Image.open("Tests/images/chi.gif") as im:
|
||||||
original_value = im.convert("RGB").getpixel((1, 1))
|
original_value = im.convert("RGB").getpixel((1, 1))
|
||||||
|
assert isinstance(original_value, tuple)
|
||||||
|
|
||||||
# Save as AVIF
|
# Save as AVIF
|
||||||
out_avif = tmp_path / "temp.avif"
|
out_avif = tmp_path / "temp.avif"
|
||||||
|
|
@ -232,6 +234,7 @@ class TestFileAvif:
|
||||||
|
|
||||||
with Image.open(out_gif) as reread:
|
with Image.open(out_gif) as reread:
|
||||||
reread_value = reread.convert("RGB").getpixel((1, 1))
|
reread_value = reread.convert("RGB").getpixel((1, 1))
|
||||||
|
assert isinstance(reread_value, tuple)
|
||||||
difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)])
|
difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)])
|
||||||
assert difference <= 6
|
assert difference <= 6
|
||||||
|
|
||||||
|
|
@ -240,6 +243,7 @@ class TestFileAvif:
|
||||||
with Image.open("Tests/images/chi.gif") as im:
|
with Image.open("Tests/images/chi.gif") as im:
|
||||||
im.save(temp_file)
|
im.save(temp_file)
|
||||||
with Image.open(temp_file) as im:
|
with Image.open(temp_file) as im:
|
||||||
|
assert isinstance(im, AvifImagePlugin.AvifImageFile)
|
||||||
assert im.n_frames == 1
|
assert im.n_frames == 1
|
||||||
|
|
||||||
def test_invalid_file(self) -> None:
|
def test_invalid_file(self) -> None:
|
||||||
|
|
@ -598,10 +602,12 @@ class TestAvifAnimation:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with Image.open(TEST_AVIF_FILE) as im:
|
with Image.open(TEST_AVIF_FILE) as im:
|
||||||
|
assert isinstance(im, AvifImagePlugin.AvifImageFile)
|
||||||
assert im.n_frames == 1
|
assert im.n_frames == 1
|
||||||
assert not im.is_animated
|
assert not im.is_animated
|
||||||
|
|
||||||
with Image.open("Tests/images/avif/star.avifs") as im:
|
with Image.open("Tests/images/avif/star.avifs") as im:
|
||||||
|
assert isinstance(im, AvifImagePlugin.AvifImageFile)
|
||||||
assert im.n_frames == 5
|
assert im.n_frames == 5
|
||||||
assert im.is_animated
|
assert im.is_animated
|
||||||
|
|
||||||
|
|
@ -612,11 +618,13 @@ class TestAvifAnimation:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with Image.open("Tests/images/avif/star.gif") as original:
|
with Image.open("Tests/images/avif/star.gif") as original:
|
||||||
|
assert isinstance(original, GifImagePlugin.GifImageFile)
|
||||||
assert original.n_frames > 1
|
assert original.n_frames > 1
|
||||||
|
|
||||||
temp_file = tmp_path / "temp.avif"
|
temp_file = tmp_path / "temp.avif"
|
||||||
original.save(temp_file, save_all=True)
|
original.save(temp_file, save_all=True)
|
||||||
with Image.open(temp_file) as im:
|
with Image.open(temp_file) as im:
|
||||||
|
assert isinstance(im, AvifImagePlugin.AvifImageFile)
|
||||||
assert im.n_frames == original.n_frames
|
assert im.n_frames == original.n_frames
|
||||||
|
|
||||||
# Compare first frame in P mode to frame from original GIF
|
# Compare first frame in P mode to frame from original GIF
|
||||||
|
|
@ -636,6 +644,7 @@ class TestAvifAnimation:
|
||||||
|
|
||||||
def check(temp_file: Path) -> None:
|
def check(temp_file: Path) -> None:
|
||||||
with Image.open(temp_file) as im:
|
with Image.open(temp_file) as im:
|
||||||
|
assert isinstance(im, AvifImagePlugin.AvifImageFile)
|
||||||
assert im.n_frames == 4
|
assert im.n_frames == 4
|
||||||
|
|
||||||
# Compare first frame to original
|
# Compare first frame to original
|
||||||
|
|
@ -708,6 +717,7 @@ class TestAvifAnimation:
|
||||||
)
|
)
|
||||||
|
|
||||||
with Image.open(temp_file) as im:
|
with Image.open(temp_file) as im:
|
||||||
|
assert isinstance(im, AvifImagePlugin.AvifImageFile)
|
||||||
assert im.n_frames == 5
|
assert im.n_frames == 5
|
||||||
assert im.is_animated
|
assert im.is_animated
|
||||||
|
|
||||||
|
|
@ -737,6 +747,7 @@ class TestAvifAnimation:
|
||||||
)
|
)
|
||||||
|
|
||||||
with Image.open(temp_file) as im:
|
with Image.open(temp_file) as im:
|
||||||
|
assert isinstance(im, AvifImagePlugin.AvifImageFile)
|
||||||
assert im.n_frames == 5
|
assert im.n_frames == 5
|
||||||
assert im.is_animated
|
assert im.is_animated
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ from pathlib import Path
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import BmpImagePlugin, Image, _binary
|
from PIL import BmpImagePlugin, Image, _binary
|
||||||
|
from PIL._binary import o16le as o16
|
||||||
|
from PIL._binary import o32le as o32
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
|
@ -114,7 +116,7 @@ def test_save_float_dpi(tmp_path: Path) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_load_dib() -> None:
|
def test_load_dib() -> None:
|
||||||
# test for #1293, Imagegrab returning Unsupported Bitfields Format
|
# test for #1293, ImageGrab returning Unsupported Bitfields Format
|
||||||
with Image.open("Tests/images/clipboard.dib") as im:
|
with Image.open("Tests/images/clipboard.dib") as im:
|
||||||
assert im.format == "DIB"
|
assert im.format == "DIB"
|
||||||
assert im.get_format_mimetype() == "image/bmp"
|
assert im.get_format_mimetype() == "image/bmp"
|
||||||
|
|
@ -219,6 +221,18 @@ def test_rle8_eof(file_name: str, length: int) -> None:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsupported_bmp_bitfields_layout() -> None:
|
||||||
|
fp = io.BytesIO(
|
||||||
|
o32(40) # header size
|
||||||
|
+ b"\x00" * 10
|
||||||
|
+ o16(1) # bits
|
||||||
|
+ o32(3) # BITFIELDS compression
|
||||||
|
+ b"\x00" * 32
|
||||||
|
)
|
||||||
|
with pytest.raises(OSError, match="Unsupported BMP bitfields layout"):
|
||||||
|
Image.open(fp)
|
||||||
|
|
||||||
|
|
||||||
def test_offset() -> None:
|
def test_offset() -> None:
|
||||||
# This image has been hexedited
|
# This image has been hexedited
|
||||||
# to exclude the palette size from the pixel data offset
|
# to exclude the palette size from the pixel data offset
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import CurImagePlugin, Image
|
from PIL import CurImagePlugin, Image
|
||||||
|
from PIL._binary import o8
|
||||||
|
from PIL._binary import o16le as o16
|
||||||
|
from PIL._binary import o32le as o32
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/deerstalker.cur"
|
TEST_FILE = "Tests/images/deerstalker.cur"
|
||||||
|
|
||||||
|
|
@ -17,6 +22,24 @@ def test_sanity() -> None:
|
||||||
assert im.getpixel((16, 16)) == (84, 87, 86, 255)
|
assert im.getpixel((16, 16)) == (84, 87, 86, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_largest_cursor() -> None:
|
||||||
|
magic = b"\x00\x00\x02\x00"
|
||||||
|
sizes = ((1, 1), (8, 8), (4, 4))
|
||||||
|
data = magic + o16(len(sizes))
|
||||||
|
for w, h in sizes:
|
||||||
|
image_offset = 6 + len(sizes) * 16 if (w, h) == max(sizes) else 0
|
||||||
|
data += o8(w) + o8(h) + o8(0) * 10 + o32(image_offset)
|
||||||
|
data += (
|
||||||
|
o32(12) # header size
|
||||||
|
+ o16(8) # width
|
||||||
|
+ o16(16) # height
|
||||||
|
+ o16(0) # planes
|
||||||
|
+ o16(1) # bits
|
||||||
|
)
|
||||||
|
with Image.open(BytesIO(data)) as im:
|
||||||
|
assert im.size == (8, 8)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file() -> None:
|
def test_invalid_file() -> None:
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
|
@ -26,6 +49,7 @@ def test_invalid_file() -> None:
|
||||||
no_cursors_file = "Tests/images/no_cursors.cur"
|
no_cursors_file = "Tests/images/no_cursors.cur"
|
||||||
|
|
||||||
cur = CurImagePlugin.CurImageFile(TEST_FILE)
|
cur = CurImagePlugin.CurImageFile(TEST_FILE)
|
||||||
|
assert cur.fp is not None
|
||||||
cur.fp.close()
|
cur.fp.close()
|
||||||
with open(no_cursors_file, "rb") as cur.fp:
|
with open(no_cursors_file, "rb") as cur.fp:
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,14 @@ def test_load_long_binary_data(prefix: bytes) -> None:
|
||||||
assert img.format == "EPS"
|
assert img.format == "EPS"
|
||||||
|
|
||||||
|
|
||||||
|
def test_begin_binary() -> None:
|
||||||
|
with open("Tests/images/eps/binary_preview_map.eps", "rb") as fp:
|
||||||
|
data = bytearray(fp.read())
|
||||||
|
data[76875 : 76875 + 11] = b"%" * 11
|
||||||
|
with Image.open(io.BytesIO(data)) as img:
|
||||||
|
assert img.size == (399, 480)
|
||||||
|
|
||||||
|
|
||||||
@mark_if_feature_version(
|
@mark_if_feature_version(
|
||||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ def test_sanity() -> None:
|
||||||
def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
|
monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
|
||||||
with Image.open(animated_test_file_with_prefix_chunk) as im:
|
with Image.open(animated_test_file_with_prefix_chunk) as im:
|
||||||
|
assert isinstance(im, FliImagePlugin.FliImageFile)
|
||||||
assert im.mode == "P"
|
assert im.mode == "P"
|
||||||
assert im.size == (320, 200)
|
assert im.size == (320, 200)
|
||||||
assert im.format == "FLI"
|
assert im.format == "FLI"
|
||||||
|
|
@ -55,6 +56,7 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
assert im.is_animated
|
assert im.is_animated
|
||||||
|
|
||||||
palette = im.getpalette()
|
palette = im.getpalette()
|
||||||
|
assert palette is not None
|
||||||
assert palette[3:6] == [255, 255, 255]
|
assert palette[3:6] == [255, 255, 255]
|
||||||
assert palette[381:384] == [204, 204, 12]
|
assert palette[381:384] == [204, 204, 12]
|
||||||
assert palette[765:] == [252, 0, 0]
|
assert palette[765:] == [252, 0, 0]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import GdImageFile, UnidentifiedImageError
|
from PIL import GdImageFile, UnidentifiedImageError
|
||||||
|
|
@ -16,6 +18,14 @@ def test_sanity() -> None:
|
||||||
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14)
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14)
|
||||||
|
|
||||||
|
|
||||||
|
def test_transparency() -> None:
|
||||||
|
with open(TEST_GD_FILE, "rb") as fp:
|
||||||
|
data = bytearray(fp.read())
|
||||||
|
data[7:11] = b"\x00\x00\x00\x05"
|
||||||
|
with GdImageFile.open(BytesIO(data)) as im:
|
||||||
|
assert im.info["transparency"] == 5
|
||||||
|
|
||||||
|
|
||||||
def test_bad_mode() -> None:
|
def test_bad_mode() -> None:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
GdImageFile.open(TEST_GD_FILE, "bad mode")
|
GdImageFile.open(TEST_GD_FILE, "bad mode")
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None:
|
||||||
im.save(out, save_all=True)
|
im.save(out, save_all=True)
|
||||||
|
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
|
assert isinstance(reread, GifImagePlugin.GifImageFile)
|
||||||
assert reread.n_frames == 5
|
assert reread.n_frames == 5
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1374,6 +1375,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
|
||||||
|
|
||||||
with Image.open(out) as im:
|
with Image.open(out) as im:
|
||||||
# Assert that the frames are correct, and each frame has the same palette
|
# Assert that the frames are correct, and each frame has the same palette
|
||||||
|
assert isinstance(im, GifImagePlugin.GifImageFile)
|
||||||
assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
|
assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
|
||||||
assert im.palette is not None
|
assert im.palette is not None
|
||||||
assert im.global_palette is not None
|
assert im.global_palette is not None
|
||||||
|
|
|
||||||
|
|
@ -330,8 +330,10 @@ class TestFileJpeg:
|
||||||
|
|
||||||
# Reading
|
# Reading
|
||||||
with Image.open("Tests/images/exif_gps.jpg") as im:
|
with Image.open("Tests/images/exif_gps.jpg") as im:
|
||||||
exif = im._getexif()
|
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
||||||
assert exif[gps_index] == expected_exif_gps
|
exif_data = im._getexif()
|
||||||
|
assert exif_data is not None
|
||||||
|
assert exif_data[gps_index] == expected_exif_gps
|
||||||
|
|
||||||
# Writing
|
# Writing
|
||||||
f = tmp_path / "temp.jpg"
|
f = tmp_path / "temp.jpg"
|
||||||
|
|
@ -340,8 +342,10 @@ class TestFileJpeg:
|
||||||
hopper().save(f, exif=exif)
|
hopper().save(f, exif=exif)
|
||||||
|
|
||||||
with Image.open(f) as reloaded:
|
with Image.open(f) as reloaded:
|
||||||
exif = reloaded._getexif()
|
assert isinstance(reloaded, JpegImagePlugin.JpegImageFile)
|
||||||
assert exif[gps_index] == expected_exif_gps
|
exif_data = reloaded._getexif()
|
||||||
|
assert exif_data is not None
|
||||||
|
assert exif_data[gps_index] == expected_exif_gps
|
||||||
|
|
||||||
def test_empty_exif_gps(self) -> None:
|
def test_empty_exif_gps(self) -> None:
|
||||||
with Image.open("Tests/images/empty_gps_ifd.jpg") as im:
|
with Image.open("Tests/images/empty_gps_ifd.jpg") as im:
|
||||||
|
|
@ -368,6 +372,7 @@ class TestFileJpeg:
|
||||||
exifs = []
|
exifs = []
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
with Image.open("Tests/images/exif-200dpcm.jpg") as im:
|
with Image.open("Tests/images/exif-200dpcm.jpg") as im:
|
||||||
|
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
||||||
exifs.append(im._getexif())
|
exifs.append(im._getexif())
|
||||||
assert exifs[0] == exifs[1]
|
assert exifs[0] == exifs[1]
|
||||||
|
|
||||||
|
|
@ -401,13 +406,17 @@ class TestFileJpeg:
|
||||||
}
|
}
|
||||||
|
|
||||||
with Image.open("Tests/images/exif_gps.jpg") as im:
|
with Image.open("Tests/images/exif_gps.jpg") as im:
|
||||||
|
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
||||||
exif = im._getexif()
|
exif = im._getexif()
|
||||||
|
assert exif is not None
|
||||||
|
|
||||||
for tag, value in expected_exif.items():
|
for tag, value in expected_exif.items():
|
||||||
assert value == exif[tag]
|
assert value == exif[tag]
|
||||||
|
|
||||||
def test_exif_gps_typeerror(self) -> None:
|
def test_exif_gps_typeerror(self) -> None:
|
||||||
with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
|
with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
|
||||||
|
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
||||||
|
|
||||||
# Should not raise a TypeError
|
# Should not raise a TypeError
|
||||||
im._getexif()
|
im._getexif()
|
||||||
|
|
||||||
|
|
@ -487,7 +496,9 @@ class TestFileJpeg:
|
||||||
|
|
||||||
def test_exif(self) -> None:
|
def test_exif(self) -> None:
|
||||||
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
||||||
|
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
||||||
info = im._getexif()
|
info = im._getexif()
|
||||||
|
assert info is not None
|
||||||
assert info[305] == "Adobe Photoshop CS Macintosh"
|
assert info[305] == "Adobe Photoshop CS Macintosh"
|
||||||
|
|
||||||
def test_get_child_images(self) -> None:
|
def test_get_child_images(self) -> None:
|
||||||
|
|
@ -690,11 +701,13 @@ class TestFileJpeg:
|
||||||
|
|
||||||
def test_save_multiple_16bit_qtables(self) -> None:
|
def test_save_multiple_16bit_qtables(self) -> None:
|
||||||
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
|
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
|
||||||
|
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
||||||
im2 = self.roundtrip(im, qtables="keep")
|
im2 = self.roundtrip(im, qtables="keep")
|
||||||
assert im.quantization == im2.quantization
|
assert im.quantization == im2.quantization
|
||||||
|
|
||||||
def test_save_single_16bit_qtable(self) -> None:
|
def test_save_single_16bit_qtable(self) -> None:
|
||||||
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
|
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
|
||||||
|
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
||||||
im2 = self.roundtrip(im, qtables={0: im.quantization[0]})
|
im2 = self.roundtrip(im, qtables={0: im.quantization[0]})
|
||||||
assert len(im2.quantization) == 1
|
assert len(im2.quantization) == 1
|
||||||
assert im2.quantization[0] == im.quantization[0]
|
assert im2.quantization[0] == im.quantization[0]
|
||||||
|
|
@ -898,7 +911,10 @@ class TestFileJpeg:
|
||||||
# in contrast to normal 8
|
# in contrast to normal 8
|
||||||
with Image.open("Tests/images/exif-ifd-offset.jpg") as im:
|
with Image.open("Tests/images/exif-ifd-offset.jpg") as im:
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
assert im._getexif()[306] == "2017:03:13 23:03:09"
|
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
||||||
|
exif = im._getexif()
|
||||||
|
assert exif is not None
|
||||||
|
assert exif[306] == "2017:03:13 23:03:09"
|
||||||
|
|
||||||
def test_multiple_exif(self) -> None:
|
def test_multiple_exif(self) -> None:
|
||||||
with Image.open("Tests/images/multiple_exif.jpg") as im:
|
with Image.open("Tests/images/multiple_exif.jpg") as im:
|
||||||
|
|
|
||||||
|
|
@ -355,6 +355,27 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
# Should not segfault
|
# Should not segfault
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
|
||||||
|
def test_whitepoint_tag(
|
||||||
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
||||||
|
|
||||||
|
out = tmp_path / "temp.tif"
|
||||||
|
hopper().save(out, tiffinfo={318: (0.3127, 0.3289)})
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
||||||
|
assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289))
|
||||||
|
|
||||||
|
# Save tag by default
|
||||||
|
out = tmp_path / "temp2.tif"
|
||||||
|
with Image.open("Tests/images/rdf.tif") as im:
|
||||||
|
im.save(out)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
||||||
|
assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289999))
|
||||||
|
|
||||||
def test_xmlpacket_tag(
|
def test_xmlpacket_tag(
|
||||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
||||||
|
|
@ -104,25 +104,27 @@ def test_exif(test_file: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_frame_size() -> None:
|
def test_frame_size() -> None:
|
||||||
# This image has been hexedited to contain a different size
|
with Image.open("Tests/images/frame_size.mpo") as im:
|
||||||
# in the SOF marker of the second frame
|
assert im.size == (56, 70)
|
||||||
with Image.open("Tests/images/sugarshack_frame_size.mpo") as im:
|
im.load()
|
||||||
assert im.size == (640, 480)
|
|
||||||
|
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.size == (680, 480)
|
assert im.size == (349, 434)
|
||||||
|
im.load()
|
||||||
|
|
||||||
im.seek(0)
|
im.seek(0)
|
||||||
assert im.size == (640, 480)
|
assert im.size == (56, 70)
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_frame_size() -> None:
|
def test_ignore_frame_size() -> None:
|
||||||
# Ignore the different size of the second frame
|
# Ignore the different size of the second frame
|
||||||
# since this is not a "Large Thumbnail" image
|
# since this is not a "Large Thumbnail" image
|
||||||
with Image.open("Tests/images/ignore_frame_size.mpo") as im:
|
with Image.open("Tests/images/ignore_frame_size.mpo") as im:
|
||||||
|
assert isinstance(im, MpoImagePlugin.MpoImageFile)
|
||||||
assert im.size == (64, 64)
|
assert im.size == (64, 64)
|
||||||
|
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
|
assert im.mpinfo is not None
|
||||||
assert (
|
assert (
|
||||||
im.mpinfo[0xB002][1]["Attribute"]["MPType"]
|
im.mpinfo[0xB002][1]["Attribute"]["MPType"]
|
||||||
== "Multi-Frame Image: (Disparity)"
|
== "Multi-Frame Image: (Disparity)"
|
||||||
|
|
@ -155,6 +157,7 @@ def test_reload_exif_after_seek() -> None:
|
||||||
@pytest.mark.parametrize("test_file", test_files)
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
def test_mp(test_file: str) -> None:
|
def test_mp(test_file: str) -> None:
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
|
assert isinstance(im, MpoImagePlugin.MpoImageFile)
|
||||||
mpinfo = im._getmp()
|
mpinfo = im._getmp()
|
||||||
assert mpinfo is not None
|
assert mpinfo is not None
|
||||||
assert mpinfo[45056] == b"0100"
|
assert mpinfo[45056] == b"0100"
|
||||||
|
|
@ -165,6 +168,7 @@ def test_mp_offset() -> None:
|
||||||
# This image has been manually hexedited to have an IFD offset of 10
|
# This image has been manually hexedited to have an IFD offset of 10
|
||||||
# in APP2 data, in contrast to normal 8
|
# in APP2 data, in contrast to normal 8
|
||||||
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
|
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
|
||||||
|
assert isinstance(im, MpoImagePlugin.MpoImageFile)
|
||||||
mpinfo = im._getmp()
|
mpinfo = im._getmp()
|
||||||
assert mpinfo is not None
|
assert mpinfo is not None
|
||||||
assert mpinfo[45056] == b"0100"
|
assert mpinfo[45056] == b"0100"
|
||||||
|
|
@ -182,6 +186,7 @@ def test_mp_no_data() -> None:
|
||||||
@pytest.mark.parametrize("test_file", test_files)
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
def test_mp_attribute(test_file: str) -> None:
|
def test_mp_attribute(test_file: str) -> None:
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
|
assert isinstance(im, MpoImagePlugin.MpoImageFile)
|
||||||
mpinfo = im._getmp()
|
mpinfo = im._getmp()
|
||||||
assert mpinfo is not None
|
assert mpinfo is not None
|
||||||
for frame_number, mpentry in enumerate(mpinfo[0xB002]):
|
for frame_number, mpentry in enumerate(mpinfo[0xB002]):
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from .helper import assert_image_equal
|
||||||
|
|
||||||
|
|
||||||
def test_load_raw() -> None:
|
def test_load_raw() -> None:
|
||||||
with Image.open("Tests/images/hopper.pcd") as im:
|
with Image.open("Tests/images/hopper.pcd") as im:
|
||||||
|
|
@ -30,3 +32,8 @@ def test_rotated(orientation: int) -> None:
|
||||||
f = BytesIO(data)
|
f = BytesIO(data)
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
assert im.size == (512, 768)
|
assert im.size == (512, 768)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/hopper.pcd") as expected:
|
||||||
|
assert_image_equal(
|
||||||
|
im, expected.rotate(90 if orientation == 1 else 270, expand=True)
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,9 @@ class TestFilePng:
|
||||||
assert_image(im, "RGBA", (162, 150))
|
assert_image(im, "RGBA", (162, 150))
|
||||||
|
|
||||||
# image has 124 unique alpha values
|
# image has 124 unique alpha values
|
||||||
assert len(im.getchannel("A").getcolors()) == 124
|
colors = im.getchannel("A").getcolors()
|
||||||
|
assert colors is not None
|
||||||
|
assert len(colors) == 124
|
||||||
|
|
||||||
def test_load_transparent_rgb(self) -> None:
|
def test_load_transparent_rgb(self) -> None:
|
||||||
test_file = "Tests/images/rgb_trns.png"
|
test_file = "Tests/images/rgb_trns.png"
|
||||||
|
|
@ -241,7 +243,9 @@ class TestFilePng:
|
||||||
assert_image(im, "RGBA", (64, 64))
|
assert_image(im, "RGBA", (64, 64))
|
||||||
|
|
||||||
# image has 876 transparent pixels
|
# image has 876 transparent pixels
|
||||||
assert im.getchannel("A").getcolors()[0][0] == 876
|
colors = im.getchannel("A").getcolors()
|
||||||
|
assert colors is not None
|
||||||
|
assert colors[0][0] == 876
|
||||||
|
|
||||||
def test_save_p_transparent_palette(self, tmp_path: Path) -> None:
|
def test_save_p_transparent_palette(self, tmp_path: Path) -> None:
|
||||||
in_file = "Tests/images/pil123p.png"
|
in_file = "Tests/images/pil123p.png"
|
||||||
|
|
@ -262,7 +266,9 @@ class TestFilePng:
|
||||||
assert_image(im, "RGBA", (162, 150))
|
assert_image(im, "RGBA", (162, 150))
|
||||||
|
|
||||||
# image has 124 unique alpha values
|
# image has 124 unique alpha values
|
||||||
assert len(im.getchannel("A").getcolors()) == 124
|
colors = im.getchannel("A").getcolors()
|
||||||
|
assert colors is not None
|
||||||
|
assert len(colors) == 124
|
||||||
|
|
||||||
def test_save_p_single_transparency(self, tmp_path: Path) -> None:
|
def test_save_p_single_transparency(self, tmp_path: Path) -> None:
|
||||||
in_file = "Tests/images/p_trns_single.png"
|
in_file = "Tests/images/p_trns_single.png"
|
||||||
|
|
@ -285,7 +291,9 @@ class TestFilePng:
|
||||||
assert im.getpixel((31, 31)) == (0, 255, 52, 0)
|
assert im.getpixel((31, 31)) == (0, 255, 52, 0)
|
||||||
|
|
||||||
# image has 876 transparent pixels
|
# image has 876 transparent pixels
|
||||||
assert im.getchannel("A").getcolors()[0][0] == 876
|
colors = im.getchannel("A").getcolors()
|
||||||
|
assert colors is not None
|
||||||
|
assert colors[0][0] == 876
|
||||||
|
|
||||||
def test_save_p_transparent_black(self, tmp_path: Path) -> None:
|
def test_save_p_transparent_black(self, tmp_path: Path) -> None:
|
||||||
# check if solid black image with full transparency
|
# check if solid black image with full transparency
|
||||||
|
|
@ -313,7 +321,9 @@ class TestFilePng:
|
||||||
assert im.info["transparency"] == 255
|
assert im.info["transparency"] == 255
|
||||||
|
|
||||||
im_rgba = im.convert("RGBA")
|
im_rgba = im.convert("RGBA")
|
||||||
assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
|
colors = im_rgba.getchannel("A").getcolors()
|
||||||
|
assert colors is not None
|
||||||
|
assert colors[0][0] == num_transparent
|
||||||
|
|
||||||
test_file = tmp_path / "temp.png"
|
test_file = tmp_path / "temp.png"
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
@ -324,7 +334,9 @@ class TestFilePng:
|
||||||
assert_image_equal(im, test_im)
|
assert_image_equal(im, test_im)
|
||||||
|
|
||||||
test_im_rgba = test_im.convert("RGBA")
|
test_im_rgba = test_im.convert("RGBA")
|
||||||
assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
|
colors = test_im_rgba.getchannel("A").getcolors()
|
||||||
|
assert colors is not None
|
||||||
|
assert colors[0][0] == num_transparent
|
||||||
|
|
||||||
def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
|
def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
|
||||||
in_file = "Tests/images/caption_6_33_22.png"
|
in_file = "Tests/images/caption_6_33_22.png"
|
||||||
|
|
@ -671,6 +683,9 @@ class TestFilePng:
|
||||||
im.save(out, bits=4, save_all=save_all)
|
im.save(out, bits=4, save_all=save_all)
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
|
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
|
||||||
|
assert reloaded.png is not None
|
||||||
|
assert reloaded.png.im_palette is not None
|
||||||
assert len(reloaded.png.im_palette[1]) == 48
|
assert len(reloaded.png.im_palette[1]) == 48
|
||||||
|
|
||||||
def test_plte_length(self, tmp_path: Path) -> None:
|
def test_plte_length(self, tmp_path: Path) -> None:
|
||||||
|
|
@ -681,6 +696,9 @@ class TestFilePng:
|
||||||
im.save(out)
|
im.save(out)
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
|
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
|
||||||
|
assert reloaded.png is not None
|
||||||
|
assert reloaded.png.im_palette is not None
|
||||||
assert len(reloaded.png.im_palette[1]) == 3
|
assert len(reloaded.png.im_palette[1]) == 3
|
||||||
|
|
||||||
def test_getxmp(self) -> None:
|
def test_getxmp(self) -> None:
|
||||||
|
|
@ -702,13 +720,17 @@ class TestFilePng:
|
||||||
def test_exif(self) -> None:
|
def test_exif(self) -> None:
|
||||||
# With an EXIF chunk
|
# With an EXIF chunk
|
||||||
with Image.open("Tests/images/exif.png") as im:
|
with Image.open("Tests/images/exif.png") as im:
|
||||||
exif = im._getexif()
|
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||||
assert exif[274] == 1
|
exif_data = im._getexif()
|
||||||
|
assert exif_data is not None
|
||||||
|
assert exif_data[274] == 1
|
||||||
|
|
||||||
# With an ImageMagick zTXt chunk
|
# With an ImageMagick zTXt chunk
|
||||||
with Image.open("Tests/images/exif_imagemagick.png") as im:
|
with Image.open("Tests/images/exif_imagemagick.png") as im:
|
||||||
exif = im._getexif()
|
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||||
assert exif[274] == 1
|
exif_data = im._getexif()
|
||||||
|
assert exif_data is not None
|
||||||
|
assert exif_data[274] == 1
|
||||||
|
|
||||||
# Assert that info still can be extracted
|
# Assert that info still can be extracted
|
||||||
# when the image is no longer a PngImageFile instance
|
# when the image is no longer a PngImageFile instance
|
||||||
|
|
@ -717,8 +739,10 @@ class TestFilePng:
|
||||||
|
|
||||||
# With a tEXt chunk
|
# With a tEXt chunk
|
||||||
with Image.open("Tests/images/exif_text.png") as im:
|
with Image.open("Tests/images/exif_text.png") as im:
|
||||||
exif = im._getexif()
|
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||||
assert exif[274] == 1
|
exif_data = im._getexif()
|
||||||
|
assert exif_data is not None
|
||||||
|
assert exif_data[274] == 1
|
||||||
|
|
||||||
# With XMP tags
|
# With XMP tags
|
||||||
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
|
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
|
||||||
|
|
@ -740,8 +764,10 @@ class TestFilePng:
|
||||||
im.save(test_file, exif=im.getexif())
|
im.save(test_file, exif=im.getexif())
|
||||||
|
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
exif = reloaded._getexif()
|
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
|
||||||
assert exif[274] == 1
|
exif_data = reloaded._getexif()
|
||||||
|
assert exif_data is not None
|
||||||
|
assert exif_data[274] == 1
|
||||||
|
|
||||||
@mark_if_feature_version(
|
@mark_if_feature_version(
|
||||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,13 @@ def test_16bit_pgm() -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff")
|
assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff")
|
||||||
|
|
||||||
|
|
||||||
|
def test_p4_save(tmp_path: Path) -> None:
|
||||||
|
with Image.open("Tests/images/hopper_1bit.pbm") as im:
|
||||||
|
filename = tmp_path / "temp.pbm"
|
||||||
|
im.save(filename)
|
||||||
|
assert_image_equal_tofile(im, filename)
|
||||||
|
|
||||||
|
|
||||||
def test_16bit_pgm_write(tmp_path: Path) -> None:
|
def test_16bit_pgm_write(tmp_path: Path) -> None:
|
||||||
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
||||||
filename = tmp_path / "temp.pgm"
|
filename = tmp_path / "temp.pgm"
|
||||||
|
|
@ -134,6 +141,12 @@ def test_pfm_big_endian(tmp_path: Path) -> None:
|
||||||
assert_image_equal_tofile(im, filename)
|
assert_image_equal_tofile(im, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_unsupported_mode(tmp_path: Path) -> None:
|
||||||
|
im = hopper("P")
|
||||||
|
with pytest.raises(OSError, match="cannot write mode P as PPM"):
|
||||||
|
im.save(tmp_path / "out.ppm")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"data",
|
"data",
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -274,13 +274,17 @@ def test_save_l_transparency(tmp_path: Path) -> None:
|
||||||
in_file = "Tests/images/la.tga"
|
in_file = "Tests/images/la.tga"
|
||||||
with Image.open(in_file) as im:
|
with Image.open(in_file) as im:
|
||||||
assert im.mode == "LA"
|
assert im.mode == "LA"
|
||||||
assert im.getchannel("A").getcolors()[0][0] == num_transparent
|
colors = im.getchannel("A").getcolors()
|
||||||
|
assert colors is not None
|
||||||
|
assert colors[0][0] == num_transparent
|
||||||
|
|
||||||
out = tmp_path / "temp.tga"
|
out = tmp_path / "temp.tga"
|
||||||
im.save(out)
|
im.save(out)
|
||||||
|
|
||||||
with Image.open(out) as test_im:
|
with Image.open(out) as test_im:
|
||||||
assert test_im.mode == "LA"
|
assert test_im.mode == "LA"
|
||||||
assert test_im.getchannel("A").getcolors()[0][0] == num_transparent
|
colors = test_im.getchannel("A").getcolors()
|
||||||
|
assert colors is not None
|
||||||
|
assert colors[0][0] == num_transparent
|
||||||
|
|
||||||
assert_image_equal(im, test_im)
|
assert_image_equal(im, test_im)
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,13 @@ except ImportError:
|
||||||
def test_read_exif_metadata() -> None:
|
def test_read_exif_metadata() -> None:
|
||||||
file_path = "Tests/images/flower.webp"
|
file_path = "Tests/images/flower.webp"
|
||||||
with Image.open(file_path) as image:
|
with Image.open(file_path) as image:
|
||||||
|
assert isinstance(image, WebPImagePlugin.WebPImageFile)
|
||||||
assert image.format == "WEBP"
|
assert image.format == "WEBP"
|
||||||
exif_data = image.info.get("exif", None)
|
exif_data = image.info.get("exif", None)
|
||||||
assert exif_data
|
assert exif_data
|
||||||
|
|
||||||
exif = image._getexif()
|
exif = image._getexif()
|
||||||
|
assert exif is not None
|
||||||
|
|
||||||
# Camera make
|
# Camera make
|
||||||
assert exif[271] == "Canon"
|
assert exif[271] == "Canon"
|
||||||
|
|
|
||||||
|
|
@ -284,33 +284,6 @@ class TestImage:
|
||||||
assert item is not None
|
assert item is not None
|
||||||
assert item != num
|
assert item != num
|
||||||
|
|
||||||
def test_expand_x(self) -> None:
|
|
||||||
# Arrange
|
|
||||||
im = hopper()
|
|
||||||
orig_size = im.size
|
|
||||||
xmargin = 5
|
|
||||||
|
|
||||||
# Act
|
|
||||||
im = im._expand(xmargin)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert im.size[0] == orig_size[0] + 2 * xmargin
|
|
||||||
assert im.size[1] == orig_size[1] + 2 * xmargin
|
|
||||||
|
|
||||||
def test_expand_xy(self) -> None:
|
|
||||||
# Arrange
|
|
||||||
im = hopper()
|
|
||||||
orig_size = im.size
|
|
||||||
xmargin = 5
|
|
||||||
ymargin = 3
|
|
||||||
|
|
||||||
# Act
|
|
||||||
im = im._expand(xmargin, ymargin)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert im.size[0] == orig_size[0] + 2 * xmargin
|
|
||||||
assert im.size[1] == orig_size[1] + 2 * ymargin
|
|
||||||
|
|
||||||
def test_getbands(self) -> None:
|
def test_getbands(self) -> None:
|
||||||
# Assert
|
# Assert
|
||||||
assert hopper("RGB").getbands() == ("R", "G", "B")
|
assert hopper("RGB").getbands() == ("R", "G", "B")
|
||||||
|
|
@ -1126,6 +1099,12 @@ class TestImage:
|
||||||
assert im.palette is not None
|
assert im.palette is not None
|
||||||
assert im.palette.colors[(27, 35, 6, 214)] == 24
|
assert im.palette.colors[(27, 35, 6, 214)] == 24
|
||||||
|
|
||||||
|
def test_merge_pa(self) -> None:
|
||||||
|
p = hopper("P")
|
||||||
|
a = Image.new("L", p.size)
|
||||||
|
pa = Image.merge("PA", (p, a))
|
||||||
|
assert p.getpalette() == pa.getpalette()
|
||||||
|
|
||||||
def test_constants(self) -> None:
|
def test_constants(self) -> None:
|
||||||
for enum in (
|
for enum in (
|
||||||
Image.Transpose,
|
Image.Transpose,
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,13 @@ def test_opaque() -> None:
|
||||||
assert_image_equal(alpha, solid)
|
assert_image_equal(alpha, solid)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rgba() -> None:
|
||||||
|
with Image.open("Tests/images/transparent.png") as im:
|
||||||
|
assert im.mode == "RGBA"
|
||||||
|
|
||||||
|
assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
|
||||||
|
|
||||||
|
|
||||||
def test_rgba_p() -> None:
|
def test_rgba_p() -> None:
|
||||||
im = hopper("RGBA")
|
im = hopper("RGBA")
|
||||||
im.putalpha(hopper("L"))
|
im.putalpha(hopper("L"))
|
||||||
|
|
@ -107,11 +114,19 @@ def test_rgba_p() -> None:
|
||||||
assert_image_similar(im, comparable, 20)
|
assert_image_similar(im, comparable, 20)
|
||||||
|
|
||||||
|
|
||||||
def test_rgba() -> None:
|
def test_rgba_pa() -> None:
|
||||||
with Image.open("Tests/images/transparent.png") as im:
|
im = hopper("RGBA").convert("PA").convert("RGB")
|
||||||
assert im.mode == "RGBA"
|
expected = hopper("RGB")
|
||||||
|
|
||||||
assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
|
assert_image_similar(im, expected, 9.3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pa() -> None:
|
||||||
|
im = hopper().convert("PA")
|
||||||
|
|
||||||
|
palette = im.palette
|
||||||
|
assert palette is not None
|
||||||
|
assert palette.colors != {}
|
||||||
|
|
||||||
|
|
||||||
def test_trns_p(tmp_path: Path) -> None:
|
def test_trns_p(tmp_path: Path) -> None:
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,21 @@ class TestImagingPaste:
|
||||||
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
|
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
|
||||||
assert_image_equal(im, im2)
|
assert_image_equal(im, im2)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("y", [10, -10])
|
||||||
|
@pytest.mark.parametrize("mode", ["L", "RGB"])
|
||||||
|
@pytest.mark.parametrize("mask_mode", ["", "1", "L", "LA", "RGBa"])
|
||||||
|
def test_image_self(self, y: int, mode: str, mask_mode: str) -> None:
|
||||||
|
im = getattr(self, "gradient_" + mode)
|
||||||
|
mask = Image.new(mask_mode, im.size, 0xFFFFFFFF) if mask_mode else None
|
||||||
|
|
||||||
|
im_self = im.copy()
|
||||||
|
im_self.paste(im_self, (0, y), mask)
|
||||||
|
|
||||||
|
im_copy = im.copy()
|
||||||
|
im_copy.paste(im_copy.copy(), (0, y), mask)
|
||||||
|
|
||||||
|
assert_image_equal(im_self, im_copy)
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
def test_image_mask_1(self, mode: str) -> None:
|
def test_image_mask_1(self, mode: str) -> None:
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ def test_putpalette_with_alpha_values() -> None:
|
||||||
expected = im.convert("RGBA")
|
expected = im.convert("RGBA")
|
||||||
|
|
||||||
palette = im.getpalette()
|
palette = im.getpalette()
|
||||||
|
assert palette is not None
|
||||||
transparency = im.info.pop("transparency")
|
transparency = im.info.pop("transparency")
|
||||||
|
|
||||||
palette_with_alpha_values = []
|
palette_with_alpha_values = []
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,15 @@ def test_quantize_kmeans(method: Image.Quantize) -> None:
|
||||||
im.quantize(kmeans=-1, method=method)
|
im.quantize(kmeans=-1, method=method)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("libimagequant")
|
||||||
|
def test_resize() -> None:
|
||||||
|
im = hopper().resize((100, 100))
|
||||||
|
converted = im.quantize(100, Image.Quantize.LIBIMAGEQUANT)
|
||||||
|
colors = converted.getcolors()
|
||||||
|
assert colors is not None
|
||||||
|
assert len(colors) == 100
|
||||||
|
|
||||||
|
|
||||||
def test_colors() -> None:
|
def test_colors() -> None:
|
||||||
im = hopper()
|
im = hopper()
|
||||||
colors = 2
|
colors = 2
|
||||||
|
|
|
||||||
|
|
@ -1494,7 +1494,9 @@ def test_default_font_size() -> None:
|
||||||
|
|
||||||
def draw_text() -> None:
|
def draw_text() -> None:
|
||||||
draw.text((0, 0), text, font_size=16)
|
draw.text((0, 0), text, font_size=16)
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
|
assert_image_similar_tofile(
|
||||||
|
im, "Tests/images/imagedraw_default_font_size.png", 1
|
||||||
|
)
|
||||||
|
|
||||||
check(draw_text)
|
check(draw_text)
|
||||||
|
|
||||||
|
|
@ -1513,7 +1515,9 @@ def test_default_font_size() -> None:
|
||||||
|
|
||||||
def draw_multiline_text() -> None:
|
def draw_multiline_text() -> None:
|
||||||
draw.multiline_text((0, 0), text, font_size=16)
|
draw.multiline_text((0, 0), text, font_size=16)
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
|
assert_image_similar_tofile(
|
||||||
|
im, "Tests/images/imagedraw_default_font_size.png", 1
|
||||||
|
)
|
||||||
|
|
||||||
check(draw_multiline_text)
|
check(draw_multiline_text)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,11 @@ class TestImageFile:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
p.close()
|
p.close()
|
||||||
|
|
||||||
|
def test_negative_offset(self) -> None:
|
||||||
|
with Image.open("Tests/images/raw_negative_stride.bin") as im:
|
||||||
|
with pytest.raises(ValueError, match="Tile offset cannot be negative"):
|
||||||
|
im.load()
|
||||||
|
|
||||||
def test_no_format(self) -> None:
|
def test_no_format(self) -> None:
|
||||||
buf = BytesIO(b"\x00" * 255)
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
assert_image_equal_tofile,
|
assert_image_equal_tofile,
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
|
has_feature_version,
|
||||||
is_win32,
|
is_win32,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
skip_unless_feature_version,
|
skip_unless_feature_version,
|
||||||
|
|
@ -492,6 +493,11 @@ def test_stroke_mask() -> None:
|
||||||
assert mask.getpixel((42, 5)) == 255
|
assert mask.getpixel((42, 5)) == 255
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_invalid_file() -> None:
|
||||||
|
with pytest.raises(SyntaxError, match="Not a PILfont file"):
|
||||||
|
ImageFont.load("Tests/images/1_trns.png")
|
||||||
|
|
||||||
|
|
||||||
def test_load_when_image_not_found() -> None:
|
def test_load_when_image_not_found() -> None:
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
||||||
pass
|
pass
|
||||||
|
|
@ -549,7 +555,7 @@ def test_default_font() -> None:
|
||||||
draw.text((10, 60), txt, font=larger_default_font)
|
draw.text((10, 60), txt, font=larger_default_font)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/default_font_freetype.png")
|
assert_image_similar_tofile(im, "Tests/images/default_font_freetype.png", 0.13)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("", "1", "RGBA"))
|
@pytest.mark.parametrize("mode", ("", "1", "RGBA"))
|
||||||
|
|
@ -1055,7 +1061,10 @@ def test_colr(layout_engine: ImageFont.Layout) -> None:
|
||||||
|
|
||||||
d.text((15, 5), "Bungee", font=font, embedded_color=True)
|
d.text((15, 5), "Bungee", font=font, embedded_color=True)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21)
|
if has_feature_version("freetype2", "2.14.0"):
|
||||||
|
assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 6.1)
|
||||||
|
else:
|
||||||
|
assert_image_similar_tofile(im, "Tests/images/colr_bungee_older.png", 21)
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature_version("freetype2", "2.10.0")
|
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||||
|
|
@ -1071,7 +1080,7 @@ def test_colr_mask(layout_engine: ImageFont.Layout) -> None:
|
||||||
|
|
||||||
d.text((15, 5), "Bungee", "black", font=font)
|
d.text((15, 5), "Bungee", "black", font=font)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
|
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 14.1)
|
||||||
|
|
||||||
|
|
||||||
def test_woff2(layout_engine: ImageFont.Layout) -> None:
|
def test_woff2(layout_engine: ImageFont.Layout) -> None:
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ def test_x_max_and_y_offset() -> None:
|
||||||
draw.text((0, 0), "لح", font=ttf, fill=500)
|
draw.text((0, 0), "لح", font=ttf, fill=500)
|
||||||
|
|
||||||
target = "Tests/images/test_x_max_and_y_offset.png"
|
target = "Tests/images/test_x_max_and_y_offset.png"
|
||||||
assert_image_similar_tofile(im, target, 0.5)
|
assert_image_similar_tofile(im, target, 3.8)
|
||||||
|
|
||||||
|
|
||||||
def test_language() -> None:
|
def test_language() -> None:
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,14 @@ def test_default_font(font: ImageFont.ImageFont) -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_mode() -> None:
|
||||||
|
font = ImageFont.ImageFont()
|
||||||
|
fp = BytesIO()
|
||||||
|
with Image.open("Tests/images/hopper.png") as im:
|
||||||
|
with pytest.raises(TypeError, match="invalid font image mode"):
|
||||||
|
font._load_pilfont_data(fp, im)
|
||||||
|
|
||||||
|
|
||||||
def test_without_freetype() -> None:
|
def test_without_freetype() -> None:
|
||||||
original_core = ImageFont.core
|
original_core = ImageFont.core
|
||||||
if features.check_module("freetype2"):
|
if features.check_module("freetype2"):
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,21 @@ def test_palette(mode: str) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rgba_palette() -> None:
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
|
||||||
|
red = (255, 0, 0, 255)
|
||||||
|
translucent_black = (0, 0, 0, 127)
|
||||||
|
im.putpalette(red + translucent_black, "RGBA")
|
||||||
|
|
||||||
|
expanded_im = ImageOps.expand(im, 1, 1)
|
||||||
|
|
||||||
|
palette = expanded_im.palette
|
||||||
|
assert palette is not None
|
||||||
|
assert palette.mode == "RGBA"
|
||||||
|
assert expanded_im.convert("RGBA").getpixel((0, 0)) == translucent_black
|
||||||
|
|
||||||
|
|
||||||
def test_pil163() -> None:
|
def test_pil163() -> None:
|
||||||
# Division by zero in equalize if < 255 pixels in image (@PIL163)
|
# Division by zero in equalize if < 255 pixels in image (@PIL163)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,14 @@ def test_consecutive() -> None:
|
||||||
def test_palette_mmap() -> None:
|
def test_palette_mmap() -> None:
|
||||||
# Using mmap in ImageFile can require to reload the palette.
|
# Using mmap in ImageFile can require to reload the palette.
|
||||||
with Image.open("Tests/images/multipage-mmap.tiff") as im:
|
with Image.open("Tests/images/multipage-mmap.tiff") as im:
|
||||||
color1 = im.getpalette()[:3]
|
palette = im.getpalette()
|
||||||
|
assert palette is not None
|
||||||
|
color1 = palette[:3]
|
||||||
im.seek(0)
|
im.seek(0)
|
||||||
color2 = im.getpalette()[:3]
|
|
||||||
|
palette = im.getpalette()
|
||||||
|
assert palette is not None
|
||||||
|
color2 = palette[:3]
|
||||||
assert color1 == color2
|
assert color1 == color2
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
72
Tests/test_imagetext.py
Normal file
|
|
@ -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
|
|
@ -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
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
from typing import Any, NamedTuple
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -244,3 +245,29 @@ def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None)
|
||||||
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
|
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
|
||||||
|
|
||||||
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)
|
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mode, metadata",
|
||||||
|
(
|
||||||
|
("LA", ["L", "X", "X", "A"]),
|
||||||
|
("RGB", ["R", "G", "B", "X"]),
|
||||||
|
("RGBX", ["R", "G", "B", "X"]),
|
||||||
|
("RGBA", ["R", "G", "B", "A"]),
|
||||||
|
("CMYK", ["C", "M", "Y", "K"]),
|
||||||
|
("YCbCr", ["Y", "Cb", "Cr", "X"]),
|
||||||
|
("HSV", ["H", "S", "V", "X"]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_image_metadata(mode: str, metadata: list[str]) -> None:
|
||||||
|
img = hopper(mode)
|
||||||
|
|
||||||
|
arr = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
|
|
||||||
|
assert arr.type.field(0).metadata
|
||||||
|
assert arr.type.field(0).metadata[b"image"]
|
||||||
|
|
||||||
|
parsed_metadata = json.loads(arr.type.field(0).metadata[b"image"].decode("utf8"))
|
||||||
|
|
||||||
|
assert "bands" in parsed_metadata
|
||||||
|
assert parsed_metadata["bands"] == metadata
|
||||||
|
|
|
||||||
1
checks/32bit_segfault_check.py
Executable file → Normal file
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
|
||||||
1
checks/check_imaging_leaks.py
Executable file → Normal file
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,6 @@ cmake \
|
||||||
"${LIBAVIF_CMAKE_FLAGS[@]}" \
|
"${LIBAVIF_CMAKE_FLAGS[@]}" \
|
||||||
.
|
.
|
||||||
|
|
||||||
sudo make install
|
make install
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install openjpeg
|
# install openjpeg
|
||||||
|
|
||||||
archive=openjpeg-2.5.3
|
archive=openjpeg-2.5.4
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,6 @@ archive=libraqm-0.10.3
|
||||||
|
|
||||||
pushd $archive
|
pushd $archive
|
||||||
|
|
||||||
meson build --prefix=/usr && sudo ninja -C build install
|
meson build --prefix=/usr && ninja -C build install
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
* **libtiff** provides compressed TIFF functionality
|
* **libtiff** provides compressed TIFF functionality
|
||||||
|
|
||||||
* Pillow has been tested with libtiff versions **4.0-4.7.0**
|
* Pillow has been tested with libtiff versions **4.0-4.7.1**
|
||||||
|
|
||||||
* **libfreetype** provides type related services
|
* **libfreetype** provides type related services
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ Many of Pillow's features require external libraries:
|
||||||
* **openjpeg** provides JPEG 2000 functionality.
|
* **openjpeg** provides JPEG 2000 functionality.
|
||||||
|
|
||||||
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
|
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
|
||||||
**2.4.0**, **2.5.0**, **2.5.2** and **2.5.3**.
|
**2.4.0**, **2.5.0**, **2.5.2**, **2.5.3** and **2.5.4**.
|
||||||
* Pillow does **not** support the earlier **1.5** series which ships
|
* Pillow does **not** support the earlier **1.5** series which ships
|
||||||
with Debian Jessie.
|
with Debian Jessie.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Gentoo | 3.12 | x86-64 |
|
| Gentoo | 3.12 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| macOS 13 Ventura | 3.10 | x86-64 |
|
| macOS 15 Sequoia | 3.10 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14 | arm64 |
|
| | 3.11, 3.12, 3.13, 3.14, | arm64 |
|
||||||
| | PyPy3 | |
|
| | PyPy3 | |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||||
|
|
@ -75,6 +75,8 @@ These platforms have been reported to work at the versions mentioned.
|
||||||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||||
| | | versions | | Pillow version | | processors |
|
| | | versions | | Pillow version | | processors |
|
||||||
+==================================+============================+==================+==============+
|
+==================================+============================+==================+==============+
|
||||||
|
| macOS 26 Tahoe | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
|
||||||
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
|
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
|
||||||
| +----------------------------+------------------+ |
|
| +----------------------------+------------------+ |
|
||||||
| | 3.8 | 10.4.0 | |
|
| | 3.8 | 10.4.0 | |
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,43 @@ Color names
|
||||||
|
|
||||||
See :ref:`color-names` for the color names supported by Pillow.
|
See :ref:`color-names` for the color names supported by Pillow.
|
||||||
|
|
||||||
|
Alpha channel
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
By default, when drawing onto an existing image, the image's pixel values are simply
|
||||||
|
replaced by the new color::
|
||||||
|
|
||||||
|
im = Image.new("RGBA", (1, 1), (255, 0, 0))
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
|
||||||
|
assert im.getpixel((0, 0)) == (0, 255, 0, 127)
|
||||||
|
|
||||||
|
# Alpha channel values have no effect when drawing with RGB mode
|
||||||
|
im = Image.new("RGB", (1, 1), (255, 0, 0))
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
|
||||||
|
assert im.getpixel((0, 0)) == (0, 255, 0)
|
||||||
|
|
||||||
|
If you would like to combine translucent color with an RGB image, then initialize the
|
||||||
|
ImageDraw instance with the RGBA mode::
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
im = Image.new("RGB", (1, 1), (255, 0, 0))
|
||||||
|
d = ImageDraw.Draw(im, "RGBA")
|
||||||
|
d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
|
||||||
|
assert im.getpixel((0, 0)) == (128, 127, 0)
|
||||||
|
|
||||||
|
If you would like to combine translucent color with an RGBA image underneath, you will
|
||||||
|
need to combine multiple images::
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
im = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
|
||||||
|
im2 = Image.new("RGBA", (1, 1))
|
||||||
|
d = ImageDraw.Draw(im2)
|
||||||
|
d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
|
||||||
|
im.paste(im2.convert("RGB"), mask=im2)
|
||||||
|
assert im.getpixel((0, 0)) == (128, 127, 0, 255)
|
||||||
|
|
||||||
Fonts
|
Fonts
|
||||||
^^^^^
|
^^^^^
|
||||||
|
|
||||||
|
|
@ -545,6 +582,8 @@ Methods
|
||||||
hello_world = hello + world # kerning is disabled, no need to adjust
|
hello_world = hello + world # kerning is disabled, no need to adjust
|
||||||
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True
|
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True
|
||||||
|
|
||||||
|
.. seealso:: :py:meth:`PIL.ImageText.Text.get_length`
|
||||||
|
|
||||||
.. versionadded:: 8.0.0
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
:param text: Text to be measured. May not contain any newline characters.
|
:param text: Text to be measured. May not contain any newline characters.
|
||||||
|
|
@ -646,6 +685,8 @@ Methods
|
||||||
1/64 pixel precision. The bounding box includes extra margins for
|
1/64 pixel precision. The bounding box includes extra margins for
|
||||||
some fonts, e.g. italics or accents.
|
some fonts, e.g. italics or accents.
|
||||||
|
|
||||||
|
.. seealso:: :py:meth:`PIL.ImageText.Text.get_bbox`
|
||||||
|
|
||||||
.. versionadded:: 8.0.0
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
:param xy: The anchor coordinates of the text.
|
:param xy: The anchor coordinates of the text.
|
||||||
|
|
|
||||||
61
docs/reference/ImageText.rst
Normal 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:
|
||||||
|
|
@ -24,6 +24,7 @@ Reference
|
||||||
ImageSequence
|
ImageSequence
|
||||||
ImageShow
|
ImageShow
|
||||||
ImageStat
|
ImageStat
|
||||||
|
ImageText
|
||||||
ImageTk
|
ImageTk
|
||||||
ImageTransform
|
ImageTransform
|
||||||
ImageWin
|
ImageWin
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,6 @@
|
||||||
12.0.0
|
12.0.0
|
||||||
------
|
------
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
:cve:`YYYY-XXXXX`: TODO
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Backwards incompatible changes
|
Backwards incompatible changes
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
|
|
@ -132,18 +119,43 @@ Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
|
||||||
API changes
|
API changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
TODO
|
Image.alpha_composite: LA images
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
:py:meth:`~PIL.Image.alpha_composite` can now use LA images as well as RGBA.
|
||||||
|
|
||||||
API additions
|
API additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
TODO
|
Added ImageText.Text
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
:py:class:`PIL.ImageText.Text` has been added, as a simpler way to use fonts with text
|
||||||
|
strings or bytes.
|
||||||
|
|
||||||
|
Without ``ImageText.Text``::
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
im = Image.new(mode, size)
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
d.textlength(text, font, direction, features, language, embedded_color)
|
||||||
|
d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color)
|
||||||
|
d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color)
|
||||||
|
|
||||||
|
With ``ImageText.Text``::
|
||||||
|
|
||||||
|
from PIL import ImageText
|
||||||
|
text = ImageText.Text(text, font, mode, spacing, direction, features, language)
|
||||||
|
text.embed_color()
|
||||||
|
text.stroke(stroke_width, stroke_fill)
|
||||||
|
|
||||||
|
text.get_length()
|
||||||
|
text.get_bbox(xy, anchor, align)
|
||||||
|
|
||||||
|
im = Image.new(mode, size)
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
d.text(xy, text, fill, anchor=anchor, align=align)
|
||||||
|
|
||||||
Other changes
|
Other changes
|
||||||
=============
|
=============
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,9 @@ optional-dependencies.mic = [
|
||||||
"olefile",
|
"olefile",
|
||||||
]
|
]
|
||||||
optional-dependencies.test-arrow = [
|
optional-dependencies.test-arrow = [
|
||||||
|
"arro3-compute",
|
||||||
|
"arro3-core",
|
||||||
|
"nanoarrow",
|
||||||
"pyarrow",
|
"pyarrow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
17
setup.py
|
|
@ -21,6 +21,10 @@ from pybind11.setup_helpers import ParallelCompile
|
||||||
from setuptools import Extension, setup
|
from setuptools import Extension, setup
|
||||||
from setuptools.command.build_ext import build_ext
|
from setuptools.command.build_ext import build_ext
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from setuptools import _BuildInfo
|
||||||
|
|
||||||
configuration: dict[str, list[str]] = {}
|
configuration: dict[str, list[str]] = {}
|
||||||
|
|
||||||
# parse configuration from _custom_build/backend.py
|
# parse configuration from _custom_build/backend.py
|
||||||
|
|
@ -1072,16 +1076,20 @@ def debug_build() -> bool:
|
||||||
return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD
|
return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD
|
||||||
|
|
||||||
|
|
||||||
|
libraries: list[tuple[str, _BuildInfo]] = [
|
||||||
|
("pil_imaging_mode", {"sources": ["src/libImaging/Mode.c"]}),
|
||||||
|
]
|
||||||
|
|
||||||
files: list[str | os.PathLike[str]] = ["src/_imaging.c"]
|
files: list[str | os.PathLike[str]] = ["src/_imaging.c"]
|
||||||
for src_file in _IMAGING:
|
for src_file in _IMAGING:
|
||||||
files.append("src/" + src_file + ".c")
|
files.append("src/" + src_file + ".c")
|
||||||
for src_file in _LIB_IMAGING:
|
for src_file in _LIB_IMAGING:
|
||||||
files.append(os.path.join("src/libImaging", src_file + ".c"))
|
files.append(os.path.join("src/libImaging", src_file + ".c"))
|
||||||
ext_modules = [
|
ext_modules = [
|
||||||
Extension("PIL._imaging", files),
|
Extension("PIL._imaging", files, libraries=["pil_imaging_mode"]),
|
||||||
Extension("PIL._imagingft", ["src/_imagingft.c"]),
|
Extension("PIL._imagingft", ["src/_imagingft.c"], libraries=["pil_imaging_mode"]),
|
||||||
Extension("PIL._imagingcms", ["src/_imagingcms.c"]),
|
Extension("PIL._imagingcms", ["src/_imagingcms.c"], libraries=["pil_imaging_mode"]),
|
||||||
Extension("PIL._webp", ["src/_webp.c"]),
|
Extension("PIL._webp", ["src/_webp.c"], libraries=["pil_imaging_mode"]),
|
||||||
Extension("PIL._avif", ["src/_avif.c"]),
|
Extension("PIL._avif", ["src/_avif.c"]),
|
||||||
Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]),
|
Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]),
|
||||||
Extension("PIL._imagingmath", ["src/_imagingmath.c"]),
|
Extension("PIL._imagingmath", ["src/_imagingmath.c"]),
|
||||||
|
|
@ -1093,6 +1101,7 @@ try:
|
||||||
setup(
|
setup(
|
||||||
cmdclass={"build_ext": pil_build_ext},
|
cmdclass={"build_ext": pil_build_ext},
|
||||||
ext_modules=ext_modules,
|
ext_modules=ext_modules,
|
||||||
|
libraries=libraries,
|
||||||
zip_safe=not (debug_build() or PLATFORM_MINGW),
|
zip_safe=not (debug_build() or PLATFORM_MINGW),
|
||||||
)
|
)
|
||||||
except RequiredDependencyException as err:
|
except RequiredDependencyException as err:
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
#
|
#
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from . import BmpImagePlugin, Image, ImageFile
|
from . import BmpImagePlugin, Image
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
|
|
||||||
|
|
@ -38,6 +38,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
format_description = "Windows Cursor"
|
format_description = "Windows Cursor"
|
||||||
|
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
|
assert self.fp is not None
|
||||||
offset = self.fp.tell()
|
offset = self.fp.tell()
|
||||||
|
|
||||||
# check magic
|
# check magic
|
||||||
|
|
@ -63,8 +64,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
|
|
||||||
# patch up the bitmap height
|
# patch up the bitmap height
|
||||||
self._size = self.size[0], self.size[1] // 2
|
self._size = self.size[0], self.size[1] // 2
|
||||||
d, e, o, a = self.tile[0]
|
self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)]
|
||||||
self.tile[0] = ImageFile._Tile(d, (0, 0) + self.size, o, a)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,9 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
read_comment(s)
|
read_comment(s)
|
||||||
elif bytes_mv[:9] == b"%%Trailer":
|
elif bytes_mv[:9] == b"%%Trailer":
|
||||||
trailer_reached = True
|
trailer_reached = True
|
||||||
|
elif bytes_mv[:14] == b"%%BeginBinary:":
|
||||||
|
bytecount = int(byte_arr[14:bytes_read])
|
||||||
|
self.fp.seek(bytecount, os.SEEK_CUR)
|
||||||
bytes_read = 0
|
bytes_read = 0
|
||||||
|
|
||||||
# A "BoundingBox" is always required,
|
# A "BoundingBox" is always required,
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,14 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
# HEAD
|
# HEAD
|
||||||
|
assert self.fp is not None
|
||||||
s = self.fp.read(128)
|
s = self.fp.read(128)
|
||||||
if not (_accept(s) and s[20:22] == b"\x00\x00"):
|
if not (
|
||||||
|
_accept(s)
|
||||||
|
and s[20:22] == b"\x00" * 2
|
||||||
|
and s[42:80] == b"\x00" * 38
|
||||||
|
and s[88:] == b"\x00" * 40
|
||||||
|
):
|
||||||
msg = "not an FLI/FLC file"
|
msg = "not an FLI/FLC file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
|
|
@ -77,8 +83,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
if i16(s, 4) == 0xF100:
|
if i16(s, 4) == 0xF100:
|
||||||
# prefix chunk; ignore it
|
# prefix chunk; ignore it
|
||||||
self.__offset = self.__offset + i32(s)
|
self.fp.seek(self.__offset + i32(s))
|
||||||
self.fp.seek(self.__offset)
|
|
||||||
s = self.fp.read(16)
|
s = self.fp.read(16)
|
||||||
|
|
||||||
if i16(s, 4) == 0xF1FA:
|
if i16(s, 4) == 0xF1FA:
|
||||||
|
|
@ -111,6 +116,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
# load palette
|
# load palette
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
|
assert self.fp is not None
|
||||||
for e in range(i16(self.fp.read(2))):
|
for e in range(i16(self.fp.read(2))):
|
||||||
s = self.fp.read(2)
|
s = self.fp.read(2)
|
||||||
i = i + s[0]
|
i = i + s[0]
|
||||||
|
|
|
||||||
|
|
@ -1009,8 +1009,14 @@ class Image:
|
||||||
new_im.info["transparency"] = transparency
|
new_im.info["transparency"] = transparency
|
||||||
return new_im
|
return new_im
|
||||||
|
|
||||||
if mode == "P" and self.mode == "RGBA":
|
if self.mode == "RGBA":
|
||||||
return self.quantize(colors)
|
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
|
trns = None
|
||||||
delete_trns = False
|
delete_trns = False
|
||||||
|
|
@ -1142,7 +1148,7 @@ class Image:
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
|
|
||||||
new_im = self._new(im)
|
new_im = self._new(im)
|
||||||
if mode == "P" and palette != Palette.ADAPTIVE:
|
if mode in ("P", "PA") and palette != Palette.ADAPTIVE:
|
||||||
from . import ImagePalette
|
from . import ImagePalette
|
||||||
|
|
||||||
new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB"))
|
new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB"))
|
||||||
|
|
@ -1336,12 +1342,6 @@ class Image:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _expand(self, xmargin: int, ymargin: int | None = None) -> Image:
|
|
||||||
if ymargin is None:
|
|
||||||
ymargin = xmargin
|
|
||||||
self.load()
|
|
||||||
return self._new(self.im.expand(xmargin, ymargin))
|
|
||||||
|
|
||||||
def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image:
|
def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image:
|
||||||
"""
|
"""
|
||||||
Filters this image using the given filter. For a list of
|
Filters this image using the given filter. For a list of
|
||||||
|
|
@ -2070,9 +2070,7 @@ class Image:
|
||||||
:param value: The pixel value.
|
:param value: The pixel value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.readonly:
|
self._ensure_mutable()
|
||||||
self._copy()
|
|
||||||
self.load()
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.mode in ("P", "PA")
|
self.mode in ("P", "PA")
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import struct
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from . import Image, ImageColor
|
from . import Image, ImageColor, ImageText
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -45,13 +45,11 @@ if TYPE_CHECKING:
|
||||||
from typing import Any, AnyStr
|
from typing import Any, AnyStr
|
||||||
|
|
||||||
from . import ImageDraw2, ImageFont
|
from . import ImageDraw2, ImageFont
|
||||||
from ._typing import Coords
|
from ._typing import Coords, _Ink
|
||||||
|
|
||||||
# experimental access to the outline API
|
# experimental access to the outline API
|
||||||
Outline: Callable[[], Image.core._Outline] = Image.core.outline
|
Outline: Callable[[], Image.core._Outline] = Image.core.outline
|
||||||
|
|
||||||
_Ink = float | tuple[int, ...] | str
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A simple 2D drawing interface for PIL images.
|
A simple 2D drawing interface for PIL images.
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -76,9 +74,7 @@ class ImageDraw:
|
||||||
must be the same as the image mode. If omitted, the mode
|
must be the same as the image mode. If omitted, the mode
|
||||||
defaults to the mode of the image.
|
defaults to the mode of the image.
|
||||||
"""
|
"""
|
||||||
im.load()
|
im._ensure_mutable()
|
||||||
if im.readonly:
|
|
||||||
im._copy() # make it writeable
|
|
||||||
blend = 0
|
blend = 0
|
||||||
if mode is None:
|
if mode is None:
|
||||||
mode = im.mode
|
mode = im.mode
|
||||||
|
|
@ -539,15 +535,10 @@ class ImageDraw:
|
||||||
right[3] -= r + 1
|
right[3] -= r + 1
|
||||||
self.draw.draw_rectangle(right, ink, 1)
|
self.draw.draw_rectangle(right, ink, 1)
|
||||||
|
|
||||||
def _multiline_check(self, text: AnyStr) -> bool:
|
|
||||||
split_character = "\n" if isinstance(text, str) else b"\n"
|
|
||||||
|
|
||||||
return split_character in text
|
|
||||||
|
|
||||||
def text(
|
def text(
|
||||||
self,
|
self,
|
||||||
xy: tuple[float, float],
|
xy: tuple[float, float],
|
||||||
text: AnyStr,
|
text: AnyStr | ImageText.Text,
|
||||||
fill: _Ink | None = None,
|
fill: _Ink | None = None,
|
||||||
font: (
|
font: (
|
||||||
ImageFont.ImageFont
|
ImageFont.ImageFont
|
||||||
|
|
@ -568,29 +559,18 @@ class ImageDraw:
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw text."""
|
"""Draw text."""
|
||||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
if isinstance(text, ImageText.Text):
|
||||||
msg = "Embedded color supported only in RGB and RGBA modes"
|
image_text = text
|
||||||
raise ValueError(msg)
|
else:
|
||||||
|
if font is None:
|
||||||
if font is None:
|
font = self._getfont(kwargs.get("font_size"))
|
||||||
font = self._getfont(kwargs.get("font_size"))
|
image_text = ImageText.Text(
|
||||||
|
text, font, self.mode, spacing, direction, features, language
|
||||||
if self._multiline_check(text):
|
|
||||||
return self.multiline_text(
|
|
||||||
xy,
|
|
||||||
text,
|
|
||||||
fill,
|
|
||||||
font,
|
|
||||||
anchor,
|
|
||||||
spacing,
|
|
||||||
align,
|
|
||||||
direction,
|
|
||||||
features,
|
|
||||||
language,
|
|
||||||
stroke_width,
|
|
||||||
stroke_fill,
|
|
||||||
embedded_color,
|
|
||||||
)
|
)
|
||||||
|
if embedded_color:
|
||||||
|
image_text.embed_color()
|
||||||
|
if stroke_width:
|
||||||
|
image_text.stroke(stroke_width, stroke_fill)
|
||||||
|
|
||||||
def getink(fill: _Ink | None) -> int:
|
def getink(fill: _Ink | None) -> int:
|
||||||
ink, fill_ink = self._getink(fill)
|
ink, fill_ink = self._getink(fill)
|
||||||
|
|
@ -599,70 +579,79 @@ class ImageDraw:
|
||||||
return fill_ink
|
return fill_ink
|
||||||
return ink
|
return ink
|
||||||
|
|
||||||
def draw_text(ink: int, stroke_width: float = 0) -> None:
|
ink = getink(fill)
|
||||||
mode = self.fontmode
|
if ink is None:
|
||||||
if stroke_width == 0 and embedded_color:
|
return
|
||||||
mode = "RGBA"
|
|
||||||
coord = []
|
stroke_ink = None
|
||||||
for i in range(2):
|
if image_text.stroke_width:
|
||||||
coord.append(int(xy[i]))
|
stroke_ink = (
|
||||||
start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
|
getink(image_text.stroke_fill)
|
||||||
try:
|
if image_text.stroke_fill is not None
|
||||||
mask, offset = font.getmask2( # type: ignore[union-attr,misc]
|
else ink
|
||||||
text,
|
)
|
||||||
mode,
|
|
||||||
direction=direction,
|
for xy, anchor, line in image_text._split(xy, anchor, align):
|
||||||
features=features,
|
|
||||||
language=language,
|
def draw_text(ink: int, stroke_width: float = 0) -> None:
|
||||||
stroke_width=stroke_width,
|
mode = self.fontmode
|
||||||
stroke_filled=True,
|
if stroke_width == 0 and embedded_color:
|
||||||
anchor=anchor,
|
mode = "RGBA"
|
||||||
ink=ink,
|
coord = []
|
||||||
start=start,
|
for i in range(2):
|
||||||
*args,
|
coord.append(int(xy[i]))
|
||||||
**kwargs,
|
start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
|
||||||
)
|
|
||||||
coord = [coord[0] + offset[0], coord[1] + offset[1]]
|
|
||||||
except AttributeError:
|
|
||||||
try:
|
try:
|
||||||
mask = font.getmask( # type: ignore[misc]
|
mask, offset = image_text.font.getmask2( # type: ignore[union-attr,misc]
|
||||||
text,
|
line,
|
||||||
mode,
|
mode,
|
||||||
direction,
|
direction=direction,
|
||||||
features,
|
features=features,
|
||||||
language,
|
language=language,
|
||||||
stroke_width,
|
stroke_width=stroke_width,
|
||||||
anchor,
|
stroke_filled=True,
|
||||||
ink,
|
anchor=anchor,
|
||||||
|
ink=ink,
|
||||||
start=start,
|
start=start,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
except TypeError:
|
coord = [coord[0] + offset[0], coord[1] + offset[1]]
|
||||||
mask = font.getmask(text)
|
except AttributeError:
|
||||||
if mode == "RGBA":
|
try:
|
||||||
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
|
mask = image_text.font.getmask( # type: ignore[misc]
|
||||||
# extract mask and set text alpha
|
line,
|
||||||
color, mask = mask, mask.getband(3)
|
mode,
|
||||||
ink_alpha = struct.pack("i", ink)[3]
|
direction,
|
||||||
color.fillband(3, ink_alpha)
|
features,
|
||||||
x, y = coord
|
language,
|
||||||
if self.im is not None:
|
stroke_width,
|
||||||
self.im.paste(
|
anchor,
|
||||||
color, (x, y, x + mask.size[0], y + mask.size[1]), mask
|
ink,
|
||||||
)
|
start=start,
|
||||||
else:
|
*args,
|
||||||
self.draw.draw_bitmap(coord, mask, ink)
|
**kwargs,
|
||||||
|
)
|
||||||
ink = getink(fill)
|
except TypeError:
|
||||||
if ink is not None:
|
mask = image_text.font.getmask(line)
|
||||||
stroke_ink = None
|
if mode == "RGBA":
|
||||||
if stroke_width:
|
# image_text.font.getmask2(mode="RGBA")
|
||||||
stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink
|
# returns color in RGB bands and mask in A
|
||||||
|
# extract mask and set text alpha
|
||||||
|
color, mask = mask, mask.getband(3)
|
||||||
|
ink_alpha = struct.pack("i", ink)[3]
|
||||||
|
color.fillband(3, ink_alpha)
|
||||||
|
x, y = coord
|
||||||
|
if self.im is not None:
|
||||||
|
self.im.paste(
|
||||||
|
color, (x, y, x + mask.size[0], y + mask.size[1]), mask
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.draw.draw_bitmap(coord, mask, ink)
|
||||||
|
|
||||||
if stroke_ink is not None:
|
if stroke_ink is not None:
|
||||||
# Draw stroked text
|
# Draw stroked text
|
||||||
draw_text(stroke_ink, stroke_width)
|
draw_text(stroke_ink, image_text.stroke_width)
|
||||||
|
|
||||||
# Draw normal text
|
# Draw normal text
|
||||||
if ink != stroke_ink:
|
if ink != stroke_ink:
|
||||||
|
|
@ -671,132 +660,6 @@ class ImageDraw:
|
||||||
# Only draw normal text
|
# Only draw normal text
|
||||||
draw_text(ink)
|
draw_text(ink)
|
||||||
|
|
||||||
def _prepare_multiline_text(
|
|
||||||
self,
|
|
||||||
xy: tuple[float, float],
|
|
||||||
text: AnyStr,
|
|
||||||
font: (
|
|
||||||
ImageFont.ImageFont
|
|
||||||
| ImageFont.FreeTypeFont
|
|
||||||
| ImageFont.TransposedFont
|
|
||||||
| None
|
|
||||||
),
|
|
||||||
anchor: str | None,
|
|
||||||
spacing: float,
|
|
||||||
align: str,
|
|
||||||
direction: str | None,
|
|
||||||
features: list[str] | None,
|
|
||||||
language: str | None,
|
|
||||||
stroke_width: float,
|
|
||||||
embedded_color: bool,
|
|
||||||
font_size: float | None,
|
|
||||||
) -> tuple[
|
|
||||||
ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
|
|
||||||
list[tuple[tuple[float, float], str, AnyStr]],
|
|
||||||
]:
|
|
||||||
if anchor is None:
|
|
||||||
anchor = "lt" if direction == "ttb" else "la"
|
|
||||||
elif len(anchor) != 2:
|
|
||||||
msg = "anchor must be a 2 character string"
|
|
||||||
raise ValueError(msg)
|
|
||||||
elif anchor[1] in "tb" and direction != "ttb":
|
|
||||||
msg = "anchor not supported for multiline text"
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
if font is None:
|
|
||||||
font = self._getfont(font_size)
|
|
||||||
|
|
||||||
lines = text.split("\n" if isinstance(text, str) else b"\n")
|
|
||||||
line_spacing = (
|
|
||||||
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
|
|
||||||
+ stroke_width
|
|
||||||
+ spacing
|
|
||||||
)
|
|
||||||
|
|
||||||
top = xy[1]
|
|
||||||
parts = []
|
|
||||||
if direction == "ttb":
|
|
||||||
left = xy[0]
|
|
||||||
for line in lines:
|
|
||||||
parts.append(((left, top), anchor, line))
|
|
||||||
left += line_spacing
|
|
||||||
else:
|
|
||||||
widths = []
|
|
||||||
max_width: float = 0
|
|
||||||
for line in lines:
|
|
||||||
line_width = self.textlength(
|
|
||||||
line,
|
|
||||||
font,
|
|
||||||
direction=direction,
|
|
||||||
features=features,
|
|
||||||
language=language,
|
|
||||||
embedded_color=embedded_color,
|
|
||||||
)
|
|
||||||
widths.append(line_width)
|
|
||||||
max_width = max(max_width, line_width)
|
|
||||||
|
|
||||||
if anchor[1] == "m":
|
|
||||||
top -= (len(lines) - 1) * line_spacing / 2.0
|
|
||||||
elif anchor[1] == "d":
|
|
||||||
top -= (len(lines) - 1) * line_spacing
|
|
||||||
|
|
||||||
for idx, line in enumerate(lines):
|
|
||||||
left = xy[0]
|
|
||||||
width_difference = max_width - widths[idx]
|
|
||||||
|
|
||||||
# align by align parameter
|
|
||||||
if align in ("left", "justify"):
|
|
||||||
pass
|
|
||||||
elif align == "center":
|
|
||||||
left += width_difference / 2.0
|
|
||||||
elif align == "right":
|
|
||||||
left += width_difference
|
|
||||||
else:
|
|
||||||
msg = 'align must be "left", "center", "right" or "justify"'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
if (
|
|
||||||
align == "justify"
|
|
||||||
and width_difference != 0
|
|
||||||
and idx != len(lines) - 1
|
|
||||||
):
|
|
||||||
words = line.split(" " if isinstance(text, str) else b" ")
|
|
||||||
if len(words) > 1:
|
|
||||||
# align left by anchor
|
|
||||||
if anchor[0] == "m":
|
|
||||||
left -= max_width / 2.0
|
|
||||||
elif anchor[0] == "r":
|
|
||||||
left -= max_width
|
|
||||||
|
|
||||||
word_widths = [
|
|
||||||
self.textlength(
|
|
||||||
word,
|
|
||||||
font,
|
|
||||||
direction=direction,
|
|
||||||
features=features,
|
|
||||||
language=language,
|
|
||||||
embedded_color=embedded_color,
|
|
||||||
)
|
|
||||||
for word in words
|
|
||||||
]
|
|
||||||
word_anchor = "l" + anchor[1]
|
|
||||||
width_difference = max_width - sum(word_widths)
|
|
||||||
for i, word in enumerate(words):
|
|
||||||
parts.append(((left, top), word_anchor, word))
|
|
||||||
left += word_widths[i] + width_difference / (len(words) - 1)
|
|
||||||
top += line_spacing
|
|
||||||
continue
|
|
||||||
|
|
||||||
# align left by anchor
|
|
||||||
if anchor[0] == "m":
|
|
||||||
left -= width_difference / 2.0
|
|
||||||
elif anchor[0] == "r":
|
|
||||||
left -= width_difference
|
|
||||||
parts.append(((left, top), anchor, line))
|
|
||||||
top += line_spacing
|
|
||||||
|
|
||||||
return font, parts
|
|
||||||
|
|
||||||
def multiline_text(
|
def multiline_text(
|
||||||
self,
|
self,
|
||||||
xy: tuple[float, float],
|
xy: tuple[float, float],
|
||||||
|
|
@ -820,9 +683,10 @@ class ImageDraw:
|
||||||
*,
|
*,
|
||||||
font_size: float | None = None,
|
font_size: float | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
font, lines = self._prepare_multiline_text(
|
return self.text(
|
||||||
xy,
|
xy,
|
||||||
text,
|
text,
|
||||||
|
fill,
|
||||||
font,
|
font,
|
||||||
anchor,
|
anchor,
|
||||||
spacing,
|
spacing,
|
||||||
|
|
@ -831,25 +695,11 @@ class ImageDraw:
|
||||||
features,
|
features,
|
||||||
language,
|
language,
|
||||||
stroke_width,
|
stroke_width,
|
||||||
|
stroke_fill,
|
||||||
embedded_color,
|
embedded_color,
|
||||||
font_size,
|
font_size=font_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
for xy, anchor, line in lines:
|
|
||||||
self.text(
|
|
||||||
xy,
|
|
||||||
line,
|
|
||||||
fill,
|
|
||||||
font,
|
|
||||||
anchor,
|
|
||||||
direction=direction,
|
|
||||||
features=features,
|
|
||||||
language=language,
|
|
||||||
stroke_width=stroke_width,
|
|
||||||
stroke_fill=stroke_fill,
|
|
||||||
embedded_color=embedded_color,
|
|
||||||
)
|
|
||||||
|
|
||||||
def textlength(
|
def textlength(
|
||||||
self,
|
self,
|
||||||
text: AnyStr,
|
text: AnyStr,
|
||||||
|
|
@ -867,17 +717,19 @@ class ImageDraw:
|
||||||
font_size: float | None = None,
|
font_size: float | None = None,
|
||||||
) -> float:
|
) -> float:
|
||||||
"""Get the length of a given string, in pixels with 1/64 precision."""
|
"""Get the length of a given string, in pixels with 1/64 precision."""
|
||||||
if self._multiline_check(text):
|
|
||||||
msg = "can't measure length of multiline text"
|
|
||||||
raise ValueError(msg)
|
|
||||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
|
||||||
msg = "Embedded color supported only in RGB and RGBA modes"
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self._getfont(font_size)
|
font = self._getfont(font_size)
|
||||||
mode = "RGBA" if embedded_color else self.fontmode
|
image_text = ImageText.Text(
|
||||||
return font.getlength(text, mode, direction, features, language)
|
text,
|
||||||
|
font,
|
||||||
|
self.mode,
|
||||||
|
direction=direction,
|
||||||
|
features=features,
|
||||||
|
language=language,
|
||||||
|
)
|
||||||
|
if embedded_color:
|
||||||
|
image_text.embed_color()
|
||||||
|
return image_text.get_length()
|
||||||
|
|
||||||
def textbbox(
|
def textbbox(
|
||||||
self,
|
self,
|
||||||
|
|
@ -901,33 +753,16 @@ class ImageDraw:
|
||||||
font_size: float | None = None,
|
font_size: float | None = None,
|
||||||
) -> tuple[float, float, float, float]:
|
) -> tuple[float, float, float, float]:
|
||||||
"""Get the bounding box of a given string, in pixels."""
|
"""Get the bounding box of a given string, in pixels."""
|
||||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
|
||||||
msg = "Embedded color supported only in RGB and RGBA modes"
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self._getfont(font_size)
|
font = self._getfont(font_size)
|
||||||
|
image_text = ImageText.Text(
|
||||||
if self._multiline_check(text):
|
text, font, self.mode, spacing, direction, features, language
|
||||||
return self.multiline_textbbox(
|
|
||||||
xy,
|
|
||||||
text,
|
|
||||||
font,
|
|
||||||
anchor,
|
|
||||||
spacing,
|
|
||||||
align,
|
|
||||||
direction,
|
|
||||||
features,
|
|
||||||
language,
|
|
||||||
stroke_width,
|
|
||||||
embedded_color,
|
|
||||||
)
|
|
||||||
|
|
||||||
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(
|
def multiline_textbbox(
|
||||||
self,
|
self,
|
||||||
|
|
@ -950,7 +785,7 @@ class ImageDraw:
|
||||||
*,
|
*,
|
||||||
font_size: float | None = None,
|
font_size: float | None = None,
|
||||||
) -> tuple[float, float, float, float]:
|
) -> tuple[float, float, float, float]:
|
||||||
font, lines = self._prepare_multiline_text(
|
return self.textbbox(
|
||||||
xy,
|
xy,
|
||||||
text,
|
text,
|
||||||
font,
|
font,
|
||||||
|
|
@ -962,37 +797,9 @@ class ImageDraw:
|
||||||
language,
|
language,
|
||||||
stroke_width,
|
stroke_width,
|
||||||
embedded_color,
|
embedded_color,
|
||||||
font_size,
|
font_size=font_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
bbox: tuple[float, float, float, float] | None = None
|
|
||||||
|
|
||||||
for xy, anchor, line in lines:
|
|
||||||
bbox_line = self.textbbox(
|
|
||||||
xy,
|
|
||||||
line,
|
|
||||||
font,
|
|
||||||
anchor,
|
|
||||||
direction=direction,
|
|
||||||
features=features,
|
|
||||||
language=language,
|
|
||||||
stroke_width=stroke_width,
|
|
||||||
embedded_color=embedded_color,
|
|
||||||
)
|
|
||||||
if bbox is None:
|
|
||||||
bbox = bbox_line
|
|
||||||
else:
|
|
||||||
bbox = (
|
|
||||||
min(bbox[0], bbox_line[0]),
|
|
||||||
min(bbox[1], bbox_line[1]),
|
|
||||||
max(bbox[2], bbox_line[2]),
|
|
||||||
max(bbox[3], bbox_line[3]),
|
|
||||||
)
|
|
||||||
|
|
||||||
if bbox is None:
|
|
||||||
return xy[0], xy[1], xy[0], xy[1]
|
|
||||||
return bbox
|
|
||||||
|
|
||||||
|
|
||||||
def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
|
def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,9 @@ class ImageFile(Image.Image):
|
||||||
and args[0] == self.mode
|
and args[0] == self.mode
|
||||||
and args[0] in Image._MAPMODES
|
and args[0] in Image._MAPMODES
|
||||||
):
|
):
|
||||||
|
if offset < 0:
|
||||||
|
msg = "Tile offset cannot be negative"
|
||||||
|
raise ValueError(msg)
|
||||||
try:
|
try:
|
||||||
# use mmap, if possible
|
# use mmap, if possible
|
||||||
import mmap
|
import mmap
|
||||||
|
|
|
||||||
|
|
@ -125,11 +125,16 @@ class ImageFont:
|
||||||
image.close()
|
image.close()
|
||||||
|
|
||||||
def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
|
def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
|
||||||
|
# check image
|
||||||
|
if image.mode not in ("1", "L"):
|
||||||
|
msg = "invalid font image mode"
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# read PILfont header
|
# read PILfont header
|
||||||
if file.readline() != b"PILfont\n":
|
if file.read(8) != b"PILfont\n":
|
||||||
msg = "Not a PILfont file"
|
msg = "Not a PILfont file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
file.readline().split(b";")
|
file.readline()
|
||||||
self.info = [] # FIXME: should be a dictionary
|
self.info = [] # FIXME: should be a dictionary
|
||||||
while True:
|
while True:
|
||||||
s = file.readline()
|
s = file.readline()
|
||||||
|
|
@ -140,11 +145,6 @@ class ImageFont:
|
||||||
# read PILfont metrics
|
# read PILfont metrics
|
||||||
data = file.read(256 * 20)
|
data = file.read(256 * 20)
|
||||||
|
|
||||||
# check image
|
|
||||||
if image.mode not in ("1", "L"):
|
|
||||||
msg = "invalid font image mode"
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
image.load()
|
image.load()
|
||||||
|
|
||||||
self.font = Image.core.font(image.im, data)
|
self.font = Image.core.font(image.im, data)
|
||||||
|
|
|
||||||
|
|
@ -499,14 +499,15 @@ def expand(
|
||||||
height = top + image.size[1] + bottom
|
height = top + image.size[1] + bottom
|
||||||
color = _color(fill, image.mode)
|
color = _color(fill, image.mode)
|
||||||
if image.palette:
|
if image.palette:
|
||||||
palette = ImagePalette.ImagePalette(palette=image.getpalette())
|
mode = image.palette.mode
|
||||||
|
palette = ImagePalette.ImagePalette(mode, image.getpalette(mode))
|
||||||
if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4):
|
if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4):
|
||||||
color = palette.getcolor(color)
|
color = palette.getcolor(color)
|
||||||
else:
|
else:
|
||||||
palette = None
|
palette = None
|
||||||
out = Image.new(image.mode, (width, height), color)
|
out = Image.new(image.mode, (width, height), color)
|
||||||
if palette:
|
if palette:
|
||||||
out.putpalette(palette.palette)
|
out.putpalette(palette.palette, mode)
|
||||||
out.paste(image, (left, top))
|
out.paste(image, (left, top))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
|
||||||
318
src/PIL/ImageText.py
Normal file
|
|
@ -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
|
||||||
|
|
@ -193,6 +193,8 @@ def SOF(self: JpegImageFile, marker: int) -> None:
|
||||||
n = i16(self.fp.read(2)) - 2
|
n = i16(self.fp.read(2)) - 2
|
||||||
s = ImageFile._safe_read(self.fp, n)
|
s = ImageFile._safe_read(self.fp, n)
|
||||||
self._size = i16(s, 3), i16(s, 1)
|
self._size = i16(s, 3), i16(s, 1)
|
||||||
|
if self._im is not None and self.size != self.im.size:
|
||||||
|
self._im = None
|
||||||
|
|
||||||
self.bits = s[0]
|
self.bits = s[0]
|
||||||
if self.bits != 8:
|
if self.bits != 8:
|
||||||
|
|
|
||||||
|
|
@ -43,16 +43,21 @@ class PcdImageFile(ImageFile.ImageFile):
|
||||||
if orientation == 1:
|
if orientation == 1:
|
||||||
self.tile_post_rotate = 90
|
self.tile_post_rotate = 90
|
||||||
elif orientation == 3:
|
elif orientation == 3:
|
||||||
self.tile_post_rotate = -90
|
self.tile_post_rotate = 270
|
||||||
|
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
self._size = (512, 768) if orientation in (1, 3) else (768, 512)
|
self._size = (512, 768) if orientation in (1, 3) else (768, 512)
|
||||||
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
|
self.tile = [ImageFile._Tile("pcd", (0, 0, 768, 512), 96 * 2048)]
|
||||||
|
|
||||||
|
def load_prepare(self) -> None:
|
||||||
|
if self._im is None and self.tile_post_rotate:
|
||||||
|
self.im = Image.core.new(self.mode, (768, 512))
|
||||||
|
ImageFile.ImageFile.load_prepare(self)
|
||||||
|
|
||||||
def load_end(self) -> None:
|
def load_end(self) -> None:
|
||||||
if self.tile_post_rotate:
|
if self.tile_post_rotate:
|
||||||
# Handle rotated PCDs
|
# Handle rotated PCDs
|
||||||
self.im = self.im.rotate(self.tile_post_rotate)
|
self.im = self.rotate(self.tile_post_rotate, expand=True).im
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import os
|
||||||
import time
|
import time
|
||||||
from typing import IO, Any
|
from typing import IO, Any
|
||||||
|
|
||||||
from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features
|
from . import Image, ImageFile, ImageSequence, PdfParser, features
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
@ -221,7 +221,7 @@ def _save(
|
||||||
|
|
||||||
existing_pdf.start_writing()
|
existing_pdf.start_writing()
|
||||||
existing_pdf.write_header()
|
existing_pdf.write_header()
|
||||||
existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver")
|
existing_pdf.write_comment("created by Pillow PDF driver")
|
||||||
|
|
||||||
#
|
#
|
||||||
# pages
|
# pages
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,7 @@ OPEN_INFO = {
|
||||||
(II, 3, (1,), 1, (8,), ()): ("P", "P"),
|
(II, 3, (1,), 1, (8,), ()): ("P", "P"),
|
||||||
(MM, 3, (1,), 1, (8,), ()): ("P", "P"),
|
(MM, 3, (1,), 1, (8,), ()): ("P", "P"),
|
||||||
(II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"),
|
(II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"),
|
||||||
|
(MM, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"),
|
||||||
(II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
|
(II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
|
||||||
(MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
|
(MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
|
||||||
(II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
|
(II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
|
||||||
|
|
@ -1177,6 +1178,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
"""Open the first image in a TIFF file"""
|
"""Open the first image in a TIFF file"""
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
|
assert self.fp is not None
|
||||||
ifh = self.fp.read(8)
|
ifh = self.fp.read(8)
|
||||||
if ifh[2] == 43:
|
if ifh[2] == 43:
|
||||||
ifh += self.fp.read(8)
|
ifh += self.fp.read(8)
|
||||||
|
|
@ -1343,6 +1345,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# To be nice on memory footprint, if there's a
|
# To be nice on memory footprint, if there's a
|
||||||
# file descriptor, use that instead of reading
|
# file descriptor, use that instead of reading
|
||||||
# into a string in python.
|
# into a string in python.
|
||||||
|
assert self.fp is not None
|
||||||
try:
|
try:
|
||||||
fp = hasattr(self.fp, "fileno") and self.fp.fileno()
|
fp = hasattr(self.fp, "fileno") and self.fp.fileno()
|
||||||
# flush the file descriptor, prevents error on pypy 2.4+
|
# flush the file descriptor, prevents error on pypy 2.4+
|
||||||
|
|
@ -1936,9 +1939,10 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
types[tag] = TiffTags.LONG8
|
types[tag] = TiffTags.LONG8
|
||||||
elif tag in ifd.tagtype:
|
elif tag in ifd.tagtype:
|
||||||
types[tag] = ifd.tagtype[tag]
|
types[tag] = ifd.tagtype[tag]
|
||||||
elif not (isinstance(value, (int, float, str, bytes))):
|
elif isinstance(value, (int, float, str, bytes)) or (
|
||||||
continue
|
isinstance(value, tuple)
|
||||||
else:
|
and all(isinstance(v, (int, float, IFDRational)) for v in value)
|
||||||
|
):
|
||||||
type = TiffTags.lookup(tag).type
|
type = TiffTags.lookup(tag).type
|
||||||
if type:
|
if type:
|
||||||
types[tag] = type
|
types[tag] = type
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,11 @@ _tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]]
|
||||||
531: ("YCbCrPositioning", SHORT, 1),
|
531: ("YCbCrPositioning", SHORT, 1),
|
||||||
532: ("ReferenceBlackWhite", RATIONAL, 6),
|
532: ("ReferenceBlackWhite", RATIONAL, 6),
|
||||||
700: ("XMP", BYTE, 0),
|
700: ("XMP", BYTE, 0),
|
||||||
|
# Four private SGI tags
|
||||||
|
32995: ("Matteing", SHORT, 1),
|
||||||
|
32996: ("DataType", SHORT, 0),
|
||||||
|
32997: ("ImageDepth", LONG, 1),
|
||||||
|
32998: ("TileDepth", LONG, 1),
|
||||||
33432: ("Copyright", ASCII, 1),
|
33432: ("Copyright", ASCII, 1),
|
||||||
33723: ("IptcNaaInfo", UNDEFINED, 1),
|
33723: ("IptcNaaInfo", UNDEFINED, 1),
|
||||||
34377: ("PhotoshopInfo", BYTE, 0),
|
34377: ("PhotoshopInfo", BYTE, 0),
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ else:
|
||||||
Buffer = Any
|
Buffer = Any
|
||||||
|
|
||||||
|
|
||||||
|
_Ink = float | tuple[int, ...] | str
|
||||||
|
|
||||||
Coords = Sequence[float] | Sequence[Sequence[float]]
|
Coords = Sequence[float] | Sequence[Sequence[float]]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,15 +121,16 @@ PyImagingPhotoPut(
|
||||||
|
|
||||||
/* Mode */
|
/* Mode */
|
||||||
|
|
||||||
if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
|
if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
|
||||||
block.pixelSize = 1;
|
block.pixelSize = 1;
|
||||||
block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0;
|
block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0;
|
||||||
} else if (strncmp(im->mode, "RGB", 3) == 0) {
|
} else if (im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA ||
|
||||||
|
im->mode == IMAGING_MODE_RGBX || im->mode == IMAGING_MODE_RGBa) {
|
||||||
block.pixelSize = 4;
|
block.pixelSize = 4;
|
||||||
block.offset[0] = 0;
|
block.offset[0] = 0;
|
||||||
block.offset[1] = 1;
|
block.offset[1] = 1;
|
||||||
block.offset[2] = 2;
|
block.offset[2] = 2;
|
||||||
if (strcmp(im->mode, "RGBA") == 0) {
|
if (im->mode == IMAGING_MODE_RGBA) {
|
||||||
block.offset[3] = 3; /* alpha (or reserved, under Tk 8.2) */
|
block.offset[3] = 3; /* alpha (or reserved, under Tk 8.2) */
|
||||||
} else {
|
} else {
|
||||||
block.offset[3] = 0; /* no alpha */
|
block.offset[3] = 0; /* no alpha */
|
||||||
|
|
|
||||||
172
src/_imaging.c
|
|
@ -297,6 +297,7 @@ ExportArrowArrayPyCapsule(ImagingObject *self) {
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_new_arrow(PyObject *self, PyObject *args) {
|
_new_arrow(PyObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode;
|
||||||
|
ModeID mode_id;
|
||||||
int xsize, ysize;
|
int xsize, ysize;
|
||||||
PyObject *schema_capsule, *array_capsule;
|
PyObject *schema_capsule, *array_capsule;
|
||||||
PyObject *ret;
|
PyObject *ret;
|
||||||
|
|
@ -307,9 +308,11 @@ _new_arrow(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mode_id = findModeID(mode);
|
||||||
|
|
||||||
// ImagingBorrowArrow is responsible for retaining the array_capsule
|
// ImagingBorrowArrow is responsible for retaining the array_capsule
|
||||||
ret = PyImagingNew(
|
ret = PyImagingNew(
|
||||||
ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule)
|
ImagingNewArrow(mode_id, xsize, ysize, schema_capsule, array_capsule)
|
||||||
);
|
);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
|
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
|
||||||
|
|
@ -368,7 +371,7 @@ ImagingError_ValueError(const char *message) {
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
getbands(const char *mode) {
|
getbands(const ModeID mode) {
|
||||||
Imaging im;
|
Imaging im;
|
||||||
int bands;
|
int bands;
|
||||||
|
|
||||||
|
|
@ -662,7 +665,7 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
memcpy(ink, &ftmp, sizeof(ftmp));
|
memcpy(ink, &ftmp, sizeof(ftmp));
|
||||||
return ink;
|
return ink;
|
||||||
case IMAGING_TYPE_SPECIAL:
|
case IMAGING_TYPE_SPECIAL:
|
||||||
if (strncmp(im->mode, "I;16", 4) == 0) {
|
if (isModeI16(im->mode)) {
|
||||||
ink[0] = (UINT8)r;
|
ink[0] = (UINT8)r;
|
||||||
ink[1] = (UINT8)(r >> 8);
|
ink[1] = (UINT8)(r >> 8);
|
||||||
ink[2] = ink[3] = 0;
|
ink[2] = ink[3] = 0;
|
||||||
|
|
@ -694,7 +697,7 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_fill(PyObject *self, PyObject *args) {
|
_fill(PyObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
int xsize, ysize;
|
int xsize, ysize;
|
||||||
PyObject *color;
|
PyObject *color;
|
||||||
char buffer[4];
|
char buffer[4];
|
||||||
|
|
@ -703,10 +706,12 @@ _fill(PyObject *self, PyObject *args) {
|
||||||
xsize = ysize = 256;
|
xsize = ysize = 256;
|
||||||
color = NULL;
|
color = NULL;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "s|(ii)O", &mode, &xsize, &ysize, &color)) {
|
if (!PyArg_ParseTuple(args, "s|(ii)O", &mode_name, &xsize, &ysize, &color)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
im = ImagingNewDirty(mode, xsize, ysize);
|
im = ImagingNewDirty(mode, xsize, ysize);
|
||||||
if (!im) {
|
if (!im) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -727,47 +732,55 @@ _fill(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_new(PyObject *self, PyObject *args) {
|
_new(PyObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
int xsize, ysize;
|
int xsize, ysize;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
|
if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
return PyImagingNew(ImagingNew(mode, xsize, ysize));
|
return PyImagingNew(ImagingNew(mode, xsize, ysize));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_new_block(PyObject *self, PyObject *args) {
|
_new_block(PyObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
int xsize, ysize;
|
int xsize, ysize;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
|
if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
return PyImagingNew(ImagingNewBlock(mode, xsize, ysize));
|
return PyImagingNew(ImagingNewBlock(mode, xsize, ysize));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_linear_gradient(PyObject *self, PyObject *args) {
|
_linear_gradient(PyObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "s", &mode)) {
|
if (!PyArg_ParseTuple(args, "s", &mode_name)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
return PyImagingNew(ImagingFillLinearGradient(mode));
|
return PyImagingNew(ImagingFillLinearGradient(mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_radial_gradient(PyObject *self, PyObject *args) {
|
_radial_gradient(PyObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "s", &mode)) {
|
if (!PyArg_ParseTuple(args, "s", &mode_name)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
return PyImagingNew(ImagingFillRadialGradient(mode));
|
return PyImagingNew(ImagingFillRadialGradient(mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -907,7 +920,7 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_color_lut_3d(ImagingObject *self, PyObject *args) {
|
_color_lut_3d(ImagingObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
int filter;
|
int filter;
|
||||||
int table_channels;
|
int table_channels;
|
||||||
int size1D, size2D, size3D;
|
int size1D, size2D, size3D;
|
||||||
|
|
@ -919,7 +932,7 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"sii(iii)O:color_lut_3d",
|
"sii(iii)O:color_lut_3d",
|
||||||
&mode,
|
&mode_name,
|
||||||
&filter,
|
&filter,
|
||||||
&table_channels,
|
&table_channels,
|
||||||
&size1D,
|
&size1D,
|
||||||
|
|
@ -930,6 +943,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
/* actually, it is trilinear */
|
/* actually, it is trilinear */
|
||||||
if (filter != IMAGING_TRANSFORM_BILINEAR) {
|
if (filter != IMAGING_TRANSFORM_BILINEAR) {
|
||||||
PyErr_SetString(PyExc_ValueError, "Only LINEAR filter is supported.");
|
PyErr_SetString(PyExc_ValueError, "Only LINEAR filter is supported.");
|
||||||
|
|
@ -976,11 +991,11 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_convert(ImagingObject *self, PyObject *args) {
|
_convert(ImagingObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
int dither = 0;
|
int dither = 0;
|
||||||
ImagingObject *paletteimage = NULL;
|
ImagingObject *paletteimage = NULL;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "s|iO", &mode, &dither, &paletteimage)) {
|
if (!PyArg_ParseTuple(args, "s|iO", &mode_name, &dither, &paletteimage)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (paletteimage != NULL) {
|
if (paletteimage != NULL) {
|
||||||
|
|
@ -997,6 +1012,8 @@ _convert(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
return PyImagingNew(ImagingConvert(
|
return PyImagingNew(ImagingConvert(
|
||||||
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither
|
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither
|
||||||
));
|
));
|
||||||
|
|
@ -1021,14 +1038,14 @@ _convert2(ImagingObject *self, PyObject *args) {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_convert_matrix(ImagingObject *self, PyObject *args) {
|
_convert_matrix(ImagingObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
float m[12];
|
float m[12];
|
||||||
if (!PyArg_ParseTuple(args, "s(ffff)", &mode, m + 0, m + 1, m + 2, m + 3)) {
|
if (!PyArg_ParseTuple(args, "s(ffff)", &mode_name, m + 0, m + 1, m + 2, m + 3)) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"s(ffffffffffff)",
|
"s(ffffffffffff)",
|
||||||
&mode,
|
&mode_name,
|
||||||
m + 0,
|
m + 0,
|
||||||
m + 1,
|
m + 1,
|
||||||
m + 2,
|
m + 2,
|
||||||
|
|
@ -1046,18 +1063,22 @@ _convert_matrix(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
return PyImagingNew(ImagingConvertMatrix(self->image, mode, m));
|
return PyImagingNew(ImagingConvertMatrix(self->image, mode, m));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_convert_transparent(ImagingObject *self, PyObject *args) {
|
_convert_transparent(ImagingObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
int r, g, b;
|
int r, g, b;
|
||||||
if (PyArg_ParseTuple(args, "s(iii)", &mode, &r, &g, &b)) {
|
if (PyArg_ParseTuple(args, "s(iii)", &mode_name, &r, &g, &b)) {
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b));
|
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b));
|
||||||
}
|
}
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
if (PyArg_ParseTuple(args, "si", &mode, &r)) {
|
if (PyArg_ParseTuple(args, "si", &mode_name, &r)) {
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, 0, 0));
|
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, 0, 0));
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -1156,9 +1177,9 @@ _getpalette(ImagingObject *self, PyObject *args) {
|
||||||
int bits;
|
int bits;
|
||||||
ImagingShuffler pack;
|
ImagingShuffler pack;
|
||||||
|
|
||||||
char *mode = "RGB";
|
char *mode_name = "RGB";
|
||||||
char *rawmode = "RGB";
|
char *rawmode_name = "RGB";
|
||||||
if (!PyArg_ParseTuple(args, "|ss", &mode, &rawmode)) {
|
if (!PyArg_ParseTuple(args, "|ss", &mode_name, &rawmode_name)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1167,6 +1188,9 @@ _getpalette(ImagingObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
pack = ImagingFindPacker(mode, rawmode, &bits);
|
pack = ImagingFindPacker(mode, rawmode, &bits);
|
||||||
if (!pack) {
|
if (!pack) {
|
||||||
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
|
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
|
||||||
|
|
@ -1193,7 +1217,7 @@ _getpalettemode(ImagingObject *self) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PyUnicode_FromString(self->image->palette->mode);
|
return PyUnicode_FromString(getModeData(self->image->palette->mode)->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
|
|
@ -1474,12 +1498,14 @@ _point(ImagingObject *self, PyObject *args) {
|
||||||
Imaging im;
|
Imaging im;
|
||||||
|
|
||||||
PyObject *list;
|
PyObject *list;
|
||||||
char *mode;
|
char *mode_name;
|
||||||
if (!PyArg_ParseTuple(args, "Oz", &list, &mode)) {
|
if (!PyArg_ParseTuple(args, "Oz", &list, &mode_name)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode && !strcmp(mode, "F")) {
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
|
if (mode == IMAGING_MODE_F) {
|
||||||
FLOAT32 *data;
|
FLOAT32 *data;
|
||||||
|
|
||||||
/* map from 8-bit data to floating point */
|
/* map from 8-bit data to floating point */
|
||||||
|
|
@ -1490,8 +1516,7 @@ _point(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
im = ImagingPoint(self->image, mode, (void *)data);
|
im = ImagingPoint(self->image, mode, (void *)data);
|
||||||
free(data);
|
free(data);
|
||||||
|
} else if (self->image->mode == IMAGING_MODE_I && mode == IMAGING_MODE_L) {
|
||||||
} else if (!strcmp(self->image->mode, "I") && mode && !strcmp(mode, "L")) {
|
|
||||||
UINT8 *data;
|
UINT8 *data;
|
||||||
|
|
||||||
/* map from 16-bit subset of 32-bit data to 8-bit */
|
/* map from 16-bit subset of 32-bit data to 8-bit */
|
||||||
|
|
@ -1503,7 +1528,6 @@ _point(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
im = ImagingPoint(self->image, mode, (void *)data);
|
im = ImagingPoint(self->image, mode, (void *)data);
|
||||||
free(data);
|
free(data);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
INT32 *data;
|
INT32 *data;
|
||||||
UINT8 lut[1024];
|
UINT8 lut[1024];
|
||||||
|
|
@ -1524,7 +1548,7 @@ _point(ImagingObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode && !strcmp(mode, "I")) {
|
if (mode == IMAGING_MODE_I) {
|
||||||
im = ImagingPoint(self->image, mode, (void *)data);
|
im = ImagingPoint(self->image, mode, (void *)data);
|
||||||
} else if (mode && bands > 1) {
|
} else if (mode && bands > 1) {
|
||||||
for (i = 0; i < 256; i++) {
|
for (i = 0; i < 256; i++) {
|
||||||
|
|
@ -1630,9 +1654,9 @@ _putdata(ImagingObject *self, PyObject *args) {
|
||||||
if (image->type == IMAGING_TYPE_SPECIAL) {
|
if (image->type == IMAGING_TYPE_SPECIAL) {
|
||||||
// I;16*
|
// I;16*
|
||||||
if (
|
if (
|
||||||
strcmp(image->mode, "I;16B") == 0
|
image->mode == IMAGING_MODE_I_16B
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(image->mode, "I;16N") == 0
|
|| image->mode == IMAGING_MODE_I_16N
|
||||||
#endif
|
#endif
|
||||||
) {
|
) {
|
||||||
bigendian = 1;
|
bigendian = 1;
|
||||||
|
|
@ -1729,7 +1753,9 @@ _quantize(ImagingObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (!self->image->xsize || !self->image->ysize) {
|
if (!self->image->xsize || !self->image->ysize) {
|
||||||
/* no content; return an empty image */
|
/* no content; return an empty image */
|
||||||
return PyImagingNew(ImagingNew("P", self->image->xsize, self->image->ysize));
|
return PyImagingNew(
|
||||||
|
ImagingNew(IMAGING_MODE_P, self->image->xsize, self->image->ysize)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans));
|
return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans));
|
||||||
|
|
@ -1740,21 +1766,33 @@ _putpalette(ImagingObject *self, PyObject *args) {
|
||||||
ImagingShuffler unpack;
|
ImagingShuffler unpack;
|
||||||
int bits;
|
int bits;
|
||||||
|
|
||||||
char *palette_mode, *rawmode;
|
char *palette_mode_name, *rawmode_name;
|
||||||
UINT8 *palette;
|
UINT8 *palette;
|
||||||
Py_ssize_t palettesize;
|
Py_ssize_t palettesize;
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize
|
args, "ssy#", &palette_mode_name, &rawmode_name, &palette, &palettesize
|
||||||
)) {
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") &&
|
if (self->image->mode != IMAGING_MODE_L && self->image->mode != IMAGING_MODE_LA &&
|
||||||
strcmp(self->image->mode, "P") && strcmp(self->image->mode, "PA")) {
|
self->image->mode != IMAGING_MODE_P && self->image->mode != IMAGING_MODE_PA) {
|
||||||
PyErr_SetString(PyExc_ValueError, wrong_mode);
|
PyErr_SetString(PyExc_ValueError, wrong_mode);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID palette_mode = findModeID(palette_mode_name);
|
||||||
|
if (palette_mode == IMAGING_MODE_UNKNOWN) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, wrong_mode);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
if (rawmode == IMAGING_RAWMODE_UNKNOWN) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
unpack = ImagingFindUnpacker(palette_mode, rawmode, &bits);
|
unpack = ImagingFindUnpacker(palette_mode, rawmode, &bits);
|
||||||
if (!unpack) {
|
if (!unpack) {
|
||||||
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
|
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
|
||||||
|
|
@ -1768,7 +1806,13 @@ _putpalette(ImagingObject *self, PyObject *args) {
|
||||||
|
|
||||||
ImagingPaletteDelete(self->image->palette);
|
ImagingPaletteDelete(self->image->palette);
|
||||||
|
|
||||||
strcpy(self->image->mode, strlen(self->image->mode) == 2 ? "PA" : "P");
|
if (self->image->mode == IMAGING_MODE_LA) {
|
||||||
|
self->image->mode = IMAGING_MODE_PA;
|
||||||
|
} else if (self->image->mode == IMAGING_MODE_L) {
|
||||||
|
self->image->mode = IMAGING_MODE_P;
|
||||||
|
} else {
|
||||||
|
// The image already has a palette mode so we don't need to change it.
|
||||||
|
}
|
||||||
|
|
||||||
self->image->palette = ImagingPaletteNew(palette_mode);
|
self->image->palette = ImagingPaletteNew(palette_mode);
|
||||||
|
|
||||||
|
|
@ -1796,7 +1840,7 @@ _putpalettealpha(ImagingObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
strcpy(self->image->palette->mode, "RGBA");
|
self->image->palette->mode = IMAGING_MODE_RGBA;
|
||||||
self->image->palette->palette[index * 4 + 3] = (UINT8)alpha;
|
self->image->palette->palette[index * 4 + 3] = (UINT8)alpha;
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
|
@ -1821,7 +1865,7 @@ _putpalettealphas(ImagingObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
strcpy(self->image->palette->mode, "RGBA");
|
self->image->palette->mode = IMAGING_MODE_RGBA;
|
||||||
for (i = 0; i < length; i++) {
|
for (i = 0; i < length; i++) {
|
||||||
self->image->palette->palette[i * 4 + 3] = (UINT8)values[i];
|
self->image->palette->palette[i * 4 + 3] = (UINT8)values[i];
|
||||||
}
|
}
|
||||||
|
|
@ -1989,8 +2033,11 @@ _reduce(ImagingObject *self, PyObject *args) {
|
||||||
return PyImagingNew(imOut);
|
return PyImagingNew(imOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define IS_RGB(mode) \
|
static int
|
||||||
(!strcmp(mode, "RGB") || !strcmp(mode, "RGBA") || !strcmp(mode, "RGBX"))
|
isRGB(const ModeID mode) {
|
||||||
|
return mode == IMAGING_MODE_RGB || mode == IMAGING_MODE_RGBA ||
|
||||||
|
mode == IMAGING_MODE_RGBX;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
im_setmode(ImagingObject *self, PyObject *args) {
|
im_setmode(ImagingObject *self, PyObject *args) {
|
||||||
|
|
@ -1998,23 +2045,25 @@ im_setmode(ImagingObject *self, PyObject *args) {
|
||||||
|
|
||||||
Imaging im;
|
Imaging im;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
Py_ssize_t modelen;
|
Py_ssize_t modelen;
|
||||||
if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) {
|
if (!PyArg_ParseTuple(args, "s#:setmode", &mode_name, &modelen)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
im = self->image;
|
im = self->image;
|
||||||
|
|
||||||
/* move all logic in here to the libImaging primitive */
|
/* move all logic in here to the libImaging primitive */
|
||||||
|
|
||||||
if (!strcmp(im->mode, mode)) {
|
if (im->mode == mode) {
|
||||||
; /* same mode; always succeeds */
|
; /* same mode; always succeeds */
|
||||||
} else if (IS_RGB(im->mode) && IS_RGB(mode)) {
|
} else if (isRGB(im->mode) && isRGB(mode)) {
|
||||||
/* color to color */
|
/* color to color */
|
||||||
strcpy(im->mode, mode);
|
im->mode = mode;
|
||||||
im->bands = modelen;
|
im->bands = modelen;
|
||||||
if (!strcmp(mode, "RGBA")) {
|
if (mode == IMAGING_MODE_RGBA) {
|
||||||
(void)ImagingFillBand(im, 3, 255);
|
(void)ImagingFillBand(im, 3, 255);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2294,7 +2343,7 @@ _getextrema(ImagingObject *self) {
|
||||||
case IMAGING_TYPE_FLOAT32:
|
case IMAGING_TYPE_FLOAT32:
|
||||||
return Py_BuildValue("dd", extrema.f[0], extrema.f[1]);
|
return Py_BuildValue("dd", extrema.f[0], extrema.f[1]);
|
||||||
case IMAGING_TYPE_SPECIAL:
|
case IMAGING_TYPE_SPECIAL:
|
||||||
if (strcmp(self->image->mode, "I;16") == 0) {
|
if (self->image->mode == IMAGING_MODE_I_16) {
|
||||||
return Py_BuildValue("HH", extrema.s[0], extrema.s[1]);
|
return Py_BuildValue("HH", extrema.s[0], extrema.s[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2383,7 +2432,7 @@ _putband(ImagingObject *self, PyObject *args) {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_merge(PyObject *self, PyObject *args) {
|
_merge(PyObject *self, PyObject *args) {
|
||||||
char *mode;
|
char *mode_name;
|
||||||
ImagingObject *band0 = NULL;
|
ImagingObject *band0 = NULL;
|
||||||
ImagingObject *band1 = NULL;
|
ImagingObject *band1 = NULL;
|
||||||
ImagingObject *band2 = NULL;
|
ImagingObject *band2 = NULL;
|
||||||
|
|
@ -2393,7 +2442,7 @@ _merge(PyObject *self, PyObject *args) {
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"sO!|O!O!O!",
|
"sO!|O!O!O!",
|
||||||
&mode,
|
&mode_name,
|
||||||
&Imaging_Type,
|
&Imaging_Type,
|
||||||
&band0,
|
&band0,
|
||||||
&Imaging_Type,
|
&Imaging_Type,
|
||||||
|
|
@ -2406,6 +2455,8 @@ _merge(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
|
||||||
if (band0) {
|
if (band0) {
|
||||||
bands[0] = band0->image;
|
bands[0] = band0->image;
|
||||||
}
|
}
|
||||||
|
|
@ -2419,7 +2470,12 @@ _merge(PyObject *self, PyObject *args) {
|
||||||
bands[3] = band3->image;
|
bands[3] = band3->image;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PyImagingNew(ImagingMerge(mode, bands));
|
Imaging imOut = ImagingMerge(mode, bands);
|
||||||
|
if (!imOut) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ImagingCopyPalette(imOut, bands[0]);
|
||||||
|
return PyImagingNew(imOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
|
@ -3711,7 +3767,7 @@ static struct PyMethodDef methods[] = {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_getattr_mode(ImagingObject *self, void *closure) {
|
_getattr_mode(ImagingObject *self, void *closure) {
|
||||||
return PyUnicode_FromString(self->image->mode);
|
return PyUnicode_FromString(getModeData(self->image->mode)->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
|
@ -4255,8 +4311,6 @@ setup_module(PyObject *m) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImagingAccessInit();
|
|
||||||
|
|
||||||
#ifdef HAVE_LIBJPEG
|
#ifdef HAVE_LIBJPEG
|
||||||
{
|
{
|
||||||
extern const char *ImagingJpegVersion(void);
|
extern const char *ImagingJpegVersion(void);
|
||||||
|
|
|
||||||
|
|
@ -212,32 +212,44 @@ cms_transform_dealloc(CmsTransformObject *self) {
|
||||||
/* internal functions */
|
/* internal functions */
|
||||||
|
|
||||||
static cmsUInt32Number
|
static cmsUInt32Number
|
||||||
findLCMStype(char *PILmode) {
|
findLCMStype(const char *const mode_name) {
|
||||||
if (strcmp(PILmode, "RGB") == 0 || strcmp(PILmode, "RGBA") == 0 ||
|
const ModeID mode = findModeID(mode_name);
|
||||||
strcmp(PILmode, "RGBX") == 0) {
|
switch (mode) {
|
||||||
return TYPE_RGBA_8;
|
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;
|
return TYPE_RGBA_16;
|
||||||
}
|
}
|
||||||
if (strcmp(PILmode, "CMYK") == 0) {
|
if (strcmp(mode_name, "L;16") == 0) {
|
||||||
return TYPE_CMYK_8;
|
|
||||||
}
|
|
||||||
if (strcmp(PILmode, "I;16") == 0 || strcmp(PILmode, "I;16L") == 0 ||
|
|
||||||
strcmp(PILmode, "L;16") == 0) {
|
|
||||||
return TYPE_GRAY_16;
|
return TYPE_GRAY_16;
|
||||||
}
|
}
|
||||||
if (strcmp(PILmode, "I;16B") == 0 || strcmp(PILmode, "L;16B") == 0) {
|
if (strcmp(mode_name, "L;16B") == 0) {
|
||||||
return TYPE_GRAY_16_SE;
|
return TYPE_GRAY_16_SE;
|
||||||
}
|
}
|
||||||
if (strcmp(PILmode, "YCbCr") == 0 || strcmp(PILmode, "YCCA") == 0 ||
|
if (strcmp(mode_name, "YCCA") == 0 || strcmp(mode_name, "YCC") == 0) {
|
||||||
strcmp(PILmode, "YCC") == 0) {
|
|
||||||
return TYPE_YCbCr_8;
|
return TYPE_YCbCr_8;
|
||||||
}
|
}
|
||||||
if (strcmp(PILmode, "LAB") == 0) {
|
|
||||||
// LabX equivalent like ALab, but not reversed -- no #define in lcms2
|
|
||||||
return (COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1));
|
|
||||||
}
|
|
||||||
/* presume "1" or "L" by default */
|
/* presume "1" or "L" by default */
|
||||||
return TYPE_GRAY_8;
|
return TYPE_GRAY_8;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -525,7 +525,7 @@ font_getlength(FontObject *self, PyObject *args) {
|
||||||
int horizontal_dir; /* is primary axis horizontal? */
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
||||||
const char *mode = NULL;
|
const char *mode_name = NULL;
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
PyObject *features = Py_None;
|
PyObject *features = Py_None;
|
||||||
|
|
@ -534,15 +534,16 @@ font_getlength(FontObject *self, PyObject *args) {
|
||||||
/* calculate size and bearing for a given string */
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang
|
args, "O|zzOz:getlength", &string, &mode_name, &dir, &features, &lang
|
||||||
)) {
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
mask = mode && strcmp(mode, "1") == 0;
|
const ModeID mode = findModeID(mode_name);
|
||||||
color = mode && strcmp(mode, "RGBA") == 0;
|
mask = mode == IMAGING_MODE_1;
|
||||||
|
color = mode == IMAGING_MODE_RGBA;
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
|
|
@ -754,7 +755,7 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
int horizontal_dir; /* is primary axis horizontal? */
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
||||||
const char *mode = NULL;
|
const char *mode_name = NULL;
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
const char *anchor = NULL;
|
const char *anchor = NULL;
|
||||||
|
|
@ -764,15 +765,23 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
/* calculate size and bearing for a given string */
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor
|
args,
|
||||||
|
"O|zzOzz:getsize",
|
||||||
|
&string,
|
||||||
|
&mode_name,
|
||||||
|
&dir,
|
||||||
|
&features,
|
||||||
|
&lang,
|
||||||
|
&anchor
|
||||||
)) {
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
mask = mode && strcmp(mode, "1") == 0;
|
const ModeID mode = findModeID(mode_name);
|
||||||
color = mode && strcmp(mode, "RGBA") == 0;
|
mask = mode == IMAGING_MODE_1;
|
||||||
|
color = mode == IMAGING_MODE_RGBA;
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
|
|
@ -839,7 +848,7 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
int stroke_filled = 0;
|
int stroke_filled = 0;
|
||||||
PY_LONG_LONG foreground_ink_long = 0;
|
PY_LONG_LONG foreground_ink_long = 0;
|
||||||
unsigned int foreground_ink;
|
unsigned int foreground_ink;
|
||||||
const char *mode = NULL;
|
const char *mode_name = NULL;
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
const char *anchor = NULL;
|
const char *anchor = NULL;
|
||||||
|
|
@ -859,7 +868,7 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
"OO|zzOzfpzL(ff):render",
|
"OO|zzOzfpzL(ff):render",
|
||||||
&string,
|
&string,
|
||||||
&fill,
|
&fill,
|
||||||
&mode,
|
&mode_name,
|
||||||
&dir,
|
&dir,
|
||||||
&features,
|
&features,
|
||||||
&lang,
|
&lang,
|
||||||
|
|
@ -873,8 +882,9 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
mask = mode && strcmp(mode, "1") == 0;
|
const ModeID mode = findModeID(mode_name);
|
||||||
color = mode && strcmp(mode, "RGBA") == 0;
|
mask = mode == IMAGING_MODE_1;
|
||||||
|
color = mode == IMAGING_MODE_RGBA;
|
||||||
|
|
||||||
foreground_ink = foreground_ink_long;
|
foreground_ink = foreground_ink_long;
|
||||||
|
|
||||||
|
|
|
||||||
16
src/_webp.c
|
|
@ -89,8 +89,8 @@ HandleMuxError(WebPMuxError err, char *chunk) {
|
||||||
|
|
||||||
static int
|
static int
|
||||||
import_frame_libwebp(WebPPicture *frame, Imaging im) {
|
import_frame_libwebp(WebPPicture *frame, Imaging im) {
|
||||||
if (strcmp(im->mode, "RGBA") && strcmp(im->mode, "RGB") &&
|
if (im->mode != IMAGING_MODE_RGBA && im->mode != IMAGING_MODE_RGB &&
|
||||||
strcmp(im->mode, "RGBX")) {
|
im->mode != IMAGING_MODE_RGBX) {
|
||||||
PyErr_SetString(PyExc_ValueError, "unsupported image mode");
|
PyErr_SetString(PyExc_ValueError, "unsupported image mode");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +104,7 @@ import_frame_libwebp(WebPPicture *frame, Imaging im) {
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ignore_fourth_channel = strcmp(im->mode, "RGBA");
|
int ignore_fourth_channel = im->mode != IMAGING_MODE_RGBA;
|
||||||
for (int y = 0; y < im->ysize; ++y) {
|
for (int y = 0; y < im->ysize; ++y) {
|
||||||
UINT8 *src = (UINT8 *)im->image32[y];
|
UINT8 *src = (UINT8 *)im->image32[y];
|
||||||
UINT32 *dst = frame->argb + frame->argb_stride * y;
|
UINT32 *dst = frame->argb + frame->argb_stride * y;
|
||||||
|
|
@ -143,7 +143,7 @@ typedef struct {
|
||||||
PyObject_HEAD WebPAnimDecoder *dec;
|
PyObject_HEAD WebPAnimDecoder *dec;
|
||||||
WebPAnimInfo info;
|
WebPAnimInfo info;
|
||||||
WebPData data;
|
WebPData data;
|
||||||
char *mode;
|
ModeID mode;
|
||||||
} WebPAnimDecoderObject;
|
} WebPAnimDecoderObject;
|
||||||
|
|
||||||
static PyTypeObject WebPAnimDecoder_Type;
|
static PyTypeObject WebPAnimDecoder_Type;
|
||||||
|
|
@ -396,7 +396,7 @@ _anim_decoder_new(PyObject *self, PyObject *args) {
|
||||||
const uint8_t *webp;
|
const uint8_t *webp;
|
||||||
Py_ssize_t size;
|
Py_ssize_t size;
|
||||||
WebPData webp_src;
|
WebPData webp_src;
|
||||||
char *mode;
|
ModeID mode;
|
||||||
WebPDecoderConfig config;
|
WebPDecoderConfig config;
|
||||||
WebPAnimDecoderObject *decp = NULL;
|
WebPAnimDecoderObject *decp = NULL;
|
||||||
WebPAnimDecoder *dec = NULL;
|
WebPAnimDecoder *dec = NULL;
|
||||||
|
|
@ -409,10 +409,10 @@ _anim_decoder_new(PyObject *self, PyObject *args) {
|
||||||
webp_src.size = size;
|
webp_src.size = size;
|
||||||
|
|
||||||
// Sniff the mode, since the decoder API doesn't tell us
|
// Sniff the mode, since the decoder API doesn't tell us
|
||||||
mode = "RGBA";
|
mode = IMAGING_MODE_RGBA;
|
||||||
if (WebPGetFeatures(webp, size, &config.input) == VP8_STATUS_OK) {
|
if (WebPGetFeatures(webp, size, &config.input) == VP8_STATUS_OK) {
|
||||||
if (!config.input.has_alpha) {
|
if (!config.input.has_alpha) {
|
||||||
mode = "RGBX";
|
mode = IMAGING_MODE_RGBX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -455,7 +455,7 @@ _anim_decoder_get_info(PyObject *self) {
|
||||||
info->loop_count,
|
info->loop_count,
|
||||||
info->bgcolor,
|
info->bgcolor,
|
||||||
info->frame_count,
|
info->frame_count,
|
||||||
decp->mode
|
getModeData(decp->mode)->name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
148
src/decode.c
|
|
@ -266,7 +266,9 @@ static PyTypeObject ImagingDecoderType = {
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
int
|
int
|
||||||
get_unpacker(ImagingDecoderObject *decoder, const char *mode, const char *rawmode) {
|
get_unpacker(
|
||||||
|
ImagingDecoderObject *decoder, const ModeID mode, const RawModeID rawmode
|
||||||
|
) {
|
||||||
int bits;
|
int bits;
|
||||||
ImagingShuffler unpack;
|
ImagingShuffler unpack;
|
||||||
|
|
||||||
|
|
@ -291,17 +293,20 @@ PyObject *
|
||||||
PyImaging_BitDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_BitDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
const char *mode_name;
|
||||||
int bits = 8;
|
int bits = 8;
|
||||||
int pad = 8;
|
int pad = 8;
|
||||||
int fill = 0;
|
int fill = 0;
|
||||||
int sign = 0;
|
int sign = 0;
|
||||||
int ystep = 1;
|
int ystep = 1;
|
||||||
if (!PyArg_ParseTuple(args, "s|iiiii", &mode, &bits, &pad, &fill, &sign, &ystep)) {
|
if (!PyArg_ParseTuple(
|
||||||
|
args, "s|iiiii", &mode_name, &bits, &pad, &fill, &sign, &ystep
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(mode, "F") != 0) {
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
if (mode != IMAGING_MODE_F) {
|
||||||
PyErr_SetString(PyExc_ValueError, "bad image mode");
|
PyErr_SetString(PyExc_ValueError, "bad image mode");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -331,34 +336,36 @@ PyObject *
|
||||||
PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *actual;
|
|
||||||
int n = 0;
|
int n = 0;
|
||||||
char *pixel_format = "";
|
char *pixel_format = "";
|
||||||
if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) {
|
if (!PyArg_ParseTuple(args, "si|s", &mode_name, &n, &pixel_format)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
ModeID actual;
|
||||||
|
|
||||||
switch (n) {
|
switch (n) {
|
||||||
case 1: /* BC1: 565 color, 1-bit alpha */
|
case 1: /* BC1: 565 color, 1-bit alpha */
|
||||||
case 2: /* BC2: 565 color, 4-bit alpha */
|
case 2: /* BC2: 565 color, 4-bit alpha */
|
||||||
case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */
|
case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */
|
||||||
case 7: /* BC7: 4-channel 8-bit via everything */
|
case 7: /* BC7: 4-channel 8-bit via everything */
|
||||||
actual = "RGBA";
|
actual = IMAGING_MODE_RGBA;
|
||||||
break;
|
break;
|
||||||
case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */
|
case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */
|
||||||
actual = "L";
|
actual = IMAGING_MODE_L;
|
||||||
break;
|
break;
|
||||||
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
|
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
|
||||||
case 6: /* BC6: 3-channel 16-bit float */
|
case 6: /* BC6: 3-channel 16-bit float */
|
||||||
actual = "RGB";
|
actual = IMAGING_MODE_RGB;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
PyErr_SetString(PyExc_ValueError, "block compression type unknown");
|
PyErr_SetString(PyExc_ValueError, "block compression type unknown");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(mode, actual) != 0) {
|
if (mode != actual) {
|
||||||
PyErr_SetString(PyExc_ValueError, "bad image mode");
|
PyErr_SetString(PyExc_ValueError, "bad image mode");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -401,15 +408,18 @@ PyObject *
|
||||||
PyImaging_GifDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_GifDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
const char *mode_name;
|
||||||
int bits = 8;
|
int bits = 8;
|
||||||
int interlace = 0;
|
int interlace = 0;
|
||||||
int transparency = -1;
|
int transparency = -1;
|
||||||
if (!PyArg_ParseTuple(args, "s|iii", &mode, &bits, &interlace, &transparency)) {
|
if (!PyArg_ParseTuple(
|
||||||
|
args, "s|iii", &mode_name, &bits, &interlace, &transparency
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(mode, "L") != 0 && strcmp(mode, "P") != 0) {
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
if (mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
|
||||||
PyErr_SetString(PyExc_ValueError, "bad image mode");
|
PyErr_SetString(PyExc_ValueError, "bad image mode");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -436,12 +446,14 @@ PyObject *
|
||||||
PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name, *rawmode_name;
|
||||||
char *rawmode;
|
if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
|
||||||
if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(0);
|
decoder = PyImaging_DecoderNew(0);
|
||||||
if (decoder == NULL) {
|
if (decoder == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -469,16 +481,21 @@ PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
|
||||||
PyObject *
|
PyObject *
|
||||||
PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode;
|
char *rawmode_name;
|
||||||
char *compname;
|
char *compname;
|
||||||
int fp;
|
int fp;
|
||||||
uint32_t ifdoffset;
|
uint32_t ifdoffset;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset)) {
|
if (!PyArg_ParseTuple(
|
||||||
|
args, "sssiI", &mode_name, &rawmode_name, &compname, &fp, &ifdoffset
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
TRACE(("new tiff decoder %s\n", compname));
|
TRACE(("new tiff decoder %s\n", compname));
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE));
|
decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE));
|
||||||
|
|
@ -511,12 +528,15 @@ PyObject *
|
||||||
PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode;
|
char *rawmode_name;
|
||||||
if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
|
if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(0);
|
decoder = PyImaging_DecoderNew(0);
|
||||||
if (decoder == NULL) {
|
if (decoder == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -545,7 +565,7 @@ PyImaging_PcdDecoderNew(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Unpack from PhotoYCC to RGB */
|
/* Unpack from PhotoYCC to RGB */
|
||||||
if (get_unpacker(decoder, "RGB", "YCC;P") < 0) {
|
if (get_unpacker(decoder, IMAGING_MODE_RGB, IMAGING_RAWMODE_YCC_P) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -562,13 +582,15 @@ PyObject *
|
||||||
PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name, *rawmode_name;
|
||||||
char *rawmode;
|
|
||||||
int stride;
|
int stride;
|
||||||
if (!PyArg_ParseTuple(args, "ssi", &mode, &rawmode, &stride)) {
|
if (!PyArg_ParseTuple(args, "ssi", &mode_name, &rawmode_name, &stride)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(0);
|
decoder = PyImaging_DecoderNew(0);
|
||||||
if (decoder == NULL) {
|
if (decoder == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -593,14 +615,16 @@ PyObject *
|
||||||
PyImaging_RawDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_RawDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name, *rawmode_name;
|
||||||
char *rawmode;
|
|
||||||
int stride = 0;
|
int stride = 0;
|
||||||
int ystep = 1;
|
int ystep = 1;
|
||||||
if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) {
|
if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &stride, &ystep)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(sizeof(RAWSTATE));
|
decoder = PyImaging_DecoderNew(sizeof(RAWSTATE));
|
||||||
if (decoder == NULL) {
|
if (decoder == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -627,14 +651,16 @@ PyObject *
|
||||||
PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name, *rawmode_name;
|
||||||
char *rawmode;
|
|
||||||
int ystep = 1;
|
int ystep = 1;
|
||||||
int bpc = 1;
|
int bpc = 1;
|
||||||
if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &bpc)) {
|
if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &ystep, &bpc)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(sizeof(SGISTATE));
|
decoder = PyImaging_DecoderNew(sizeof(SGISTATE));
|
||||||
if (decoder == NULL) {
|
if (decoder == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -661,12 +687,14 @@ PyObject *
|
||||||
PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name, *rawmode_name;
|
||||||
char *rawmode;
|
if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
|
||||||
if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(0);
|
decoder = PyImaging_DecoderNew(0);
|
||||||
if (decoder == NULL) {
|
if (decoder == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -689,14 +717,16 @@ PyObject *
|
||||||
PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name, *rawmode_name;
|
||||||
char *rawmode;
|
|
||||||
int ystep = 1;
|
int ystep = 1;
|
||||||
int depth = 8;
|
int depth = 8;
|
||||||
if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &depth)) {
|
if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &ystep, &depth)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(0);
|
decoder = PyImaging_DecoderNew(0);
|
||||||
if (decoder == NULL) {
|
if (decoder == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -727,7 +757,7 @@ PyImaging_XbmDecoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_unpacker(decoder, "1", "1;R") < 0) {
|
if (get_unpacker(decoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -748,13 +778,15 @@ PyObject *
|
||||||
PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name, *rawmode_name;
|
||||||
char *rawmode;
|
|
||||||
int interlaced = 0;
|
int interlaced = 0;
|
||||||
if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &interlaced)) {
|
if (!PyArg_ParseTuple(args, "ss|i", &mode_name, &rawmode_name, &interlaced)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE));
|
decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE));
|
||||||
if (decoder == NULL) {
|
if (decoder == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -798,19 +830,21 @@ PyObject *
|
||||||
PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
|
PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingDecoderObject *decoder;
|
ImagingDecoderObject *decoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode; /* what we want from the decoder */
|
char *rawmode_name; /* what we want from the decoder */
|
||||||
char *jpegmode; /* what's in the file */
|
char *jpegmode_name; /* what's in the file */
|
||||||
int scale = 1;
|
int scale = 1;
|
||||||
int draft = 0;
|
int draft = 0;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "ssz|ii", &mode, &rawmode, &jpegmode, &scale, &draft)) {
|
if (!PyArg_ParseTuple(
|
||||||
|
args, "ssz|ii", &mode_name, &rawmode_name, &jpegmode_name, &scale, &draft
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!jpegmode) {
|
const ModeID mode = findModeID(mode_name);
|
||||||
jpegmode = "";
|
RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
}
|
const RawModeID jpegmode = findRawModeID(jpegmode_name);
|
||||||
|
|
||||||
decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE));
|
decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE));
|
||||||
if (decoder == NULL) {
|
if (decoder == NULL) {
|
||||||
|
|
@ -820,8 +854,8 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
|
||||||
// libjpeg-turbo supports different output formats.
|
// libjpeg-turbo supports different output formats.
|
||||||
// We are choosing Pillow's native format (3 color bytes + 1 padding)
|
// We are choosing Pillow's native format (3 color bytes + 1 padding)
|
||||||
// to avoid extra conversion in Unpack.c.
|
// to avoid extra conversion in Unpack.c.
|
||||||
if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) {
|
if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) {
|
||||||
rawmode = "RGBX";
|
rawmode = IMAGING_RAWMODE_RGBX;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_unpacker(decoder, mode, rawmode) < 0) {
|
if (get_unpacker(decoder, mode, rawmode) < 0) {
|
||||||
|
|
@ -831,11 +865,13 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
|
||||||
decoder->decode = ImagingJpegDecode;
|
decoder->decode = ImagingJpegDecode;
|
||||||
decoder->cleanup = ImagingJpegDecodeCleanup;
|
decoder->cleanup = ImagingJpegDecodeCleanup;
|
||||||
|
|
||||||
strncpy(((JPEGSTATE *)decoder->state.context)->rawmode, rawmode, 8);
|
JPEGSTATE *jpeg_decoder_state_context = (JPEGSTATE *)decoder->state.context;
|
||||||
strncpy(((JPEGSTATE *)decoder->state.context)->jpegmode, jpegmode, 8);
|
|
||||||
|
|
||||||
((JPEGSTATE *)decoder->state.context)->scale = scale;
|
jpeg_decoder_state_context->rawmode = rawmode;
|
||||||
((JPEGSTATE *)decoder->state.context)->draft = draft;
|
jpeg_decoder_state_context->jpegmode = jpegmode;
|
||||||
|
|
||||||
|
jpeg_decoder_state_context->scale = scale;
|
||||||
|
jpeg_decoder_state_context->draft = draft;
|
||||||
|
|
||||||
return (PyObject *)decoder;
|
return (PyObject *)decoder;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ typedef struct {
|
||||||
static PyTypeObject ImagingDisplayType;
|
static PyTypeObject ImagingDisplayType;
|
||||||
|
|
||||||
static ImagingDisplayObject *
|
static ImagingDisplayObject *
|
||||||
_new(const char *mode, int xsize, int ysize) {
|
_new(const ModeID mode, int xsize, int ysize) {
|
||||||
ImagingDisplayObject *display;
|
ImagingDisplayObject *display;
|
||||||
|
|
||||||
if (PyType_Ready(&ImagingDisplayType) < 0) {
|
if (PyType_Ready(&ImagingDisplayType) < 0) {
|
||||||
|
|
@ -235,7 +235,7 @@ static struct PyMethodDef methods[] = {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_getattr_mode(ImagingDisplayObject *self, void *closure) {
|
_getattr_mode(ImagingDisplayObject *self, void *closure) {
|
||||||
return Py_BuildValue("s", self->dib->mode);
|
return Py_BuildValue("s", getModeData(self->dib->mode)->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
|
@ -258,13 +258,14 @@ static PyTypeObject ImagingDisplayType = {
|
||||||
PyObject *
|
PyObject *
|
||||||
PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
|
PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
|
||||||
ImagingDisplayObject *display;
|
ImagingDisplayObject *display;
|
||||||
char *mode;
|
char *mode_name;
|
||||||
int xsize, ysize;
|
int xsize, ysize;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
|
if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
display = _new(mode, xsize, ysize);
|
display = _new(mode, xsize, ysize);
|
||||||
if (display == NULL) {
|
if (display == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -275,12 +276,9 @@ PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) {
|
PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) {
|
||||||
char *mode;
|
|
||||||
int size[2];
|
int size[2];
|
||||||
|
const ModeID mode = ImagingGetModeDIB(size);
|
||||||
mode = ImagingGetModeDIB(size);
|
return Py_BuildValue("s(ii)", getModeData(mode)->name, size[0], size[1]);
|
||||||
|
|
||||||
return Py_BuildValue("s(ii)", mode, size[0], size[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
|
||||||
108
src/encode.c
|
|
@ -334,14 +334,19 @@ static PyTypeObject ImagingEncoderType = {
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
int
|
int
|
||||||
get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode) {
|
get_packer(ImagingEncoderObject *encoder, const ModeID mode, const RawModeID rawmode) {
|
||||||
int bits;
|
int bits;
|
||||||
ImagingShuffler pack;
|
ImagingShuffler pack;
|
||||||
|
|
||||||
pack = ImagingFindPacker(mode, rawmode, &bits);
|
pack = ImagingFindPacker(mode, rawmode, &bits);
|
||||||
if (!pack) {
|
if (!pack) {
|
||||||
Py_DECREF(encoder);
|
Py_DECREF(encoder);
|
||||||
PyErr_Format(PyExc_ValueError, "No packer found from %s to %s", mode, rawmode);
|
PyErr_Format(
|
||||||
|
PyExc_ValueError,
|
||||||
|
"No packer found from %s to %s",
|
||||||
|
getModeData(mode)->name,
|
||||||
|
getRawModeData(rawmode)->name
|
||||||
|
);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -402,11 +407,13 @@ PyObject *
|
||||||
PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
|
PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingEncoderObject *encoder;
|
ImagingEncoderObject *encoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode;
|
char *rawmode_name;
|
||||||
Py_ssize_t bits = 8;
|
Py_ssize_t bits = 8;
|
||||||
Py_ssize_t interlace = 0;
|
Py_ssize_t interlace = 0;
|
||||||
if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace)) {
|
if (!PyArg_ParseTuple(
|
||||||
|
args, "ss|nn", &mode_name, &rawmode_name, &bits, &interlace
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -415,6 +422,9 @@ PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
if (get_packer(encoder, mode, rawmode) < 0) {
|
if (get_packer(encoder, mode, rawmode) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -435,11 +445,11 @@ PyObject *
|
||||||
PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
|
PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingEncoderObject *encoder;
|
ImagingEncoderObject *encoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode;
|
char *rawmode_name;
|
||||||
Py_ssize_t bits = 8;
|
Py_ssize_t bits = 8;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &bits)) {
|
if (!PyArg_ParseTuple(args, "ss|n", &mode_name, &rawmode_name, &bits)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -448,6 +458,9 @@ PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
if (get_packer(encoder, mode, rawmode) < 0) {
|
if (get_packer(encoder, mode, rawmode) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -465,12 +478,12 @@ PyObject *
|
||||||
PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
|
PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingEncoderObject *encoder;
|
ImagingEncoderObject *encoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode;
|
char *rawmode_name;
|
||||||
Py_ssize_t stride = 0;
|
Py_ssize_t stride = 0;
|
||||||
Py_ssize_t ystep = 1;
|
Py_ssize_t ystep = 1;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep)) {
|
if (!PyArg_ParseTuple(args, "ss|nn", &mode_name, &rawmode_name, &stride, &ystep)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,6 +492,9 @@ PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
if (get_packer(encoder, mode, rawmode) < 0) {
|
if (get_packer(encoder, mode, rawmode) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -499,11 +515,11 @@ PyObject *
|
||||||
PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
|
PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingEncoderObject *encoder;
|
ImagingEncoderObject *encoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode;
|
char *rawmode_name;
|
||||||
Py_ssize_t ystep = 1;
|
Py_ssize_t ystep = 1;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep)) {
|
if (!PyArg_ParseTuple(args, "ss|n", &mode_name, &rawmode_name, &ystep)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -512,6 +528,9 @@ PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
if (get_packer(encoder, mode, rawmode) < 0) {
|
if (get_packer(encoder, mode, rawmode) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -536,7 +555,7 @@ PyImaging_XbmEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_packer(encoder, "1", "1;R") < 0) {
|
if (get_packer(encoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -557,8 +576,8 @@ PyObject *
|
||||||
PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
|
PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingEncoderObject *encoder;
|
ImagingEncoderObject *encoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode;
|
char *rawmode_name;
|
||||||
Py_ssize_t optimize = 0;
|
Py_ssize_t optimize = 0;
|
||||||
Py_ssize_t compress_level = -1;
|
Py_ssize_t compress_level = -1;
|
||||||
Py_ssize_t compress_type = -1;
|
Py_ssize_t compress_type = -1;
|
||||||
|
|
@ -567,8 +586,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"ss|nnny#",
|
"ss|nnny#",
|
||||||
&mode,
|
&mode_name,
|
||||||
&rawmode,
|
&rawmode_name,
|
||||||
&optimize,
|
&optimize,
|
||||||
&compress_level,
|
&compress_level,
|
||||||
&compress_type,
|
&compress_type,
|
||||||
|
|
@ -597,6 +616,9 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
if (get_packer(encoder, mode, rawmode) < 0) {
|
if (get_packer(encoder, mode, rawmode) < 0) {
|
||||||
free(dictionary);
|
free(dictionary);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -605,7 +627,7 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
|
||||||
encoder->encode = ImagingZipEncode;
|
encoder->encode = ImagingZipEncode;
|
||||||
encoder->cleanup = ImagingZipEncodeCleanup;
|
encoder->cleanup = ImagingZipEncodeCleanup;
|
||||||
|
|
||||||
if (rawmode[0] == 'P') {
|
if (rawmode == IMAGING_RAWMODE_P || rawmode == IMAGING_RAWMODE_PA) {
|
||||||
/* disable filtering */
|
/* disable filtering */
|
||||||
((ZIPSTATE *)encoder->state.context)->mode = ZIP_PNG_PALETTE;
|
((ZIPSTATE *)encoder->state.context)->mode = ZIP_PNG_PALETTE;
|
||||||
}
|
}
|
||||||
|
|
@ -634,8 +656,8 @@ PyObject *
|
||||||
PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingEncoderObject *encoder;
|
ImagingEncoderObject *encoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode;
|
char *rawmode_name;
|
||||||
char *compname;
|
char *compname;
|
||||||
char *filename;
|
char *filename;
|
||||||
Py_ssize_t fp;
|
Py_ssize_t fp;
|
||||||
|
|
@ -655,7 +677,15 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
PyObject *item;
|
PyObject *item;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types
|
args,
|
||||||
|
"sssnsOO",
|
||||||
|
&mode_name,
|
||||||
|
&rawmode_name,
|
||||||
|
&compname,
|
||||||
|
&fp,
|
||||||
|
&filename,
|
||||||
|
&tags,
|
||||||
|
&types
|
||||||
)) {
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -693,6 +723,9 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
if (get_packer(encoder, mode, rawmode) < 0) {
|
if (get_packer(encoder, mode, rawmode) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -922,6 +955,18 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
);
|
);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
|
} else if (type == TIFF_RATIONAL) {
|
||||||
|
FLOAT32 *av;
|
||||||
|
/* malloc check ok, calloc checks for overflow */
|
||||||
|
av = calloc(len, sizeof(FLOAT32));
|
||||||
|
if (av) {
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i));
|
||||||
|
}
|
||||||
|
status =
|
||||||
|
ImagingLibTiffSetField(&encoder->state, (ttag_t)key_int, av);
|
||||||
|
free(av);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (type == TIFF_SHORT) {
|
if (type == TIFF_SHORT) {
|
||||||
|
|
@ -1076,8 +1121,8 @@ PyObject *
|
||||||
PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
ImagingEncoderObject *encoder;
|
ImagingEncoderObject *encoder;
|
||||||
|
|
||||||
char *mode;
|
char *mode_name;
|
||||||
char *rawmode;
|
char *rawmode_name;
|
||||||
Py_ssize_t quality = 0;
|
Py_ssize_t quality = 0;
|
||||||
Py_ssize_t progressive = 0;
|
Py_ssize_t progressive = 0;
|
||||||
Py_ssize_t smooth = 0;
|
Py_ssize_t smooth = 0;
|
||||||
|
|
@ -1101,8 +1146,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"ss|nnnnpn(nn)nnnOz#y#y#",
|
"ss|nnnnpn(nn)nnnOz#y#y#",
|
||||||
&mode,
|
&mode_name,
|
||||||
&rawmode,
|
&rawmode_name,
|
||||||
&quality,
|
&quality,
|
||||||
&progressive,
|
&progressive,
|
||||||
&smooth,
|
&smooth,
|
||||||
|
|
@ -1130,11 +1175,14 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModeID mode = findModeID(mode_name);
|
||||||
|
RawModeID rawmode = findRawModeID(rawmode_name);
|
||||||
|
|
||||||
// libjpeg-turbo supports different output formats.
|
// libjpeg-turbo supports different output formats.
|
||||||
// We are choosing Pillow's native format (3 color bytes + 1 padding)
|
// We are choosing Pillow's native format (3 color bytes + 1 padding)
|
||||||
// to avoid extra conversion in Pack.c.
|
// to avoid extra conversion in Pack.c.
|
||||||
if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) {
|
if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) {
|
||||||
rawmode = "RGBX";
|
rawmode = IMAGING_RAWMODE_RGBX;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_packer(encoder, mode, rawmode) < 0) {
|
if (get_packer(encoder, mode, rawmode) < 0) {
|
||||||
|
|
@ -1192,7 +1240,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
encoder->encode = ImagingJpegEncode;
|
encoder->encode = ImagingJpegEncode;
|
||||||
|
|
||||||
JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context;
|
JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context;
|
||||||
strncpy(jpeg_encoder_state->rawmode, rawmode, 8);
|
jpeg_encoder_state->rawmode = rawmode;
|
||||||
jpeg_encoder_state->keep_rgb = keep_rgb;
|
jpeg_encoder_state->keep_rgb = keep_rgb;
|
||||||
jpeg_encoder_state->quality = quality;
|
jpeg_encoder_state->quality = quality;
|
||||||
jpeg_encoder_state->qtables = qarrays;
|
jpeg_encoder_state->qtables = qarrays;
|
||||||
|
|
|
||||||
|
|
@ -11,39 +11,6 @@
|
||||||
|
|
||||||
#include "Imaging.h"
|
#include "Imaging.h"
|
||||||
|
|
||||||
/* use make_hash.py from the pillow-scripts repository to calculate these values */
|
|
||||||
#define ACCESS_TABLE_SIZE 35
|
|
||||||
#define ACCESS_TABLE_HASH 8940
|
|
||||||
|
|
||||||
static struct ImagingAccessInstance access_table[ACCESS_TABLE_SIZE];
|
|
||||||
|
|
||||||
static inline UINT32
|
|
||||||
hash(const char *mode) {
|
|
||||||
UINT32 i = ACCESS_TABLE_HASH;
|
|
||||||
while (*mode) {
|
|
||||||
i = ((i << 5) + i) ^ (UINT8)*mode++;
|
|
||||||
}
|
|
||||||
return i % ACCESS_TABLE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ImagingAccess
|
|
||||||
add_item(const char *mode) {
|
|
||||||
UINT32 i = hash(mode);
|
|
||||||
/* printf("hash %s => %d\n", mode, i); */
|
|
||||||
if (access_table[i].mode && strcmp(access_table[i].mode, mode) != 0) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"AccessInit: hash collision: %d for both %s and %s\n",
|
|
||||||
i,
|
|
||||||
mode,
|
|
||||||
access_table[i].mode
|
|
||||||
);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
access_table[i].mode = mode;
|
|
||||||
return &access_table[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fetch individual pixel */
|
/* fetch individual pixel */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
@ -64,7 +31,7 @@ static void
|
||||||
get_pixel_16L(Imaging im, int x, int y, void *color) {
|
get_pixel_16L(Imaging im, int x, int y, void *color) {
|
||||||
UINT8 *in = (UINT8 *)&im->image[y][x + x];
|
UINT8 *in = (UINT8 *)&im->image[y][x + x];
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
UINT16 out = in[0] + (in[1] << 8);
|
UINT16 out = in[0] + ((UINT16)in[1] << 8);
|
||||||
memcpy(color, &out, sizeof(out));
|
memcpy(color, &out, sizeof(out));
|
||||||
#else
|
#else
|
||||||
memcpy(color, in, sizeof(UINT16));
|
memcpy(color, in, sizeof(UINT16));
|
||||||
|
|
@ -77,7 +44,7 @@ get_pixel_16B(Imaging im, int x, int y, void *color) {
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
memcpy(color, in, sizeof(UINT16));
|
memcpy(color, in, sizeof(UINT16));
|
||||||
#else
|
#else
|
||||||
UINT16 out = in[1] + (in[0] << 8);
|
UINT16 out = in[1] + ((UINT16)in[0] << 8);
|
||||||
memcpy(color, &out, sizeof(out));
|
memcpy(color, &out, sizeof(out));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -87,28 +54,6 @@ get_pixel_32(Imaging im, int x, int y, void *color) {
|
||||||
memcpy(color, &im->image32[y][x], sizeof(INT32));
|
memcpy(color, &im->image32[y][x], sizeof(INT32));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
get_pixel_32L(Imaging im, int x, int y, void *color) {
|
|
||||||
UINT8 *in = (UINT8 *)&im->image[y][x * 4];
|
|
||||||
#ifdef WORDS_BIGENDIAN
|
|
||||||
INT32 out = in[0] + (in[1] << 8) + (in[2] << 16) + (in[3] << 24);
|
|
||||||
memcpy(color, &out, sizeof(out));
|
|
||||||
#else
|
|
||||||
memcpy(color, in, sizeof(INT32));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
get_pixel_32B(Imaging im, int x, int y, void *color) {
|
|
||||||
UINT8 *in = (UINT8 *)&im->image[y][x * 4];
|
|
||||||
#ifdef WORDS_BIGENDIAN
|
|
||||||
memcpy(color, in, sizeof(INT32));
|
|
||||||
#else
|
|
||||||
INT32 out = in[3] + (in[2] << 8) + (in[1] << 16) + (in[0] << 24);
|
|
||||||
memcpy(color, &out, sizeof(out));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/* store individual pixel */
|
/* store individual pixel */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
@ -129,71 +74,46 @@ put_pixel_16B(Imaging im, int x, int y, const void *color) {
|
||||||
out[1] = in[0];
|
out[1] = in[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
put_pixel_32L(Imaging im, int x, int y, const void *color) {
|
|
||||||
memcpy(&im->image8[y][x * 4], color, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
put_pixel_32B(Imaging im, int x, int y, const void *color) {
|
|
||||||
const char *in = color;
|
|
||||||
UINT8 *out = (UINT8 *)&im->image8[y][x * 4];
|
|
||||||
out[0] = in[3];
|
|
||||||
out[1] = in[2];
|
|
||||||
out[2] = in[1];
|
|
||||||
out[3] = in[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
put_pixel_32(Imaging im, int x, int y, const void *color) {
|
put_pixel_32(Imaging im, int x, int y, const void *color) {
|
||||||
memcpy(&im->image32[y][x], color, sizeof(INT32));
|
memcpy(&im->image32[y][x], color, sizeof(INT32));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static struct ImagingAccessInstance accessors[] = {
|
||||||
ImagingAccessInit(void) {
|
{IMAGING_MODE_1, get_pixel_8, put_pixel_8},
|
||||||
#define ADD(mode_, get_pixel_, put_pixel_) \
|
{IMAGING_MODE_L, get_pixel_8, put_pixel_8},
|
||||||
{ \
|
{IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32},
|
||||||
ImagingAccess access = add_item(mode_); \
|
{IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32},
|
||||||
access->get_pixel = get_pixel_; \
|
{IMAGING_MODE_I, get_pixel_32, put_pixel_32},
|
||||||
access->put_pixel = put_pixel_; \
|
{IMAGING_MODE_I_16, get_pixel_16L, put_pixel_16L},
|
||||||
}
|
{IMAGING_MODE_I_16L, get_pixel_16L, put_pixel_16L},
|
||||||
|
{IMAGING_MODE_I_16B, get_pixel_16B, put_pixel_16B},
|
||||||
/* populate access table */
|
|
||||||
ADD("1", get_pixel_8, put_pixel_8);
|
|
||||||
ADD("L", get_pixel_8, put_pixel_8);
|
|
||||||
ADD("LA", get_pixel_32_2bands, put_pixel_32);
|
|
||||||
ADD("La", get_pixel_32_2bands, put_pixel_32);
|
|
||||||
ADD("I", get_pixel_32, put_pixel_32);
|
|
||||||
ADD("I;16", get_pixel_16L, put_pixel_16L);
|
|
||||||
ADD("I;16L", get_pixel_16L, put_pixel_16L);
|
|
||||||
ADD("I;16B", get_pixel_16B, put_pixel_16B);
|
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
ADD("I;16N", get_pixel_16B, put_pixel_16B);
|
{IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B},
|
||||||
#else
|
#else
|
||||||
ADD("I;16N", get_pixel_16L, put_pixel_16L);
|
{IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L},
|
||||||
#endif
|
#endif
|
||||||
ADD("I;32L", get_pixel_32L, put_pixel_32L);
|
{IMAGING_MODE_F, get_pixel_32, put_pixel_32},
|
||||||
ADD("I;32B", get_pixel_32B, put_pixel_32B);
|
{IMAGING_MODE_P, get_pixel_8, put_pixel_8},
|
||||||
ADD("F", get_pixel_32, put_pixel_32);
|
{IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32},
|
||||||
ADD("P", get_pixel_8, put_pixel_8);
|
{IMAGING_MODE_RGB, get_pixel_32, put_pixel_32},
|
||||||
ADD("PA", get_pixel_32_2bands, put_pixel_32);
|
{IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32},
|
||||||
ADD("RGB", get_pixel_32, put_pixel_32);
|
{IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32},
|
||||||
ADD("RGBA", get_pixel_32, put_pixel_32);
|
{IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32},
|
||||||
ADD("RGBa", get_pixel_32, put_pixel_32);
|
{IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32},
|
||||||
ADD("RGBX", get_pixel_32, put_pixel_32);
|
{IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32},
|
||||||
ADD("CMYK", get_pixel_32, put_pixel_32);
|
{IMAGING_MODE_LAB, get_pixel_32, put_pixel_32},
|
||||||
ADD("YCbCr", get_pixel_32, put_pixel_32);
|
{IMAGING_MODE_HSV, get_pixel_32, put_pixel_32},
|
||||||
ADD("LAB", get_pixel_32, put_pixel_32);
|
};
|
||||||
ADD("HSV", get_pixel_32, put_pixel_32);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImagingAccess
|
ImagingAccess
|
||||||
ImagingAccessNew(Imaging im) {
|
ImagingAccessNew(const Imaging im) {
|
||||||
ImagingAccess access = &access_table[hash(im->mode)];
|
for (size_t i = 0; i < sizeof(accessors) / sizeof(*accessors); i++) {
|
||||||
if (im->mode[0] != access->mode[0] || strcmp(im->mode, access->mode) != 0) {
|
if (im->mode == accessors[i].mode) {
|
||||||
return NULL;
|
return &accessors[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return access;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,11 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) {
|
||||||
|
|
||||||
/* Check arguments */
|
/* Check arguments */
|
||||||
if (!imDst || !imSrc ||
|
if (!imDst || !imSrc ||
|
||||||
(strcmp(imDst->mode, "RGBA") && strcmp(imDst->mode, "LA"))) {
|
(imDst->mode != IMAGING_MODE_RGBA && imDst->mode != IMAGING_MODE_LA)) {
|
||||||
return ImagingError_ModeError();
|
return ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(imDst->mode, imSrc->mode) || imDst->xsize != imSrc->xsize ||
|
if (imDst->mode != imSrc->mode || imDst->xsize != imSrc->xsize ||
|
||||||
imDst->ysize != imSrc->ysize) {
|
imDst->ysize != imSrc->ysize) {
|
||||||
return ImagingError_Mismatch();
|
return ImagingError_Mismatch();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,98 @@ ReleaseExportedSchema(struct ArrowSchema *array) {
|
||||||
// Mark array released
|
// Mark array released
|
||||||
array->release = NULL;
|
array->release = NULL;
|
||||||
}
|
}
|
||||||
|
char *
|
||||||
|
image_band_json(Imaging im) {
|
||||||
|
char *format = "{\"bands\": [\"%s\", \"%s\", \"%s\", \"%s\"]}";
|
||||||
|
char *json;
|
||||||
|
// Bands can be 4 bands * 2 characters each
|
||||||
|
int len = strlen(format) + 8 + 1;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
json = calloc(1, len);
|
||||||
|
|
||||||
|
if (!json) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = PyOS_snprintf(
|
||||||
|
json,
|
||||||
|
len,
|
||||||
|
format,
|
||||||
|
im->band_names[0],
|
||||||
|
im->band_names[1],
|
||||||
|
im->band_names[2],
|
||||||
|
im->band_names[3]
|
||||||
|
);
|
||||||
|
if (err < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
single_band_json(Imaging im) {
|
||||||
|
char *format = "{\"bands\": [\"%s\"]}";
|
||||||
|
char *json;
|
||||||
|
// Bands can be 1 band * (maybe but probably not) 2 characters each
|
||||||
|
int len = strlen(format) + 2 + 1;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
json = calloc(1, len);
|
||||||
|
|
||||||
|
if (!json) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = PyOS_snprintf(json, len, format, im->band_names[0]);
|
||||||
|
if (err < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
assemble_metadata(const char *band_json) {
|
||||||
|
/* format is
|
||||||
|
int32: number of key/value pairs (noted N below)
|
||||||
|
int32: byte length of key 0
|
||||||
|
key 0 (not null-terminated)
|
||||||
|
int32: byte length of value 0
|
||||||
|
value 0 (not null-terminated)
|
||||||
|
...
|
||||||
|
int32: byte length of key N - 1
|
||||||
|
key N - 1 (not null-terminated)
|
||||||
|
int32: byte length of value N - 1
|
||||||
|
value N - 1 (not null-terminated)
|
||||||
|
*/
|
||||||
|
const char *key = "image";
|
||||||
|
INT32 key_len = strlen(key);
|
||||||
|
INT32 band_json_len = strlen(band_json);
|
||||||
|
|
||||||
|
char *buf;
|
||||||
|
INT32 *dest_int;
|
||||||
|
char *dest;
|
||||||
|
|
||||||
|
buf = calloc(1, key_len + band_json_len + 4 + 1 * 8);
|
||||||
|
if (!buf) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
dest_int = (void *)buf;
|
||||||
|
|
||||||
|
dest_int[0] = 1;
|
||||||
|
dest_int[1] = key_len;
|
||||||
|
dest_int += 2;
|
||||||
|
dest = (void *)dest_int;
|
||||||
|
memcpy(dest, key, key_len);
|
||||||
|
dest += key_len;
|
||||||
|
dest_int = (void *)dest;
|
||||||
|
dest_int[0] = band_json_len;
|
||||||
|
dest_int += 1;
|
||||||
|
memcpy(dest_int, band_json, band_json_len);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
export_named_type(struct ArrowSchema *schema, char *format, char *name) {
|
export_named_type(struct ArrowSchema *schema, char *format, char *name) {
|
||||||
|
|
@ -95,6 +187,7 @@ export_named_type(struct ArrowSchema *schema, char *format, char *name) {
|
||||||
int
|
int
|
||||||
export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
|
export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
char *band_json;
|
||||||
|
|
||||||
if (strcmp(im->arrow_band_format, "") == 0) {
|
if (strcmp(im->arrow_band_format, "") == 0) {
|
||||||
return IMAGING_ARROW_INCOMPATIBLE_MODE;
|
return IMAGING_ARROW_INCOMPATIBLE_MODE;
|
||||||
|
|
@ -106,7 +199,17 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (im->bands == 1) {
|
if (im->bands == 1) {
|
||||||
return export_named_type(schema, im->arrow_band_format, im->band_names[0]);
|
retval = export_named_type(schema, im->arrow_band_format, im->band_names[0]);
|
||||||
|
if (retval != 0) {
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
// band related metadata
|
||||||
|
band_json = single_band_json(im);
|
||||||
|
if (band_json) {
|
||||||
|
schema->metadata = assemble_metadata(band_json);
|
||||||
|
free(band_json);
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
retval = export_named_type(schema, "+w:4", "");
|
retval = export_named_type(schema, "+w:4", "");
|
||||||
|
|
@ -117,13 +220,26 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
|
||||||
schema->n_children = 1;
|
schema->n_children = 1;
|
||||||
schema->children = calloc(1, sizeof(struct ArrowSchema *));
|
schema->children = calloc(1, sizeof(struct ArrowSchema *));
|
||||||
schema->children[0] = (struct ArrowSchema *)calloc(1, sizeof(struct ArrowSchema));
|
schema->children[0] = (struct ArrowSchema *)calloc(1, sizeof(struct ArrowSchema));
|
||||||
retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel");
|
retval = export_named_type(
|
||||||
|
schema->children[0], im->arrow_band_format, getModeData(im->mode)->name
|
||||||
|
);
|
||||||
if (retval != 0) {
|
if (retval != 0) {
|
||||||
free(schema->children[0]);
|
free(schema->children[0]);
|
||||||
free(schema->children);
|
free(schema->children);
|
||||||
schema->release(schema);
|
schema->release(schema);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// band related metadata
|
||||||
|
band_json = image_band_json(im);
|
||||||
|
if (band_json) {
|
||||||
|
// adding the metadata to the child array.
|
||||||
|
// Accessible in pyarrow via pa.array(img).type.field(0).metadata
|
||||||
|
// adding it to the top level is not accessible.
|
||||||
|
schema->children[0]->metadata = assemble_metadata(band_json);
|
||||||
|
free(band_json);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ ImagingGetBand(Imaging imIn, int band) {
|
||||||
band = 3;
|
band = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
imOut = ImagingNewDirty("L", imIn->xsize, imIn->ysize);
|
imOut = ImagingNewDirty(IMAGING_MODE_L, imIn->xsize, imIn->ysize);
|
||||||
if (!imOut) {
|
if (!imOut) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +82,7 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < imIn->bands; i++) {
|
for (i = 0; i < imIn->bands; i++) {
|
||||||
bands[i] = ImagingNewDirty("L", imIn->xsize, imIn->ysize);
|
bands[i] = ImagingNewDirty(IMAGING_MODE_L, imIn->xsize, imIn->ysize);
|
||||||
if (!bands[i]) {
|
if (!bands[i]) {
|
||||||
for (j = 0; j < i; ++j) {
|
for (j = 0; j < i; ++j) {
|
||||||
ImagingDelete(bands[j]);
|
ImagingDelete(bands[j]);
|
||||||
|
|
@ -240,7 +240,7 @@ ImagingFillBand(Imaging imOut, int band, int color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingMerge(const char *mode, Imaging bands[4]) {
|
ImagingMerge(const ModeID mode, Imaging bands[4]) {
|
||||||
int i, x, y;
|
int i, x, y;
|
||||||
int bandsCount = 0;
|
int bandsCount = 0;
|
||||||
Imaging imOut;
|
Imaging imOut;
|
||||||
|
|
|
||||||
|
|
@ -603,7 +603,7 @@ static void
|
||||||
bc6_sign_extend(UINT16 *v, int prec) {
|
bc6_sign_extend(UINT16 *v, int prec) {
|
||||||
int x = *v;
|
int x = *v;
|
||||||
if (x & (1 << (prec - 1))) {
|
if (x & (1 << (prec - 1))) {
|
||||||
x |= -1 << prec;
|
x |= -(1 << prec);
|
||||||
}
|
}
|
||||||
*v = (UINT16)x;
|
*v = (UINT16)x;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,9 @@ decode_565(UINT16 x) {
|
||||||
|
|
||||||
static UINT16
|
static UINT16
|
||||||
encode_565(rgba item) {
|
encode_565(rgba item) {
|
||||||
UINT8 r, g, b;
|
UINT16 r = item.color[0] >> (8 - 5);
|
||||||
r = item.color[0] >> (8 - 5);
|
UINT8 g = item.color[1] >> (8 - 6);
|
||||||
g = item.color[1] >> (8 - 6);
|
UINT8 b = item.color[2] >> (8 - 5);
|
||||||
b = item.color[2] >> (8 - 5);
|
|
||||||
return (r << (5 + 6)) | (g << 5) | b;
|
return (r << (5 + 6)) | (g << 5) | b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +156,8 @@ encode_bc1_color(Imaging im, ImagingCodecState state, UINT8 *dst, int separate_a
|
||||||
static void
|
static void
|
||||||
encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) {
|
encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) {
|
||||||
int i, j;
|
int i, j;
|
||||||
UINT8 block[16], current_alpha;
|
UINT8 block[16];
|
||||||
|
UINT32 current_alpha;
|
||||||
for (i = 0; i < 4; i++) {
|
for (i = 0; i < 4; i++) {
|
||||||
for (j = 0; j < 4; j++) {
|
for (j = 0; j < 4; j++) {
|
||||||
int x = state->x + i * im->pixelsize;
|
int x = state->x + i * im->pixelsize;
|
||||||
|
|
@ -253,7 +253,7 @@ int
|
||||||
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
int n = state->state;
|
int n = state->state;
|
||||||
int has_alpha_channel =
|
int has_alpha_channel =
|
||||||
strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "LA") == 0;
|
im->mode == IMAGING_MODE_RGBA || im->mode == IMAGING_MODE_LA;
|
||||||
|
|
||||||
UINT8 *dst = buf;
|
UINT8 *dst = buf;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha) {
|
||||||
|
|
||||||
/* Check arguments */
|
/* Check arguments */
|
||||||
if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 || imIn1->palette ||
|
if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 || imIn1->palette ||
|
||||||
strcmp(imIn1->mode, "1") == 0 || imIn2->palette ||
|
imIn1->mode == IMAGING_MODE_1 || imIn2->palette ||
|
||||||
strcmp(imIn2->mode, "1") == 0) {
|
imIn2->mode == IMAGING_MODE_1) {
|
||||||
return ImagingError_ModeError();
|
return ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
|
||||||
return ImagingError_ValueError("radius must be >= 0");
|
return ImagingError_ValueError("radius must be >= 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(imIn->mode, imOut->mode) || imIn->type != imOut->type ||
|
if (imIn->mode != imOut->mode || imIn->type != imOut->type ||
|
||||||
imIn->bands != imOut->bands || imIn->xsize != imOut->xsize ||
|
imIn->bands != imOut->bands || imIn->xsize != imOut->xsize ||
|
||||||
imIn->ysize != imOut->ysize) {
|
imIn->ysize != imOut->ysize) {
|
||||||
return ImagingError_Mismatch();
|
return ImagingError_Mismatch();
|
||||||
|
|
@ -258,10 +258,10 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
|
||||||
return ImagingError_ModeError();
|
return ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "RGBA") == 0 ||
|
if (imIn->mode != IMAGING_MODE_RGB && imIn->mode != IMAGING_MODE_RGBA &&
|
||||||
strcmp(imIn->mode, "RGBa") == 0 || strcmp(imIn->mode, "RGBX") == 0 ||
|
imIn->mode != IMAGING_MODE_RGBa && imIn->mode != IMAGING_MODE_RGBX &&
|
||||||
strcmp(imIn->mode, "CMYK") == 0 || strcmp(imIn->mode, "L") == 0 ||
|
imIn->mode != IMAGING_MODE_CMYK && imIn->mode != IMAGING_MODE_L &&
|
||||||
strcmp(imIn->mode, "LA") == 0 || strcmp(imIn->mode, "La") == 0)) {
|
imIn->mode != IMAGING_MODE_LA && imIn->mode != IMAGING_MODE_La) {
|
||||||
return ImagingError_ModeError();
|
return ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,28 +18,28 @@
|
||||||
|
|
||||||
#include "Imaging.h"
|
#include "Imaging.h"
|
||||||
|
|
||||||
#define CHOP(operation) \
|
#define CHOP(operation) \
|
||||||
int x, y; \
|
int x, y; \
|
||||||
Imaging imOut; \
|
Imaging imOut; \
|
||||||
imOut = create(imIn1, imIn2, NULL); \
|
imOut = create(imIn1, imIn2, IMAGING_MODE_UNKNOWN); \
|
||||||
if (!imOut) { \
|
if (!imOut) { \
|
||||||
return NULL; \
|
return NULL; \
|
||||||
} \
|
} \
|
||||||
for (y = 0; y < imOut->ysize; y++) { \
|
for (y = 0; y < imOut->ysize; y++) { \
|
||||||
UINT8 *out = (UINT8 *)imOut->image[y]; \
|
UINT8 *out = (UINT8 *)imOut->image[y]; \
|
||||||
UINT8 *in1 = (UINT8 *)imIn1->image[y]; \
|
UINT8 *in1 = (UINT8 *)imIn1->image[y]; \
|
||||||
UINT8 *in2 = (UINT8 *)imIn2->image[y]; \
|
UINT8 *in2 = (UINT8 *)imIn2->image[y]; \
|
||||||
for (x = 0; x < imOut->linesize; x++) { \
|
for (x = 0; x < imOut->linesize; x++) { \
|
||||||
int temp = operation; \
|
int temp = operation; \
|
||||||
if (temp <= 0) { \
|
if (temp <= 0) { \
|
||||||
out[x] = 0; \
|
out[x] = 0; \
|
||||||
} else if (temp >= 255) { \
|
} else if (temp >= 255) { \
|
||||||
out[x] = 255; \
|
out[x] = 255; \
|
||||||
} else { \
|
} else { \
|
||||||
out[x] = temp; \
|
out[x] = temp; \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
return imOut;
|
return imOut;
|
||||||
|
|
||||||
#define CHOP2(operation, mode) \
|
#define CHOP2(operation, mode) \
|
||||||
|
|
@ -60,11 +60,12 @@
|
||||||
return imOut;
|
return imOut;
|
||||||
|
|
||||||
static Imaging
|
static Imaging
|
||||||
create(Imaging im1, Imaging im2, char *mode) {
|
create(Imaging im1, Imaging im2, const ModeID mode) {
|
||||||
int xsize, ysize;
|
int xsize, ysize;
|
||||||
|
|
||||||
if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 ||
|
if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 ||
|
||||||
(mode != NULL && (strcmp(im1->mode, "1") || strcmp(im2->mode, "1")))) {
|
(mode != IMAGING_MODE_UNKNOWN &&
|
||||||
|
(im1->mode != IMAGING_MODE_1 || im2->mode != IMAGING_MODE_1))) {
|
||||||
return (Imaging)ImagingError_ModeError();
|
return (Imaging)ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
if (im1->type != im2->type || im1->bands != im2->bands) {
|
if (im1->type != im2->type || im1->bands != im2->bands) {
|
||||||
|
|
@ -114,27 +115,27 @@ ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset) {
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingChopAnd(Imaging imIn1, Imaging imIn2) {
|
ImagingChopAnd(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2((in1[x] && in2[x]) ? 255 : 0, "1");
|
CHOP2((in1[x] && in2[x]) ? 255 : 0, IMAGING_MODE_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingChopOr(Imaging imIn1, Imaging imIn2) {
|
ImagingChopOr(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2((in1[x] || in2[x]) ? 255 : 0, "1");
|
CHOP2((in1[x] || in2[x]) ? 255 : 0, IMAGING_MODE_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingChopXor(Imaging imIn1, Imaging imIn2) {
|
ImagingChopXor(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, "1");
|
CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, IMAGING_MODE_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) {
|
ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2(in1[x] + in2[x], NULL);
|
CHOP2(in1[x] + in2[x], IMAGING_MODE_UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) {
|
ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2(in1[x] - in2[x], NULL);
|
CHOP2(in1[x] - in2[x], IMAGING_MODE_UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
|
|
@ -142,7 +143,7 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2(
|
CHOP2(
|
||||||
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
|
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
|
||||||
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
|
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
|
||||||
NULL
|
IMAGING_MODE_UNKNOWN
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,7 +152,7 @@ ImagingChopHardLight(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2(
|
CHOP2(
|
||||||
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
||||||
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
|
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
|
||||||
NULL
|
IMAGING_MODE_UNKNOWN
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,6 +161,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2(
|
CHOP2(
|
||||||
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
||||||
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
|
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
|
||||||
NULL
|
IMAGING_MODE_UNKNOWN
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -877,147 +877,12 @@ I16_RGB(UINT8 *out, const UINT8 *in, int xsize) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct {
|
|
||||||
const char *from;
|
|
||||||
const char *to;
|
|
||||||
ImagingShuffler convert;
|
|
||||||
} converters[] = {
|
|
||||||
|
|
||||||
{"1", "L", bit2l},
|
|
||||||
{"1", "I", bit2i},
|
|
||||||
{"1", "F", bit2f},
|
|
||||||
{"1", "RGB", bit2rgb},
|
|
||||||
{"1", "RGBA", bit2rgb},
|
|
||||||
{"1", "RGBX", bit2rgb},
|
|
||||||
{"1", "CMYK", bit2cmyk},
|
|
||||||
{"1", "YCbCr", bit2ycbcr},
|
|
||||||
{"1", "HSV", bit2hsv},
|
|
||||||
|
|
||||||
{"L", "1", l2bit},
|
|
||||||
{"L", "LA", l2la},
|
|
||||||
{"L", "I", l2i},
|
|
||||||
{"L", "F", l2f},
|
|
||||||
{"L", "RGB", l2rgb},
|
|
||||||
{"L", "RGBA", l2rgb},
|
|
||||||
{"L", "RGBX", l2rgb},
|
|
||||||
{"L", "CMYK", l2cmyk},
|
|
||||||
{"L", "YCbCr", l2ycbcr},
|
|
||||||
{"L", "HSV", l2hsv},
|
|
||||||
|
|
||||||
{"LA", "L", la2l},
|
|
||||||
{"LA", "La", lA2la},
|
|
||||||
{"LA", "RGB", la2rgb},
|
|
||||||
{"LA", "RGBA", la2rgb},
|
|
||||||
{"LA", "RGBX", la2rgb},
|
|
||||||
{"LA", "CMYK", la2cmyk},
|
|
||||||
{"LA", "YCbCr", la2ycbcr},
|
|
||||||
{"LA", "HSV", la2hsv},
|
|
||||||
|
|
||||||
{"La", "LA", la2lA},
|
|
||||||
|
|
||||||
{"I", "L", i2l},
|
|
||||||
{"I", "F", i2f},
|
|
||||||
{"I", "RGB", i2rgb},
|
|
||||||
{"I", "RGBA", i2rgb},
|
|
||||||
{"I", "RGBX", i2rgb},
|
|
||||||
{"I", "HSV", i2hsv},
|
|
||||||
|
|
||||||
{"F", "L", f2l},
|
|
||||||
{"F", "I", f2i},
|
|
||||||
|
|
||||||
{"RGB", "1", rgb2bit},
|
|
||||||
{"RGB", "L", rgb2l},
|
|
||||||
{"RGB", "LA", rgb2la},
|
|
||||||
{"RGB", "La", rgb2la},
|
|
||||||
{"RGB", "I", rgb2i},
|
|
||||||
{"RGB", "I;16", rgb2i16l},
|
|
||||||
{"RGB", "I;16L", rgb2i16l},
|
|
||||||
{"RGB", "I;16B", rgb2i16b},
|
|
||||||
#ifdef WORDS_BIGENDIAN
|
|
||||||
{"RGB", "I;16N", rgb2i16b},
|
|
||||||
#else
|
|
||||||
{"RGB", "I;16N", rgb2i16l},
|
|
||||||
#endif
|
|
||||||
{"RGB", "F", rgb2f},
|
|
||||||
{"RGB", "RGBA", rgb2rgba},
|
|
||||||
{"RGB", "RGBa", rgb2rgba},
|
|
||||||
{"RGB", "RGBX", rgb2rgba},
|
|
||||||
{"RGB", "CMYK", rgb2cmyk},
|
|
||||||
{"RGB", "YCbCr", ImagingConvertRGB2YCbCr},
|
|
||||||
{"RGB", "HSV", rgb2hsv},
|
|
||||||
|
|
||||||
{"RGBA", "1", rgb2bit},
|
|
||||||
{"RGBA", "L", rgb2l},
|
|
||||||
{"RGBA", "LA", rgba2la},
|
|
||||||
{"RGBA", "I", rgb2i},
|
|
||||||
{"RGBA", "F", rgb2f},
|
|
||||||
{"RGBA", "RGB", rgba2rgb},
|
|
||||||
{"RGBA", "RGBa", rgbA2rgba},
|
|
||||||
{"RGBA", "RGBX", rgb2rgba},
|
|
||||||
{"RGBA", "CMYK", rgb2cmyk},
|
|
||||||
{"RGBA", "YCbCr", ImagingConvertRGB2YCbCr},
|
|
||||||
{"RGBA", "HSV", rgb2hsv},
|
|
||||||
|
|
||||||
{"RGBa", "RGBA", rgba2rgbA},
|
|
||||||
{"RGBa", "RGB", rgba2rgb_},
|
|
||||||
|
|
||||||
{"RGBX", "1", rgb2bit},
|
|
||||||
{"RGBX", "L", rgb2l},
|
|
||||||
{"RGBX", "LA", rgb2la},
|
|
||||||
{"RGBX", "I", rgb2i},
|
|
||||||
{"RGBX", "F", rgb2f},
|
|
||||||
{"RGBX", "RGB", rgba2rgb},
|
|
||||||
{"RGBX", "CMYK", rgb2cmyk},
|
|
||||||
{"RGBX", "YCbCr", ImagingConvertRGB2YCbCr},
|
|
||||||
{"RGBX", "HSV", rgb2hsv},
|
|
||||||
|
|
||||||
{"CMYK", "RGB", cmyk2rgb},
|
|
||||||
{"CMYK", "RGBA", cmyk2rgb},
|
|
||||||
{"CMYK", "RGBX", cmyk2rgb},
|
|
||||||
{"CMYK", "HSV", cmyk2hsv},
|
|
||||||
|
|
||||||
{"YCbCr", "L", ycbcr2l},
|
|
||||||
{"YCbCr", "LA", ycbcr2la},
|
|
||||||
{"YCbCr", "RGB", ImagingConvertYCbCr2RGB},
|
|
||||||
|
|
||||||
{"HSV", "RGB", hsv2rgb},
|
|
||||||
|
|
||||||
{"I", "I;16", I_I16L},
|
|
||||||
{"I;16", "I", I16L_I},
|
|
||||||
{"I;16", "RGB", I16_RGB},
|
|
||||||
{"L", "I;16", L_I16L},
|
|
||||||
{"I;16", "L", I16L_L},
|
|
||||||
|
|
||||||
{"I", "I;16L", I_I16L},
|
|
||||||
{"I;16L", "I", I16L_I},
|
|
||||||
{"I", "I;16B", I_I16B},
|
|
||||||
{"I;16B", "I", I16B_I},
|
|
||||||
|
|
||||||
{"L", "I;16L", L_I16L},
|
|
||||||
{"I;16L", "L", I16L_L},
|
|
||||||
{"L", "I;16B", L_I16B},
|
|
||||||
{"I;16B", "L", I16B_L},
|
|
||||||
#ifdef WORDS_BIGENDIAN
|
|
||||||
{"L", "I;16N", L_I16B},
|
|
||||||
{"I;16N", "L", I16B_L},
|
|
||||||
#else
|
|
||||||
{"L", "I;16N", L_I16L},
|
|
||||||
{"I;16N", "L", I16L_L},
|
|
||||||
#endif
|
|
||||||
|
|
||||||
{"I;16", "F", I16L_F},
|
|
||||||
{"I;16L", "F", I16L_F},
|
|
||||||
{"I;16B", "F", I16B_F},
|
|
||||||
|
|
||||||
{NULL}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* FIXME: translate indexed versions to pointer versions below this line */
|
|
||||||
|
|
||||||
/* ------------------- */
|
/* ------------------- */
|
||||||
/* Palette conversions */
|
/* Palette conversions */
|
||||||
/* ------------------- */
|
/* ------------------- */
|
||||||
|
|
||||||
|
/* FIXME: translate indexed versions to pointer versions below this line */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
||||||
int x;
|
int x;
|
||||||
|
|
@ -1065,13 +930,13 @@ pa2p(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
||||||
static void
|
static void
|
||||||
p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
||||||
int x;
|
int x;
|
||||||
int rgb = strcmp(palette->mode, "RGB");
|
const int rgb = palette->mode == IMAGING_MODE_RGB;
|
||||||
for (x = 0; x < xsize; x++, in++) {
|
for (x = 0; x < xsize; x++, in++) {
|
||||||
const UINT8 *rgba = &palette->palette[in[0] * 4];
|
const UINT8 *rgba = &palette->palette[in[0] * 4];
|
||||||
*out++ = in[0];
|
*out++ = in[0];
|
||||||
*out++ = in[0];
|
*out++ = in[0];
|
||||||
*out++ = in[0];
|
*out++ = in[0];
|
||||||
*out++ = rgb == 0 ? 255 : rgba[3];
|
*out++ = rgb ? 255 : rgba[3];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1225,7 +1090,7 @@ pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Imaging
|
static Imaging
|
||||||
frompalette(Imaging imOut, Imaging imIn, const char *mode) {
|
frompalette(Imaging imOut, Imaging imIn, const ModeID mode) {
|
||||||
ImagingSectionCookie cookie;
|
ImagingSectionCookie cookie;
|
||||||
int alpha;
|
int alpha;
|
||||||
int y;
|
int y;
|
||||||
|
|
@ -1237,31 +1102,31 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
|
||||||
return (Imaging)ImagingError_ValueError("no palette");
|
return (Imaging)ImagingError_ValueError("no palette");
|
||||||
}
|
}
|
||||||
|
|
||||||
alpha = !strcmp(imIn->mode, "PA");
|
alpha = imIn->mode == IMAGING_MODE_PA;
|
||||||
|
|
||||||
if (strcmp(mode, "1") == 0) {
|
if (mode == IMAGING_MODE_1) {
|
||||||
convert = alpha ? pa2bit : p2bit;
|
convert = alpha ? pa2bit : p2bit;
|
||||||
} else if (strcmp(mode, "L") == 0) {
|
} else if (mode == IMAGING_MODE_L) {
|
||||||
convert = alpha ? pa2l : p2l;
|
convert = alpha ? pa2l : p2l;
|
||||||
} else if (strcmp(mode, "LA") == 0) {
|
} else if (mode == IMAGING_MODE_LA) {
|
||||||
convert = alpha ? pa2la : p2la;
|
convert = alpha ? pa2la : p2la;
|
||||||
} else if (strcmp(mode, "P") == 0) {
|
} else if (mode == IMAGING_MODE_P) {
|
||||||
convert = pa2p;
|
convert = pa2p;
|
||||||
} else if (strcmp(mode, "PA") == 0) {
|
} else if (mode == IMAGING_MODE_PA) {
|
||||||
convert = p2pa;
|
convert = p2pa;
|
||||||
} else if (strcmp(mode, "I") == 0) {
|
} else if (mode == IMAGING_MODE_I) {
|
||||||
convert = alpha ? pa2i : p2i;
|
convert = alpha ? pa2i : p2i;
|
||||||
} else if (strcmp(mode, "F") == 0) {
|
} else if (mode == IMAGING_MODE_F) {
|
||||||
convert = alpha ? pa2f : p2f;
|
convert = alpha ? pa2f : p2f;
|
||||||
} else if (strcmp(mode, "RGB") == 0) {
|
} else if (mode == IMAGING_MODE_RGB) {
|
||||||
convert = alpha ? pa2rgb : p2rgb;
|
convert = alpha ? pa2rgb : p2rgb;
|
||||||
} else if (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBX") == 0) {
|
} else if (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBX) {
|
||||||
convert = alpha ? pa2rgba : p2rgba;
|
convert = alpha ? pa2rgba : p2rgba;
|
||||||
} else if (strcmp(mode, "CMYK") == 0) {
|
} else if (mode == IMAGING_MODE_CMYK) {
|
||||||
convert = alpha ? pa2cmyk : p2cmyk;
|
convert = alpha ? pa2cmyk : p2cmyk;
|
||||||
} else if (strcmp(mode, "YCbCr") == 0) {
|
} else if (mode == IMAGING_MODE_YCbCr) {
|
||||||
convert = alpha ? pa2ycbcr : p2ycbcr;
|
convert = alpha ? pa2ycbcr : p2ycbcr;
|
||||||
} else if (strcmp(mode, "HSV") == 0) {
|
} else if (mode == IMAGING_MODE_HSV) {
|
||||||
convert = alpha ? pa2hsv : p2hsv;
|
convert = alpha ? pa2hsv : p2hsv;
|
||||||
} else {
|
} else {
|
||||||
return (Imaging)ImagingError_ValueError("conversion not supported");
|
return (Imaging)ImagingError_ValueError("conversion not supported");
|
||||||
|
|
@ -1271,7 +1136,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
|
||||||
if (!imOut) {
|
if (!imOut) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
|
if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) {
|
||||||
ImagingPaletteDelete(imOut->palette);
|
ImagingPaletteDelete(imOut->palette);
|
||||||
imOut->palette = ImagingPaletteDuplicate(imIn->palette);
|
imOut->palette = ImagingPaletteDuplicate(imIn->palette);
|
||||||
}
|
}
|
||||||
|
|
@ -1295,24 +1160,26 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
|
||||||
#endif
|
#endif
|
||||||
static Imaging
|
static Imaging
|
||||||
topalette(
|
topalette(
|
||||||
Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither
|
Imaging imOut, Imaging imIn, const ModeID mode, ImagingPalette inpalette, int dither
|
||||||
) {
|
) {
|
||||||
ImagingSectionCookie cookie;
|
ImagingSectionCookie cookie;
|
||||||
int alpha;
|
int alpha;
|
||||||
int x, y;
|
int x, y;
|
||||||
ImagingPalette palette = inpalette;
|
ImagingPalette palette = inpalette;
|
||||||
|
|
||||||
/* Map L or RGB/RGBX/RGBA to palette image */
|
/* Map L or RGB/RGBX/RGBA/RGBa to palette image */
|
||||||
if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) {
|
if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB &&
|
||||||
|
imIn->mode != IMAGING_MODE_RGBX && imIn->mode != IMAGING_MODE_RGBA &&
|
||||||
|
imIn->mode != IMAGING_MODE_RGBa) {
|
||||||
return (Imaging)ImagingError_ValueError("conversion not supported");
|
return (Imaging)ImagingError_ValueError("conversion not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
alpha = !strcmp(mode, "PA");
|
alpha = mode == IMAGING_MODE_PA;
|
||||||
|
|
||||||
if (palette == NULL) {
|
if (palette == NULL) {
|
||||||
/* FIXME: make user configurable */
|
/* FIXME: make user configurable */
|
||||||
if (imIn->bands == 1) {
|
if (imIn->bands == 1) {
|
||||||
palette = ImagingPaletteNew("RGB");
|
palette = ImagingPaletteNew(IMAGING_MODE_RGB);
|
||||||
|
|
||||||
palette->size = 256;
|
palette->size = 256;
|
||||||
int i;
|
int i;
|
||||||
|
|
@ -1499,11 +1366,11 @@ tobilevel(Imaging imOut, Imaging imIn) {
|
||||||
int *errors;
|
int *errors;
|
||||||
|
|
||||||
/* Map L or RGB to dithered 1 image */
|
/* Map L or RGB to dithered 1 image */
|
||||||
if (strcmp(imIn->mode, "L") != 0 && strcmp(imIn->mode, "RGB") != 0) {
|
if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB) {
|
||||||
return (Imaging)ImagingError_ValueError("conversion not supported");
|
return (Imaging)ImagingError_ValueError("conversion not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
imOut = ImagingNew2Dirty("1", imOut, imIn);
|
imOut = ImagingNew2Dirty(IMAGING_MODE_1, imOut, imIn);
|
||||||
if (!imOut) {
|
if (!imOut) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -1585,19 +1452,152 @@ tobilevel(Imaging imOut, Imaging imIn) {
|
||||||
#pragma optimize("", on)
|
#pragma optimize("", on)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* ------------------- */
|
||||||
|
/* Conversion handlers */
|
||||||
|
/* ------------------- */
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
const ModeID from;
|
||||||
|
const ModeID to;
|
||||||
|
ImagingShuffler convert;
|
||||||
|
} converters[] = {
|
||||||
|
{IMAGING_MODE_1, IMAGING_MODE_L, bit2l},
|
||||||
|
{IMAGING_MODE_1, IMAGING_MODE_I, bit2i},
|
||||||
|
{IMAGING_MODE_1, IMAGING_MODE_F, bit2f},
|
||||||
|
{IMAGING_MODE_1, IMAGING_MODE_RGB, bit2rgb},
|
||||||
|
{IMAGING_MODE_1, IMAGING_MODE_RGBA, bit2rgb},
|
||||||
|
{IMAGING_MODE_1, IMAGING_MODE_RGBX, bit2rgb},
|
||||||
|
{IMAGING_MODE_1, IMAGING_MODE_CMYK, bit2cmyk},
|
||||||
|
{IMAGING_MODE_1, IMAGING_MODE_YCbCr, bit2ycbcr},
|
||||||
|
{IMAGING_MODE_1, IMAGING_MODE_HSV, bit2hsv},
|
||||||
|
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_1, l2bit},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_LA, l2la},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_I, l2i},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_F, l2f},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_RGB, l2rgb},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_RGBA, l2rgb},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_RGBX, l2rgb},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_CMYK, l2cmyk},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_YCbCr, l2ycbcr},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_HSV, l2hsv},
|
||||||
|
|
||||||
|
{IMAGING_MODE_LA, IMAGING_MODE_L, la2l},
|
||||||
|
{IMAGING_MODE_LA, IMAGING_MODE_La, lA2la},
|
||||||
|
{IMAGING_MODE_LA, IMAGING_MODE_RGB, la2rgb},
|
||||||
|
{IMAGING_MODE_LA, IMAGING_MODE_RGBA, la2rgb},
|
||||||
|
{IMAGING_MODE_LA, IMAGING_MODE_RGBX, la2rgb},
|
||||||
|
{IMAGING_MODE_LA, IMAGING_MODE_CMYK, la2cmyk},
|
||||||
|
{IMAGING_MODE_LA, IMAGING_MODE_YCbCr, la2ycbcr},
|
||||||
|
{IMAGING_MODE_LA, IMAGING_MODE_HSV, la2hsv},
|
||||||
|
|
||||||
|
{IMAGING_MODE_La, IMAGING_MODE_LA, la2lA},
|
||||||
|
|
||||||
|
{IMAGING_MODE_I, IMAGING_MODE_L, i2l},
|
||||||
|
{IMAGING_MODE_I, IMAGING_MODE_F, i2f},
|
||||||
|
{IMAGING_MODE_I, IMAGING_MODE_RGB, i2rgb},
|
||||||
|
{IMAGING_MODE_I, IMAGING_MODE_RGBA, i2rgb},
|
||||||
|
{IMAGING_MODE_I, IMAGING_MODE_RGBX, i2rgb},
|
||||||
|
{IMAGING_MODE_I, IMAGING_MODE_HSV, i2hsv},
|
||||||
|
|
||||||
|
{IMAGING_MODE_F, IMAGING_MODE_L, f2l},
|
||||||
|
{IMAGING_MODE_F, IMAGING_MODE_I, f2i},
|
||||||
|
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_1, rgb2bit},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_L, rgb2l},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_LA, rgb2la},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_La, rgb2la},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_I, rgb2i},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_I_16, rgb2i16l},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_I_16L, rgb2i16l},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_I_16B, rgb2i16b},
|
||||||
|
#ifdef WORDS_BIGENDIAN
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16b},
|
||||||
|
#else
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16l},
|
||||||
|
#endif
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_F, rgb2f},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_RGBA, rgb2rgba},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_RGBa, rgb2rgba},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_RGBX, rgb2rgba},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_CMYK, rgb2cmyk},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
|
||||||
|
{IMAGING_MODE_RGB, IMAGING_MODE_HSV, rgb2hsv},
|
||||||
|
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_1, rgb2bit},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_L, rgb2l},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_LA, rgba2la},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_I, rgb2i},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_F, rgb2f},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_RGB, rgba2rgb},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_RGBa, rgbA2rgba},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_RGBX, rgb2rgba},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_CMYK, rgb2cmyk},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
|
||||||
|
{IMAGING_MODE_RGBA, IMAGING_MODE_HSV, rgb2hsv},
|
||||||
|
|
||||||
|
{IMAGING_MODE_RGBa, IMAGING_MODE_RGBA, rgba2rgbA},
|
||||||
|
{IMAGING_MODE_RGBa, IMAGING_MODE_RGB, rgba2rgb_},
|
||||||
|
|
||||||
|
{IMAGING_MODE_RGBX, IMAGING_MODE_1, rgb2bit},
|
||||||
|
{IMAGING_MODE_RGBX, IMAGING_MODE_L, rgb2l},
|
||||||
|
{IMAGING_MODE_RGBX, IMAGING_MODE_LA, rgb2la},
|
||||||
|
{IMAGING_MODE_RGBX, IMAGING_MODE_I, rgb2i},
|
||||||
|
{IMAGING_MODE_RGBX, IMAGING_MODE_F, rgb2f},
|
||||||
|
{IMAGING_MODE_RGBX, IMAGING_MODE_RGB, rgba2rgb},
|
||||||
|
{IMAGING_MODE_RGBX, IMAGING_MODE_CMYK, rgb2cmyk},
|
||||||
|
{IMAGING_MODE_RGBX, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
|
||||||
|
{IMAGING_MODE_RGBX, IMAGING_MODE_HSV, rgb2hsv},
|
||||||
|
|
||||||
|
{IMAGING_MODE_CMYK, IMAGING_MODE_RGB, cmyk2rgb},
|
||||||
|
{IMAGING_MODE_CMYK, IMAGING_MODE_RGBA, cmyk2rgb},
|
||||||
|
{IMAGING_MODE_CMYK, IMAGING_MODE_RGBX, cmyk2rgb},
|
||||||
|
{IMAGING_MODE_CMYK, IMAGING_MODE_HSV, cmyk2hsv},
|
||||||
|
|
||||||
|
{IMAGING_MODE_YCbCr, IMAGING_MODE_L, ycbcr2l},
|
||||||
|
{IMAGING_MODE_YCbCr, IMAGING_MODE_LA, ycbcr2la},
|
||||||
|
{IMAGING_MODE_YCbCr, IMAGING_MODE_RGB, ImagingConvertYCbCr2RGB},
|
||||||
|
|
||||||
|
{IMAGING_MODE_HSV, IMAGING_MODE_RGB, hsv2rgb},
|
||||||
|
|
||||||
|
{IMAGING_MODE_I, IMAGING_MODE_I_16, I_I16L},
|
||||||
|
{IMAGING_MODE_I_16, IMAGING_MODE_I, I16L_I},
|
||||||
|
{IMAGING_MODE_I_16, IMAGING_MODE_RGB, I16_RGB},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_I_16, L_I16L},
|
||||||
|
{IMAGING_MODE_I_16, IMAGING_MODE_L, I16L_L},
|
||||||
|
|
||||||
|
{IMAGING_MODE_I, IMAGING_MODE_I_16L, I_I16L},
|
||||||
|
{IMAGING_MODE_I_16L, IMAGING_MODE_I, I16L_I},
|
||||||
|
{IMAGING_MODE_I, IMAGING_MODE_I_16B, I_I16B},
|
||||||
|
{IMAGING_MODE_I_16B, IMAGING_MODE_I, I16B_I},
|
||||||
|
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_I_16L, L_I16L},
|
||||||
|
{IMAGING_MODE_I_16L, IMAGING_MODE_L, I16L_L},
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_I_16B, L_I16B},
|
||||||
|
{IMAGING_MODE_I_16B, IMAGING_MODE_L, I16B_L},
|
||||||
|
#ifdef WORDS_BIGENDIAN
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16B},
|
||||||
|
{IMAGING_MODE_I_16N, IMAGING_MODE_L, I16B_L},
|
||||||
|
#else
|
||||||
|
{IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16L},
|
||||||
|
{IMAGING_MODE_I_16N, IMAGING_MODE_L, I16L_L},
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{IMAGING_MODE_I_16, IMAGING_MODE_F, I16L_F},
|
||||||
|
{IMAGING_MODE_I_16L, IMAGING_MODE_F, I16L_F},
|
||||||
|
{IMAGING_MODE_I_16B, IMAGING_MODE_F, I16B_F}
|
||||||
|
};
|
||||||
|
|
||||||
static Imaging
|
static Imaging
|
||||||
convert(
|
convert(Imaging imOut, Imaging imIn, ModeID mode, ImagingPalette palette, int dither) {
|
||||||
Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither
|
|
||||||
) {
|
|
||||||
ImagingSectionCookie cookie;
|
ImagingSectionCookie cookie;
|
||||||
ImagingShuffler convert;
|
ImagingShuffler convert;
|
||||||
int y;
|
|
||||||
|
|
||||||
if (!imIn) {
|
if (!imIn) {
|
||||||
return (Imaging)ImagingError_ModeError();
|
return (Imaging)ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mode) {
|
if (mode == IMAGING_MODE_UNKNOWN) {
|
||||||
/* Map palette image to full depth */
|
/* Map palette image to full depth */
|
||||||
if (!imIn->palette) {
|
if (!imIn->palette) {
|
||||||
return (Imaging)ImagingError_ModeError();
|
return (Imaging)ImagingError_ModeError();
|
||||||
|
|
@ -1605,33 +1605,31 @@ convert(
|
||||||
mode = imIn->palette->mode;
|
mode = imIn->palette->mode;
|
||||||
} else {
|
} else {
|
||||||
/* Same mode? */
|
/* Same mode? */
|
||||||
if (!strcmp(imIn->mode, mode)) {
|
if (imIn->mode == mode) {
|
||||||
return ImagingCopy2(imOut, imIn);
|
return ImagingCopy2(imOut, imIn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* test for special conversions */
|
/* test for special conversions */
|
||||||
|
|
||||||
if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) {
|
if (imIn->mode == IMAGING_MODE_P || imIn->mode == IMAGING_MODE_PA) {
|
||||||
return frompalette(imOut, imIn, mode);
|
return frompalette(imOut, imIn, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
|
if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) {
|
||||||
return topalette(imOut, imIn, mode, palette, dither);
|
return topalette(imOut, imIn, mode, palette, dither);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dither && strcmp(mode, "1") == 0) {
|
if (dither && mode == IMAGING_MODE_1) {
|
||||||
return tobilevel(imOut, imIn);
|
return tobilevel(imOut, imIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* standard conversion machinery */
|
/* standard conversion machinery */
|
||||||
|
|
||||||
convert = NULL;
|
convert = NULL;
|
||||||
|
for (size_t i = 0; i < sizeof(converters) / sizeof(*converters); i++) {
|
||||||
for (y = 0; converters[y].from; y++) {
|
if (imIn->mode == converters[i].from && mode == converters[i].to) {
|
||||||
if (!strcmp(imIn->mode, converters[y].from) &&
|
convert = converters[i].convert;
|
||||||
!strcmp(mode, converters[y].to)) {
|
|
||||||
convert = converters[y].convert;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1642,7 +1640,11 @@ convert(
|
||||||
#else
|
#else
|
||||||
static char buf[100];
|
static char buf[100];
|
||||||
snprintf(
|
snprintf(
|
||||||
buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode
|
buf,
|
||||||
|
100,
|
||||||
|
"conversion from %.10s to %.10s not supported",
|
||||||
|
getModeData(imIn->mode)->name,
|
||||||
|
getModeData(mode)->name
|
||||||
);
|
);
|
||||||
return (Imaging)ImagingError_ValueError(buf);
|
return (Imaging)ImagingError_ValueError(buf);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1654,7 +1656,7 @@ convert(
|
||||||
}
|
}
|
||||||
|
|
||||||
ImagingSectionEnter(&cookie);
|
ImagingSectionEnter(&cookie);
|
||||||
for (y = 0; y < imIn->ysize; y++) {
|
for (int y = 0; y < imIn->ysize; y++) {
|
||||||
(*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
|
(*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
|
||||||
}
|
}
|
||||||
ImagingSectionLeave(&cookie);
|
ImagingSectionLeave(&cookie);
|
||||||
|
|
@ -1663,7 +1665,7 @@ convert(
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingConvert(Imaging imIn, const char *mode, ImagingPalette palette, int dither) {
|
ImagingConvert(Imaging imIn, const ModeID mode, ImagingPalette palette, int dither) {
|
||||||
return convert(NULL, imIn, mode, palette, dither);
|
return convert(NULL, imIn, mode, palette, dither);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1673,7 +1675,7 @@ ImagingConvert2(Imaging imOut, Imaging imIn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
|
ImagingConvertTransparent(Imaging imIn, const ModeID mode, int r, int g, int b) {
|
||||||
ImagingSectionCookie cookie;
|
ImagingSectionCookie cookie;
|
||||||
ImagingShuffler convert;
|
ImagingShuffler convert;
|
||||||
Imaging imOut = NULL;
|
Imaging imOut = NULL;
|
||||||
|
|
@ -1687,27 +1689,27 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
|
||||||
return (Imaging)ImagingError_ModeError();
|
return (Imaging)ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(imIn->mode, "RGB") == 0 &&
|
if (imIn->mode == IMAGING_MODE_RGB &&
|
||||||
(strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) {
|
(mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBa)) {
|
||||||
convert = rgb2rgba;
|
convert = rgb2rgba;
|
||||||
if (strcmp(mode, "RGBa") == 0) {
|
if (mode == IMAGING_MODE_RGBa) {
|
||||||
premultiplied = 1;
|
premultiplied = 1;
|
||||||
}
|
}
|
||||||
} else if (strcmp(imIn->mode, "RGB") == 0 &&
|
} else if (imIn->mode == IMAGING_MODE_RGB &&
|
||||||
(strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) {
|
(mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)) {
|
||||||
convert = rgb2la;
|
convert = rgb2la;
|
||||||
source_transparency = 1;
|
source_transparency = 1;
|
||||||
if (strcmp(mode, "La") == 0) {
|
if (mode == IMAGING_MODE_La) {
|
||||||
premultiplied = 1;
|
premultiplied = 1;
|
||||||
}
|
}
|
||||||
} else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 ||
|
} else if ((imIn->mode == IMAGING_MODE_1 || imIn->mode == IMAGING_MODE_I ||
|
||||||
strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) &&
|
imIn->mode == IMAGING_MODE_I_16 || imIn->mode == IMAGING_MODE_L) &&
|
||||||
(strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) {
|
(mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_LA)) {
|
||||||
if (strcmp(imIn->mode, "1") == 0) {
|
if (imIn->mode == IMAGING_MODE_1) {
|
||||||
convert = bit2rgb;
|
convert = bit2rgb;
|
||||||
} else if (strcmp(imIn->mode, "I") == 0) {
|
} else if (imIn->mode == IMAGING_MODE_I) {
|
||||||
convert = i2rgb;
|
convert = i2rgb;
|
||||||
} else if (strcmp(imIn->mode, "I;16") == 0) {
|
} else if (imIn->mode == IMAGING_MODE_I_16) {
|
||||||
convert = I16_RGB;
|
convert = I16_RGB;
|
||||||
} else {
|
} else {
|
||||||
convert = l2rgb;
|
convert = l2rgb;
|
||||||
|
|
@ -1719,8 +1721,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
|
||||||
buf,
|
buf,
|
||||||
100,
|
100,
|
||||||
"conversion from %.10s to %.10s not supported in convert_transparent",
|
"conversion from %.10s to %.10s not supported in convert_transparent",
|
||||||
imIn->mode,
|
getModeData(imIn->mode)->name,
|
||||||
mode
|
getModeData(mode)->name
|
||||||
);
|
);
|
||||||
return (Imaging)ImagingError_ValueError(buf);
|
return (Imaging)ImagingError_ValueError(buf);
|
||||||
}
|
}
|
||||||
|
|
@ -1743,15 +1745,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingConvertInPlace(Imaging imIn, const char *mode) {
|
ImagingConvertInPlace(Imaging imIn, const ModeID mode) {
|
||||||
ImagingSectionCookie cookie;
|
ImagingSectionCookie cookie;
|
||||||
ImagingShuffler convert;
|
ImagingShuffler convert;
|
||||||
int y;
|
int y;
|
||||||
|
|
||||||
/* limited support for inplace conversion */
|
/* limited support for inplace conversion */
|
||||||
if (strcmp(imIn->mode, "L") == 0 && strcmp(mode, "1") == 0) {
|
if (imIn->mode == IMAGING_MODE_L && mode == IMAGING_MODE_1) {
|
||||||
convert = l2bit;
|
convert = l2bit;
|
||||||
} else if (strcmp(imIn->mode, "1") == 0 && strcmp(mode, "L") == 0) {
|
} else if (imIn->mode == IMAGING_MODE_1 && mode == IMAGING_MODE_L) {
|
||||||
convert = bit2l;
|
convert = bit2l;
|
||||||
} else {
|
} else {
|
||||||
return ImagingError_ModeError();
|
return ImagingError_ModeError();
|
||||||
|
|
|
||||||
|
|
@ -25,20 +25,17 @@
|
||||||
|
|
||||||
#include "ImDib.h"
|
#include "ImDib.h"
|
||||||
|
|
||||||
char *
|
ModeID
|
||||||
ImagingGetModeDIB(int size_out[2]) {
|
ImagingGetModeDIB(int size_out[2]) {
|
||||||
/* Get device characteristics */
|
/* Get device characteristics */
|
||||||
|
|
||||||
HDC dc;
|
const HDC dc = CreateCompatibleDC(NULL);
|
||||||
char *mode;
|
|
||||||
|
|
||||||
dc = CreateCompatibleDC(NULL);
|
ModeID mode = IMAGING_MODE_P;
|
||||||
|
|
||||||
mode = "P";
|
|
||||||
if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) {
|
if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) {
|
||||||
mode = "RGB";
|
mode = IMAGING_MODE_RGB;
|
||||||
if (GetDeviceCaps(dc, BITSPIXEL) == 1) {
|
if (GetDeviceCaps(dc, BITSPIXEL) == 1) {
|
||||||
mode = "1";
|
mode = IMAGING_MODE_1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +50,7 @@ ImagingGetModeDIB(int size_out[2]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ImagingDIB
|
ImagingDIB
|
||||||
ImagingNewDIB(const char *mode, int xsize, int ysize) {
|
ImagingNewDIB(const ModeID mode, int xsize, int ysize) {
|
||||||
/* Create a Windows bitmap */
|
/* Create a Windows bitmap */
|
||||||
|
|
||||||
ImagingDIB dib;
|
ImagingDIB dib;
|
||||||
|
|
@ -61,10 +58,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* Check mode */
|
/* Check mode */
|
||||||
if (strcmp(mode, "1") != 0 && strcmp(mode, "L") != 0 && strcmp(mode, "RGB") != 0) {
|
if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_L && mode != IMAGING_MODE_RGB) {
|
||||||
return (ImagingDIB)ImagingError_ModeError();
|
return (ImagingDIB)ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int pixelsize = mode == IMAGING_MODE_RGB ? 3 : 1;
|
||||||
|
|
||||||
/* Create DIB context and info header */
|
/* Create DIB context and info header */
|
||||||
/* malloc check ok, small constant allocation */
|
/* malloc check ok, small constant allocation */
|
||||||
dib = (ImagingDIB)malloc(sizeof(*dib));
|
dib = (ImagingDIB)malloc(sizeof(*dib));
|
||||||
|
|
@ -83,7 +82,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
|
||||||
dib->info->bmiHeader.biWidth = xsize;
|
dib->info->bmiHeader.biWidth = xsize;
|
||||||
dib->info->bmiHeader.biHeight = ysize;
|
dib->info->bmiHeader.biHeight = ysize;
|
||||||
dib->info->bmiHeader.biPlanes = 1;
|
dib->info->bmiHeader.biPlanes = 1;
|
||||||
dib->info->bmiHeader.biBitCount = strlen(mode) * 8;
|
dib->info->bmiHeader.biBitCount = pixelsize * 8;
|
||||||
dib->info->bmiHeader.biCompression = BI_RGB;
|
dib->info->bmiHeader.biCompression = BI_RGB;
|
||||||
|
|
||||||
/* Create DIB */
|
/* Create DIB */
|
||||||
|
|
@ -103,12 +102,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
|
||||||
return (ImagingDIB)ImagingError_MemoryError();
|
return (ImagingDIB)ImagingError_MemoryError();
|
||||||
}
|
}
|
||||||
|
|
||||||
strcpy(dib->mode, mode);
|
dib->mode = mode;
|
||||||
dib->xsize = xsize;
|
dib->xsize = xsize;
|
||||||
dib->ysize = ysize;
|
dib->ysize = ysize;
|
||||||
|
|
||||||
dib->pixelsize = strlen(mode);
|
dib->pixelsize = pixelsize;
|
||||||
dib->linesize = (xsize * dib->pixelsize + 3) & -4;
|
dib->linesize = (xsize * pixelsize + 3) & -4;
|
||||||
|
|
||||||
if (dib->pixelsize == 1) {
|
if (dib->pixelsize == 1) {
|
||||||
dib->pack = dib->unpack = (ImagingShuffler)memcpy;
|
dib->pack = dib->unpack = (ImagingShuffler)memcpy;
|
||||||
|
|
@ -132,7 +131,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create an associated palette (for 8-bit displays only) */
|
/* Create an associated palette (for 8-bit displays only) */
|
||||||
if (strcmp(ImagingGetModeDIB(NULL), "P") == 0) {
|
if (ImagingGetModeDIB(NULL) == IMAGING_MODE_P) {
|
||||||
char palbuf[sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)];
|
char palbuf[sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)];
|
||||||
LPLOGPALETTE pal = (LPLOGPALETTE)palbuf;
|
LPLOGPALETTE pal = (LPLOGPALETTE)palbuf;
|
||||||
int i, r, g, b;
|
int i, r, g, b;
|
||||||
|
|
@ -142,7 +141,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
|
||||||
pal->palNumEntries = 256;
|
pal->palNumEntries = 256;
|
||||||
GetSystemPaletteEntries(dib->dc, 0, 256, pal->palPalEntry);
|
GetSystemPaletteEntries(dib->dc, 0, 256, pal->palPalEntry);
|
||||||
|
|
||||||
if (strcmp(mode, "L") == 0) {
|
if (mode == IMAGING_MODE_L) {
|
||||||
/* Grayscale DIB. Fill all 236 slots with a grayscale ramp
|
/* Grayscale DIB. Fill all 236 slots with a grayscale ramp
|
||||||
* (this is usually overkill on Windows since VGA only offers
|
* (this is usually overkill on Windows since VGA only offers
|
||||||
* 6 bits grayscale resolution). Ignore the slots already
|
* 6 bits grayscale resolution). Ignore the slots already
|
||||||
|
|
@ -156,8 +155,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
dib->palette = CreatePalette(pal);
|
dib->palette = CreatePalette(pal);
|
||||||
|
} else if (mode == IMAGING_MODE_RGB) {
|
||||||
} else if (strcmp(mode, "RGB") == 0) {
|
|
||||||
#ifdef CUBE216
|
#ifdef CUBE216
|
||||||
|
|
||||||
/* Colour DIB. Create a 6x6x6 colour cube (216 entries) and
|
/* Colour DIB. Create a 6x6x6 colour cube (216 entries) and
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging);
|
||||||
static inline void
|
static inline void
|
||||||
point8(Imaging im, int x, int y, int ink) {
|
point8(Imaging im, int x, int y, int ink) {
|
||||||
if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) {
|
if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) {
|
||||||
if (strncmp(im->mode, "I;16", 4) == 0) {
|
if (isModeI16(im->mode)) {
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
im->image8[y][x * 2] = (UINT8)(ink >> 8);
|
im->image8[y][x * 2] = (UINT8)(ink >> 8);
|
||||||
im->image8[y][x * 2 + 1] = (UINT8)ink;
|
im->image8[y][x * 2 + 1] = (UINT8)ink;
|
||||||
|
|
@ -117,13 +117,13 @@ hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
|
||||||
}
|
}
|
||||||
if (x0 <= x1) {
|
if (x0 <= x1) {
|
||||||
int bigendian = -1;
|
int bigendian = -1;
|
||||||
if (strncmp(im->mode, "I;16", 4) == 0) {
|
if (isModeI16(im->mode)) {
|
||||||
bigendian =
|
bigendian =
|
||||||
(
|
(
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16L") == 0
|
im->mode == IMAGING_MODE_I_16 || im->mode == IMAGING_MODE_I_16L
|
||||||
#else
|
#else
|
||||||
strcmp(im->mode, "I;16B") == 0
|
im->mode == IMAGING_MODE_I_16B
|
||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
? 1
|
? 1
|
||||||
|
|
@ -672,17 +672,17 @@ DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba};
|
||||||
/* Interface */
|
/* Interface */
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
#define DRAWINIT() \
|
#define DRAWINIT() \
|
||||||
if (im->image8) { \
|
if (im->image8) { \
|
||||||
draw = &draw8; \
|
draw = &draw8; \
|
||||||
if (strncmp(im->mode, "I;16", 4) == 0) { \
|
if (isModeI16(im->mode)) { \
|
||||||
ink = INK16(ink_); \
|
ink = INK16(ink_); \
|
||||||
} else { \
|
} else { \
|
||||||
ink = INK8(ink_); \
|
ink = INK8(ink_); \
|
||||||
} \
|
} \
|
||||||
} else { \
|
} else { \
|
||||||
draw = (op) ? &draw32rgba : &draw32; \
|
draw = (op) ? &draw32rgba : &draw32; \
|
||||||
memcpy(&ink, ink_, sizeof(ink)); \
|
memcpy(&ink, ink_, sizeof(ink)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) {
|
||||||
return (Imaging)ImagingError_ValueError(NULL);
|
return (Imaging)ImagingError_ValueError(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
im = ImagingNewDirty("L", xsize, ysize);
|
im = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize);
|
||||||
if (!im) {
|
if (!im) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) {
|
||||||
int nextok;
|
int nextok;
|
||||||
double this, next;
|
double this, next;
|
||||||
|
|
||||||
imOut = ImagingNewDirty("L", xsize, ysize);
|
imOut = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize);
|
||||||
if (!imOut) {
|
if (!imOut) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,13 @@ int
|
||||||
ImagingSaveRaw(Imaging im, FILE *fp) {
|
ImagingSaveRaw(Imaging im, FILE *fp) {
|
||||||
int x, y, i;
|
int x, y, i;
|
||||||
|
|
||||||
if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
|
if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
|
||||||
/* @PIL227: FIXME: for mode "1", map != 0 to 255 */
|
/* @PIL227: FIXME: for mode "1", map != 0 to 255 */
|
||||||
|
|
||||||
/* PGM "L" */
|
/* PGM "L" */
|
||||||
for (y = 0; y < im->ysize; y++) {
|
for (y = 0; y < im->ysize; y++) {
|
||||||
fwrite(im->image[y], 1, im->xsize, fp);
|
fwrite(im->image[y], 1, im->xsize, fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
/* PPM "RGB" or other internal format */
|
/* PPM "RGB" or other internal format */
|
||||||
for (y = 0; y < im->ysize; y++) {
|
for (y = 0; y < im->ysize; y++) {
|
||||||
|
|
@ -58,10 +57,10 @@ ImagingSavePPM(Imaging im, const char *outfile) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
|
if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
|
||||||
/* Write "PGM" */
|
/* Write "PGM" */
|
||||||
fprintf(fp, "P5\n%d %d\n255\n", im->xsize, im->ysize);
|
fprintf(fp, "P5\n%d %d\n255\n", im->xsize, im->ysize);
|
||||||
} else if (strcmp(im->mode, "RGB") == 0) {
|
} else if (im->mode == IMAGING_MODE_RGB) {
|
||||||
/* Write "PPM" */
|
/* Write "PPM" */
|
||||||
fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize);
|
fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,12 @@ ImagingFill(Imaging im, const void *colour) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingFillLinearGradient(const char *mode) {
|
ImagingFillLinearGradient(const ModeID mode) {
|
||||||
Imaging im;
|
Imaging im;
|
||||||
int y;
|
int y;
|
||||||
|
|
||||||
if (strlen(mode) != 1) {
|
if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
|
||||||
|
mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
|
||||||
return (Imaging)ImagingError_ModeError();
|
return (Imaging)ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,12 +103,13 @@ ImagingFillLinearGradient(const char *mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingFillRadialGradient(const char *mode) {
|
ImagingFillRadialGradient(const ModeID mode) {
|
||||||
Imaging im;
|
Imaging im;
|
||||||
int x, y;
|
int x, y;
|
||||||
int d;
|
int d;
|
||||||
|
|
||||||
if (strlen(mode) != 1) {
|
if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
|
||||||
|
mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
|
||||||
return (Imaging)ImagingError_ModeError();
|
return (Imaging)ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||