Merge branch 'main' into fix-qtables-and-quality-scaling

This commit is contained in:
Andrew Murray 2025-06-28 19:20:21 +10:00 committed by GitHub
commit 6938379542
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
84 changed files with 12833 additions and 417 deletions

View File

@ -1,4 +1,4 @@
mypy==1.16.0 mypy==1.16.1
IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6 IceSpringPySideStubs-PySide6
ipython ipython

View File

@ -1,3 +1,3 @@
python.exe -c "from PIL import Image" python.exe -c "from PIL import Image"
IF ERRORLEVEL 1 EXIT /B IF ERRORLEVEL 1 EXIT /B
python.exe -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests python.exe -bb -m pytest -vv -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests

View File

@ -4,4 +4,4 @@ set -e
python3 -c "from PIL import Image" python3 -c "from PIL import Image"
python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests $REVERSE python3 -bb -m pytest -vv -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests $REVERSE

View File

@ -51,6 +51,7 @@ LIBWEBP_VERSION=1.5.0
BZIP2_VERSION=1.0.8 BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0 LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0 BROTLI_VERSION=1.1.0
LIBAVIF_VERSION=1.3.0
function build_pkg_config { function build_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi if [ -e pkg-config-stamp ]; then return; fi
@ -98,6 +99,59 @@ function build_harfbuzz {
touch harfbuzz-stamp touch harfbuzz-stamp
} }
function build_libavif {
if [ -e libavif-stamp ]; then return; fi
python3 -m pip install meson ninja
if [[ "$PLAT" == "x86_64" ]] || [ -n "$SANITIZER" ]; then
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
fi
local build_type=MinSizeRel
local lto=ON
local libavif_cmake_flags
if [ -n "$IS_MACOS" ]; then
lto=OFF
libavif_cmake_flags=(
-DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
)
else
if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then
build_type=Release
fi
libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
fi
local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)
# CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
# of libavif) that disables support for encoding high bit depth images.
(cd $out_dir \
&& cmake \
-DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
-DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
-DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
-DBUILD_SHARED_LIBS=ON \
-DAVIF_LIBSHARPYUV=LOCAL \
-DAVIF_LIBYUV=LOCAL \
-DAVIF_CODEC_AOM=LOCAL \
-DCONFIG_AV1_HIGHBITDEPTH=0 \
-DAVIF_CODEC_AOM_DECODE=OFF \
-DAVIF_CODEC_DAV1D=LOCAL \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
-DCMAKE_C_VISIBILITY_PRESET=hidden \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
-DCMAKE_BUILD_TYPE=$build_type \
"${libavif_cmake_flags[@]}" \
. \
&& make install)
touch libavif-stamp
}
function build { function build {
build_xz build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
@ -132,6 +186,7 @@ function build {
build_tiff build_tiff
fi fi
build_libavif
build_libpng build_libpng
build_lcms2 build_lcms2
build_openjpeg build_openjpeg

View File

@ -23,7 +23,7 @@ cd $pillow
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }
& $venv\Scripts\$python selftest.py & $venv\Scripts\$python selftest.py
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }
& $venv\Scripts\$python -m pytest -vx Tests\check_wheel.py & $venv\Scripts\$python -m pytest -vv -x Tests\check_wheel.py
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }
& $venv\Scripts\$python -m pytest -vx Tests & $venv\Scripts\$python -m pytest -vv -x Tests
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }

View File

@ -35,5 +35,5 @@ fi
# Runs tests # Runs tests
python3 selftest.py python3 selftest.py
python3 -m pytest Tests/check_wheel.py python3 -m pytest -vv -x Tests/check_wheel.py
python3 -m pytest python3 -m pytest -vv -x

View File

@ -58,7 +58,7 @@ jobs:
- name: "macOS 10.13 x86_64" - name: "macOS 10.13 x86_64"
os: macos-13 os: macos-13
cibw_arch: x86_64 cibw_arch: x86_64
build: "cp3{12,13}*" build: "cp3{12,13,14}*"
macosx_deployment_target: "10.13" macosx_deployment_target: "10.13"
- name: "macOS 10.15 x86_64" - name: "macOS 10.15 x86_64"
os: macos-13 os: macos-13
@ -159,7 +159,7 @@ jobs:
# Install extra test images # Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images xcopy /S /Y Tests\test-images\* Tests\images
& python.exe winbuild\build_prepare.py -v --no-imagequant --no-avif --architecture=${{ matrix.cibw_arch }} & python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.cibw_arch }}
shell: pwsh shell: pwsh
- name: Build wheels - name: Build wheels

View File

@ -1,8 +1,8 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12 rev: v0.12.0
hooks: hooks:
- id: ruff - 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
@ -11,7 +11,7 @@ repos:
- id: black - id: black
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
rev: 1.8.3 rev: 1.8.5
hooks: hooks:
- id: bandit - id: bandit
args: [--severity-level=high] args: [--severity-level=high]
@ -24,7 +24,7 @@ repos:
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: v20.1.5 rev: v20.1.6
hooks: hooks:
- id: clang-format - id: clang-format
types: [c] types: [c]
@ -51,7 +51,7 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/python-jsonschema/check-jsonschema - repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.0 rev: 0.33.1
hooks: hooks:
- id: check-github-workflows - id: check-github-workflows
- id: check-readthedocs - id: check-readthedocs

View File

@ -9,7 +9,7 @@ from .helper import is_pypy
def test_wheel_modules() -> None: def test_wheel_modules() -> None:
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"} expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp", "avif"}
if sys.platform == "win32": if sys.platform == "win32":
# tkinter is not available in cibuildwheel installed CPython on Windows # tkinter is not available in cibuildwheel installed CPython on Windows
@ -20,6 +20,10 @@ def test_wheel_modules() -> None:
except ImportError: except ImportError:
expected_modules.remove("tkinter") expected_modules.remove("tkinter")
# libavif is not available on Windows for ARM64 architectures
if platform.machine() == "ARM64":
expected_modules.remove("avif")
assert set(features.get_supported_modules()) == expected_modules assert set(features.get_supported_modules()) == expected_modules

View File

@ -272,7 +272,7 @@ def _cached_hopper(mode: str) -> Image.Image:
else: else:
im = hopper() im = hopper()
if mode.startswith("BGR;"): if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="BGR;"):
im = im.convert(mode) im = im.convert(mode)
else: else:
try: try:

View File

@ -0,0 +1,390 @@
/* XPM */
static const char *hopper[] = {
/* columns rows colors chars-per-pixel */
"128 128 256 2 ",
" c #0C0C0D",
". c #0A0708",
"X c #1C0A04",
"o c #120B0C",
"O c #170808",
"+ c #0B110D",
"@ c #16120C",
"# c #0D0D12",
"$ c #0D0D1A",
"% c #070A16",
"& c #120D13",
"* c #120E1A",
"= c #1A0C16",
"- c #0D1114",
"; c #0D121B",
": c #091518",
"> c #131215",
", c #14131B",
"< c #1A141C",
"1 c #1B191D",
"2 c #191517",
"3 c #250906",
"4 c #390904",
"5 c #27150A",
"6 c #250A18",
"7 c #251719",
"8 c #361410",
"9 c #342215",
"0 c #0C0C24",
"q c #0C0D2B",
"w c #060927",
"e c #130D24",
"r c #150D2A",
"t c #0C1225",
"y c #0C122C",
"u c #061227",
"i c #151422",
"p c #1A1522",
"a c #1C1B23",
"s c #13132C",
"d c #19172A",
"f c #0C0D35",
"g c #130E37",
"h c #0D1436",
"j c #131333",
"k c #13143C",
"l c #191838",
"z c #241926",
"x c #231B38",
"c c #2E1226",
"v c #372628",
"b c #292538",
"n c #362B37",
"m c #2F2A2F",
"M c #1A2233",
"N c #4C150D",
"B c #740F10",
"V c #512916",
"C c #793419",
"Z c #6D2C13",
"A c #4E1524",
"S c #741624",
"D c #4E332E",
"F c #6F3629",
"G c #574438",
"H c #744831",
"J c #775A2E",
"K c #0E1444",
"L c #141443",
"P c #1B1A44",
"I c #14144B",
"U c #1A1B4C",
"Y c #181747",
"T c #1B1B53",
"R c #181955",
"E c #0F0E44",
"W c #231C46",
"Q c #231C56",
"! c #1C234E",
"~ c #272547",
"^ c #2E2F52",
"/ c #2E3765",
"( c #483947",
") c #742D4A",
"_ c #364970",
"` c #534A51",
"' c #6E534D",
"] c #756654",
"[ c #53556D",
"{ c #6B5B69",
"} c #746B71",
"| c #5E616A",
" . c #880C15",
".. c #881217",
"X. c #8D0D0F",
"o. c #8B3218",
"O. c #8C3828",
"+. c #AC2F30",
"@. c #9A1825",
"#. c #CE202B",
"$. c #8A452A",
"%. c #974A2B",
"&. c #884934",
"*. c #954B35",
"=. c #995539",
"-. c #895736",
";. c #A75738",
":. c #A84E30",
">. c #996839",
",. c #B6683B",
"<. c #AE6835",
"1. c #A35419",
"2. c #D26D19",
"3. c #CC712E",
"4. c #CD6922",
"5. c #A83152",
"6. c #985845",
"7. c #8A5748",
"8. c #AE5A46",
"9. c #916A4F",
"0. c #A96647",
"q. c #B76947",
"w. c #BA744A",
"e. c #B97757",
"r. c #AB6F53",
"t. c #8D736D",
"y. c #B27669",
"u. c #91566F",
"i. c #C56B4A",
"p. c #C8764B",
"a. c #C87856",
"s. c #D47A59",
"d. c #C96E53",
"f. c #C77C64",
"g. c #D17969",
"h. c #D45D68",
"j. c #C52A46",
"k. c #D58932",
"l. c #B38355",
"z. c #968775",
"x. c #BA8667",
"c. c #B38C74",
"v. c #AB9C73",
"b. c #C9845A",
"n. c #D7855B",
"m. c #D39454",
"M. c #E28C5B",
"N. c #F7B251",
"B. c #C78867",
"V. c #D98866",
"C. c #D8956A",
"Z. c #C79878",
"A. c #D89876",
"S. c #CD8C70",
"D. c #E38A68",
"F. c #E5956A",
"G. c #E79776",
"H. c #ED9176",
"J. c #D6A371",
"K. c #E8A379",
"L. c #F3A677",
"P. c #D8A05D",
"I. c #3D65AB",
"U. c #3F67B2",
"Y. c #3B5C9C",
"T. c #506796",
"R. c #72748D",
"E. c #446AAE",
"W. c #4869A9",
"Q. c #4166B2",
"!. c #436BB3",
"~. c #496EB4",
"^. c #476DB9",
"/. c #4A71B6",
"(. c #4C73BA",
"). c #4772B6",
"_. c #5176BC",
"`. c #547BBD",
"'. c #577BB7",
"]. c #5572A9",
"[. c #6B7CAA",
"{. c #505B8C",
"}. c #557CC1",
"|. c #4C73C2",
" X c #897987",
".X c #9F7593",
"XX c #C46B87",
"oX c #5981BF",
"OX c #5884BD",
"+X c #768AB9",
"@X c #7288B5",
"#X c #5C83C3",
"$X c #5D8AC5",
"%X c #6186C5",
"&X c #648AC6",
"*X c #6B8DC6",
"=X c #668BC9",
"-X c #6B8ECA",
";X c #6586C6",
":X c #738DC7",
">X c #6D91CB",
",X c #6C94C6",
"<X c #7294CC",
"1X c #7895C8",
"2X c #6E92D1",
"3X c #7294D3",
"4X c #7698D5",
"5X c #708ED1",
"6X c #7799E3",
"7X c #9B9399",
"8X c #928890",
"9X c #B89887",
"0X c #A99191",
"qX c #B9A598",
"wX c #B1A394",
"eX c #8C8EAA",
"rX c #AB9AA6",
"tX c #ABA4A9",
"yX c #B7A9A8",
"uX c #B7ABB4",
"iX c #B6AFB7",
"pX c #C69B86",
"aX c #D4978B",
"sX c #EF9C83",
"dX c #CAA487",
"fX c #D7A787",
"gX c #C7A899",
"hX c #D1B294",
"jX c #E9A887",
"kX c #F8A886",
"lX c #F9B798",
"zX c #F1B291",
"xX c #C9B3AD",
"cX c #F4B9A5",
"vX c #D497B3",
"bX c #D5C6B1",
"nX c #FEC4A6",
"mX c #EAD0B2",
"MX c #EDD1A4",
"NX c #8399C8",
"BX c #B2B4CD",
"VX c #C7BBC7",
"CX c #D3CBCF",
"ZX c #ECDAD1",
"AX c #F6E6DA",
"SX c #F7EACF",
"DX c #D1D1E9",
"FX c #E7DDE4",
"GX c #E9E5E8",
"HX c #F7EAE6",
"JX c #FDF6E9",
"KX c #FEFCFE",
"LX c #FAF7F7",
"PX c #F1EBF6",
"IX c #DCE2E5",
"UX c #BEC5DF",
/* pixels */
"L k f k P l y j T R I I U U L U R Q T L E E E R R R E U j } GX9XfXpXxXR.j ~ ~ = V Z.G > b R.DXPXLXHXHXHXHXCX~ / T.Y.T.T.W.T.W.E.Q.I.E.I.I.E.E.I.I.I.I.I.Y.I.Q.^.Q.E.E.E.E.Q.Q.~.U.U.U.U.U.U.Q.Q.U.U.U.U.U.U.Q.Q.Q.Q.U.U.U.Q.~.~.Q.U.Q.~.^._._._._._._._._.(.(.(.",
"L k f L L k y h T R I L U U L U R R T L E E E R I R I U l XuX' fXV v [ / P h z V Z.G a y l [ 7XCXHXJXHXHXCXb ! {.{.T.{._ _ {.T.W.W.T.T.W.I.I.U.U.I.I.E.W.I.I.Q.Q.E.E.E.Q.Q.~.~.~.U.U.U.U.Q.Q.Q.Q.U.U.U.U.U.Q.~.~.~.U.U.U.U.U.Q.~.Q.Q.Q.~.~._._._.'.`._._._._._.",
"L k f L L k 0 h T T I E U U L T T T U L h h E U R R E U W R.{ D pXF z l L U ^ p F fXD i P W Y ~ n CXHXHXHXFX8Xl W ~ ~ l ^ b b ^ ^ / [ T.W._.U.^.U.U.Q.E.W.W.~.^.E.E.E.~.~.Q.~.~.~.~.~.~.~.~.Q.Q.Q.Q.U.U.U.U.U.U.~.Q.U.U.U.U.U.U.^.~.~.Q.~.~._._.`.`.`.`._._.|.|.",
"k k f L L k 0 h U T L h I U I T T T U k h k E U Q I E U k ` m ' hXV z k U I Q d V fX( j L U W W z VXLX8X XuX( z b x d ` X X` n n b b ! {.W.~.I.Q.Q.Q.E.W.].~.I.~.~.~.~.~.~.~.Q.~.~.~.~.~.~.~.~.~.Q.Q.Q.U.U.U.U.U.Q.U.U.~.~.Q.Q.Q.Q.Q.Q.Q.~._._._._._._._._.|.|.",
"k k f L L k q j U T L h U U I T R T U k h h E I E R I I k b p ' Z.V z k ! T U p H Z.c k U L U W n CXCXn z = c c v 7X8X` 8XPX} c R.tX` n b / {.].W.~.~.E.W.E.~.^.~.~.~.~.^.~.~.~.~.~.E.E.~.~.~.~.~.~.~.~.~.~.U.U.~.U.~.~.~.~.~.Q.U.Q.~.~.E.~.~./././.(.(.(.(.(.(.",
"h k h P L k q j U T L h U U I T R T U h h h E E I R I E k d p ' dXV x P U L L z J fXv l L L P j n IX` m = = 7 ' HXLXKXCX7XKXtXrXLXKXJXqXv n ^ {.T.T.T.W.].E.Q.^.~.~.~.~.^.^.~.~.E.E.E.E.E.~.~.~.~.~.~.~.~.~.U.U.~.~.~.~.U.U.U.U.Q.Q.~.~.E.~.~.E.~.~.~./././.(.(.",
"j k k P Y k q h U U k h U R I R R T U h h h E E R R E I Y d d ' Z.V p L ! ! Y = H Z.v j h ! l b n iXtX7Xa p t.LXZX0XHXPXKXKXPXLXLXCXbXAXVXn m 8XVXeX[.T.W.W.^.^.~.~.~.^.^.^.^.~.E.E.E.E.E.~.U.U.~.~.~.~.~.~.~.~.~.~.~.U.I.I.I.~.~.Q.Q.~.~.~.~.E.~.~.~./.(.(.(.(.",
"j k k U P k 0 y L U k h U R I R T Q T E f E E E E I I I f k z ` Z.V d U T L P z >.B.c j l l l } IXKXKX8Xp ` t.` t.' G ] tXIXIXwX] ' z.t.` { c n PXKXLXUX+X].E.~.~.~.~.~.^.^.^.~.Q.E.E.E.E.U.U.U.~.~.~.~.~.~.~.~.~.~.I.I.I.I.~.~.~.Q.Q.Q.~.~.~.~.~.~.~./.(.(.(.(.",
"j k R.~ k k q q Y T k f Q T I I I Q I L E L E E R I I E f d x ` dXV d T T T U z -.Z.c b s b CXLXKXLXKX} z 7 7 z.hXSXSXz.AXbXmXbX0XJXmXmX` 1 n b iXKXLXLXLXDX@XT.].W.E.(.U.|.^.^.~.~.W.E.~.~.~.~.~.~.U.~.~.~.~.~.~.U.I.I.I.U.~.~.Q.~.~.~.~.E.~.~.~.~.~.~.~.~.^.^.",
"j ~ DX[ W q s q Q I f h Q L R R R R I I L f E I I I I L P d x ` dXV d R R T Y z -.Z.7 0 ` GXLXKXJXLXJX` 7 7 7 t.hXMXmXJXLXJXJXJXJXSXSXmX' n z b 7XKXKXPXKXLXPXBX].T.W./.^.^.U.|.~.~.~.~.~.~.~.^.~.U.U.~.~.~.~.~.~.~.U.U.I.U.~.~.~.~.~.~.E.E.~.~.~.~.~.~.~.~.^.^.",
"r x DXIX~ s $ b L U L L Y Q L I I I T L f L U E T T L k k d x ` dXV x T R U L p 9.Z.7 | KXKXLXLXJXSXZXD v 7 7 D mXMXmXSXZXZXCXZXAXZXdXmXG v n n 7XKXLXKXKXKXLXKXDX[.T.]./.I.}.U.~.~.~.~.~.~.~.U.~.U.U.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.U.~.~.~.~.^.^.^.^.",
"r b CXPX X[ iX[ Y U L k P [.~ k U T U L f f f L I U U k f d x ` dXV z T T U L z 9.x.D LXHXJXJXZXqXqXmXD @ 7 7 9 ] mXbXJXKXKXKXLXJXJXMXv.9 7 7 7 } HXKXKXHXLXJXLXKXDX[.T.W.(.~.^.Q.Q.E.E.E.~.U.U.~.U.U.U.~.U.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.U.~.~.~.~.~.^.^.^.",
"d x VXKXPXGXCX` P U L Y ~ BX| l k P k k k P w k h L L P j d d ( dXZ z P ! L k z 9.B.mXJXJXSX9Xz.t.D 5 5 5 7 7 9 hXmXv.mXLXKXKXKXJXSXv.mX] v c v D t.xXZXJXJXJXLXPXKXUXT.W.E.~.~.Q.Q.E.E.E.E.Q.Q.~.I.I.~.~.E.E.~.~.~.~.~.~.~.~.~.~.~.~.I.~.~.U.U.~.~.~.~.^.^.^.(.",
"x 8XGXPXHXHXtXb k U U k l CXtXd b ~ | {.j q k f P / h k k d d ( dXF < k ! L k z 7.zXSXJXSXt.] V 3 3 X 5 @ 2 c 7 z.v.bXSXAXKXLXLXZXmXhXMX' 7 n 7 9 3 8 ] qXZXJXLXLXKXPX@X].W.I.^.~.~.~.E.E.~.~.~.E.I.E.~.~.E.E.~.~.~.~.~.~.~.~.~.~.~.~.~.~.U.U.U.~.~.~.~.^.^.(.(.",
"uXGXHXLXJXAX} & W Q g g ~ DXCX` [ VXDX[ s j s y ^ eX~ j j d l ( pXF 7 k ! L L z 7.nXJXAX] D 3 3 3 X ' ] 7 1 = 9 t.SXSXMX9XZXJXJXxXmXSXSXJ v v 7 9 ] 9 9 5 ' 9XxXJXHXGXDX{.'.).~.~.~.E.E.E.E.~.~.~.~.~.~.~.E.I.~.~.~.~.~.~.I.I.~.~.~.~.~.~.~.U.~.U.U.~.~.^.(.(.(.",
"iXFXPXLXLXLXyX( k W k ~ b CXGXFXPXGXtXl l s 0 j ^ DX` d d d x D pXF z P T L P z ' AXAXz.5 X 3 9.] 9 5 v.5 G ` 9 J hXhXhXmX9X' ] qXhXhXMX] 9 D 9 G z.5 ] t.8 8 G wXHXPXIX[.T.W.].~.~.~.~.E.E.~.~.~.~.~.~.~.E.E.~.I.~.~.~.~.I.I.I.~.~.~.~.U.U.U.~.U.U.U.~.^.(.(.(.",
"d n } LXLXCXVX[ W W d ` tXHXAXAXHXIX^ x j l w s ` GX7X7 n } ~ D dXH p k ! P k l ` xX8X2 @ 5 7 gXbXhXhXv.hXmXSXmXMX9.J 5 5 V 9 9 9 V G dXhXSXSXdXhXl.MXMXdXV J V 9 wXLXFXtXR.T.T.W.~.^.~./.E.E.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).~.~.~.^.^.^.^.~.^.(.(.(.",
"d d [ LXVX( ^ ~ k ^ 7XFXLXHXJXHXAX} x l k w l i ` GXCX8XbX Xx v Z.H z k ! P d d i . & @ . 2 7 v z.v.V dXmXdXZ.mXSXSXSXbXt.` 7 D ] bXJXSXSXSXMXSXSXl.hXMXhXmXMXV 5 v xXxX} ^ ! {.W.~.^.U.).E.E.E.~.~.E.E.~.~.~.E.~.~.~.~.~.~.~.~.~.~.).).).).(.(.(.(.(.(.(.(.(.(.",
"f ~ ` PXR.l l j Y ~ { uXFXLXJXHXFXuX~ W f f d a } HXZXxXyXn d n Z.H 7 j P l j r p & o @ @ @ o 7 X 9 D ] V 5 hXbXqXv.] G D ` n ` G ' 0X9XmXmXz.G 9.9.3 8 ] hXgX9XwXv D z > $ l ! W.~.^.U.).E.~.E.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.~.)./.(././.(.(.(.(.(.(.(.(.(.(.",
"g W ^ DX( l l q L W r b ` CXLXLXPXPXR.k k ^ | 8XCXHXbXxX{ < d v Z.J 7 j l d r * . > 2 o . @ 2 = 7 X X 5 D 5 5 5 9 9 9 @ 7 7 2 v 7 7 v 9 v V D G qXgXxXD 3 3 ' z.D 9 2 > 1 # d u Y.W.~.~.).E.W.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.~.)././././.(.(.(.(.(.(.(.(.(.(.",
"U U / eXP P l f L L d r b CXPXR. XUXDX/ k ~ ` 7XCXZXZXyXv p k v B.-.o d l i * * & o o 2 @ . & . < & o 7 o 7 2 @ @ @ 7 2 v < z < z 7 2 7 9 7 5 9 ] z.ZXCX` 7 5 X @ @ & . o # % u _ W.E.E.E.E.E././././././.~.~.~.~.~.~.~.~.~.~.~././.(.(./.(.(.(.(.(.(.(.(.(.(.(.",
"f U ~ / L U k f U Y k x d DXVX~ x W {.[ f d 2 7 t.ZXZXxX} x k z x.-.3 d a $ & & . 2 o . @ . # p # , & . > & o z 7 2 2 2 o & < & < . > 7 > 7 7 7 v ' m 7 @ 2 . @ . + . > . > % y _ W.E.E.E.E.~./././.(.(././.~.~.(.(.~./.~.~.~.~.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"I T P P ^ ~ k k L U q l ~ DX{.W W Q Q ~ d * * o { HXxXVXCX^ q z x.>.5 i , # & & > o > . + + > . # . * * . < & . o o 2 7 & 2 2 2 > > < > 2 7 o o @ . @ . o . . + . . # & . . ; u / ].W././././.(././.(.(././././.(.(.(.(.(.(././.(.(.(.(.(.(.(.(.(.(.(.(.(.(.}.}.",
"E Q L P Q Q P f f L k k ^ BXU ~ P W T Y j i * * XFX` b 7XR.l 7 l.>.7 , # # < o o . > . - - - $ # # . & , . & . o o o . . o . o o . . . . . o o o @ . . . . 2 . + . - . > > . w _ ].W./././.~.(./.(.(.(.(././.(.(.(.(.(.(.(.(.(.(./.(.(.).).(.(.(.(.(.(.(.(.(.}.",
"I U U U T Q k h L L h P Q / T L U T T U j 0 0 r 7XuXd r d ^ l < r.0.5 ; - - - & . > # # - + % # . . + . # # . . . . . . . . . . & # . . o o o o . o o . . . . . + . # # . > ; w _ ].]./.E.(.(.}.(.(.(.(./././././.(.(.(.(.(.(.(././././.(.(.(.(._.(.(.(.(.(.(.(.",
"L U U U T Q k q k L k P U Q U U U T T U k q q r X( j d 0 t y 7 r.0.@ ; + - - & . > & . . - - , - + + + . . + > . . . . . . . . . . . . o o o o o o o . . . . . . . % . . . - t / ].].]./.`.(.|.(.(._._.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"L U U T T Q k q k k k P I I I T R R T U L f q f / L W q w s s 2 0.r.X ; % - # # > > . # > + . . . . . @ @ o . o o o o o o o o o o o o o o o X X o o o o o . # # - . # > o . # w ^ ].].'./.`.(.}.(._.`.`.`._._._.`.`.`.`._.(.(.(._._.(.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"L U I T R Q j q k k h L I I R T R R T U L f q f E T U f j j 0 7 0.l.X ; % - # . . # . . . . o @ X X X X X X X 3 3 3 3 3 o o 3 3 o o 3 3 3 3 3 3 3 3 X X o X o o . . . & o o - % ! ].].'./.(.(.}.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.(.`.`._.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"k U U T R Q k q l k f L T I T T R R T U k f 0 q I R E L q q q 7 >.l.X , % + . . . & = o @ X X X 3 4 N N N V V V V N N N N N N N N N V V F F H H H H F V 8 3 3 3 o . & o . . > % P ].].'./.'._.}.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.(.(.(._._.`.(.(.(.(.(.(._._.",
"k P U T R Q k q k k k L T I T T R R T U k q 0 q I I T f w j j 6 >.r.3 - . + + . > . X @ X X 3 N F 6.r.y.y.y.y.y.y.r.r.0.6.7.6.7.7.6.6.0.r.y.B.B.y.y.x.x.y.7.V 3 X . & & @ . # % h ].].'.'.'.`.}.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`._._._._.(._._.}.",
"k P U T Q Q k q f l k L T E T T R R R U j 0 0 y k E I L k j q 7 0.<.3 # . + . . o o X X 3 9 ' c.Z.A.aXaXaXaXjXjXjXA.A.A.S.B.S.B.S.S.A.A.G.K.K.G.A.C.C.Z.A.Z.r.' 9 o X o o @ - ; u ].].'.'.'.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.}.`.`.`.`.`._._._.}.}.",
"d W U R R Q k q f k g L T I Q R T R R U j 0 0 y k L I I f f f 6 r.>.3 # . . . . X 7 7 8 G t.pXaXaXA.A.fXzXzXzXlXzXjXzXlXzXzXzXzXkXzXkXzXlXlXzXjXK.K.K.A.A.K.Z.c.t.G 5 3 X o . t u '.'.'.'.'.'.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.`.}.}.`.`.`.`.`.`.}.}.}.}.",
"` b U Q R Q k q q l L L L T T R T T R U k 0 0 t j U I E L f f 7 r.r.X . . @ o o v ' F H y.Z.fXfXK.jXjXzXzXzXzXlXcXlXzXlXzXzXnXcXzXlXlXlXzXnXnXcXlXjXzXA.jXA.J.B.c.t.-.D 3 X & % K '.'.].'.'.oX`.oX`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.}.`.}.}.}.#X#X",
"xXz W Q I _ ~ y j j f U I I U U U R T U k q 0 t k U I L L f f = 0.l.X @ @ . . 9 ' ' H 0.B.A.fXfXjXjXzXzXzXlXcXcXzXzXlXnXcXzXzXzXcXcXlXcXzXzXzXzXzXcXjXA.G.A.C.B.Z.y.e.-.D o @ % K '.'./.'.'.'.oXoXoX`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.}.}.}.}.}.}.}.}.}.",
"HX7Xx ^ eXBXM $ l x Y U R R I R I I T U k q 0 y k U I I k j j = 0.l.X > o o 7 D ' H 7.y.B.A.fXfXjXzXzXzXzXzXjXjXzXzXlXnXlXzXzXzXjXzXzXsXsXD.B.e.x.x.S.A.B.B.S.Z.Z.c.l.e.' 7 @ , ! [.'.`.'.'.oX#XoXoXoXoXoXoXoXoXoXoXoXoXoX`.`.`.`.}.}.#X#X}.#X#X}.}.}.#X}.#X}.}.",
"AXZX{ CXPX| d 0 ` R.d Y U R I U E L R U k q q y L U U U k h l o r.l.X > . o v D H H r.x.l.B.A.A.B.B.B.e.e.e.B.S.jXzXzXlXzXzXcXzXfXjXjXD.D.a.q.e.r.e.Z.A.jXA.Z.Z.Z.c.e.e.9.G 5 # ^ [.'.`.'.'.'.'.oXoXoXoX#X#XoXoX#XoXoXoXoXoXoXoX#X#X#X#X#X#X#X#X}.}.#X#X#X#X#X#X",
"ZXHXHXGXVXb i i ` FX^ W Y g ~ P k L U I k q q q L U I U k q y @ >.l.X $ & 7 9 F ' 7.r.e.x.Z.A.A.A.V.a.a.a.f.B.A.A.jXzXzXzXzXlXzXzXfXA.D.D.8.*.=.*.6.r.B.fXaXfXc.c.Z.B.9.t.` v 7 ^ @XoX'.'.#X%X}.#XoXoXoX#X#X#XoXoXoXoXoX#X#X#XoX#X#X#X#X#X#X}.}.#X#X}.}.#X#X$X$X",
"HXAXZXFX{ x b # { FXrXx x {.eX^ k L U L k q 0 q f L E E f 0 t @ >.<.X , & 7 G 7.7.9.r.x.Z.Z.S.S.B.e.0.6.6.0.0.0.B.A.jXK.A.jXlXzXjXfXA.g.i.:.%.*.O.Z F Z Z F F F H ' -.7.t.` v c [ @X@X'.oX=X#X#XoXoXoX#X#X#X#XoX#XoXoX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X;X;X",
"ZXAXZXGX` b & & ` ZXHX} uXGX8Xl k L U L j r r 0 r W Y f q 0 i 5 w.>.5 , . 9 7.9.-.-.9.c.c.x.B.B.x.r.9.7.7.6.r.y.r.B.A.jXG.jXlXlXzXK.B.e.0.=.C N Z &.6.*.&.H N V t.' V F ' { v v [ @XoX'.oX#X`.#X#X#XoX#X#X#X#XoX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X$X",
"AXJXHXFXIX^ x = ] ZXAXAXCXVXb j k L U Y d i & & x ^ ~ f q q i X 0.<.5 & 7 D 9.9.-.J H ' G V V N 4 8 N N N N V Z $.r.G.lXlXzXlXnXlXK.b.0.C C 6.B.r.y.y.S.r.c.SX9.8 V A D ` ' D D R.eX+X%X$XoXoX$X#X#X#X#X#X#X#XoX&X#X#X#X#X#X#X#X#X#X#X#X%X%X#X#X#X#X#X#X#X#X#X#X",
"gXAXCXFXPXuXz D bXAXSXZXCX{ b k L L Y W r ` n v 7XtXx j w r r 3 0.>.8 2 9 ] y.9.H F 8 D D V N 0.cXaXy.r.r.B.S.*.O.Z <.n.kXkXL.L.F.p.;.0.jXy.9.V N N F F r.r.c.MXD V ' F D ' u.' XR.+X%X$X$XoX#X#X#X#X#X#X#X#X#X#X#X%X&X&X%X#X#X#X#X#X%X%X%X#X#X%X#X#X#X#X#X;X;X",
"z.ZX` ( { eX{ n CXZXZXZXxXm x k L U Y W r ` } z.iX` r w r r 0 3 0.>.8 D G 9.r.-.F V 3 8 3 N r.y.y.7.F V V N &.B.a.w.p.p.F.F.F.b.p.,.,.q.0.6.V H N V V N C $.H C H F V N V H r.9XgXrX[.*X&XOX&X=X%X%X%X#X#X#X#X#X&X&X&X&X&X&X%X%X%X%X%X%X%X%X%X#X#X#X#X%X%X;X=X=X",
"z.xXx ~ f x x z t.ZXAXAXCX} x Y U T U k p v ] ] } z r r 0 r s 6 r.r.8 D 9.r.6.C V V D D 8 7.9.r.' V N N N N Z F 0.;.;.s.n.p.a.p.3.1.p.p.;.Z O.V 4 N N N B o.o.%.0.-.H H &.r.f.6.y.yX@X:X'.oX-X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X%X%X%X%X#X%X%X%X&X=X=X=X",
"} } k W U k 0 & v CXAXCXGXCX~ f L T U k z D t.] 9 o p d w r d = >.l.N D c.e.0.6.F H V F V H F 6.V N V 4 4 N &.6.C O.%.s.i.F.zXkXF.n.M.s.;.i.8.=.&.O.F O.%.%.;.q.B.e.b.w.;.;.q.o.Z 0XeX:X[.@X-X'.&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X%X=X=X=X=X=X%X%X",
"[ ( Q L I U r * 7 CX8Xv } iXR.j L T U k r ( } t.{ . r r s 0 r 6 >.w.N ' y.$.$.=.6.6.6.r.6.$.O.O.O.C O.O.=.=.=.w.d.n.n.M.:.G.lXlXL.,.H.H.D.d.q.g.a.q.0.,.q.d.s.p.a.b.C.w.<.;.d.s.Z t. X[.:X;X;X=X&X&X&X&X&X&X&X&X=X=X=X=X&X&X&X&X&X&X&X=X=X=X=X=X&X=X=X=X=X;X;X%X",
"~ k L T T U 0 $ b CX` x x x ^ k L L U P d ( ' } 7X` r r s s 0 o 0.w.4 7.6.O.8.8.0.6.B.B.0.;.o.o.o.o.:.s.G.V.s.V.s.H.kXF.%.G.lXlXlXF.H.L.D.s.H.G.kXzXsXD.s.D.F.n.V.V.V.w.<.n.V.a.O.y. X[.:X;X;X2X=X=X=X=X=X=X=X=X=X=X=X=X=X&X&X&X&X&X=X=X=X=X=X=X%X=X=X=X=X=X=X=X",
"f k Y U L k 0 $ b 7Xj W Q L k q L U U Y r p 2 7 [ } d d 0 s t o r.r.4 F &.o.q.8.=.;.a.C.w.w.:.O.O.;.d.sXsXkXH.s.i.L.lXs.q.kXlXzXlXK.a.kXkXV.a.G.L.H.D.M.p.D.F.V.A.A.A.B.q.D.kXV.%.y.eXoX@X*X$X=X=X=X=X=X=X=X=X-X=X=X=X=X=X=X&X&X&X&X=X=X=X=X=X=X=X=X=X=X=X=X=X=X",
"k j L k P P q 0 x ^ Y Q R R L k h U U k j * # * p x k l q 0 t @ >.w.8 V 6.C a.g.q.%.w.n.p.p.d.q.:.i.i.V.s.i.q.d.lXkXH.,.V.lXlXlXzXlXs.G.K.lXkXn.n.i.p.i.p.p.G.C.B.V.B.B.a.q.V.A.=..X@X*XoX-X=X$X=X=X=X=X>X-X-X>X-X-X-X-X-X=X=X&X-X-X-X=X=X=X&X&X=X=X=X=X=X=X=X=X",
"j k L U U k q 0 j j Y U U L L L Y W L k y w ; $ 0 q h k q y y o >.w.4 8 r.O.V.s.8.%.d.s.n.p.n.q.%.i.p.a.a.f.sXlXlXzXF.q.kXlXnXcXnXnXjXB.zXlXlXnXlXkXG.V.V.G.L.F.V.n.b.b.8.o.i.D.r.t.@X+X@X*X-X>X>X-X*X*X*X-X*X*X*X*X-X>X-X*X&X*X-X-X-X-X=X=X&X&X=X=X=X=X=X=X=X=X",
"j k L U U k q 0 j f k Y Y k k L U W L k y u t 0 w j f f k y s 3 0.l.3 V r.=.D.s.:.%.i.i.n.n.V.q.,.s.G.kXlXnXnXcXlXnXb.C.zXlXcXnXnXlXnXS.G.zXlXlXlXlXzXK.K.K.F.V.V.n.,.C.d.o.;.S.c.8XeX:X@X;X-X=X>X-X*X*X*X-X*X*X>X-X-X-X-X*X*X*X-X-X-X-X=X=X=X=X=X=X=X=X=X-X-X=X",
"j k L L P j 0 0 j k L P Y k k L U U L h y 0 r r f f f f k w i 5 >.w.V 9XcXe.V.V.%.q.s.i.p.n.b.w.:.,.L.nXnXnXnXnXnXlXq.jXlXlXcXnXnXlXlXjXq.sXzXzXlXlXK.F.G.F.V.n.V.p.w.C.sX8.q.f.9X8X+X*X*X-X2X-X>X-X-X-X-X-X-X-X>X-X-X-X-X-X*X>X-X-X-X-X-X=X=X=X=X=X-X-X-X-X-X-X",
"h k L L P j 0 0 j k Y U P Y k Y Y U k h y w r r j r j f y t r X C C.c.hXcXA.K.p.w.A.C.q.<.p.a.b.p.i.F.lXcXnXlXzXjXq.q.G.kXlXcXlXnXnXnXzX0.:.s.H.G.G.G.K.kXkXF.n.a.e.b.C.kXD.q.y.0XeX+X*X*X-X-X=X>X>X-X-X-X-X-X-X>X-X-X-X>X-X-X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"h k L L k s $ $ y j L Y Y L L Y Y U k h y 0 r r r f k r r d & 9 w.C.c.hXcXA.G.n.C.jXG.q.<.p.w.b.D.i.p.D.kXkXV.i.;.:.D.H.kXlXnXcXcXlXlXlXsXi.8.8.:.;.a.G.G.G.F.a.a.a.l.C.kXs.q.S.8X@X:X>X-X-X>X=X>X-X-X-X-X>X-X-X-X-X*X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"k k L L l t $ $ y k L Y L L L Y Y P k h y 0 r r r f f s r # 7 t.J.P.c.hXMXfXC.G.C.K.K.a.q.i.w.p.F.M.i.:.%.;.;.:.o.s.kXH.kXzXcXlXlXlXzXzXsXH.8.H.H.H.sXkXD.V.V.a.b.e.B.A.G.q.V.B.8X@X*X-X-X-X2X2X>X>X-X-X>X>X>X-X-X-X-X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"k L L L l t % $ y k L I P P L L L Y h h y y s q r r r s * & ' hXK.J.x.hXcXx.B.K.C.A.J.b.i.i.p.b.a.F.D.s.V.H.V.i.:.H.D.V.G.sXzXzXjXG.sXV.s.s.:.q.H.kXkXH.D.n.n.V.b.e.B.A.b.V.G.c.eX:X>X-X&X*X*X&X>X>X-X-X>X>X>X>X>X-X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"k k L k j t # ; y k L I L L L k g L f h y y s 0 q r s r * D wXgXJ.P.x.cXMXl.b.C.jXA.C.e.q.i.a.b.p.n.V.H.sXkXV.%.o.;.O.%.q.q.f.B.B.f.8.:.Z O.B O.s.G.H.D.H.V.V.C.B.e.B.0.C.K.s.pXeX&X=X-X,X,X<X*X>X>X-X-X>X>X>X>X>X>X>X>X>X-X-X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"f P k f ~ 0 $ ; j w I T L L f L Y P k y y y y % 0 w y i ` 0XgXhXP.P.x.cXhXl.x.A.b.b.%.w.p.i.a.n.p.n.V.D.G.V.:.o.V.q.Z Z C o.$.$.$.O.=.o.%.g.:.B :.s.g.s.V.n.V.C.V.B.e.q.q.w.A.9X+X-X=X>X*X*X*X,X*X>X>X>X>X-X>X>X<X>X>X-X-X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X2X3X3X",
"d j f W s s $ % s k g L L k h g g k f y t t t t w y t | tXwXgXfXP.P.B.cXhXc.x.A.C.B.C.K.V.i.p.n.n.a.F.V.V.i.o.i.G.G.V.V.V.0.o.N C 8.g.V.g.D.i.Z B ;.d.s.s.s.V.C.B.C.kXG.K.C.fX0X+X=X=X-X*X*X*X:X*X>X>X>X>X-X>X>X>X>X>X-X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X>X>X",
"p d W / d $ i i 0 k P U P k L k g f q q i i $ i $ d 8XiXrXgXgXfXP.P.B.cXhXZ.x.S.jXA.jXkXsXa.p.n.F.n.n.V.i.O.:.D.H.D.H.V.H.H.g.q.a.V.s.V.s.a.V.q.B o.:.,.i.s.V.C.V.C.jXzXC.fXgX8X+X*X-X>X,X*X:X,X*X>X>X>X-X-X>X>X>X-X-X*X*X*X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X>X",
"( m X[ d s $ $ l q j k k k h f g k j 0 i , & * x 7XiXtXyXqXgXdXP.P.B.cXhXZ.r.e.jXzXjXlXjXa.b.V.C.n.a.p.%.o.s.D.H.H.G.H.G.G.sXH.sXH.V.V.D.V.H.s.O.B O.;.a.V.s.b.b.A.zXjXK.dX0X8X+X*X-X>X,X:X:X,X*X>X>X>X-X-X-X>X-X-X-X*X*X*X*X*X-X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"qX0X8Xx r r * $ 0 s j j j f g k g j j r * & 6 D XrXuXyXyXgXgXfXP.P.B.zXhXZ.9.=.A.jXjXzXr.e.V.b.F.b.n.<.o.:.s.D.G.sXsXkXsXkXlXzXzXsXsXD.H.G.G.V.s.O.o.:.i.s.V.C.B.B.zXjXB.c.7XeX+X*X-X>X>X*X*X-X-X>X>X>X-X-X-X>X-X-X*X*X*X*X-X-X-X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X",
"mXqX} z p z $ * b ` b j l w k k h f s i = 6 A u.rXrXyXxXqXqXxXfXJ.P.B.zXhXZ.-.H >.B.A.0.N -.e.C.C.C.n.:.o.p.d.H.G.sXG.kXsXkXlXzXzXkXsXsXsXG.G.V.V.i.o.d.n.V.F.b.B.*.r.B.e.9XeX@X+X*X-X-X-X*X*X-X-X-X>X>X-X-X-X>X-X-X-X-X-X-X-X-X*X*X*X*X-X-X*X*X-X-X-X-X-X-X-X-X",
"hXqXD z z e * ( R.[ d $ d s q q h h j r 6 c A u..XvXxXyXqXqXxXdXP.P.x.hXzXZ.7.H -.0.0.Z 4 H w.V.V.G.V.,.:.s.s.D.s.a.q.d.i.d.H.H.g.s.i.s.s.a.s.D.s.V.;.s.H.F.G.V.e.C F 6.fXwX+X+X+X+X-X-X-X*X-X-X-X-X>X-X-X*X-X>X>X>X-X-X-X-X>X>X*X*X*X*X*X*X*X*X-X-X-X-X-X-X-X-X",
"bXqX( z = p & ` } p d d 0 s q k q h j $ 6 c A &.y.gXxXxXyXqXgXhXP.P.B.fXzXZ.9.H H =.x.9.V V e.C.C.F.kXs.i.d.s.d.%.o.o.o.o.o.:.:.o.o.o.o.o.o.o.:.q.d.D.F.kXF.n.B.B.C *.c.c.z.eX+X:X:X-X5X-X-X>X>X-X-X>X-X-X-X-X>X>X>X>X-X-X>X>X>X-X*X*X*X*X*X-X*X*X-X-X-X-X-X-X-X",
"ZXxX.Xz z = . ` n x d s l q q h f j s r = A S S r.9XgXyXxXqXgXfXC.F.m.fXfXe.6.H F V D D V N 0.b.V.G.L.L.i.i.:.O.o.%.:.;.8.8.+.+.:.d.H.D.s.D.n.a.i.s.n.L.L.F.S.Z.e.H -.y.qXtX@X,X*X-X5X5X5X>X>X>X>X,X,X,X,X,X-X-X<X>X,X*X>X>X*X*X-X-X-X>X-X-X-X*X*X-X-X-X-X-X-X2X",
"0XyXuX( = p . { ^ * d j j h y s r j s r 6 A S B 6.pXgXyXgXgXgXfXF.F.m.jXZ.0.>.;.Z N 3 9 v V =.b.b.n.G.L.V.p.a.p.s.s.g.H.sXsXH.sXsXH.G.sXH.D.s.n.V.p.F.L.F.n.A.B.9.H 9.c.yXtX+X+X-X2X5X5X5X-X>X>X>X>X,X,X>X>X-X-X>X,X,X*X*X*X*X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X>X",
"` = { } z & < 7X{ * d r q j 0 s s r y r 6 A S B ..g.gXgXgXgXgXfXF.P.B.fXZ.>.;.,.<.Z N N N N F w.p.n.L.F.n.p.M.n.s.n.n.V.H.sXsXH.H.D.H.V.d.p.n.F.F.p.F.L.n.n.B.x.-.H 9.0XtXeX+X<X2X>X>X>X*X,X>X>X>X>X>X>X>X-X-X-X*X*X,X*X*X*X*X,X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X>X",
"z p r z r * & } Xx d r r s 0 s s s t * 6 N S B B r.gXgXgXgXhXfXP.P.B.J.Z.0.,.<.3.3.<.Z N N Z 0.a.b.V.F.M.3.n.M.s.p.p.p.i.q.8.;.:.:.8.q.p.D.F.V.V.a.M.M.M.a.B.r.J -.t.8X7X@X:X<X2X>X>X,X,X,X>X>X<X>X>X>X>X>X-X-X*X*X*X*X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"i d r s r * $ < } b d r d r 0 d y s i & 3 N B B B O.aXgXgXgXhXA.P.P.l.J.Z.0.,.<.3.2.k.k.<.Z N *.w.p.p.F.n.p.n.n.F.D.n.d.,.;.;.;.;.:.;.<.p.n.n.p.V.V.n.n.s.a.y.7.7.9.} 8XeX:X<X-X<X>X>X>X>X>X<X5X<X<X>X>X>X>X>X>X<X>X*X*X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"y q j q $ r * * b x d r d q i i t i = 6 N B B B B ..h.aXgXgXhXZ.P.m.b.J.A.>.<.<.3.2.2.2.3.1.B *.q.n.p.p.,.n.D.s.p.s.s.a.V.G.G.G.D.V.V.a.p.p.p.n.C.V.s.D.i.a.y.H 9.t.} eX+X1X<X>X>X>X,X>X>X<X3X5X<X<X<X>X>X>X>X>X<X>X>X*X>X>X>X*X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"l w h l s $ . & $ i s q j q y 0 t * 6 A B B B B B .+.aXaXaXgXZ.P.m.b.K.A.<.,.3.4.2.2.2.4.1...O.d.w.q.a.n.n.D.F.F.D.V.H.zXzXzXzXzXL.kXkXL.L.L.L.C.a.n.d.s.q.B.' } 8XeX+X+X,X,X,X,X,X,X>X2X3X3X3X<X<X<X<X>X<X<X<X<X<X>X>X>X>X>X>X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"y l h s $ $ * & i 0 j f h q t ; , o 4 A S .B B B .. .g.aXaXgXZ.m.P.m.G.C.q.3.4.4.2.2.2.4.4.o.o.s.q.p.p.V.a.n.V.s.D.D.D.L.lXlXlXzXH.kXkXL.L.F.L.B.b.a.;.a.e.c.t.} eXeX+X>X>X,X,X,X,X,X2X2X3X3X3X<X<X<X<X<X<X<X<X>X>X<X>X>X>X>X<X>X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"y h j s r $ # # i t q y h q t , = c A S . .B B .X...+.aXpXpXx.k.N.m.n.n.,.p.4.4.2.2.2.4.3.Z B e.b.q.w.p.b.p.n.n.M.n.n.F.F.L.kXH.G.kXzXkXL.C.C.e.0.;.q.q.r.y.t. XeX:X1X3X2X1X,X,X,X,X>X2X2X3X3X<X<X<X<X<X<X<X<X>X>X<X<X>X>X>X>X<X>X>X>X>X-X-X-X-X-X-X-X-X-X-X-X",
"y s s s r $ # # $ d l q q q i = c A S ..X.X.B B .X...@.f.S.aXe.k.N.k.p.p.<.3.4.3.2.2.2.3.,.Z F A.b.;.;.w.p.,.p.n.n.n.p.n.n.F.V.V.D.G.A.F.F.n.e.0.$.%.o.%.x.y.D 7X[.+X5X3X3X-X>X,X,X,X,X>X>X<X3X<X<X<X<X<X<X<X<X<X<X<X>X>X>X>X<X<X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"r s s i r $ # # ; d j q r d = 6 A S S ..X.X. .X. .....B :.S.aXe.k.N.k.<.<.<.3.<.2.2.2.k.<.$.8 gXaXB.r.%.$.%.%.;.a.n.b.a.s.e.e.q.e.g.B.f.a.a.q.=.F C Z C r.Z.t.o ^ R.+X5X3X6X6X2X>X,X,X,X>X>X<X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X<X<X<X<X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"r s s r r $ # - ; i q r r 6 6 A S S ....X.X. . . ..... .o.g.S.b.k.N.k.<.<.1.3.3.k.2.2.3.;.4 9 AXc.S.f.*.Z N Z C =.0.q.0.=.$.&.=.6.6.6.=.=.%.%.o.Z N Z r.B.Z.t.X < M T.:X5X3X3X4X3X<X,X,X,X,X<X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X>X<X<X<X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"s s s r r $ # - ; i r r 6 6 N S S .X.X.X.X. .B . . . .B i.V.b.k.N.k.<.<.:.3.3.3.4.k.<.4 X t.bX9.S.f.e.$.N N N Z F F Z V N V F F V N N Z Z C o.Z Z 0.B.B.Z.t.@ > $ y {.NX3X3X-X3X3X<X,X1X1X1X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X>X>X<X<X>X>X>X-X-X-X-X-X-X-X-X-X-X-X",
"j j j r 0 $ # ; i p p 6 8 A S .. .X.X.X.X. . .B B B ..B X.:.D.,.k.N.k.1.:.%.p.<.,.<.-.8 X X wXgXH S.f.f.r.C C Z Z Z N 4 4 8 8 4 4 4 4 4 N C $.$.F 6.f.a.Z.Z.t.o . - $ y [ +X1X6X3X4X4X<X1X1X<X>X<X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X<X<X<X>X>X>X>X-X>X>X>X>X2X2X2X2X",
"j j j q 0 $ # ; , * c c A S .. . .X.X.X.X. .B B B B .. .X.+.D.;.k.N.N.1.1.%.w.%.0.V 3 3 = & xXgXZ A.B.e.r.*.&.&.&.H V N 8 8 3 3 3 4 N V F =.6.*.*.0.e.B.B.pXz.X < # # ; % ^ @X,X>X3X4X3X1X<X>X>X<X<X<X<X<X<X<X<X1X<X<X<X<X<X>X>X<X<X<X<X>X>X>X>X>X>X>X>X2X2X2X2X",
"j j j q 0 $ # - , z ( ) S S ..X. .X.X.X.X.X.B B B S B ..X.X.g.;.m.N.P.1.%.$.r.H 3 3 @ o * . CXmXV C.B.e.e.;.$.6.=.7.F V N 8 8 8 4 N V F &.6.6.*.6.f.e.B.B.mXt.o # , & . - + h _ @X:X1XNX1X<X4X4X<X<X<X<X<X3X<X<X4X4X4X4X1X<X>X>X<X<X<X<X<X<X3X3X>X>X>X>X>X2X2X2X",
"s s d w r $ # . 7 } vXXXS ..X.X.X.X.X.X.X.B B B B B B X.....f.;.m.L.P.-.Z N 3 X o o # # # & CXZXF e.B.e.e.;.;.=.6.6.&.F Z V N 8 N N Z C =.6.*.*.0.f.e.B.hXZX} ; # # & & # # ; u w ! {.+XNX,X,X4X1X<X>X<X4X4X3X>X3X4X4X1X<X<X<X<X<X<X<X3X3X3X3X<X>X*X*X*X-X<X5X5X",
"i r s r s $ . v 8XxXgXy.*...X.X. . .X.X.X. .B B B B X.X. .B y.r.0.>.Z 3 X X . . # # # # > @ CXZXF 0.A.e.0.q.;.=.;.=.O.O.C Z V V F C O.%.6.;.%.;.e.e.B.Z.SXAX| % # # # # # # # # ; t u h _ +XNX+X<X<X<X<X<X<X4X4X4X4X4X4X<X<X<X<X<X<X<X3X3X3X3X<X<X,X,X,X<X5X5X5X",
"i r s s 0 # 2 } yXxX9Xy.g.O.....B ....X.X. .B B B B .X...S F V 3 3 X X X @ & # # # # # & @ CXZXV -.B.C.a.<.;.;.;.*.O.&.F F F H &.O.$.=.8.=.%.0.q.e.B.hXAXAX` # # # # # # # # # ; % t l h u / [.1X1X1X1X1X1X<X<X1X>X>X4X4X<X1X4X<X<X3X3X3X3X3X>X<X<X,X,X<X<X5X5X",
"d r s t % # 7 qXyXgX9Xc.aXr.@...B B ... .B B B B B S B N 4 3 X X o o o . . # - # # # & = X xXHXD F S.V.w.w.;.<.;.=.$.&.F F F =.=.*.*.;.0.;.;.q.e.e.pXAXZXGXn # # # # # # . # # > , i t t y h h ^ T.+X+X+X1X1X<X4X1X4X4X4X>X>X>X<X<X3X3X3X3X3X2X<X<X,X,X<X<X5X5X",
"d r 0 t ; , 7 qXxXgXqX9XgXaX8... . .....S B N 8 8 8 4 3 3 3 . . # # $ ; $ ; ; # # . . & = o tXHXhXV e.V.a.q.<.w.0.6.*.*.&.$.*.8.;.;.;.8.0.;.;.w.b.r.mXHXGXFX= > # # # # # # # # & # # i d i r t h w h {.eXNX1X1X,X,X1X1X1X<X>X4X<X<X3X3X3X3X3X3X<X>X,X,X<X<X5X5X",
"r r y t ; 2 7 9XqXxXqXqXxXcXaXO.S S B N N 4 3 X @ X o o o & % % $ $ $ % % # - . . . o > & o XFXHXt.&.f.a.,.w.<.q.0.=.&.*.*.=.8.;.:.;.0.q.;.;.e.0.pXAXZXPXVXo & # # # # # # # # & & < < , * , a i d y y l / T.+XNX1X1X1X,X,X1X4X<X<X3X3X3X2X2X2X>X>X>X-X5X<X5X5X",
"r s y 0 # o 9 gXgXxXqXqXxXbXcX7.N 8 4 3 X @ . + - . . . . . # # . # > > > + + + . . . & # # | CXHXZXF 0.s.,.<.w.w.;.=.&.=.*.;.8.;.:.;.w.q.;.0.r.r.ZXAXGXGX} = o # # # # # # # # & & & < < < , < , i i i t y h h ^ _ ].+X1XNX1X,X1X<X<X<X3X3X2X>X2X>X-X-X5X5X5X5X",
"s q q 0 , o 9 9XgXgXxXyXxXCXgXD 3 o o @ + + + . - . # - > # , . > . . . @ . . . . . . # # , | IXHXHXt.F e.a.,.<.w.;.=.&.=.%.;.8.;.;.;.q.;.=.=.-.hXAXHXGXGX` o & . # # . . . # # & < < * , < < > < < , , i d s y h l h h ! [ @XNX1X1X1X1X<X<X>X-X3X2X>X-X5X-X5X-X",
"j q s 0 * o 9 9XgXxXxXyXtX X7 o o o . + . . . . & # # - - - - + + . . . o o . . > . # . # # ` IXFXLXZXF =.e.p.<.w.q.=.*.*.o.;.;.;.=.,.q.;.*.F c.ZXJXHXGXFX< & . . . # . . # # # # # * , i i , , < < < p i i i i s d l l y q l ! T.NX1X,X,X1X*X<X>X>X2X2X-X-X5X5X",
"y y r r = o 5 9XqXbXwX7 2 2 . 2 & & & > # # & o & & # + + . . . . . . . o & & & # # , > - # n GXFXHXJXmX&.r.w.p.w.e.=.$.=.C $.$.$.=.%.w.;.C 9.ZXHXHXLXGX7X& $ # . . # . . . # # * , , , i i i i < < < < < p p p a i i d d s s j h ! T.NX1X,X,X*X<X>X2X2X-X-X5X5X",
"0 s r r p & @ qXbXyX7 2 o & > & & = & & & # & # . . . . o o o o . . . # # & * * * * * , # & z FXPXHXLXAXZX6.a.s.e.e.6.*.=.C $.*.$.O.0.0.$.7.AXHXKXPXPXGXD . $ # . . . # # # # # * ; ; ; i i i i , < p p < < < < z < < p a p i r y h L _ @XeX1X,X<X<X3X>X-X-X-X-X",
"i i $ r * & o wXxX` o < . # # # = = & & & & # - . . . . o o o o # # # # $ $ $ * * * * & . & 2 tXFXLXPXLXJXcX6.g.A.e.r.0.%.C *.&.$.&.e.0.H SXHXKXKXLXPXCX2 . # # & # # # . # # - # $ ; ; i t i i i i i i < < < < 7 < < < < < < p d s y h h ^ [.1X:X5X5X5X-X-X-X-X",
"p = * * * & o 0XyXo 7 . $ 1 # > & & & & & > - - . . . . o o o o # # # $ $ $ $ $ $ # * * & & & ` FXGXKXKXLXJXpX6.B.B.b.e.=.$.r.&.&.y.r.H SXLXLXKXKXKXLX X& & & & > # # # # - - - # - ; i i i i i i i i i , , < < < < < < < < p a a x i t y h / NX:X5X5X5X-X-X-X-X",
"v = * i # & = 0X' o . 1 , . & > - - & & & > & - . . . . . . . . # # # # # # # # # # & & , & < < xXFXPXKXKXLXJXc.r.B.A.B.e.0.y.0.r.y.7.AXLXLXKXKXKXKXPXn , > & > > > # # - - > > > , , i i i i i i i i i i , p < p , < , p i , , & & z i t y u [.:X<X5X5X5X-X-X-X",
"{ 7 & , # # = 8Xv o & . - > > . + - & & & & & & # # # # . . . . . o o . . . . + + + + . - # p * t.GXPXKXKXKXJXJXx.e.B.B.x.e.y.e.e.c.AXLXLXKXKXKXKXKXtX# , > > > , ; # # - > > > > > > , , , i i i i i i i i i p i i , i i i t $ 7 o 7 > $ s u _ :X5X5X5X5X-X-X-X",
"yXD & > # # o } o X > > - # o @ + + o o & & & & . # # # # # # . o o o o . . . . + . . . - # , p ` GXGXKXKXLXLXJXAXx.Z.fXMXnXcXcXcX9XAXKXKXKXKXKXKXLX| , , # < & , ; # - - > > > > > > > , , , i i i i i i i i i , i i i * t i i = 2 & > a % y K :X5X5X5X-X-X-X-X",
"xX8X3 o o o 2 n o o > > > > & & > - # & o . & # . . . # # # # . # o & o . . . . . . # # # & , , m IXGXLXKXKXKXKXLXJXHXAXAXZXxXwXD 5 D FXKXKXKXKXLXCX, , 1 > . > > > > > > > , , , , , , , , , , i i i i i i , , , , , , , , , , # > , , > - $ q @X:X5X3X-X>X*X>X",
"xXqXG X o o = z & & o > > & & # & # # . # # # # . . # # # # # . . # # - # # # # # # # # # # & & < tXPXPXLXKXKXKXKX8X` ` n v z < < < & m CXKXKXKXKX} , - > > > > > > > > , , , , , , , i , , , , i i i i i i , , i i i i , , * $ $ ; , , > & # 0 {.:X3X-X>X,X*X<X",
"gXgXt.D X 3 7 & & & o & & & # # & > # # # . . . - - - # # # . . . . # # # # # # # # # # # # # & < } FXPXKXLXKXLX} < . a > & < > & > z > m IXKXLXGXb 1 , > < > > , , , , , , , , , , i i i i , , p i p p p p i i p p i , , * $ $ $ , , , & & - t ! 1X3X*X>X*X>X>X",
"gXgXgXt.3 7 7 & & & o & & & # # # - # # # # # . > > > # . . . # . . # # # # # # . # # # # # # & > ` FXKXKXKXLX{ > & 1 1 < a < & 1 o . . a n GXKX8X< , 1 > < > > , , , , , , , , p p p p i , , , i i i p p p i i i i , , * * * $ $ , * * & & - t h +X*X<X*X*X3X-X",
"hX9XhX9Xv 3 o o o o o & & & & & # # # # # # . . > > # # . . . . . . - - # # # # . # # # # # # & , b CXPXLXKX| # & , , . > , > 1 > 2 < < > & ` GXn , > , > > < > , , , , , , , , a a p p p , , , , i i i i i i i i i , , , , * $ , * * * & # # $ q {.+X<X*X>X3X-X",
"hXpXgX0X' 7 X o o o o & & & & & # # # - > # # . > > # # # # . . . # - # # # . . # # # # # # # # & a R.PXKX| , , , # & & & > & > & & & & . z # ` 1 # 1 > > > 1 > < < , , , , , , p p p p p i , , , i i i i i i i i i i i , , , * * * * * & # # $ q ! 1X:X,X<X-X3X",
"fXpXpXc.t.5 o o o o & # # # & & # . > > > > . . # . . . - # # . . . # # # # # # # # # # # # # & # a ` PX} , # # & . > > . > > # > 2 < & a . , 1 a > z , > , < > , , , , , , , , p p p p i , , , i i i i i i i p i i i , i , , * $ $ * * & # # $ t u [.+X,X<X-X3X",
"hXpXpX9Xt.3 7 . o . # # # # # # # # - > > > . . . . . . - - # . . . # . # # # # . # # # # # > , , # z | & , > . # # > # . # . > & & < > # a , 1 , , , , & 1 > > , , , , , , , , p p p p i , , , , , , , , , , i , * , * , , , , $ $ * & & # # # % u _ +X,X-X3X3X",
"fXpXpX9Xt.9 X o o . . # # # & > # . . . . # # - > # . # # # . . . . . . # # # # # # # # # # # > , , a # - 1 . > 1 > > > < . > m 7 z m , , , , < , , , , , , , > , , , , , , i p p i , i p p , , , , , * * , , i , , , , , , , * $ $ $ # & & & - ; u P +X1X5X3X2X",
"fXpXpX9Xt.5 X o o . # # # # # # # - . . # # > > # . . # # # # . . . . . # # # # # # # # # # # & , , 1 > # # . > # # # 8XtXCXCXCXCXCXiX7Xa > , > , , , , , , , > , > > > , , , , , , , i p p < , , , , * * , , i i , , , , , * * $ $ $ # # # # # # % y [.:X<X3X3X",
"fXdXpX9Xt.3 X o o # # - # # # . # - - - # > > > - # . . . # # # # # . . # # # # # # # # # # # # # ; , , & , 1 & 1 , | CXPXKXKXKXKXKXPXIX| 1 > # > > > , , , , > , , > > & > , , , , , p p p , , , , , * * , , , i , , , * * * $ , , ; - # # # # # % % [ +X1X3X3X",
"fXdXpX9Xt.3 o o o # # # # # # . - - - - # > > > # . . . . # - # # # # # # # # # # & # # # # # # # ; ; , > , 1 # > 1 uXIXKXKXLXKXKXKXPXIX} 1 # , > > > , , , > > , , > > , , , , , , , p p i , , , , , * * * , , , , , * * * * $ , # # # # # # # # # % ^ :X1X2X3X",
"fXdX9X9Xt.X o o o # . . # # # # . . . # # # # # - # . . . # # # # - - - # # # # # # # # # # # # , # # , , > < a < { CXLXKXKXKXKXKXKXPXGX( > , ; , , , , , , , < , , , , , , , , , i p p i , & # , , , * $ $ * , , , * * * * * $ # # # # # $ $ # o . $ P NX1X3X2X",
"fXdXpX9X' X o o # # . . # # # & . . # # # # # # > > # . . # # # - - # - # # # # # # # # # # # # # ; , , a 1 > > n tXFXKXKXKXKXKXKXPXGXtX, , a # , , , , , , , < , , , , , , , , p p p i , * # # , , * * $ $ * , , * * * * * * # # # . # # $ * # o . ; u +X1X3X2X",
"fXpXpX9XG X o o # # # # # # # # . . . . - # # # > > - . # . . . . . # # # # # # # # # # # # # # # , , # , , & m 7XCXLXKXKXKXKXKXKXPXCX` , , 1 , , , 1 1 , , , , , < , , , , i i p p i , * $ & & , , , , * $ * , , , * * * * $ # # # . . # $ * & o . # w [.1X<X3X",
"fXpXpX9XD o o & # # # # # # # # . . # @ > @ - . > > - # # . . . . . # # # # # # # # # # # # # & , # , a a # m 7XCXGXKXKXLXKXKXKXPXCXuX< 1 a , i 1 1 1 1 , > > , 1 1 1 < 1 , p a p p < , $ # & * , , , , , ; , , , , , * & & # # # # . # # $ $ # & & # y {.1X<X3X",
"Z.pXpX9XD @ & . > & # . . # # # . . > . 1 @ . @ > . . > . . . . . 2 & . # # > . > > & # # # # & , # a , a , 7XCXGXKXLXKXKXKXKXLXCXVX( z a 1 , i a 1 , , > > > > 1 - 1 , , 1 , , , & = o , # * = < & , $ , , ; , , - ; & & & # # $ # & # # # $ $ ; # $ t [ NX4X3X",
"aXpXpX9X9 X & # & & & o . . # # > . . 1 . . 1 . @ @ o 2 . o > > . . . & , < . 1 & & & # # # # # # , , , i M R.CXFXKXKXKXKXKXPXIXrX} z < < a , 1 1 1 , < , > > > < > < a 1 , a < < = = = . & = 6 7 & a # - % ; , , # # # & & & - # # # & # # $ $ $ $ $ 0 _ NX4X3X",
"fXZ.9X9X5 X & # # # & o . . # # . o 8X8X8X} } | ` ` n m 2 > o . 2 & > & . < . # # & # # # # # # # 1 # , , a ` iXIXPXLXKXKXLXCX7X} n , , , a , , < < < < < > > , , a < < , , < = 6 6 6 c 6 c 6 6 6 6 , ; t : & & = * & & & & . # # # # & # # $ $ $ ; * 0 ^ NX4X4X",
"fXZ.9X9XX o & # # . o o # . # # 7 . 8XxXKXKXLX7X7X7XrXuXiXxXtXrX8X X{ ` & . 1 > # & # # # # # # # , # , , , a 7XVXHXKXLXLXFXrX{ ` i i a i a i , i < p < < , , , * x p p p < 1 ` D ) ) ) ) ) ) ) ) u.2 + + ; o = & & o > * # # - # # # o # # $ $ $ ; * 0 ! NX3X4X",
"dXZ.9Xz.X o & # . # o o & # # # . z 8XCXLXKXCXt.8X7X7XrXtXiXxXxXiXiXtXtX{ > . . & & > # # # # # & # , a i i , | iXFXLXHXZX7X} } n t i i a $ i p i p p p p , , , d r * < z & o 8X5.5.5.5.5.5.5.5.5.vX7 + ; , = = 2 > @ > $ % . . # # # & # # $ $ # ; # $ l +X<X3X",
"pXpXpXt.X o o # # . o o . # # # . v tXxXIXFXCXrX8XtXuXiXiXxXiXxXiXuXtXuX7X` > & & & & & # # - > , # , , i a , m uXVXZXFXtX X8X8Xi t i i a * p p p p p i i 1 , , ; i x = < < 6 0X5.5.5.5.5.5.5.5.5.vXv @ = = 8 3 3 3 X 2 , % # . # # # & & & ; ; ; - , $ h [.1X4X",
"pXpXpX' o o . # & & o o . . # # < D yXCXCXVXFX7X8XuXVXCXCXCXVXCXCXVXxXiXVX7X` . & & & & # & > > , , , , , i a & 8XCXFXxX8XrXVX` , i p , p p p p i a i i , 1 , , 1 > 6 7 { rXu.u.u.) .Xu.u.u.XXu..X7X' t.u.7.O.@.O.7.9.] ( r o o # # & & > > ; ; ; , < , y {.1X4X",
"pXpX9XD o o . & > o o o . . # # & ` XuXFXFX7Xt.8XrXrXrXtXuXuXiXiXuXyXtXuXtX7Xz & & & & # # > > # , # a i * a < ` VXxXrX8X8X7X= < * < * * p , , i a i 1 , , , > @ 5 A ' yXPXvXu..X} uX X.Xu.vX{ BX} ] cXXXh.+.j.+.h.pX9X' = o = & - & > > > ; ; - # , $ y [ 1X4X",
"dX9X9X9 o > # # . . o o . . # # & n { } } } X7X7X7X7XrXtXtXuXuXtXtXtXtXrXrX7Xz # & # & # # # # > , ; ; , , , < & uXyX} ` 8Xv & < * * * * , , , i , , , < < < 7 4 V O.u.vXPXvXu..X{ VX X7Xt.vX} BXR.9.aXg.h.j.#.#.h.g.pX' z & > & > > > > $ ; ; ; > , $ y / 1X4X",
"dXpX9X9 @ > . # # . o o o o & > . & & # . . < z m n n ` ` { } } 8X7X8X7X7X7X7Xz # # # # # # # # > > , , , * , * < ' 8X` D { & = , , , , , , , , ; ; , < = 6 6 8 Z *.8.8.5.h.5.u. XrX8X X X7Xu.t.8X} ' 8.5.j.#.#.#.#.+.r.A & . > # > > > , ; ; ; ; > > , y ^ NXNX",
"pXpXz.5 @ > . - . . o o o . # # # # . # & & # # & & & # . . # & & & 2 z m n n & # . # # # # # > > & # # $ * * & = 7 ` D ( c & & # - - - , , 1 1 1 1 , 2 6 c A F 0.G.G.D.+.j.5.XXrXVX` 7XiXGX( { uXrXu.5.j.#.#.#.#.#.+.h.c * # > # & > > , , ; ; ; - > i t h NX4X",
"pXpXt.X @ > . > . . . o . . # # . . . # # - # # # # # # # # # & & . o & . . # & # # . # # # # & > & # # $ $ # & & & 7 ( D . < & # - - - - ; 1 1 1 1 , = 6 c D F e.K.A.B.+.+.5.u.rXVX| 7XrXCX' t.uX.Xu.5.j.+.+.+.+.+.+.y.v ; . . # , > > , ; ; ; ; > > i t u NX4X",
"pX9X] X o & . # # . o o o . # # - > > . . . . # . . # # # # # # o o 2 . # < & . # # # # # # # # & & # # # # # # & & > m < > # , - - - - - - , , 1 , > 2 7 c A A F H F D A A A A ( ( a M b ` c v n m c 4 N N 4 4 N 4 N N O . - , ; , > & & $ ; ; ; , , , t u NX1X",
"pX9X' X o & . . . . o o o o . # # + - > > > > # # > > > > > > & > & < . . & . # # # # # # # # # & & & # # # # - > & > & . < # - - - - - > , < 1 , ; - > < 7 6 6 6 7 o 7 6 6 7 z z < i a z < , 1 < < 6 c o 2 @ @ 7 3 3 X = . - - ; , * & * $ ; ; ; , , * t u @X1X",
"pX9XD X o . . . . . . . . . . . > . . . . . # & # & # # . . . . . . > & , . . > # # # # # # # # & # # # # - - , & , > > # - , # - - , * < < p < , ; ; - , > < < p < z z a < = 7 z z a , p x d - , < < z < , , p = < z = = o . # , * * * * $ ; ; ; - , # t u {.NX",
"pXc.D X o . # # . . o o o o # & # . @ > - > & & & & & & & & & & > . > . > # , # # # # . # # # # & . # # # ; ; , , . # > . - , , , , , < z 6 = * # ; : : : , 1 1 i i p p ; a 7 7 < < < z z = i i , 1 < & , p i r r r * * o & & > $ * , , * , ; ; ; # , # i q _ NX"
};

11174
Tests/images/hopper_rgb.xpm Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -188,5 +188,5 @@ class TestEnvVars:
), ),
) )
def test_warnings(self, var: dict[str, str]) -> None: def test_warnings(self, var: dict[str, str]) -> None:
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match=list(var)[0]):
Image._apply_env_variables(var) Image._apply_env_variables(var)

View File

@ -19,7 +19,7 @@ def test_check() -> None:
assert features.check_codec(codec) == features.check(codec) assert features.check_codec(codec) == features.check(codec)
for feature in features.features: for feature in features.features:
if "webp" in feature: if "webp" in feature:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="webp"):
assert features.check_feature(feature) == features.check(feature) assert features.check_feature(feature) == features.check(feature)
else: else:
assert features.check_feature(feature) == features.check(feature) assert features.check_feature(feature) == features.check(feature)
@ -49,24 +49,24 @@ def test_version() -> None:
test(codec, features.version_codec) test(codec, features.version_codec)
for feature in features.features: for feature in features.features:
if "webp" in feature: if "webp" in feature:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="webp"):
test(feature, features.version_feature) test(feature, features.version_feature)
else: else:
test(feature, features.version_feature) test(feature, features.version_feature)
def test_webp_transparency() -> None: def test_webp_transparency() -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="transp_webp"):
assert (features.check("transp_webp") or False) == features.check_module("webp") assert (features.check("transp_webp") or False) == features.check_module("webp")
def test_webp_mux() -> None: def test_webp_mux() -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="webp_mux"):
assert (features.check("webp_mux") or False) == features.check_module("webp") assert (features.check("webp_mux") or False) == features.check_module("webp")
def test_webp_anim() -> None: def test_webp_anim() -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="webp_anim"):
assert (features.check("webp_anim") or False) == features.check_module("webp") assert (features.check("webp_anim") or False) == features.check_module("webp")
@ -95,10 +95,9 @@ def test_check_codecs(feature: str) -> None:
def test_check_warns_on_nonexistent() -> None: def test_check_warns_on_nonexistent() -> None:
with pytest.warns(UserWarning) as cm: with pytest.warns(UserWarning, match="Unknown feature 'typo'."):
has_feature = features.check("typo") has_feature = features.check("typo")
assert has_feature is False assert has_feature is False
assert str(cm[-1].message) == "Unknown feature 'typo'."
def test_supported_modules() -> None: def test_supported_modules() -> None:

View File

@ -303,11 +303,11 @@ def test_apng_chunk_errors() -> None:
assert isinstance(im, PngImagePlugin.PngImageFile) assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="Invalid APNG"):
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im: im = Image.open("Tests/images/apng/chunk_multi_actl.png")
im.load() assert isinstance(im, PngImagePlugin.PngImageFile)
assert isinstance(im, PngImagePlugin.PngImageFile) assert not im.is_animated
assert not im.is_animated im.close()
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im: with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile) assert isinstance(im, PngImagePlugin.PngImageFile)
@ -330,18 +330,20 @@ def test_apng_chunk_errors() -> None:
def test_apng_syntax_errors() -> None: def test_apng_syntax_errors() -> None:
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="Invalid APNG"):
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im: im = Image.open("Tests/images/apng/syntax_num_frames_zero.png")
assert isinstance(im, PngImagePlugin.PngImageFile) assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
with pytest.raises(OSError): with pytest.raises(OSError):
im.load() im.load()
im.close()
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="Invalid APNG"):
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im: im = Image.open("Tests/images/apng/syntax_num_frames_zero_default.png")
assert isinstance(im, PngImagePlugin.PngImageFile) assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
im.load() im.load()
im.close()
# we can handle this case gracefully # we can handle this case gracefully
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
@ -354,11 +356,12 @@ def test_apng_syntax_errors() -> None:
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
im.load() im.load()
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="Invalid APNG"):
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im: im = Image.open("Tests/images/apng/syntax_num_frames_invalid.png")
assert isinstance(im, PngImagePlugin.PngImageFile) assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
im.load() im.load()
im.close()
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -77,8 +77,8 @@ class TestUnsupportedAvif:
def test_unsupported(self, monkeypatch: pytest.MonkeyPatch) -> None: def test_unsupported(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(AvifImagePlugin, "SUPPORTED", False) monkeypatch.setattr(AvifImagePlugin, "SUPPORTED", False)
with pytest.warns(UserWarning): with pytest.raises(UnidentifiedImageError):
with pytest.raises(UnidentifiedImageError): with pytest.warns(UserWarning, match="AVIF support not installed"):
with Image.open(TEST_AVIF_FILE): with Image.open(TEST_AVIF_FILE):
pass pass
@ -254,7 +254,9 @@ class TestFileAvif:
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] == (876, 0) colors = im.getchannel("A").getcolors()
assert colors is not None
assert colors[0] == (876, 0)
def test_save_transparent(self, tmp_path: Path) -> None: def test_save_transparent(self, tmp_path: Path) -> None:
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))

View File

@ -511,3 +511,20 @@ def test_save_dx10_bc5(tmp_path: Path) -> None:
im = hopper("L") im = hopper("L")
with pytest.raises(OSError, match="only RGB mode can be written as BC5"): with pytest.raises(OSError, match="only RGB mode can be written as BC5"):
im.save(out, pixel_format="BC5") im.save(out, pixel_format="BC5")
@pytest.mark.parametrize(
"pixel_format, mode",
(
("DXT1", "RGBA"),
("DXT3", "RGBA"),
("DXT5", "RGBA"),
("BC2", "RGBA"),
("BC3", "RGBA"),
("BC5", "RGB"),
),
)
def test_save_large_file(tmp_path: Path, pixel_format: str, mode: str) -> None:
im = hopper(mode).resize((440, 440))
# should not error in valgrind
im.save(tmp_path / "img.dds", pixel_format=pixel_format)

View File

@ -224,6 +224,7 @@ def test_optimize_if_palette_can_be_reduced_by_half() -> None:
out = BytesIO() out = BytesIO()
im.save(out, "GIF", optimize=optimize) im.save(out, "GIF", optimize=optimize)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert reloaded.palette is not None
assert len(reloaded.palette.palette) // 3 == colors assert len(reloaded.palette.palette) // 3 == colors
@ -540,7 +541,9 @@ def test_dispose_background_transparency() -> None:
img.seek(2) img.seek(2)
px = img.load() px = img.load()
assert px is not None assert px is not None
assert px[35, 30][3] == 0 value = px[35, 30]
assert isinstance(value, tuple)
assert value[3] == 0
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -1229,7 +1232,9 @@ def test_removed_transparency(tmp_path: Path) -> None:
im.putpixel((x, 0), (x, 0, 0)) im.putpixel((x, 0), (x, 0, 0))
im.info["transparency"] = (255, 255, 255) im.info["transparency"] = (255, 255, 255)
with pytest.warns(UserWarning): with pytest.warns(
UserWarning, match="Couldn't allocate palette entry for transparency"
):
im.save(out) im.save(out)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
@ -1251,7 +1256,7 @@ def test_rgb_transparency(tmp_path: Path) -> None:
im = Image.new("RGB", (1, 1)) im = Image.new("RGB", (1, 1))
im.info["transparency"] = b"" im.info["transparency"] = b""
ims = [Image.new("RGB", (1, 1))] ims = [Image.new("RGB", (1, 1))]
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="should be converted to RGBA images"):
im.save(out, save_all=True, append_images=ims) im.save(out, save_all=True, append_images=ims)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
@ -1359,6 +1364,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
# 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_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.palette.palette == im.global_palette.palette assert im.palette.palette == im.global_palette.palette
im.seek(1) im.seek(1)
@ -1422,7 +1428,9 @@ def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
def test_lzw_bits() -> None: def test_lzw_bits() -> None:
# see https://github.com/python-pillow/Pillow/issues/2811 # see https://github.com/python-pillow/Pillow/issues/2811
with Image.open("Tests/images/issue_2811.gif") as im: with Image.open("Tests/images/issue_2811.gif") as im:
assert im.tile[0][3][0] == 11 # LZW bits args = im.tile[0][3]
assert isinstance(args, tuple)
assert args[0] == 11 # LZW bits
# codec error prepatch # codec error prepatch
im.load() im.load()
@ -1477,7 +1485,11 @@ def test_saving_rgba(tmp_path: Path) -> None:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
reloaded_rgba = reloaded.convert("RGBA") reloaded_rgba = reloaded.convert("RGBA")
assert reloaded_rgba.load()[0, 0][3] == 0 px = reloaded_rgba.load()
assert px is not None
value = px[0, 0]
assert isinstance(value, tuple)
assert value[3] == 0
@pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False})) @pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False}))

View File

@ -95,7 +95,9 @@ def test_sizes() -> None:
for w, h, r in im.info["sizes"]: for w, h, r in im.info["sizes"]:
wr = w * r wr = w * r
hr = h * r hr = h * r
with pytest.warns(DeprecationWarning): with pytest.warns(
DeprecationWarning, match=r"Setting size to \(width, height, scale\)"
):
im.size = (w, h, r) im.size = (w, h, r)
im.load() im.load()
assert im.mode == "RGBA" assert im.mode == "RGBA"

View File

@ -99,6 +99,7 @@ def test_getpixel(tmp_path: Path) -> None:
reloaded.load() reloaded.load()
reloaded.size = (32, 32) reloaded.size = (32, 32)
assert reloaded.load() is not None
assert reloaded.getpixel((0, 0)) == (18, 20, 62) assert reloaded.getpixel((0, 0)) == (18, 20, 62)
@ -233,7 +234,7 @@ def test_save_append_images(tmp_path: Path) -> None:
def test_unexpected_size() -> None: def test_unexpected_size() -> None:
# This image has been manually hexedited to state that it is 16x32 # This image has been manually hexedited to state that it is 16x32
# while the image within is still 16x16 # while the image within is still 16x16
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="Image was not the expected size"):
with Image.open("Tests/images/hopper_unexpected.ico") as im: with Image.open("Tests/images/hopper_unexpected.ico") as im:
assert im.size == (16, 16) assert im.size == (16, 16)

View File

@ -23,6 +23,9 @@ def test_open() -> None:
assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")] assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
assert_image_equal(im, expected) assert_image_equal(im, expected)
with Image.open(f) as im:
assert im.load() is not None
def test_getiptcinfo_jpg_none() -> None: def test_getiptcinfo_jpg_none() -> None:
# Arrange # Arrange
@ -99,7 +102,7 @@ def test_i() -> None:
c = b"a" c = b"a"
# Act # Act
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="IptcImagePlugin.i"):
ret = IptcImagePlugin.i(c) ret = IptcImagePlugin.i(c)
# Assert # Assert
@ -114,7 +117,7 @@ def test_dump(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(sys, "stdout", mystdout) monkeypatch.setattr(sys, "stdout", mystdout)
# Act # Act
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="IptcImagePlugin.dump"):
IptcImagePlugin.dump(c) IptcImagePlugin.dump(c)
# Assert # Assert
@ -122,5 +125,5 @@ def test_dump(monkeypatch: pytest.MonkeyPatch) -> None:
def test_pad_deprecation() -> None: def test_pad_deprecation() -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="IptcImagePlugin.PAD"):
assert IptcImagePlugin.PAD == b"\0\0\0\0" assert IptcImagePlugin.PAD == b"\0\0\0\0"

View File

@ -130,21 +130,7 @@ class TestFileJpeg:
def test_cmyk(self) -> None: def test_cmyk(self) -> None:
# Test CMYK handling. Thanks to Tim and Charlie for test data, # Test CMYK handling. Thanks to Tim and Charlie for test data,
# Michael for getting me to look one more time. # Michael for getting me to look one more time.
f = "Tests/images/pil_sample_cmyk.jpg" def check(im: ImageFile.ImageFile) -> None:
with Image.open(f) as im:
# the source image has red pixels in the upper left corner.
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))
assert c == 0.0
assert m > 0.8
assert y > 0.8
assert k == 0.0
# the opposite corner is black
c, m, y, k = (
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))
)
assert k > 0.9
# roundtrip, and check again
im = self.roundtrip(im)
cmyk = im.getpixel((0, 0)) cmyk = im.getpixel((0, 0))
assert isinstance(cmyk, tuple) assert isinstance(cmyk, tuple)
c, m, y, k = (x / 255.0 for x in cmyk) c, m, y, k = (x / 255.0 for x in cmyk)
@ -152,11 +138,19 @@ class TestFileJpeg:
assert m > 0.8 assert m > 0.8
assert y > 0.8 assert y > 0.8
assert k == 0.0 assert k == 0.0
# the opposite corner is black
cmyk = im.getpixel((im.size[0] - 1, im.size[1] - 1)) cmyk = im.getpixel((im.size[0] - 1, im.size[1] - 1))
assert isinstance(cmyk, tuple) assert isinstance(cmyk, tuple)
k = cmyk[3] / 255.0 k = cmyk[3] / 255.0
assert k > 0.9 assert k > 0.9
with Image.open("Tests/images/pil_sample_cmyk.jpg") as im:
# the source image has red pixels in the upper left corner.
check(im)
# roundtrip, and check again
check(self.roundtrip(im))
def test_rgb(self) -> None: def test_rgb(self) -> None:
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]: def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]:
return tuple(v[0] for v in im.layer) return tuple(v[0] for v in im.layer)
@ -764,10 +758,13 @@ class TestFileJpeg:
# Act # Act
# Shouldn't raise error # Shouldn't raise error
fn = "Tests/images/sugarshack_bad_mpo_header.jpg" with pytest.warns(UserWarning, match="malformed MPO file"):
with pytest.warns(UserWarning, Image.open, fn) as im: im = Image.open("Tests/images/sugarshack_bad_mpo_header.jpg")
# Assert
assert im.format == "JPEG" # Assert
assert im.format == "JPEG"
im.close()
@pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr")) @pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr"))
def test_save_correct_modes(self, mode: str) -> None: def test_save_correct_modes(self, mode: str) -> None:
@ -1079,10 +1076,16 @@ class TestFileJpeg:
for marker in b"\xff\xd8", b"\xff\xd9": for marker in b"\xff\xd8", b"\xff\xd9":
assert marker in data[1] assert marker in data[1]
assert marker in data[2] assert marker in data[2]
# DHT, DQT
for marker in b"\xff\xc4", b"\xff\xdb": # DQT
markers = [b"\xff\xdb"]
if features.check_feature("libjpeg_turbo"):
# DHT
markers.append(b"\xff\xc4")
for marker in markers:
assert marker in data[1] assert marker in data[1]
assert marker not in data[2] assert marker not in data[2]
# SOF0, SOS, APP0 (JFIF header) # SOF0, SOS, APP0 (JFIF header)
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0": for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
assert marker not in data[1] assert marker not in data[1]
@ -1109,9 +1112,9 @@ class TestFileJpeg:
def test_deprecation(self) -> None: def test_deprecation(self) -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile) assert isinstance(im, JpegImagePlugin.JpegImageFile)
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="huffman_ac"):
assert im.huffman_ac == {} assert im.huffman_ac == {}
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="huffman_dc"):
assert im.huffman_dc == {} assert im.huffman_dc == {}

View File

@ -156,6 +156,7 @@ def test_reload_exif_after_seek() -> None:
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:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2 assert mpinfo[45057] == 2
@ -165,6 +166,7 @@ def test_mp_offset() -> None:
# 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:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2 assert mpinfo[45057] == 2
@ -181,6 +183,7 @@ def test_mp_no_data() -> None:
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:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None
for frame_number, mpentry in enumerate(mpinfo[0xB002]): for frame_number, mpentry in enumerate(mpinfo[0xB002]):
mpattr = mpentry["Attribute"] mpattr = mpentry["Attribute"]
if frame_number: if frame_number:
@ -315,6 +318,9 @@ def test_save_xmp() -> None:
im2.encoderinfo = {"xmp": b"Second frame"} im2.encoderinfo = {"xmp": b"Second frame"}
im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2]) im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2])
# Test that encoderinfo is unchanged
assert im2.encoderinfo == {"xmp": b"Second frame"}
assert im_reloaded.info["xmp"] == b"First frame" assert im_reloaded.info["xmp"] == b"First frame"
im_reloaded.seek(1) im_reloaded.seek(1)

View File

@ -100,11 +100,11 @@ class TestFilePng:
assert im.format == "PNG" assert im.format == "PNG"
assert im.get_format_mimetype() == "image/png" assert im.get_format_mimetype() == "image/png"
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]: for mode in ["1", "L", "P", "RGB", "I;16", "I;16B"]:
im = hopper(mode) im = hopper(mode)
im.save(test_file) im.save(test_file)
with Image.open(test_file) as reloaded: with Image.open(test_file) as reloaded:
if mode in ("I", "I;16B"): if mode == "I;16B":
reloaded = reloaded.convert(mode) reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im) assert_image_equal(reloaded, im)
@ -801,6 +801,16 @@ class TestFilePng:
with Image.open("Tests/images/truncated_end_chunk.png") as im: with Image.open("Tests/images/truncated_end_chunk.png") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.png") assert_image_equal_tofile(im, "Tests/images/hopper.png")
def test_deprecation(self, tmp_path: Path) -> None:
test_file = tmp_path / "out.png"
im = hopper("I")
with pytest.warns(DeprecationWarning, match="Saving I mode images as PNG"):
im.save(test_file)
with Image.open(test_file) as reloaded:
assert_image_equal(im, reloaded.convert("I"))
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
@skip_unless_feature("zlib") @skip_unless_feature("zlib")

View File

@ -1,10 +1,12 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import Image, QoiImagePlugin from PIL import Image, QoiImagePlugin
from .helper import assert_image_equal_tofile from .helper import assert_image_equal_tofile, hopper
def test_sanity() -> None: def test_sanity() -> None:
@ -34,3 +36,22 @@ def test_op_index() -> None:
# QOI_OP_INDEX as the first chunk # QOI_OP_INDEX as the first chunk
with Image.open("Tests/images/op_index.qoi") as im: with Image.open("Tests/images/op_index.qoi") as im:
assert im.getpixel((0, 0)) == (0, 0, 0, 0) assert im.getpixel((0, 0)) == (0, 0, 0, 0)
def test_save(tmp_path: Path) -> None:
f = tmp_path / "temp.qoi"
im = hopper()
im.save(f, colorspace="sRGB")
assert_image_equal_tofile(im, f)
for path in ("Tests/images/default_font.png", "Tests/images/pil123rgba.png"):
with Image.open(path) as im:
im.save(f)
assert_image_equal_tofile(im, f)
im = hopper("P")
with pytest.raises(ValueError, match="Unsupported QOI image mode"):
im.save(f)

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from io import BytesIO
from pathlib import Path from pathlib import Path
import pytest import pytest
@ -71,6 +72,15 @@ def test_invalid_file() -> None:
SgiImagePlugin.SgiImageFile(invalid_file) SgiImagePlugin.SgiImageFile(invalid_file)
def test_unsupported_image_mode() -> None:
with open("Tests/images/hopper.rgb", "rb") as fp:
data = fp.read()
data = data[:3] + b"\x03" + data[4:]
with pytest.raises(ValueError, match="Unsupported SGI image mode"):
with Image.open(BytesIO(data)):
pass
def roundtrip(img: Image.Image, tmp_path: Path) -> None: def roundtrip(img: Image.Image, tmp_path: Path) -> None:
out = tmp_path / "temp.sgi" out = tmp_path / "temp.sgi"
img.save(out, format="sgi") img.save(out, format="sgi")
@ -109,3 +119,11 @@ def test_unsupported_mode(tmp_path: Path) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.save(out, format="sgi") im.save(out, format="sgi")
def test_unsupported_number_of_bytes_per_pixel(tmp_path: Path) -> None:
im = hopper()
out = tmp_path / "temp.sgi"
with pytest.raises(ValueError, match="Unsupported number of bytes per pixel"):
im.save(out, bpc=3)

View File

@ -190,7 +190,9 @@ def test_save_id_section(tmp_path: Path) -> None:
# Save with custom id section greater than 255 characters # Save with custom id section greater than 255 characters
id_section = b"Test content" * 25 id_section = b"Test content" * 25
with pytest.warns(UserWarning): with pytest.warns(
UserWarning, match="id_section has been trimmed to 255 characters"
):
im.save(out, id_section=id_section) im.save(out, id_section=id_section)
with Image.open(out) as test_im: with Image.open(out) as test_im:
@ -220,12 +222,16 @@ def test_horizontal_orientations() -> None:
with Image.open("Tests/images/rgb32rle_top_right.tga") as im: with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
px = im.load() px = im.load()
assert px is not None assert px is not None
assert px[90, 90][:3] == (0, 0, 0) value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 0, 0)
with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im: with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im:
px = im.load() px = im.load()
assert px is not None assert px is not None
assert px[90, 90][:3] == (0, 255, 0) value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 255, 0)
def test_save_rle(tmp_path: Path) -> None: def test_save_rle(tmp_path: Path) -> None:

View File

@ -49,25 +49,10 @@ class TestFileTiff:
assert im.size == (128, 128) assert im.size == (128, 128)
assert im.format == "TIFF" assert im.format == "TIFF"
hopper("1").save(filename) for mode in ("1", "L", "P", "RGB", "I", "I;16", "I;16L"):
with Image.open(filename): hopper(mode).save(filename)
pass with Image.open(filename):
pass
hopper("L").save(filename)
with Image.open(filename):
pass
hopper("P").save(filename)
with Image.open(filename):
pass
hopper("RGB").save(filename)
with Image.open(filename):
pass
hopper("I").save(filename)
with Image.open(filename):
pass
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(self) -> None: def test_unclosed_file(self) -> None:
@ -236,7 +221,7 @@ class TestFileTiff:
assert isinstance(im, JpegImagePlugin.JpegImageFile) assert isinstance(im, JpegImagePlugin.JpegImageFile)
# Should not raise struct.error. # Should not raise struct.error.
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="Corrupt EXIF data"):
im._getexif() im._getexif()
def test_save_rgba(self, tmp_path: Path) -> None: def test_save_rgba(self, tmp_path: Path) -> None:
@ -1029,7 +1014,7 @@ class TestFileTiff:
@timeout_unless_slower_valgrind(2) @timeout_unless_slower_valgrind(2)
def test_oom(self, test_file: str) -> None: def test_oom(self, test_file: str) -> None:
with pytest.raises(UnidentifiedImageError): with pytest.raises(UnidentifiedImageError):
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="Corrupt EXIF data"):
with Image.open(test_file): with Image.open(test_file):
pass pass

View File

@ -300,7 +300,7 @@ def test_empty_metadata() -> None:
head = f.read(8) head = f.read(8)
info = TiffImagePlugin.ImageFileDirectory(head) info = TiffImagePlugin.ImageFileDirectory(head)
# Should not raise struct.error. # Should not raise struct.error.
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="Corrupt EXIF data"):
info.load(f) info.load(f)
@ -481,7 +481,7 @@ def test_too_many_entries() -> None:
ifd.tagtype[277] = TiffTags.SHORT ifd.tagtype[277] = TiffTags.SHORT
# Should not raise ValueError. # Should not raise ValueError.
with pytest.warns(UserWarning): with pytest.warns(UserWarning, match="Metadata Warning"):
assert ifd[277] == 4 assert ifd[277] == 4

View File

@ -33,8 +33,8 @@ class TestUnsupportedWebp:
monkeypatch.setattr(WebPImagePlugin, "SUPPORTED", False) monkeypatch.setattr(WebPImagePlugin, "SUPPORTED", False)
file_path = "Tests/images/hopper.webp" file_path = "Tests/images/hopper.webp"
with pytest.warns(UserWarning): with pytest.raises(OSError):
with pytest.raises(OSError): with pytest.warns(UserWarning, match="WEBP support not installed"):
with Image.open(file_path): with Image.open(file_path):
pass pass
@ -219,6 +219,7 @@ class TestFileWebp:
# Save P mode GIF with background # Save P mode GIF with background
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 WEBP # Save as WEBP
im.save(out_webp, save_all=True) im.save(out_webp, save_all=True)
@ -230,6 +231,7 @@ class TestFileWebp:
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 < 5 assert difference < 5

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from io import BytesIO
import pytest import pytest
from PIL import Image, XpmImagePlugin from PIL import Image, XpmImagePlugin
@ -17,7 +19,45 @@ def test_sanity() -> None:
assert im.format == "XPM" assert im.format == "XPM"
# large error due to quantization->44 colors. # large error due to quantization->44 colors.
assert_image_similar(im.convert("RGB"), hopper("RGB"), 60) assert_image_similar(im.convert("RGB"), hopper(), 23)
def test_bpp2() -> None:
with Image.open("Tests/images/hopper_bpp2.xpm") as im:
assert_image_similar(im.convert("RGB"), hopper(), 11)
def test_rgb() -> None:
with Image.open("Tests/images/hopper_rgb.xpm") as im:
assert im.mode == "RGB"
assert_image_similar(im, hopper(), 16)
def test_truncated_header() -> None:
data = b"/* XPM */"
with pytest.raises(SyntaxError, match="broken XPM file"):
with XpmImagePlugin.XpmImageFile(BytesIO(data)):
pass
def test_cannot_read_color() -> None:
with open(TEST_FILE, "rb") as fp:
data = fp.read().split(b"#")[0]
with pytest.raises(ValueError, match="cannot read this XPM file"):
with Image.open(BytesIO(data)):
pass
with pytest.raises(ValueError, match="cannot read this XPM file"):
with Image.open(BytesIO(data + b"invalid")):
pass
def test_not_enough_image_data() -> None:
with open(TEST_FILE, "rb") as fp:
data = fp.read().split(b"/* pixels */")[0]
with Image.open(BytesIO(data)) as im:
with pytest.raises(ValueError, match="not enough image data"):
im.load()
def test_invalid_file() -> None: def test_invalid_file() -> None:

View File

@ -53,7 +53,7 @@ except ImportError:
# Deprecation helper # Deprecation helper
def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image: def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image:
if mode.startswith("BGR;"): if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="BGR;"):
return Image.new(mode, size) return Image.new(mode, size)
else: else:
return Image.new(mode, size) return Image.new(mode, size)
@ -141,8 +141,8 @@ class TestImage:
monkeypatch.setattr(Image, "WARN_POSSIBLE_FORMATS", True) monkeypatch.setattr(Image, "WARN_POSSIBLE_FORMATS", True)
im = io.BytesIO(b"") im = io.BytesIO(b"")
with pytest.warns(UserWarning): with pytest.raises(UnidentifiedImageError):
with pytest.raises(UnidentifiedImageError): with pytest.warns(UserWarning, match="opening failed"):
with Image.open(im): with Image.open(im):
pass pass
@ -1008,7 +1008,7 @@ class TestImage:
def test_get_child_images(self) -> None: def test_get_child_images(self) -> None:
im = Image.new("RGB", (1, 1)) im = Image.new("RGB", (1, 1))
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="Image.Image.get_child_images"):
assert im.get_child_images() == [] assert im.get_child_images() == []
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
@ -1139,7 +1139,7 @@ class TestImage:
assert im.fp is None assert im.fp is None
def test_deprecation(self) -> None: def test_deprecation(self) -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="Image.isImageType"):
assert not Image.isImageType(None) assert not Image.isImageType(None)
@ -1150,7 +1150,7 @@ class TestImageBytes:
source_bytes = im.tobytes() source_bytes = im.tobytes()
if mode.startswith("BGR;"): if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match=mode):
reloaded = Image.frombytes(mode, im.size, source_bytes) reloaded = Image.frombytes(mode, im.size, source_bytes)
else: else:
reloaded = Image.frombytes(mode, im.size, source_bytes) reloaded = Image.frombytes(mode, im.size, source_bytes)

View File

@ -193,7 +193,7 @@ class TestImageGetPixel:
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) @pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
def test_deprecated(self, mode: str) -> None: def test_deprecated(self, mode: str) -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="BGR;"):
self.check(mode) self.check(mode)
def test_list(self) -> None: def test_list(self) -> None:

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Any from typing import Any
import pytest import pytest
from packaging.version import parse as parse_version from packaging.version import parse as parse_version
@ -13,6 +13,7 @@ numpy = pytest.importorskip("numpy", reason="NumPy not installed")
im = hopper().resize((128, 100)) im = hopper().resize((128, 100))
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
import numpy.typing as npt import numpy.typing as npt
@ -47,7 +48,7 @@ def test_toarray() -> None:
with pytest.raises(OSError): with pytest.raises(OSError):
numpy.array(im_truncated) numpy.array(im_truncated)
else: else:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="__array_interface__"):
numpy.array(im_truncated) numpy.array(im_truncated)
@ -101,7 +102,8 @@ def test_fromarray_strides_without_tobytes() -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)}) wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)})
Image.fromarray(wrapped, "L") with pytest.warns(DeprecationWarning, match="'mode' parameter"):
Image.fromarray(wrapped, "L")
def test_fromarray_palette() -> None: def test_fromarray_palette() -> None:
@ -110,7 +112,8 @@ def test_fromarray_palette() -> None:
a = numpy.array(i) a = numpy.array(i)
# Act # Act
out = Image.fromarray(a, "P") with pytest.warns(DeprecationWarning, match="'mode' parameter"):
out = Image.fromarray(a, "P")
# Assert that the Python and C palettes match # Assert that the Python and C palettes match
assert out.palette is not None assert out.palette is not None

View File

@ -203,7 +203,10 @@ def test_trns_RGB(tmp_path: Path) -> None:
assert "transparency" not in im_rgba.info assert "transparency" not in im_rgba.info
assert im_rgba.getpixel((0, 0)) == (0, 0, 0, 0) assert im_rgba.getpixel((0, 0)) == (0, 0, 0, 0)
im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.Palette.ADAPTIVE) with pytest.warns(
UserWarning, match="Couldn't allocate palette entry for transparency"
):
im_p = im.convert("P", palette=Image.Palette.ADAPTIVE)
assert "transparency" not in im_p.info assert "transparency" not in im_p.info
im_p.save(f) im_p.save(f)

View File

@ -11,9 +11,9 @@ def test_sanity() -> None:
type_repr = repr(type(im.getim())) type_repr = repr(type(im.getim()))
assert "PyCapsule" in type_repr assert "PyCapsule" in type_repr
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="id property"):
assert isinstance(im.im.id, int) assert isinstance(im.im.id, int)
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="unsafe_ptrs property"):
ptrs = dict(im.im.unsafe_ptrs) ptrs = dict(im.im.unsafe_ptrs)
assert ptrs.keys() == {"image8", "image32", "image"} assert ptrs.keys() == {"image8", "image32", "image"}

View File

@ -81,7 +81,7 @@ def test_mode_F() -> None:
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) @pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
def test_mode_BGR(mode: str) -> None: def test_mode_BGR(mode: str) -> None:
data = [(16, 32, 49), (32, 32, 98)] data = [(16, 32, 49), (32, 32, 98)]
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match=mode):
im = Image.new(mode, (1, 2)) im = Image.new(mode, (1, 2))
im.putdata(data) im.putdata(data)

View File

@ -70,6 +70,7 @@ def test_quantize_no_dither() -> None:
converted = image.quantize(dither=Image.Dither.NONE, palette=palette) converted = image.quantize(dither=Image.Dither.NONE, palette=palette)
assert converted.mode == "P" assert converted.mode == "P"
assert converted.palette is not None assert converted.palette is not None
assert palette.palette is not None
assert converted.palette.palette == palette.palette.palette assert converted.palette.palette == palette.palette.palette

View File

@ -48,6 +48,7 @@ class TestImageTransform:
im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0] im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0]
) )
assert im.palette is not None assert im.palette is not None
assert transformed.palette is not None
assert im.palette.palette == transformed.palette.palette assert im.palette.palette == transformed.palette.palette
def test_extent(self) -> None: def test_extent(self) -> None:

View File

@ -54,7 +54,7 @@ def skip_missing() -> None:
def test_sanity() -> None: def test_sanity() -> None:
# basic smoke test. # basic smoke test.
# this mostly follows the cms_test outline. # this mostly follows the cms_test outline.
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="PIL.ImageCms.versions"):
v = ImageCms.versions() # should return four strings v = ImageCms.versions() # should return four strings
assert v[0] == "1.0.0 pil" assert v[0] == "1.0.0 pil"
assert list(map(type, v)) == [str, str, str, str] assert list(map(type, v)) == [str, str, str, str]
@ -679,7 +679,7 @@ def test_auxiliary_channels_isolated() -> None:
def test_long_modes() -> None: def test_long_modes() -> None:
p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc") p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc")
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="ABCDEFGHI"):
ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI") ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI")
@ -703,15 +703,15 @@ def test_cmyk_lab() -> None:
def test_deprecation() -> None: def test_deprecation() -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="ImageCms.DESCRIPTION"):
assert ImageCms.DESCRIPTION.strip().startswith("pyCMS") assert ImageCms.DESCRIPTION.strip().startswith("pyCMS")
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="ImageCms.VERSION"):
assert ImageCms.VERSION == "1.0.0 pil" assert ImageCms.VERSION == "1.0.0 pil"
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="ImageCms.FLAGS"):
assert isinstance(ImageCms.FLAGS, dict) assert isinstance(ImageCms.FLAGS, dict)
profile = ImageCmsProfile(ImageCms.createProfile("sRGB")) profile = ImageCmsProfile(ImageCms.createProfile("sRGB"))
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="RGBA;16B"):
ImageCms.ImageCmsTransform(profile, profile, "RGBA;16B", "RGB") ImageCms.ImageCmsTransform(profile, profile, "RGBA;16B", "RGB")
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="RGBA;16B"):
ImageCms.ImageCmsTransform(profile, profile, "RGB", "RGBA;16B") ImageCms.ImageCmsTransform(profile, profile, "RGB", "RGBA;16B")

View File

@ -1735,5 +1735,5 @@ def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:
def test_getdraw() -> None: def test_getdraw() -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="'hints' parameter"):
ImageDraw.getdraw(None, []) ImageDraw.getdraw(None, [])

View File

@ -152,7 +152,7 @@ class TestImageFile:
assert reads.count(im.decodermaxblock) == 1 assert reads.count(im.decodermaxblock) == 1
def test_raise_oserror(self) -> None: def test_raise_oserror(self) -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="raise_oserror"):
with pytest.raises(OSError): with pytest.raises(OSError):
ImageFile.raise_oserror(1) ImageFile.raise_oserror(1)

View File

@ -267,6 +267,23 @@ def test_render_multiline_text_align(
assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01) assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01)
def test_render_multiline_text_justify_anchor(
font: ImageFont.FreeTypeFont,
) -> None:
im = Image.new("RGB", (280, 240))
draw = ImageDraw.Draw(im)
for xy, anchor in (((0, 0), "la"), ((140, 80), "ma"), ((280, 160), "ra")):
draw.multiline_text(
xy,
"hey you you are awesome\nthis looks awkward\nthis\nlooks awkward",
font=font,
anchor=anchor,
align="justify",
)
assert_image_equal_tofile(im, "Tests/images/multiline_text_justify_anchor.png")
def test_unknown_align(font: ImageFont.FreeTypeFont) -> None: def test_unknown_align(font: ImageFont.FreeTypeFont) -> None:
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -1175,15 +1192,15 @@ def test_oom(test_file: str) -> None:
def test_raqm_missing_warning(monkeypatch: pytest.MonkeyPatch) -> None: def test_raqm_missing_warning(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(ImageFont.core, "HAVE_RAQM", False) monkeypatch.setattr(ImageFont.core, "HAVE_RAQM", False)
with pytest.warns(UserWarning) as record: with pytest.warns(
UserWarning,
match="Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout.",
):
font = ImageFont.truetype( font = ImageFont.truetype(
FONT_PATH, FONT_SIZE, layout_engine=ImageFont.Layout.RAQM FONT_PATH, FONT_SIZE, layout_engine=ImageFont.Layout.RAQM
) )
assert font.layout_engine == ImageFont.Layout.BASIC assert font.layout_engine == ImageFont.Layout.BASIC
assert str(record[-1].message) == (
"Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout."
)
@pytest.mark.parametrize("size", [-1, 0]) @pytest.mark.parametrize("size", [-1, 0])
@ -1202,5 +1219,5 @@ def test_freetype_deprecation(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(features, "version_module", fake_version_module) monkeypatch.setattr(features, "version_module", fake_version_module)
# Act / Assert # Act / Assert
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="FreeType 2.9.0"):
ImageFont.truetype(FONT_PATH, FONT_SIZE) ImageFont.truetype(FONT_PATH, FONT_SIZE)

View File

@ -56,7 +56,7 @@ def test_sanity() -> None:
def test_options_deprecated() -> None: def test_options_deprecated() -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="ImageMath.lambda_eval options"):
assert ImageMath.lambda_eval(lambda args: 1, images) == 1 assert ImageMath.lambda_eval(lambda args: 1, images) == 1

View File

@ -36,12 +36,12 @@ def test_sanity() -> None:
def test_eval_deprecated() -> None: def test_eval_deprecated() -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="ImageMath.eval"):
assert ImageMath.eval("1") == 1 assert ImageMath.eval("1") == 1
def test_options_deprecated() -> None: def test_options_deprecated() -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="ImageMath.unsafe_eval options"):
assert ImageMath.unsafe_eval("1", images) == 1 assert ImageMath.unsafe_eval("1", images) == 1

View File

@ -362,13 +362,15 @@ class TestLibUnpack:
) )
def test_BGR(self) -> None: def test_BGR(self) -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning, match="BGR;15"):
self.assert_unpack( self.assert_unpack(
"BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8) "BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8)
) )
with pytest.warns(DeprecationWarning, match="BGR;16"):
self.assert_unpack( self.assert_unpack(
"BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0) "BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0)
) )
with pytest.warns(DeprecationWarning, match="BGR;24"):
self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9))
def test_RGBA(self) -> None: def test_RGBA(self) -> None:

View File

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
import warnings import warnings
from typing import TYPE_CHECKING
import pytest import pytest
@ -9,6 +8,7 @@ from PIL import Image, _typing
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
import numpy import numpy
import numpy.typing as npt import numpy.typing as npt

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Union from typing import Union
import pytest import pytest
@ -9,6 +9,7 @@ from PIL import Image, ImageQt
from .helper import assert_image_equal_tofile, assert_image_similar, hopper from .helper import assert_image_equal_tofile, assert_image_similar, hopper
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
import PyQt6 import PyQt6
import PySide6 import PySide6

View File

@ -8,8 +8,8 @@ from __future__ import annotations
import re import re
import subprocess import subprocess
from typing import TYPE_CHECKING
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
from sphinx.application import Sphinx from sphinx.application import Sphinx

View File

@ -193,6 +193,28 @@ Image.Image.get_child_images()
method uses an image's file pointer, and so child images could only be retrieved from method uses an image's file pointer, and so child images could only be retrieved from
an :py:class:`PIL.ImageFile.ImageFile` instance. an :py:class:`PIL.ImageFile.ImageFile` instance.
Image.fromarray mode parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.3.0
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
mode can be automatically determined from the object's shape and type instead.
Saving I mode images as PNG
^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.3.0
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
changing the data, this is now deprecated. Instead, the image can be converted to
another mode before saving::
from PIL import Image
im = Image.new("I", (1, 1))
im.convert("I;16").save("out.png")
Removed features Removed features
---------------- ----------------

View File

@ -1089,6 +1089,26 @@ Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I
Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well. Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
QOI
^^^
.. versionadded:: 9.5.0
Pillow reads and writes images in Quite OK Image format using a Python codec. If you
wish to write code specifically for this format, :pypi:`qoi` is an alternative library
that uses C to decode the image and interfaces with NumPy.
.. _qoi-saving:
Saving
~~~~~~
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
**colorspace**
If set to "sRGB", the colorspace will be written as sRGB with linear alpha, instead
of all channels being linear.
SGI SGI
^^^ ^^^
@ -1585,15 +1605,6 @@ PSD
Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0. Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
QOI
^^^
.. versionadded:: 9.5.0
Pillow reads images in Quite OK Image format using a Python decoder. If you wish to
write code specifically for this format, :pypi:`qoi` is an alternative library that
uses C to decode the image and interfaces with NumPy.
SUN SUN
^^^ ^^^
@ -1657,7 +1668,8 @@ handler. ::
XPM XPM
^^^ ^^^
Pillow reads X pixmap files (mode ``P``) with 256 colors or less. Pillow reads X pixmap files as P mode images if there are 256 colors or less, and as
RGB images otherwise.
.. _xpm-opening: .. _xpm-opening:

View File

@ -4,49 +4,53 @@
Security Security
======== ========
TODO :cve:`2025-48379`: Write buffer overflow on BCn encoding
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO There is a heap buffer overflow when writing a sufficiently large (>64k encoded with
default settings) image in the DDS format due to writing into a buffer without checking
for available space.
:cve:`YYYY-XXXXX`: TODO This only affects users who save untrusted data as a compressed DDS image.
^^^^^^^^^^^^^^^^^^^^^^^
TODO * Unclear how large the potential write could be. It is likely limited by process
segfault, so it's not necessarily deterministic. It may be practically unbounded.
* Unclear if there's a restriction on the bytes that could be emitted. It's likely that
the only restriction is that the bytes would be emitted in chunks of 8 or 16.
Backwards incompatible changes This was introduced in Pillow 11.2.0 when the feature was added.
==============================
TODO
^^^^
Deprecations Deprecations
============ ============
TODO Image.fromarray mode parameter
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
mode can be automatically determined from the object's shape and type instead.
API changes Saving I mode images as PNG
=========== ^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
^^^^ at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
changing the data, this is now deprecated. Instead, the image can be converted to
another mode before saving::
TODO from PIL import Image
im = Image.new("I", (1, 1))
API additions im.convert("I;16").save("out.png")
=============
TODO
^^^^
TODO
Other changes Other changes
============= =============
Added QOI saving
^^^^^^^^^^^^^^^^
Support has been added for saving QOI images. ``colorspace`` can be used to specify the
colorspace as sRGB with linear alpha, e.g. ``im.save("out.qoi", colorspace="sRGB")``.
By default, all channels will be linear.
Support using more screenshot utilities with ImageGrab on Linux Support using more screenshot utilities with ImageGrab on Linux
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -60,6 +64,12 @@ Pillow only supports libavif 1.0.0 or later. In order to prevent errors when bui
from source, if a user happens to have an earlier libavif on their system, Pillow will from source, if a user happens to have an earlier libavif on their system, Pillow will
now ignore it. now ignore it.
AVIF support in wheels
^^^^^^^^^^^^^^^^^^^^^^
Support for reading and writing AVIF images is now included in Pillow's wheels, except
for Windows ARM64. libaom is available as an encoder and dav1d as a decoder.
Python 3.14 beta Python 3.14 beta
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^

View File

@ -16,7 +16,6 @@ import subprocess
import sys import sys
import warnings import warnings
from collections.abc import Iterator from collections.abc import Iterator
from typing import Any
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
@ -148,7 +147,7 @@ class RequiredDependencyException(Exception):
PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version
def _dbg(s: str, tp: Any = None) -> None: def _dbg(s: str, tp: str | tuple[str, ...] | None = None) -> None:
if DEBUG: if DEBUG:
if tp: if tp:
print(s % tp) print(s % tp)
@ -509,11 +508,11 @@ class pil_build_ext(build_ext):
if root is None and pkg_config: if root is None and pkg_config:
if isinstance(lib_name, str): if isinstance(lib_name, str):
_dbg(f"Looking for `{lib_name}` using pkg-config.") _dbg("Looking for `%s` using pkg-config.", lib_name)
root = pkg_config(lib_name) root = pkg_config(lib_name)
else: else:
for lib_name2 in lib_name: for lib_name2 in lib_name:
_dbg(f"Looking for `{lib_name2}` using pkg-config.") _dbg("Looking for `%s` using pkg-config.", lib_name2)
root = pkg_config(lib_name2) root = pkg_config(lib_name2)
if root: if root:
break break
@ -732,7 +731,7 @@ class pil_build_ext(build_ext):
best_path = os.path.join(directory, name) best_path = os.path.join(directory, name)
_dbg( _dbg(
"Best openjpeg version %s so far in %s", "Best openjpeg version %s so far in %s",
(best_version, best_path), (str(best_version), best_path),
) )
if best_version and _find_library_file(self, "openjp2"): if best_version and _find_library_file(self, "openjp2"):
@ -754,12 +753,12 @@ class pil_build_ext(build_ext):
if feature.want("tiff"): if feature.want("tiff"):
_dbg("Looking for tiff") _dbg("Looking for tiff")
if _find_include_file(self, "tiff.h"): if _find_include_file(self, "tiff.h"):
if _find_library_file(self, "tiff"):
feature.set("tiff", "tiff")
if sys.platform in ["win32", "darwin"] and _find_library_file( if sys.platform in ["win32", "darwin"] and _find_library_file(
self, "libtiff" self, "libtiff"
): ):
feature.set("tiff", "libtiff") feature.set("tiff", "libtiff")
elif _find_library_file(self, "tiff"):
feature.set("tiff", "tiff")
if feature.want("freetype"): if feature.want("freetype"):
_dbg("Looking for freetype") _dbg("Looking for freetype")

View File

@ -445,9 +445,9 @@ def _save(
image = stride * im.size[1] image = stride * im.size[1]
if im.mode == "1": if im.mode == "1":
palette = b"".join(o8(i) * 4 for i in (0, 255)) palette = b"".join(o8(i) * 3 + b"\x00" for i in (0, 255))
elif im.mode == "L": elif im.mode == "L":
palette = b"".join(o8(i) * 4 for i in range(256)) palette = b"".join(o8(i) * 3 + b"\x00" for i in range(256))
elif im.mode == "P": elif im.mode == "P":
palette = im.im.getpalette("RGB", "BGRX") palette = im.im.getpalette("RGB", "BGRX")
colors = len(palette) // 4 colors = len(palette) // 4

View File

@ -362,7 +362,7 @@ class IcoImageFile(ImageFile.ImageFile):
self.info["sizes"] = set(sizes) self.info["sizes"] = set(sizes)
self.size = im.size self.size = im.size
return None return Image.Image.load(self)
def load_seek(self, pos: int) -> None: def load_seek(self, pos: int) -> None:
# Flag the ImageFile.Parser so that it # Flag the ImageFile.Parser so that it

View File

@ -2556,7 +2556,8 @@ class Image:
self.load() self.load()
save_all = params.pop("save_all", None) save_all = params.pop("save_all", None)
self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params} encoderinfo = getattr(self, "encoderinfo", {})
self.encoderinfo = {**encoderinfo, **params}
self.encoderconfig: tuple[Any, ...] = () self.encoderconfig: tuple[Any, ...] = ()
if format.upper() not in SAVE: if format.upper() not in SAVE:
@ -2594,10 +2595,7 @@ class Image:
pass pass
raise raise
finally: finally:
try: self.encoderinfo = encoderinfo
del self.encoderinfo
except AttributeError:
pass
if open_fp: if open_fp:
fp.close() fp.close()
@ -3272,7 +3270,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
:param obj: Object with array interface :param obj: Object with array interface
:param mode: Optional mode to use when reading ``obj``. Will be determined from :param mode: Optional mode to use when reading ``obj``. Will be determined from
type if ``None``. type if ``None``. Deprecated.
This will not be used to convert the data after reading, but will be used to This will not be used to convert the data after reading, but will be used to
change how the data is read:: change how the data is read::
@ -3307,6 +3305,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
msg = f"Cannot handle this data type: {typekey_shape}, {typestr}" msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
raise TypeError(msg) from e raise TypeError(msg) from e
else: else:
deprecate("'mode' parameter", 13)
rawmode = mode rawmode = mode
if mode in ["1", "L", "I", "P", "F"]: if mode in ["1", "L", "I", "P", "F"]:
ndmax = 2 ndmax = 2

View File

@ -248,6 +248,9 @@ class ImageCmsProfile:
low-level profile object low-level profile object
""" """
self.filename = None
self.product_name = None # profile.product_name
self.product_info = None # profile.product_info
if isinstance(profile, str): if isinstance(profile, str):
if sys.platform == "win32": if sys.platform == "win32":
@ -256,23 +259,18 @@ class ImageCmsProfile:
profile_bytes_path.decode("ascii") profile_bytes_path.decode("ascii")
except UnicodeDecodeError: except UnicodeDecodeError:
with open(profile, "rb") as f: with open(profile, "rb") as f:
self._set(core.profile_frombytes(f.read())) self.profile = core.profile_frombytes(f.read())
return return
self._set(core.profile_open(profile), profile) self.filename = profile
self.profile = core.profile_open(profile)
elif hasattr(profile, "read"): elif hasattr(profile, "read"):
self._set(core.profile_frombytes(profile.read())) self.profile = core.profile_frombytes(profile.read())
elif isinstance(profile, core.CmsProfile): elif isinstance(profile, core.CmsProfile):
self._set(profile) self.profile = profile
else: else:
msg = "Invalid type for Profile" # type: ignore[unreachable] msg = "Invalid type for Profile" # type: ignore[unreachable]
raise TypeError(msg) raise TypeError(msg)
def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None:
self.profile = profile
self.filename = filename
self.product_name = None # profile.product_name
self.product_info = None # profile.product_info
def tobytes(self) -> bytes: def tobytes(self) -> bytes:
""" """
Returns the profile in a format suitable for embedding in Returns the profile in a format suitable for embedding in

View File

@ -690,8 +690,7 @@ class ImageDraw:
font_size: float | None, font_size: float | None,
) -> tuple[ ) -> tuple[
ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont, ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
str, list[tuple[tuple[float, float], str, AnyStr]],
list[tuple[tuple[float, float], AnyStr]],
]: ]:
if direction == "ttb": if direction == "ttb":
msg = "ttb direction is unsupported for multiline text" msg = "ttb direction is unsupported for multiline text"
@ -741,13 +740,7 @@ class ImageDraw:
left = xy[0] left = xy[0]
width_difference = max_width - widths[idx] width_difference = max_width - widths[idx]
# first align left by anchor # align by align parameter
if anchor[0] == "m":
left -= width_difference / 2.0
elif anchor[0] == "r":
left -= width_difference
# then align by align parameter
if align in ("left", "justify"): if align in ("left", "justify"):
pass pass
elif align == "center": elif align == "center":
@ -758,29 +751,43 @@ class ImageDraw:
msg = 'align must be "left", "center", "right" or "justify"' msg = 'align must be "left", "center", "right" or "justify"'
raise ValueError(msg) raise ValueError(msg)
if align == "justify" and width_difference != 0: if align == "justify" and width_difference != 0 and idx != len(lines) - 1:
words = line.split(" " if isinstance(text, str) else b" ") words = line.split(" " if isinstance(text, str) else b" ")
word_widths = [ if len(words) > 1:
self.textlength( # align left by anchor
word, if anchor[0] == "m":
font, left -= max_width / 2.0
direction=direction, elif anchor[0] == "r":
features=features, left -= max_width
language=language,
embedded_color=embedded_color,
)
for word in words
]
width_difference = max_width - sum(word_widths)
for i, word in enumerate(words):
parts.append(((left, top), word))
left += word_widths[i] + width_difference / (len(words) - 1)
else:
parts.append(((left, top), line))
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 top += line_spacing
return font, anchor, parts return font, parts
def multiline_text( def multiline_text(
self, self,
@ -805,7 +812,7 @@ class ImageDraw:
*, *,
font_size: float | None = None, font_size: float | None = None,
) -> None: ) -> None:
font, anchor, lines = self._prepare_multiline_text( font, lines = self._prepare_multiline_text(
xy, xy,
text, text,
font, font,
@ -820,7 +827,7 @@ class ImageDraw:
font_size, font_size,
) )
for xy, line in lines: for xy, anchor, line in lines:
self.text( self.text(
xy, xy,
line, line,
@ -935,7 +942,7 @@ class ImageDraw:
*, *,
font_size: float | None = None, font_size: float | None = None,
) -> tuple[float, float, float, float]: ) -> tuple[float, float, float, float]:
font, anchor, lines = self._prepare_multiline_text( font, lines = self._prepare_multiline_text(
xy, xy,
text, text,
font, font,
@ -952,7 +959,7 @@ class ImageDraw:
bbox: tuple[float, float, float, float] | None = None bbox: tuple[float, float, float, float] | None = None
for xy, line in lines: for xy, anchor, line in lines:
bbox_line = self.textbbox( bbox_line = self.textbbox(
xy, xy,
line, line,

View File

@ -175,7 +175,9 @@ class MacViewer(Viewer):
if not os.path.exists(path): if not os.path.exists(path):
raise FileNotFoundError raise FileNotFoundError
subprocess.call(["open", "-a", "Preview.app", path]) subprocess.call(["open", "-a", "Preview.app", path])
executable = sys.executable or shutil.which("python3")
pyinstaller = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
executable = (not pyinstaller and sys.executable) or shutil.which("python3")
if executable: if executable:
subprocess.Popen( subprocess.Popen(
[ [

View File

@ -179,7 +179,8 @@ class IptcImageFile(ImageFile.ImageFile):
with Image.open(o) as _im: with Image.open(o) as _im:
_im.load() _im.load()
self.im = _im.im self.im = _im.im
return None self.tile = []
return Image.Image.load(self)
Image.register_open(IptcImageFile.format, IptcImageFile) Image.register_open(IptcImageFile.format, IptcImageFile)

View File

@ -48,6 +48,7 @@ from ._binary import i32be as i32
from ._binary import o8 from ._binary import o8
from ._binary import o16be as o16 from ._binary import o16be as o16
from ._binary import o32be as o32 from ._binary import o32be as o32
from ._deprecate import deprecate
from ._util import DeferredError from ._util import DeferredError
TYPE_CHECKING = False TYPE_CHECKING = False
@ -1368,6 +1369,8 @@ def _save(
except KeyError as e: except KeyError as e:
msg = f"cannot write mode {mode} as PNG" msg = f"cannot write mode {mode} as PNG"
raise OSError(msg) from e raise OSError(msg) from e
if outmode == "I":
deprecate("Saving I mode images as PNG", 13, stacklevel=4)
# #
# write minimal PNG file # write minimal PNG file

View File

@ -8,9 +8,12 @@
from __future__ import annotations from __future__ import annotations
import os import os
from typing import IO
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i32be as i32 from ._binary import i32be as i32
from ._binary import o8
from ._binary import o32be as o32
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
@ -110,6 +113,122 @@ class QoiDecoder(ImageFile.PyDecoder):
return -1, 0 return -1, 0
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode == "RGB":
channels = 3
elif im.mode == "RGBA":
channels = 4
else:
msg = "Unsupported QOI image mode"
raise ValueError(msg)
colorspace = 0 if im.encoderinfo.get("colorspace") == "sRGB" else 1
fp.write(b"qoif")
fp.write(o32(im.size[0]))
fp.write(o32(im.size[1]))
fp.write(o8(channels))
fp.write(o8(colorspace))
ImageFile._save(im, fp, [ImageFile._Tile("qoi", (0, 0) + im.size)])
class QoiEncoder(ImageFile.PyEncoder):
_pushes_fd = True
_previous_pixel: tuple[int, int, int, int] | None = None
_previously_seen_pixels: dict[int, tuple[int, int, int, int]] = {}
_run = 0
def _write_run(self) -> bytes:
data = o8(0b11000000 | (self._run - 1)) # QOI_OP_RUN
self._run = 0
return data
def _delta(self, left: int, right: int) -> int:
result = (left - right) & 255
if result >= 128:
result -= 256
return result
def encode(self, bufsize: int) -> tuple[int, int, bytes]:
assert self.im is not None
self._previously_seen_pixels = {0: (0, 0, 0, 0)}
self._previous_pixel = (0, 0, 0, 255)
data = bytearray()
w, h = self.im.size
bands = Image.getmodebands(self.mode)
for y in range(h):
for x in range(w):
pixel = self.im.getpixel((x, y))
if bands == 3:
pixel = (*pixel, 255)
if pixel == self._previous_pixel:
self._run += 1
if self._run == 62:
data += self._write_run()
else:
if self._run:
data += self._write_run()
r, g, b, a = pixel
hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64
if self._previously_seen_pixels.get(hash_value) == pixel:
data += o8(hash_value) # QOI_OP_INDEX
elif self._previous_pixel:
self._previously_seen_pixels[hash_value] = pixel
prev_r, prev_g, prev_b, prev_a = self._previous_pixel
if prev_a == a:
delta_r = self._delta(r, prev_r)
delta_g = self._delta(g, prev_g)
delta_b = self._delta(b, prev_b)
if (
-2 <= delta_r < 2
and -2 <= delta_g < 2
and -2 <= delta_b < 2
):
data += o8(
0b01000000
| (delta_r + 2) << 4
| (delta_g + 2) << 2
| (delta_b + 2)
) # QOI_OP_DIFF
else:
delta_gr = self._delta(delta_r, delta_g)
delta_gb = self._delta(delta_b, delta_g)
if (
-8 <= delta_gr < 8
and -32 <= delta_g < 32
and -8 <= delta_gb < 8
):
data += o8(
0b10000000 | (delta_g + 32)
) # QOI_OP_LUMA
data += o8((delta_gr + 8) << 4 | (delta_gb + 8))
else:
data += o8(0b11111110) # QOI_OP_RGB
data += bytes(pixel[:3])
else:
data += o8(0b11111111) # QOI_OP_RGBA
data += bytes(pixel)
self._previous_pixel = pixel
if self._run:
data += self._write_run()
data += bytes((0, 0, 0, 0, 0, 0, 0, 1)) # padding
return len(data), 0, data
Image.register_open(QoiImageFile.format, QoiImageFile, _accept) Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
Image.register_decoder("qoi", QoiDecoder) Image.register_decoder("qoi", QoiDecoder)
Image.register_extension(QoiImageFile.format, ".qoi") Image.register_extension(QoiImageFile.format, ".qoi")
Image.register_save(QoiImageFile.format, _save)
Image.register_encoder("qoi", QoiEncoder)

View File

@ -82,17 +82,10 @@ class SgiImageFile(ImageFile.ImageFile):
# zsize : channels count # zsize : channels count
zsize = i16(s, 10) zsize = i16(s, 10)
# layout
layout = bpc, dimension, zsize
# determine mode from bits/zsize # determine mode from bits/zsize
rawmode = ""
try: try:
rawmode = MODES[layout] rawmode = MODES[(bpc, dimension, zsize)]
except KeyError: except KeyError:
pass
if rawmode == "":
msg = "Unsupported SGI image mode" msg = "Unsupported SGI image mode"
raise ValueError(msg) raise ValueError(msg)
@ -156,24 +149,15 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# Run-Length Encoding Compression - Unsupported at this time # Run-Length Encoding Compression - Unsupported at this time
rle = 0 rle = 0
# Number of dimensions (x,y,z)
dim = 3
# X Dimension = width / Y Dimension = height # X Dimension = width / Y Dimension = height
x, y = im.size x, y = im.size
if im.mode == "L" and y == 1:
dim = 1
elif im.mode == "L":
dim = 2
# Z Dimension: Number of channels # Z Dimension: Number of channels
z = len(im.mode) z = len(im.mode)
# Number of dimensions (x,y,z)
if dim in {1, 2}: if im.mode == "L":
z = 1 dimension = 1 if y == 1 else 2
else:
# assert we've got the right number of bands. dimension = 3
if len(im.getbands()) != z:
msg = f"incorrect number of bands in SGI write: {z} vs {len(im.getbands())}"
raise ValueError(msg)
# Minimum Byte value # Minimum Byte value
pinmin = 0 pinmin = 0
@ -188,7 +172,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(struct.pack(">h", magic_number)) fp.write(struct.pack(">h", magic_number))
fp.write(o8(rle)) fp.write(o8(rle))
fp.write(o8(bpc)) fp.write(o8(bpc))
fp.write(struct.pack(">H", dim)) fp.write(struct.pack(">H", dimension))
fp.write(struct.pack(">H", x)) fp.write(struct.pack(">H", x))
fp.write(struct.pack(">H", y)) fp.write(struct.pack(">H", y))
fp.write(struct.pack(">H", z)) fp.write(struct.pack(">H", z))

View File

@ -1680,7 +1680,7 @@ SAVE_INFO = {
"PA": ("PA", II, 3, 1, (8, 8), 2), "PA": ("PA", II, 3, 1, (8, 8), 2),
"I": ("I;32S", II, 1, 2, (32,), None), "I": ("I;32S", II, 1, 2, (32,), None),
"I;16": ("I;16", II, 1, 1, (16,), None), "I;16": ("I;16", II, 1, 1, (16,), None),
"I;16S": ("I;16S", II, 1, 2, (16,), None), "I;16L": ("I;16L", II, 1, 1, (16,), None),
"F": ("F;32F", II, 1, 3, (32,), None), "F": ("F;32F", II, 1, 3, (32,), None),
"RGB": ("RGB", II, 2, 1, (8, 8, 8), None), "RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
"RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0), "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
@ -1688,10 +1688,7 @@ SAVE_INFO = {
"CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None), "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
"YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None), "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
"LAB": ("LAB", II, 8, 1, (8, 8, 8), None), "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
"I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
"I;16B": ("I;16B", MM, 1, 1, (16,), None), "I;16B": ("I;16B", MM, 1, 1, (16,), None),
"I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
"F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
} }
@ -1967,7 +1964,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# we're storing image byte order. So, if the rawmode # we're storing image byte order. So, if the rawmode
# contains I;16, we need to convert from native to image # contains I;16, we need to convert from native to image
# byte order. # byte order.
if im.mode in ("I;16B", "I;16"): if im.mode in ("I;16", "I;16B", "I;16L"):
rawmode = "I;16N" rawmode = "I;16N"
# Pass tags as sorted list so that the tags are set in a fixed order. # Pass tags as sorted list so that the tags are set in a fixed order.

View File

@ -37,43 +37,36 @@ class XpmImageFile(ImageFile.ImageFile):
format_description = "X11 Pixel Map" format_description = "X11 Pixel Map"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
if not _accept(self.fp.read(9)): if not _accept(self.fp.read(9)):
msg = "not an XPM file" msg = "not an XPM file"
raise SyntaxError(msg) raise SyntaxError(msg)
# skip forward to next string # skip forward to next string
while True: while True:
s = self.fp.readline() line = self.fp.readline()
if not s: if not line:
msg = "broken XPM file" msg = "broken XPM file"
raise SyntaxError(msg) raise SyntaxError(msg)
m = xpm_head.match(s) m = xpm_head.match(line)
if m: if m:
break break
self._size = int(m.group(1)), int(m.group(2)) self._size = int(m.group(1)), int(m.group(2))
pal = int(m.group(3)) palette_length = int(m.group(3))
bpp = int(m.group(4)) bpp = int(m.group(4))
if pal > 256 or bpp != 1:
msg = "cannot read this XPM file"
raise ValueError(msg)
# #
# load palette description # load palette description
palette = [b"\0\0\0"] * 256 palette = {}
for _ in range(pal): for _ in range(palette_length):
s = self.fp.readline() line = self.fp.readline().rstrip()
if s.endswith(b"\r\n"):
s = s[:-2]
elif s.endswith((b"\r", b"\n")):
s = s[:-1]
c = s[1] c = line[1 : bpp + 1]
s = s[2:-2].split() s = line[bpp + 1 : -2].split()
for i in range(0, len(s), 2): for i in range(0, len(s), 2):
if s[i] == b"c": if s[i] == b"c":
@ -82,10 +75,11 @@ class XpmImageFile(ImageFile.ImageFile):
if rgb == b"None": if rgb == b"None":
self.info["transparency"] = c self.info["transparency"] = c
elif rgb.startswith(b"#"): elif rgb.startswith(b"#"):
# FIXME: handle colour names (see ImagePalette.py) rgb_int = int(rgb[1:], 16)
rgb = int(rgb[1:], 16)
palette[c] = ( palette[c] = (
o8((rgb >> 16) & 255) + o8((rgb >> 8) & 255) + o8(rgb & 255) o8((rgb_int >> 16) & 255)
+ o8((rgb_int >> 8) & 255)
+ o8(rgb_int & 255)
) )
else: else:
# unknown colour # unknown colour
@ -98,10 +92,16 @@ class XpmImageFile(ImageFile.ImageFile):
msg = "cannot read this XPM file" msg = "cannot read this XPM file"
raise ValueError(msg) raise ValueError(msg)
self._mode = "P" args: tuple[int, dict[bytes, bytes] | tuple[bytes, ...]]
self.palette = ImagePalette.raw("RGB", b"".join(palette)) if palette_length > 256:
self._mode = "RGB"
args = (bpp, palette)
else:
self._mode = "P"
self.palette = ImagePalette.raw("RGB", b"".join(palette.values()))
args = (bpp, tuple(palette.keys()))
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), "P")] self.tile = [ImageFile._Tile("xpm", (0, 0) + self.size, self.fp.tell(), args)]
def load_read(self, read_bytes: int) -> bytes: def load_read(self, read_bytes: int) -> bytes:
# #
@ -109,16 +109,48 @@ class XpmImageFile(ImageFile.ImageFile):
xsize, ysize = self.size xsize, ysize = self.size
assert self.fp is not None
s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)] s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)]
return b"".join(s) return b"".join(s)
class XpmDecoder(ImageFile.PyDecoder):
_pulls_fd = True
def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
data = bytearray()
bpp, palette = self.args
dest_length = self.state.xsize * self.state.ysize
if self.mode == "RGB":
dest_length *= 3
pixel_header = False
while len(data) < dest_length:
line = self.fd.readline()
if not line:
break
if line.rstrip() == b"/* pixels */" and not pixel_header:
pixel_header = True
continue
line = b'"'.join(line.split(b'"')[1:-1])
for i in range(0, len(line), bpp):
key = line[i : i + bpp]
if self.mode == "RGB":
data += palette[key]
else:
data += o8(palette.index(key))
self.set_as_raw(bytes(data))
return -1, 0
# #
# Registry # Registry
Image.register_open(XpmImageFile.format, XpmImageFile, _accept) Image.register_open(XpmImageFile.format, XpmImageFile, _accept)
Image.register_decoder("xpm", XpmDecoder)
Image.register_extension(XpmImageFile.format, ".xpm") Image.register_extension(XpmImageFile.format, ".xpm")

View File

@ -12,6 +12,7 @@ def deprecate(
*, *,
action: str | None = None, action: str | None = None,
plural: bool = False, plural: bool = False,
stacklevel: int = 3,
) -> None: ) -> None:
""" """
Deprecations helper. Deprecations helper.
@ -67,5 +68,5 @@ def deprecate(
warnings.warn( warnings.warn(
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}", f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
DeprecationWarning, DeprecationWarning,
stacklevel=3, stacklevel=stacklevel,
) )

View File

@ -881,26 +881,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__avif(void) { PyInit__avif(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_avif", .m_name = "_avif",
.m_size = -1,
.m_methods = avifMethods, .m_methods = avifMethods,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
Py_DECREF(m);
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -4463,27 +4463,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imaging(void) { PyInit__imaging(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imaging", .m_name = "_imaging",
.m_size = -1,
.m_methods = functions, .m_methods = functions,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
Py_DECREF(m);
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -1463,28 +1463,24 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingcms(void) { PyInit__imagingcms(void) {
PyObject *m; PyDateTime_IMPORT;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingcms", .m_name = "_imagingcms",
.m_size = -1,
.m_methods = pyCMSdll_methods, .m_methods = pyCMSdll_methods,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
return NULL;
}
PyDateTime_IMPORT;
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -1601,26 +1601,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingft(void) { PyInit__imagingft(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingft", .m_name = "_imagingft",
.m_size = -1,
.m_methods = _functions, .m_methods = _functions,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -302,26 +302,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingmath(void) { PyInit__imagingmath(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingmath", .m_name = "_imagingmath",
.m_size = -1,
.m_methods = _functions, .m_methods = _functions,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -246,23 +246,22 @@ static PyMethodDef functions[] = {
{NULL, NULL, 0, NULL} {NULL, NULL, 0, NULL}
}; };
static PyModuleDef_Slot slots[] = {
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingmorph(void) { PyInit__imagingmorph(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingmorph", .m_name = "_imagingmorph",
.m_doc = "A module for doing image morphology", .m_doc = "A module for doing image morphology",
.m_size = -1,
.m_methods = functions, .m_methods = functions,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -46,24 +46,22 @@ static PyMethodDef functions[] = {
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, load_tkinter_funcs},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingtk(void) { PyInit__imagingtk(void) {
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingtk", .m_name = "_imagingtk",
.m_size = -1,
.m_methods = functions, .m_methods = functions,
.m_slots = slots
}; };
PyObject *m;
m = PyModule_Create(&module_def);
if (load_tkinter_funcs() != 0) {
Py_DECREF(m);
return NULL;
}
#ifdef Py_GIL_DISABLED return PyModuleDef_Init(&module_def);
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -780,26 +780,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__webp(void) { PyInit__webp(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_webp", .m_name = "_webp",
.m_size = -1,
.m_methods = webpMethods, .m_methods = webpMethods,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
Py_DECREF(m);
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -258,6 +258,10 @@ ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
UINT8 *dst = buf; UINT8 *dst = buf;
for (;;) { for (;;) {
// Loop writes a max of 16 bytes per iteration
if (dst + 16 >= bytes + buf) {
break;
}
if (n == 5) { if (n == 5) {
encode_bc3_alpha(im, state, dst, 0); encode_bc3_alpha(im, state, dst, 0);
dst += 8; dst += 8;

View File

@ -1032,7 +1032,10 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
TRACE(("Encode Error, row %d\n", state->y)); TRACE(("Encode Error, row %d\n", state->y));
state->errcode = IMAGING_CODEC_BROKEN; state->errcode = IMAGING_CODEC_BROKEN;
if (!clientstate->fp) { if (clientstate->fp) {
TIFFCleanup(tiff);
clientstate->tiff = NULL;
} else {
free(clientstate->data); free(clientstate->data);
} }
return -1; return -1;

View File

@ -0,0 +1,26 @@
Copyright (c) 2016, Alliance for Open Media. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,23 @@
Copyright © 2018-2019, VideoLAN and dav1d authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,387 @@
Copyright 2019 Joe Drago. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
------------------------------------------------------------------------------
Files: src/obu.c
Copyright © 2018-2019, VideoLAN and dav1d authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
------------------------------------------------------------------------------
Files: third_party/iccjpeg/*
In plain English:
1. We don't promise that this software works. (But if you find any bugs,
please let us know!)
2. You can use this software for whatever you want. You don't have to pay us.
3. You may not pretend that you wrote this software. If you use it in a
program, you must acknowledge somewhere in your documentation that
you've used the IJG code.
In legalese:
The authors make NO WARRANTY or representation, either express or implied,
with respect to this software, its quality, accuracy, merchantability, or
fitness for a particular purpose. This software is provided "AS IS", and you,
its user, assume the entire risk as to its quality and accuracy.
This software is copyright (C) 1991-2013, Thomas G. Lane, Guido Vollbeding.
All Rights Reserved except as specified below.
Permission is hereby granted to use, copy, modify, and distribute this
software (or portions thereof) for any purpose, without fee, subject to these
conditions:
(1) If any part of the source code for this software is distributed, then this
README file must be included, with this copyright and no-warranty notice
unaltered; and any additions, deletions, or changes to the original files
must be clearly indicated in accompanying documentation.
(2) If only executable code is distributed, then the accompanying
documentation must state that "this software is based in part on the work of
the Independent JPEG Group".
(3) Permission for use of this software is granted only if the user accepts
full responsibility for any undesirable consequences; the authors accept
NO LIABILITY for damages of any kind.
These conditions apply to any software derived from or based on the IJG code,
not just to the unmodified library. If you use our work, you ought to
acknowledge us.
Permission is NOT granted for the use of any IJG author's name or company name
in advertising or publicity relating to this software or products derived from
it. This software may be referred to only as "the Independent JPEG Group's
software".
We specifically permit and encourage the use of this software as the basis of
commercial products, provided that all warranty or liability claims are
assumed by the product vendor.
The Unix configuration script "configure" was produced with GNU Autoconf.
It is copyright by the Free Software Foundation but is freely distributable.
The same holds for its supporting scripts (config.guess, config.sub,
ltmain.sh). Another support script, install-sh, is copyright by X Consortium
but is also freely distributable.
The IJG distribution formerly included code to read and write GIF files.
To avoid entanglement with the Unisys LZW patent, GIF reading support has
been removed altogether, and the GIF writer has been simplified to produce
"uncompressed GIFs". This technique does not use the LZW algorithm; the
resulting GIF files are larger than usual, but are readable by all standard
GIF decoders.
We are required to state that
"The Graphics Interchange Format(c) is the Copyright property of
CompuServe Incorporated. GIF(sm) is a Service Mark property of
CompuServe Incorporated."
------------------------------------------------------------------------------
Files: contrib/gdk-pixbuf/*
Copyright 2020 Emmanuel Gil Peyrot. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
------------------------------------------------------------------------------
Files: android_jni/gradlew*
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
------------------------------------------------------------------------------
Files: third_party/libyuv/*
Copyright 2011 The LibYuv Project Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Google nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,29 @@
Copyright 2011 The LibYuv Project Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Google nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -24,6 +24,6 @@ cd ..
%PYTHON%\python.exe -m pip install -v -C raqm=vendor -C fribidi=vendor . %PYTHON%\python.exe -m pip install -v -C raqm=vendor -C fribidi=vendor .
path C:\Pillow\winbuild\build\bin;%PATH% path C:\Pillow\winbuild\build\bin;%PATH%
%PYTHON%\python.exe selftest.py %PYTHON%\python.exe selftest.py
%PYTHON%\python.exe -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests %PYTHON%\python.exe -m pytest -vv -x --cov PIL --cov Tests --cov-report term --cov-report xml Tests
%PYTHON%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor . %PYTHON%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor .
``` ```

View File

@ -124,5 +124,5 @@ Here's an example script to build on Windows::
%PYTHON%\python.exe -m pip install -v -C raqm=vendor -C fribidi=vendor . %PYTHON%\python.exe -m pip install -v -C raqm=vendor -C fribidi=vendor .
path C:\Pillow\winbuild\build\bin;%PATH% path C:\Pillow\winbuild\build\bin;%PATH%
%PYTHON%\python.exe selftest.py %PYTHON%\python.exe selftest.py
%PYTHON%\python.exe -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests %PYTHON%\python.exe -m pytest -vv -x --cov PIL --cov Tests --cov-report term --cov-report xml Tests
%PYTHON%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor . %PYTHON%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor .

View File

@ -57,7 +57,10 @@ def cmd_nmake(
def cmds_cmake( def cmds_cmake(
target: str | tuple[str, ...] | list[str], *params: str, build_dir: str = "." target: str | tuple[str, ...] | list[str],
*params: str,
build_dir: str = ".",
build_type: str = "Release",
) -> list[str]: ) -> list[str]:
if not isinstance(target, str): if not isinstance(target, str):
target = " ".join(target) target = " ".join(target)
@ -66,7 +69,7 @@ def cmds_cmake(
" ".join( " ".join(
[ [
"{cmake}", "{cmake}",
"-DCMAKE_BUILD_TYPE=Release", f"-DCMAKE_BUILD_TYPE={build_type}",
"-DCMAKE_VERBOSE_MAKEFILE=ON", "-DCMAKE_VERBOSE_MAKEFILE=ON",
"-DCMAKE_RULE_MESSAGES:BOOL=OFF", # for NMake "-DCMAKE_RULE_MESSAGES:BOOL=OFF", # for NMake
"-DCMAKE_C_COMPILER=cl.exe", # for Ninja "-DCMAKE_C_COMPILER=cl.exe", # for Ninja
@ -385,8 +388,8 @@ DEPS: dict[str, dict[str, Any]] = {
"bins": [r"*.dll"], "bins": [r"*.dll"],
}, },
"libavif": { "libavif": {
"url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.zip", "url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.tar.gz",
"filename": f"libavif-{V['LIBAVIF']}.zip", "filename": f"libavif-{V['LIBAVIF']}.tar.gz",
"license": "LICENSE", "license": "LICENSE",
"build": [ "build": [
"rustup update", "rustup update",
@ -397,9 +400,11 @@ DEPS: dict[str, dict[str, Any]] = {
"-DAVIF_LIBSHARPYUV=LOCAL", "-DAVIF_LIBSHARPYUV=LOCAL",
"-DAVIF_LIBYUV=LOCAL", "-DAVIF_LIBYUV=LOCAL",
"-DAVIF_CODEC_AOM=LOCAL", "-DAVIF_CODEC_AOM=LOCAL",
"-DCONFIG_AV1_HIGHBITDEPTH=0",
"-DAVIF_CODEC_AOM_DECODE=OFF",
"-DAVIF_CODEC_DAV1D=LOCAL", "-DAVIF_CODEC_DAV1D=LOCAL",
"-DAVIF_CODEC_RAV1E=LOCAL", "-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON",
"-DAVIF_CODEC_SVT=LOCAL", build_type="MinSizeRel",
), ),
cmd_xcopy("include", "{inc_dir}"), cmd_xcopy("include", "{inc_dir}"),
], ],
@ -755,7 +760,7 @@ def main() -> None:
disabled += ["libimagequant"] disabled += ["libimagequant"]
if args.no_fribidi: if args.no_fribidi:
disabled += ["fribidi"] disabled += ["fribidi"]
if args.no_avif or args.architecture != "AMD64": if args.no_avif or args.architecture == "ARM64":
disabled += ["libavif"] disabled += ["libavif"]
prefs = { prefs = {