Merge branch 'main' into font

This commit is contained in:
Andrew Murray 2023-12-04 22:25:58 +11:00
commit 9a6c47a9d2
32 changed files with 326 additions and 133 deletions

View File

@ -0,0 +1 @@
cibuildwheel==2.16.2

View File

@ -72,10 +72,10 @@ jobs:
- name: Install dependencies
id: install
run: |
7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\"
echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH
choco install nasm --no-progress
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
choco install ghostscript --version=10.0.0.20230317
choco install ghostscript --version=10.0.0.20230317 --no-progress
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
# Install extra test images
@ -167,7 +167,6 @@ jobs:
- name: Build Pillow
run: |
$FLAGS="-C raqm=vendor -C fribidi=vendor"
if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" }
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
& $env:pythonLocation\python.exe selftest.py --installed
shell: pwsh
@ -209,47 +208,6 @@ jobs:
flags: GHA_Windows
name: ${{ runner.os }} Python ${{ matrix.python-version }}
- name: Build wheel
id: wheel
if: "github.event_name != 'pull_request'"
run: |
mkdir fribidi
copy winbuild\build\bin\fribidi* fribidi
setlocal EnableDelayedExpansion
for %%f in (winbuild\build\license\*) do (
set x=%%~nf
rem Skip FriBiDi license, it is not included in the wheel.
set fribidi=!x:~0,7!
if NOT !fribidi!==fribidi (
rem Skip imagequant license, it is not included in the wheel.
set libimagequant=!x:~0,13!
if NOT !libimagequant!==libimagequant (
echo. >> LICENSE
echo ===== %%~nf ===== >> LICENSE
echo. >> LICENSE
type %%f >> LICENSE
)
)
)
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT%
call winbuild\\build\\build_env.cmd
%pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable .
shell: cmd
- name: Upload wheel
uses: actions/upload-artifact@v3
if: "github.event_name != 'pull_request'"
with:
name: ${{ steps.wheel.outputs.dist }}
path: "*.whl"
- name: Upload fribidi.dll
if: "github.event_name != 'pull_request' && matrix.python-version == 3.11"
uses: actions/upload-artifact@v3
with:
name: fribidi
path: fribidi\*
success:
permissions:
contents: none

View File

@ -16,13 +16,13 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds
FREETYPE_VERSION=2.13.2
HARFBUZZ_VERSION=8.2.1
HARFBUZZ_VERSION=8.3.0
LIBPNG_VERSION=1.6.40
JPEGTURBO_VERSION=3.0.1
OPENJPEG_VERSION=2.5.0
XZ_VERSION=5.4.5
TIFF_VERSION=4.6.0
LCMS2_VERSION=2.15
LCMS2_VERSION=2.16
if [[ -n "$IS_MACOS" ]]; then
GIFLIB_VERSION=5.1.4
else

22
.github/workflows/wheels-test.ps1 vendored Normal file
View File

@ -0,0 +1,22 @@
param ([string]$venv, [string]$pillow="C:\pillow")
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
Set-PSDebug -Trace 1
if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
# unlike CPython, PyPy requires Visual C++ Redistributable to be installed
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri 'https://aka.ms/vs/15/release/vc_redist.x64.exe' -OutFile 'vc_redist.x64.exe'
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
}
$env:path += ";$pillow\winbuild\build\bin\"
& "$venv\Scripts\activate.ps1"
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
cd $pillow
& python -VV
if (!$?) { exit $LASTEXITCODE }
& python selftest.py
if (!$?) { exit $LASTEXITCODE }
& python -m pytest -vx Tests\check_wheel.py
if (!$?) { exit $LASTEXITCODE }
& python -m pytest -vx Tests
if (!$?) { exit $LASTEXITCODE }

View File

@ -1,10 +1,6 @@
#!/bin/bash
set -e
EXP_CODECS="jpg jpg_2000 libtiff zlib"
EXP_MODULES="freetype2 littlecms2 pil tkinter webp"
EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux xcb"
if [[ "$OSTYPE" == "darwin"* ]]; then
brew install fribidi
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
@ -25,21 +21,5 @@ fi
# Runs tests
python3 selftest.py
python3 -m pytest Tests/check_wheel.py
python3 -m pytest
# Test against expected codecs, modules and features
codecs=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_codecs())))')
if [ "$codecs" != "$EXP_CODECS" ]; then
echo "Codecs should be: '$EXP_CODECS'; but are '$codecs'"
exit 1
fi
modules=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_modules())))')
if [ "$modules" != "$EXP_MODULES" ]; then
echo "Modules should be: '$EXP_MODULES'; but are '$modules'"
exit 1
fi
features=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_features())))')
if [ "$features" != "$EXP_FEATURES" ]; then
echo "Features should be: '$EXP_FEATURES'; but are '$features'"
exit 1
fi

View File

@ -3,14 +3,20 @@ name: Wheels
on:
push:
paths:
- ".github/workflows/wheels*.yml"
- ".ci/requirements-cibw.txt"
- ".github/workflows/wheel*"
- "wheels/*"
- "winbuild/build_prepare.py"
- "winbuild/fribidi.cmake"
tags:
- "*"
pull_request:
paths:
- ".github/workflows/wheels*.yml"
- ".ci/requirements-cibw.txt"
- ".github/workflows/wheel*"
- "wheels/*"
- "winbuild/build_prepare.py"
- "winbuild/fribidi.cmake"
workflow_dispatch:
permissions:
@ -52,14 +58,17 @@ jobs:
with:
submodules: true
- name: Build wheels
uses: pypa/cibuildwheel@v2.16.2
- uses: actions/setup-python@v4
with:
output-dir: wheelhouse
python-version: "3.x"
- name: Build wheels
run: |
python3 -m pip install -r .ci/requirements-cibw.txt
python3 -m cibuildwheel --output-dir wheelhouse
env:
CIBW_ARCHS: ${{ matrix.archs }}
CIBW_BUILD: ${{ matrix.build }}
CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_SKIP: pp38-*
@ -71,6 +80,102 @@ jobs:
name: dist
path: ./wheelhouse/*.whl
windows:
name: Windows ${{ matrix.arch }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- arch: x86
cibw_arch: x86
- arch: x64
cibw_arch: AMD64
- arch: ARM64
cibw_arch: ARM64
steps:
- uses: actions/checkout@v4
- name: Checkout extra test images
uses: actions/checkout@v4
with:
repository: python-pillow/test-images
path: Tests\test-images
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Prepare for build
run: |
choco install nasm --no-progress
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
# Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images
& python.exe -m pip install -r .ci/requirements-cibw.txt
# Cannot cross-compile FriBiDi (only used for tests)
$FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}")
if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" }
& python.exe winbuild\build_prepare.py -v @FLAGS
shell: pwsh
- name: Build wheels
run: |
setlocal EnableDelayedExpansion
for %%f in (winbuild\build\license\*) do (
set x=%%~nf
rem Skip FriBiDi license, it is not included in the wheel.
set fribidi=!x:~0,7!
if NOT !fribidi!==fribidi (
rem Skip imagequant license, it is not included in the wheel.
set libimagequant=!x:~0,13!
if NOT !libimagequant!==libimagequant (
echo. >> LICENSE
echo ===== %%~nf ===== >> LICENSE
echo. >> LICENSE
type %%f >> LICENSE
)
)
)
call winbuild\\build\\build_env.cmd
%pythonLocation%\python.exe -m cibuildwheel . --output-dir wheelhouse
env:
CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw"
CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm
-v {project}:C:\pillow
-v C:\cibw:C:\cibw
-v %CD%\..\venv-test:%CD%\..\venv-test
-e CI -e GITHUB_ACTIONS
mcr.microsoft.com/windows/servercore:ltsc2022
powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test'
shell: cmd
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: dist
path: ./wheelhouse/*.whl
- name: Prepare to upload FriBiDi
if: "matrix.arch != 'ARM64'"
run: |
mkdir fribidi\${{ matrix.arch }}
copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }}
shell: cmd
- name: Upload fribidi.dll
if: "matrix.arch != 'ARM64'"
uses: actions/upload-artifact@v3
with:
name: fribidi
path: fribidi\*
sdist:
runs-on: ubuntu-latest
steps:
@ -93,7 +198,7 @@ jobs:
success:
permissions:
contents: none
needs: [build, sdist]
needs: [build, windows, sdist]
runs-on: ubuntu-latest
name: Wheels Successful
steps:

View File

@ -3,7 +3,6 @@ if: tag IS present OR type = api
env:
global:
- CIBW_ARCHS=aarch64
- CIBW_CONFIG_SETTINGS="raqm=enable raqm=vendor fribidi=vendor"
- CIBW_SKIP=pp38-*
language: python
@ -35,7 +34,7 @@ jobs:
- CIBW_BUILD="*musllinux*"
install:
- python3 -m pip install cibuildwheel==2.16.2
- python3 -m pip install -r .ci/requirements-cibw.txt
script:
- python3 -m cibuildwheel --output-dir wheelhouse

View File

@ -5,6 +5,15 @@ Changelog (Pillow)
10.2.0 (unreleased)
-------------------
- Correct PDF palette size when saving #7555
[radarhere]
- Fixed closing file pointer with olefile 0.47 #7594
[radarhere]
- Raise ValueError when TrueType font size is not greater than zero #7584, #7587
[akx, radarhere]
- If absent, do not try to close fp when closing image #7557
[RaphaelVRossi, radarhere]

View File

@ -94,7 +94,6 @@ Released as needed privately to individual vendors for critical security-related
## Source and Binary Distributions
### macOS and Linux
* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
```bash
@ -104,14 +103,6 @@ Released as needed privately to individual vendors for critical security-related
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
and copy into `dist`.
### Windows
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
```bash
gh run download --dir dist
# select dist-x.y.z
```
## Publicize Release
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010

41
Tests/check_wheel.py Normal file
View File

@ -0,0 +1,41 @@
import sys
from PIL import features
def test_wheel_modules():
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
# tkinter is not available in cibuildwheel installed CPython on Windows
try:
import tkinter
assert tkinter
except ImportError:
expected_modules.remove("tkinter")
assert set(features.get_supported_modules()) == expected_modules
def test_wheel_codecs():
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
assert set(features.get_supported_codecs()) == expected_codecs
def test_wheel_features():
expected_features = {
"webp_anim",
"webp_mux",
"transp_webp",
"raqm",
"fribidi",
"harfbuzz",
"libjpeg_turbo",
"xcb",
}
if sys.platform == "win32":
expected_features.remove("xcb")
assert set(features.get_supported_features()) == expected_features

View File

@ -5,6 +5,7 @@ Helper functions.
import logging
import os
import shutil
import subprocess
import sys
import sysconfig
import tempfile
@ -258,11 +259,21 @@ def hopper(mode=None, cache={}):
def djpeg_available():
return bool(shutil.which("djpeg"))
if shutil.which("djpeg"):
try:
subprocess.check_call(["djpeg", "-version"])
return True
except subprocess.CalledProcessError: # pragma: no cover
return False
def cjpeg_available():
return bool(shutil.which("cjpeg"))
if shutil.which("cjpeg"):
try:
subprocess.check_call(["cjpeg", "-version"])
return True
except subprocess.CalledProcessError: # pragma: no cover
return False
def netpbm_available():

View File

@ -67,7 +67,7 @@ def test_quantize_no_dither():
def test_quantize_no_dither2():
im = Image.new("RGB", (9, 1))
im.putdata(list((p,) * 3 for p in range(0, 36, 4)))
im.putdata([(p,) * 3 for p in range(0, 36, 4)])
palette = Image.new("P", (1, 1))
data = (0, 0, 0, 32, 32, 32)

View File

@ -1073,3 +1073,9 @@ def test_raqm_missing_warning(monkeypatch):
"Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout."
)
@pytest.mark.parametrize("size", [-1, 0])
def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size):
with pytest.raises(ValueError):
ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine)

View File

@ -11,6 +11,10 @@ from .helper import assert_image_equal_tofile, skip_unless_feature
class TestImageGrab:
@pytest.mark.skipif(
os.environ.get("USERNAME") == "ContainerAdministrator",
reason="can't grab screen when running in Docker",
)
@pytest.mark.skipif(
sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS"
)

View File

@ -166,6 +166,12 @@ html_static_path = ["resources"]
# directly to the root of the documentation.
# html_extra_path = []
html_css_files = ["css/dark.css"]
html_js_files = [
"js/activate_tab.js",
]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
@ -313,10 +319,6 @@ texinfo_documents = [
# texinfo_no_detailmenu = False
def setup(app):
app.add_css_file("css/dark.css")
linkcheck_allowed_redirects = {
r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*",
r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg",

View File

@ -1,6 +1,14 @@
Installation
============
.. raw:: html
<script>
document.addEventListener('DOMContentLoaded', function() {
activateTab(getOS());
});
</script>
Warnings
--------
@ -87,11 +95,10 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images::
.. tab:: Windows
.. warning:: Pillow > 9.5.0 no longer includes 32-bit wheels.
We provide Pillow binaries for Windows compiled for the matrix of
supported Pythons in 64-bit versions in the wheel format. These binaries include
support for all optional libraries except libimagequant and libxcb. Raqm support
We provide Pillow binaries for Windows compiled for the matrix of supported
Pythons in the wheel format. These include x86, x86-64 and arm64 versions
(with the exception of Python 3.8 on arm64). These binaries include support
for all optional libraries except libimagequant and libxcb. Raqm support
requires FriBiDi to be installed separately::
python3 -m pip install --upgrade pip
@ -168,7 +175,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.7-2.15**.
above uses liblcms2. Tested with **1.19** and **2.7-2.16**.
* **libwebp** provides the WebP format.

View File

@ -0,0 +1,36 @@
// Based on https://stackoverflow.com/a/38241481/724176
function getOS() {
const userAgent = window.navigator.userAgent,
platform = window.navigator.userAgentData?.platform || window.navigator.platform,
macosPlatforms = ["macOS", "Macintosh", "MacIntel", "MacPPC", "Mac68K"],
windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"];
if (macosPlatforms.includes(platform)) {
return "macOS";
} else if (windowsPlatforms.includes(platform)) {
return "Windows";
} else if (/Android/.test(userAgent)) {
return "Android";
} else if (/Linux/.test(platform)) {
return "Linux";
}
}
function activateTab(tabName) {
// Find all label elements with the specified tab name
const labels = document.querySelectorAll(".tab-label");
labels.forEach((label) => {
if (label.textContent == tabName) {
// Find the associated input element using the "for" attribute
const tabInputId = label.getAttribute("for");
const tabInput = document.getElementById(tabInputId);
// Check if the input element exists before attempting to set the "checked" attribute
if (tabInput) {
// Activate the tab by setting its "checked" attribute to true
tabInput.checked = true;
}
}
});
}

View File

@ -47,6 +47,12 @@ docs = [
"sphinx-removed-in",
"sphinxext-opengraph",
]
fpx = [
"olefile",
]
mic = [
"olefile",
]
tests = [
"check-manifest",
"coverage",
@ -59,6 +65,9 @@ tests = [
"pytest-cov",
"pytest-timeout",
]
xmp = [
"defusedxml",
]
[project.urls]
Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst"
Documentation = "https://pillow.readthedocs.io"
@ -79,12 +88,15 @@ version = {attr = "PIL.__version__"}
[tool.cibuildwheel]
before-all = ".github/workflows/wheels-dependencies.sh"
build-verbosity = 1
config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable"
test-command = "cd {project} && .github/workflows/wheels-test.sh"
test-extras = "tests"
[tool.ruff]
line-length = 88
select = [
"C4", # flake8-comprehensions
"E", # pycodestyle errors
"EM", # flake8-errmsg
"F", # pyflakes errors

View File

@ -440,17 +440,17 @@ class pil_build_ext(build_ext):
#
# add configured kits
for root_name, lib_name in dict(
JPEG_ROOT="libjpeg",
JPEG2K_ROOT="libopenjp2",
TIFF_ROOT=("libtiff-5", "libtiff-4"),
ZLIB_ROOT="zlib",
FREETYPE_ROOT="freetype2",
HARFBUZZ_ROOT="harfbuzz",
FRIBIDI_ROOT="fribidi",
LCMS_ROOT="lcms2",
IMAGEQUANT_ROOT="libimagequant",
).items():
for root_name, lib_name in {
"JPEG_ROOT": "libjpeg",
"JPEG2K_ROOT": "libopenjp2",
"TIFF_ROOT": ("libtiff-5", "libtiff-4"),
"ZLIB_ROOT": "zlib",
"FREETYPE_ROOT": "freetype2",
"HARFBUZZ_ROOT": "harfbuzz",
"FRIBIDI_ROOT": "fribidi",
"LCMS_ROOT": "lcms2",
"IMAGEQUANT_ROOT": "libimagequant",
}.items():
root = globals()[root_name]
if root is None and root_name in os.environ:

View File

@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True):
dpi = info.get("dpi", (96, 96))
# 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi)
stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
header = 40 # or 64 for OS/2 version 2

View File

@ -64,8 +64,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
d, e, o, a = self.tile[0]
self.tile[0] = d, (0, 0) + self.size, o, a
return
#
# --------------------------------------------------------------------

View File

@ -227,6 +227,7 @@ class FpxImageFile(ImageFile.ImageFile):
break # isn't really required
self.stream = stream
self._fp = self.fp
self.fp = None
def load(self):

View File

@ -40,7 +40,7 @@ from enum import IntEnum
from pathlib import Path
try:
import defusedxml.ElementTree as ElementTree
from defusedxml import ElementTree
except ImportError:
ElementTree = None
@ -1160,7 +1160,7 @@ class Image:
if palette.mode != "P":
msg = "bad mode for palette image"
raise ValueError(msg)
if self.mode != "RGB" and self.mode != "L":
if self.mode not in {"RGB", "L"}:
msg = "only RGB or L mode images can be quantized to a palette"
raise ValueError(msg)
im = self.im.convert("P", dither, palette.im)

View File

@ -921,7 +921,7 @@ def floodfill(image, xy, value, border=None, thresh=0):
if border is None:
fill = _color_diff(p, background) <= thresh
else:
fill = p != value and p != border
fill = p not in (value, border)
if fill:
pixel[s, t] = value
new_edge.add((s, t))

View File

@ -198,6 +198,10 @@ class FreeTypeFont:
) -> None:
# FIXME: use service provider instead
if size <= 0:
msg = "font size must be greater than 0"
raise ValueError(msg)
self.path = font
self.size = size
self.index = index
@ -800,6 +804,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
.. versionadded:: 4.2.0
:return: A font object.
:exception OSError: If the file could not be read.
:exception ValueError: If the font size is not greater than zero.
"""
def freetype(font):

View File

@ -334,10 +334,7 @@ def _save(im, fp, filename):
if quality_layers is not None and not (
isinstance(quality_layers, (list, tuple))
and all(
[
isinstance(quality_layer, (int, float))
for quality_layer in quality_layers
]
isinstance(quality_layer, (int, float)) for quality_layer in quality_layers
)
):
msg = "quality_layers must be a sequence of numbers"

View File

@ -397,7 +397,7 @@ class JpegImageFile(ImageFile.ImageFile):
# self.__offset = self.fp.tell()
break
s = self.fp.read(1)
elif i == 0 or i == 0xFFFF:
elif i in {0, 0xFFFF}:
# padded marker or junk; move on
s = b"\xff"
elif i == 0xFF00: # Skip extraneous data (escaped 0xFF)

View File

@ -66,6 +66,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self._n_frames = len(self.images)
self.is_animated = self._n_frames > 1
self.__fp = self.fp
self.seek(0)
def seek(self, frame):
@ -87,10 +88,12 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
return self.frame
def close(self):
self.__fp.close()
self.ole.close()
super().close()
def __exit__(self, *args):
self.__fp.close()
self.ole.close()
super().__exit__()

View File

@ -96,7 +96,7 @@ def _write_image(im, filename, existing_pdf, image_refs):
dict_obj["ColorSpace"] = [
PdfParser.PdfName("Indexed"),
PdfParser.PdfName("DeviceRGB"),
255,
len(palette) // 3 - 1,
PdfParser.PdfBinary(palette),
]
procset = "ImageI" # indexed color

View File

@ -123,7 +123,7 @@ class SgiImageFile(ImageFile.ImageFile):
def _save(im, fp, filename):
if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L":
if im.mode not in {"RGB", "RGBA", "L"}:
msg = "Unsupported SGI image mode"
raise ValueError(msg)
@ -155,7 +155,7 @@ def _save(im, fp, filename):
# Z Dimension: Number of channels
z = len(im.mode)
if dim == 1 or dim == 2:
if dim in {1, 2}:
z = 1
# assert we've got the right number of bands.

View File

@ -427,7 +427,7 @@ def _populate():
TAGS_V2[k] = TagInfo(k, *v)
for group, tags in TAGS_V2_GROUPS.items():
for tags in TAGS_V2_GROUPS.values():
for k, v in tags.items():
tags[k] = TagInfo(k, *v)

View File

@ -279,10 +279,10 @@ DEPS = {
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
},
"lcms2": {
"url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download",
"filename": "lcms2-2.15.tar.gz",
"dir": "lcms2-2.15",
"license": "COPYING",
"url": SF_PROJECTS + "/lcms/files/lcms/2.16/lcms2-2.16.tar.gz/download",
"filename": "lcms2-2.16.tar.gz",
"dir": "lcms2-2.16",
"license": "LICENSE",
"patch": {
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
# default is /MD for x86 and /MT for x64, we need /MD always
@ -345,9 +345,9 @@ DEPS = {
"libs": [r"imagequant.lib"],
},
"harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/8.2.1.zip",
"filename": "harfbuzz-8.2.1.zip",
"dir": "harfbuzz-8.2.1",
"url": "https://github.com/harfbuzz/harfbuzz/archive/8.3.0.zip",
"filename": "harfbuzz-8.3.0.zip",
"dir": "harfbuzz-8.3.0",
"license": "COPYING",
"build": [
*cmds_cmake(
@ -482,7 +482,7 @@ def extract_dep(url: str, filename: str) -> None:
msg = "Attempted Path Traversal in Zip File"
raise RuntimeError(msg)
zf.extractall(sources_dir)
elif filename.endswith(".tar.gz") or filename.endswith(".tgz"):
elif filename.endswith((".tar.gz", ".tgz")):
with tarfile.open(file, "r:gz") as tgz:
for member in tgz.getnames():
member_abspath = os.path.abspath(os.path.join(sources_dir, member))
@ -586,14 +586,19 @@ def build_dep(name: str) -> str:
def build_dep_all() -> None:
lines = [r'call "{build_dir}\build_env.cmd"']
gha_groups = "GITHUB_ACTIONS" in os.environ
for dep_name in DEPS:
print()
if dep_name in disabled:
print(f"Skipping disabled dependency {dep_name}")
continue
script = build_dep(dep_name)
if gha_groups:
lines.append(f"@echo ::group::Running {script}")
lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')
lines.append("if errorlevel 1 echo Build failed! && exit /B 1")
if gha_groups:
lines.append("@echo ::endgroup::")
print()
lines.append("@echo All Pillow dependencies built successfully!")
write_script("build_dep_all.cmd", lines)