mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-12 01:20:53 +03:00
Merge branch 'main' into jpeg2000_cmyk_save
This commit is contained in:
commit
439d5cf2a0
|
@ -21,7 +21,7 @@ set -e
|
|||
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
||||
ghostscript libjpeg-turbo-progs libopenjp2-7-dev\
|
||||
ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
|
||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||
sway wl-clipboard libopenblas-dev
|
||||
fi
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mypy==1.13.0
|
||||
mypy==1.14.0
|
||||
IceSpringPySideStubs-PyQt6
|
||||
IceSpringPySideStubs-PySide6
|
||||
ipython
|
||||
|
|
2
.github/workflows/macos-install.sh
vendored
2
.github/workflows/macos-install.sh
vendored
|
@ -8,8 +8,8 @@ fi
|
|||
brew install \
|
||||
freetype \
|
||||
ghostscript \
|
||||
jpeg-turbo \
|
||||
libimagequant \
|
||||
libjpeg \
|
||||
libtiff \
|
||||
little-cms2 \
|
||||
openjpeg \
|
||||
|
|
26
.github/workflows/wheels-dependencies.sh
vendored
26
.github/workflows/wheels-dependencies.sh
vendored
|
@ -40,7 +40,7 @@ ARCHIVE_SDIR=pillow-depends-main
|
|||
FREETYPE_VERSION=2.13.2
|
||||
HARFBUZZ_VERSION=10.1.0
|
||||
LIBPNG_VERSION=1.6.44
|
||||
JPEGTURBO_VERSION=3.0.4
|
||||
JPEGTURBO_VERSION=3.1.0
|
||||
OPENJPEG_VERSION=2.5.3
|
||||
XZ_VERSION=5.6.3
|
||||
TIFF_VERSION=4.6.0
|
||||
|
@ -50,12 +50,8 @@ if [[ -n "$IS_MACOS" ]]; then
|
|||
else
|
||||
GIFLIB_VERSION=5.2.1
|
||||
fi
|
||||
if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then
|
||||
ZLIB_VERSION=1.3.1
|
||||
else
|
||||
ZLIB_VERSION=1.2.8
|
||||
fi
|
||||
LIBWEBP_VERSION=1.4.0
|
||||
ZLIB_NG_VERSION=2.2.2
|
||||
LIBWEBP_VERSION=1.5.0
|
||||
BZIP2_VERSION=1.0.8
|
||||
LIBXCB_VERSION=1.17.0
|
||||
BROTLI_VERSION=1.1.0
|
||||
|
@ -74,6 +70,16 @@ function build_pkg_config {
|
|||
touch pkg-config-stamp
|
||||
}
|
||||
|
||||
function build_zlib_ng {
|
||||
if [ -e zlib-stamp ]; then return; fi
|
||||
fetch_unpack https://github.com/zlib-ng/zlib-ng/archive/$ZLIB_NG_VERSION.tar.gz zlib-ng-$ZLIB_NG_VERSION.tar.gz
|
||||
(cd zlib-ng-$ZLIB_NG_VERSION \
|
||||
&& ./configure --prefix=$BUILD_PREFIX --zlib-compat \
|
||||
&& make -j4 \
|
||||
&& make install)
|
||||
touch zlib-stamp
|
||||
}
|
||||
|
||||
function build_brotli {
|
||||
if [ -e brotli-stamp ]; then return; fi
|
||||
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
|
||||
|
@ -87,7 +93,7 @@ function build_harfbuzz {
|
|||
if [ -e harfbuzz-stamp ]; then return; fi
|
||||
python3 -m pip install meson ninja
|
||||
|
||||
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
|
||||
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
|
||||
(cd $out_dir \
|
||||
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled)
|
||||
(cd $out_dir/build \
|
||||
|
@ -100,12 +106,12 @@ function build {
|
|||
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
|
||||
yum remove -y zlib-devel
|
||||
fi
|
||||
build_new_zlib
|
||||
build_zlib_ng
|
||||
|
||||
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
||||
if [ -n "$IS_MACOS" ]; then
|
||||
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
|
||||
build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
|
||||
build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
|
||||
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
|
||||
else
|
||||
sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
|
||||
|
|
|
@ -34,6 +34,7 @@ def test_wheel_features() -> None:
|
|||
"fribidi",
|
||||
"harfbuzz",
|
||||
"libjpeg_turbo",
|
||||
"zlib_ng",
|
||||
"xcb",
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 533 B |
|
@ -388,10 +388,12 @@ class TestColorLut3DFilter:
|
|||
|
||||
table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16)
|
||||
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||
assert isinstance(lut.table, numpy.ndarray)
|
||||
assert lut.table.shape == (table.size,)
|
||||
|
||||
table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
|
||||
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||
assert isinstance(lut.table, numpy.ndarray)
|
||||
assert lut.table.shape == (table.size,)
|
||||
|
||||
# Check application
|
||||
|
|
|
@ -36,10 +36,11 @@ def test_version() -> None:
|
|||
else:
|
||||
assert function(name) == version
|
||||
if name != "PIL":
|
||||
if name == "zlib" and version is not None:
|
||||
version = re.sub(".zlib-ng$", "", version)
|
||||
elif name == "libtiff" and version is not None:
|
||||
version = re.sub("t$", "", version)
|
||||
if version is not None:
|
||||
if name == "zlib" and features.check_feature("zlib_ng"):
|
||||
version = re.sub(".zlib-ng$", "", version)
|
||||
elif name == "libtiff":
|
||||
version = re.sub("t$", "", version)
|
||||
assert version is None or re.search(r"\d+(\.\d+)*$", version)
|
||||
|
||||
for module in features.modules:
|
||||
|
|
|
@ -83,4 +83,4 @@ def test_handler(tmp_path: Path) -> None:
|
|||
im.save(temp_file)
|
||||
assert handler.saved
|
||||
|
||||
BufrStubImagePlugin._handler = None
|
||||
BufrStubImagePlugin.register_handler(None)
|
||||
|
|
|
@ -4,8 +4,6 @@ import pytest
|
|||
|
||||
from PIL import ContainerIO, Image
|
||||
|
||||
from .helper import hopper
|
||||
|
||||
TEST_FILE = "Tests/images/dummy.container"
|
||||
|
||||
|
||||
|
@ -15,15 +13,15 @@ def test_sanity() -> None:
|
|||
|
||||
|
||||
def test_isatty() -> None:
|
||||
with hopper() as im:
|
||||
container = ContainerIO.ContainerIO(im, 0, 0)
|
||||
with open(TEST_FILE, "rb") as fh:
|
||||
container = ContainerIO.ContainerIO(fh, 0, 0)
|
||||
|
||||
assert container.isatty() is False
|
||||
|
||||
|
||||
def test_seekable() -> None:
|
||||
with hopper() as im:
|
||||
container = ContainerIO.ContainerIO(im, 0, 0)
|
||||
with open(TEST_FILE, "rb") as fh:
|
||||
container = ContainerIO.ContainerIO(fh, 0, 0)
|
||||
|
||||
assert container.seekable() is True
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import warnings
|
|||
from collections.abc import Generator
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -1435,7 +1436,8 @@ def test_saving_rgba(tmp_path: Path) -> None:
|
|||
assert reloaded_rgba.load()[0, 0][3] == 0
|
||||
|
||||
|
||||
def test_optimizing_p_rgba(tmp_path: Path) -> None:
|
||||
@pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False}))
|
||||
def test_p_rgba(tmp_path: Path, params: dict[str, Any]) -> None:
|
||||
out = str(tmp_path / "temp.gif")
|
||||
|
||||
im1 = Image.new("P", (100, 100))
|
||||
|
@ -1447,7 +1449,7 @@ def test_optimizing_p_rgba(tmp_path: Path) -> None:
|
|||
im2 = Image.new("P", (100, 100))
|
||||
im2.putpalette(data, "RGBA")
|
||||
|
||||
im1.save(out, save_all=True, append_images=[im2])
|
||||
im1.save(out, save_all=True, append_images=[im2], **params)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.n_frames == 2
|
||||
|
|
|
@ -83,4 +83,4 @@ def test_handler(tmp_path: Path) -> None:
|
|||
im.save(temp_file)
|
||||
assert handler.saved
|
||||
|
||||
GribStubImagePlugin._handler = None
|
||||
GribStubImagePlugin.register_handler(None)
|
||||
|
|
|
@ -85,4 +85,4 @@ def test_handler(tmp_path: Path) -> None:
|
|||
im.save(temp_file)
|
||||
assert handler.saved
|
||||
|
||||
Hdf5StubImagePlugin._handler = None
|
||||
Hdf5StubImagePlugin.register_handler(None)
|
||||
|
|
|
@ -1000,8 +1000,13 @@ class TestFileJpeg:
|
|||
with Image.open(f) as reloaded:
|
||||
assert reloaded.info["xmp"] == b"XMP test"
|
||||
|
||||
im.info["xmp"] = b"1" * 65504
|
||||
im.save(f)
|
||||
# Check that XMP is not saved from image info
|
||||
reloaded.save(f)
|
||||
|
||||
with Image.open(f) as reloaded:
|
||||
assert "xmp" not in reloaded.info
|
||||
|
||||
im.save(f, xmp=b"1" * 65504)
|
||||
with Image.open(f) as reloaded:
|
||||
assert reloaded.info["xmp"] == b"1" * 65504
|
||||
|
||||
|
|
|
@ -297,3 +297,15 @@ def test_save_all() -> None:
|
|||
# Test that a single frame image will not be saved as an MPO
|
||||
jpg = roundtrip(im, save_all=True)
|
||||
assert "mp" not in jpg.info
|
||||
|
||||
|
||||
def test_save_xmp() -> None:
|
||||
im = Image.new("RGB", (1, 1))
|
||||
im2 = Image.new("RGB", (1, 1), "#f00")
|
||||
im2.encoderinfo = {"xmp": b"Second frame"}
|
||||
im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2])
|
||||
|
||||
assert im_reloaded.info["xmp"] == b"First frame"
|
||||
|
||||
im_reloaded.seek(1)
|
||||
assert im_reloaded.info["xmp"] == b"Second frame"
|
||||
|
|
|
@ -35,6 +35,13 @@ def test_load() -> None:
|
|||
assert im.load()[0, 0] == (255, 255, 255)
|
||||
|
||||
|
||||
def test_load_zero_inch() -> None:
|
||||
b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x00" * 10)
|
||||
with pytest.raises(ValueError):
|
||||
with Image.open(b):
|
||||
pass
|
||||
|
||||
|
||||
def test_register_handler(tmp_path: Path) -> None:
|
||||
class TestHandler(ImageFile.StubHandler):
|
||||
methodCalled = False
|
||||
|
|
|
@ -104,20 +104,20 @@ def test_transposed() -> None:
|
|||
assert im.size == (590, 88)
|
||||
|
||||
|
||||
def test_load_first_unless_jpeg() -> None:
|
||||
def test_load_first_unless_jpeg(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Test that thumbnail() still uses draft() for JPEG
|
||||
with Image.open("Tests/images/hopper.jpg") as im:
|
||||
draft = im.draft
|
||||
original_draft = im.draft
|
||||
|
||||
def im_draft(
|
||||
mode: str, size: tuple[int, int]
|
||||
mode: str | None, size: tuple[int, int] | None
|
||||
) -> tuple[str, tuple[int, int, float, float]] | None:
|
||||
result = draft(mode, size)
|
||||
result = original_draft(mode, size)
|
||||
assert result is not None
|
||||
|
||||
return result
|
||||
|
||||
im.draft = im_draft
|
||||
monkeypatch.setattr(im, "draft", im_draft)
|
||||
|
||||
im.thumbnail((64, 64))
|
||||
|
||||
|
|
|
@ -1674,6 +1674,9 @@ def test_continuous_horizontal_edges_polygon() -> None:
|
|||
def test_discontiguous_corners_polygon() -> None:
|
||||
img, draw = create_base_image_draw((84, 68))
|
||||
draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK)
|
||||
draw.polygon(
|
||||
((82, 29), (82, 26), (82, 24), (67, 22), (52, 29), (52, 15), (67, 22)), BLACK
|
||||
)
|
||||
draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK)
|
||||
draw.polygon(
|
||||
((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)),
|
||||
|
|
|
@ -93,6 +93,19 @@ class TestImageFile:
|
|||
assert p.image is not None
|
||||
assert (48, 48) == p.image.size
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:Corrupt EXIF data")
|
||||
def test_incremental_tiff(self) -> None:
|
||||
with ImageFile.Parser() as p:
|
||||
with open("Tests/images/hopper.tif", "rb") as f:
|
||||
p.feed(f.read(1024))
|
||||
|
||||
# Check that insufficient data was given in the first feed
|
||||
assert not p.image
|
||||
|
||||
p.feed(f.read())
|
||||
assert p.image is not None
|
||||
assert (128, 128) == p.image.size
|
||||
|
||||
@skip_unless_feature("webp")
|
||||
def test_incremental_webp(self) -> None:
|
||||
with ImageFile.Parser() as p:
|
||||
|
|
|
@ -74,6 +74,17 @@ def test_pickle_image(
|
|||
helper_pickle_file(tmp_path, protocol, test_file, test_mode)
|
||||
|
||||
|
||||
def test_pickle_jpeg() -> None:
|
||||
# Arrange
|
||||
with Image.open("Tests/images/hopper.jpg") as image:
|
||||
# Act: roundtrip
|
||||
unpickled_image = pickle.loads(pickle.dumps(image))
|
||||
|
||||
# Assert
|
||||
assert len(unpickled_image.layer) == 3
|
||||
assert unpickled_image.layers == 3
|
||||
|
||||
|
||||
def test_pickle_la_mode_with_palette(tmp_path: Path) -> None:
|
||||
# Arrange
|
||||
filename = str(tmp_path / "temp.pkl")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
# install webp
|
||||
|
||||
archive=libwebp-1.4.0
|
||||
archive=libwebp-1.5.0
|
||||
|
||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ Feature version numbers are available only where stated.
|
|||
Support for the following features can be checked:
|
||||
|
||||
* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available.
|
||||
* ``zlib_ng``: (compile time) Whether Pillow was compiled against the zlib-ng version of zlib. Compile-time version number is available.
|
||||
* ``raqm``: Raqm library, required for ``ImageFont.Layout.RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer.
|
||||
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available.
|
||||
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.
|
||||
|
|
59
docs/releasenotes/11.1.0.rst
Normal file
59
docs/releasenotes/11.1.0.rst
Normal file
|
@ -0,0 +1,59 @@
|
|||
11.1.0
|
||||
------
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
:cve:`YYYY-XXXXX`: TODO
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Additions
|
||||
=============
|
||||
|
||||
Check for zlib-ng
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can check if Pillow has been built against the zlib-ng version of the
|
||||
zlib library, and what version of zlib-ng is being used::
|
||||
|
||||
from PIL import features
|
||||
features.check_feature("zlib_ng") # True or False
|
||||
features.version_feature("zlib_ng") # "2.2.2" for example, or None
|
||||
|
||||
Other Changes
|
||||
=============
|
||||
|
||||
zlib-ng in wheels
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Wheels are now built against zlib-ng for improved speed. In tests, saving a PNG
|
||||
was found to be more than twice as fast at higher compression levels.
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
11.1.0
|
||||
11.0.0
|
||||
10.4.0
|
||||
10.3.0
|
||||
|
|
|
@ -104,6 +104,7 @@ test-extras = "tests"
|
|||
|
||||
[tool.cibuildwheel.macos.environment]
|
||||
PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
|
||||
DYLD_LIBRARY_PATH = "$(pwd)/build/deps/darwin/lib"
|
||||
|
||||
[tool.black]
|
||||
exclude = "wheels/multibuild"
|
||||
|
|
2
setup.py
2
setup.py
|
@ -344,7 +344,7 @@ class pil_build_ext(build_ext):
|
|||
for x in ("raqm", "fribidi")
|
||||
]
|
||||
+ [
|
||||
("disable-platform-guessing", None, "Disable platform guessing on Linux"),
|
||||
("disable-platform-guessing", None, "Disable platform guessing"),
|
||||
("debug", None, "Debug logging"),
|
||||
]
|
||||
+ [("add-imaging-libs=", None, "Add libs to _imaging build")]
|
||||
|
|
|
@ -273,7 +273,7 @@ class BlpImageFile(ImageFile.ImageFile):
|
|||
raise BLPFormatError(msg)
|
||||
|
||||
self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
||||
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
||||
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, self.mode)]
|
||||
|
||||
|
||||
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||
|
|
|
@ -560,9 +560,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
|
||||
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
||||
)
|
||||
ImageFile._save(
|
||||
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
|
||||
)
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
|
||||
|
||||
|
||||
def _accept(prefix: bytes) -> bool:
|
||||
|
|
|
@ -454,7 +454,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes, eps: int = 1) -
|
|||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size, 0, None)])
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size)])
|
||||
|
||||
fp.write(b"\n%%%%EndBinary\n")
|
||||
fp.write(b"grestore end\n")
|
||||
|
|
|
@ -303,38 +303,38 @@ TAGS = {
|
|||
|
||||
|
||||
class GPS(IntEnum):
|
||||
GPSVersionID = 0
|
||||
GPSLatitudeRef = 1
|
||||
GPSLatitude = 2
|
||||
GPSLongitudeRef = 3
|
||||
GPSLongitude = 4
|
||||
GPSAltitudeRef = 5
|
||||
GPSAltitude = 6
|
||||
GPSTimeStamp = 7
|
||||
GPSSatellites = 8
|
||||
GPSStatus = 9
|
||||
GPSMeasureMode = 10
|
||||
GPSDOP = 11
|
||||
GPSSpeedRef = 12
|
||||
GPSSpeed = 13
|
||||
GPSTrackRef = 14
|
||||
GPSTrack = 15
|
||||
GPSImgDirectionRef = 16
|
||||
GPSImgDirection = 17
|
||||
GPSMapDatum = 18
|
||||
GPSDestLatitudeRef = 19
|
||||
GPSDestLatitude = 20
|
||||
GPSDestLongitudeRef = 21
|
||||
GPSDestLongitude = 22
|
||||
GPSDestBearingRef = 23
|
||||
GPSDestBearing = 24
|
||||
GPSDestDistanceRef = 25
|
||||
GPSDestDistance = 26
|
||||
GPSProcessingMethod = 27
|
||||
GPSAreaInformation = 28
|
||||
GPSDateStamp = 29
|
||||
GPSDifferential = 30
|
||||
GPSHPositioningError = 31
|
||||
GPSVersionID = 0x00
|
||||
GPSLatitudeRef = 0x01
|
||||
GPSLatitude = 0x02
|
||||
GPSLongitudeRef = 0x03
|
||||
GPSLongitude = 0x04
|
||||
GPSAltitudeRef = 0x05
|
||||
GPSAltitude = 0x06
|
||||
GPSTimeStamp = 0x07
|
||||
GPSSatellites = 0x08
|
||||
GPSStatus = 0x09
|
||||
GPSMeasureMode = 0x0A
|
||||
GPSDOP = 0x0B
|
||||
GPSSpeedRef = 0x0C
|
||||
GPSSpeed = 0x0D
|
||||
GPSTrackRef = 0x0E
|
||||
GPSTrack = 0x0F
|
||||
GPSImgDirectionRef = 0x10
|
||||
GPSImgDirection = 0x11
|
||||
GPSMapDatum = 0x12
|
||||
GPSDestLatitudeRef = 0x13
|
||||
GPSDestLatitude = 0x14
|
||||
GPSDestLongitudeRef = 0x15
|
||||
GPSDestLongitude = 0x16
|
||||
GPSDestBearingRef = 0x17
|
||||
GPSDestBearing = 0x18
|
||||
GPSDestDistanceRef = 0x19
|
||||
GPSDestDistance = 0x1A
|
||||
GPSProcessingMethod = 0x1B
|
||||
GPSAreaInformation = 0x1C
|
||||
GPSDateStamp = 0x1D
|
||||
GPSDifferential = 0x1E
|
||||
GPSHPositioningError = 0x1F
|
||||
|
||||
|
||||
"""Maps EXIF GPS tags to tag names."""
|
||||
|
@ -342,40 +342,40 @@ GPSTAGS = {i.value: i.name for i in GPS}
|
|||
|
||||
|
||||
class Interop(IntEnum):
|
||||
InteropIndex = 1
|
||||
InteropVersion = 2
|
||||
RelatedImageFileFormat = 4096
|
||||
RelatedImageWidth = 4097
|
||||
RelatedImageHeight = 4098
|
||||
InteropIndex = 0x0001
|
||||
InteropVersion = 0x0002
|
||||
RelatedImageFileFormat = 0x1000
|
||||
RelatedImageWidth = 0x1001
|
||||
RelatedImageHeight = 0x1002
|
||||
|
||||
|
||||
class IFD(IntEnum):
|
||||
Exif = 34665
|
||||
GPSInfo = 34853
|
||||
Makernote = 37500
|
||||
Interop = 40965
|
||||
Exif = 0x8769
|
||||
GPSInfo = 0x8825
|
||||
MakerNote = 0x927C
|
||||
Interop = 0xA005
|
||||
IFD1 = -1
|
||||
|
||||
|
||||
class LightSource(IntEnum):
|
||||
Unknown = 0
|
||||
Daylight = 1
|
||||
Fluorescent = 2
|
||||
Tungsten = 3
|
||||
Flash = 4
|
||||
Fine = 9
|
||||
Cloudy = 10
|
||||
Shade = 11
|
||||
DaylightFluorescent = 12
|
||||
DayWhiteFluorescent = 13
|
||||
CoolWhiteFluorescent = 14
|
||||
WhiteFluorescent = 15
|
||||
StandardLightA = 17
|
||||
StandardLightB = 18
|
||||
StandardLightC = 19
|
||||
D55 = 20
|
||||
D65 = 21
|
||||
D75 = 22
|
||||
D50 = 23
|
||||
ISO = 24
|
||||
Other = 255
|
||||
Unknown = 0x00
|
||||
Daylight = 0x01
|
||||
Fluorescent = 0x02
|
||||
Tungsten = 0x03
|
||||
Flash = 0x04
|
||||
Fine = 0x09
|
||||
Cloudy = 0x0A
|
||||
Shade = 0x0B
|
||||
DaylightFluorescent = 0x0C
|
||||
DayWhiteFluorescent = 0x0D
|
||||
CoolWhiteFluorescent = 0x0E
|
||||
WhiteFluorescent = 0x0F
|
||||
StandardLightA = 0x11
|
||||
StandardLightB = 0x12
|
||||
StandardLightC = 0x13
|
||||
D55 = 0x14
|
||||
D65 = 0x15
|
||||
D75 = 0x16
|
||||
D50 = 0x17
|
||||
ISO = 0x18
|
||||
Other = 0xFF
|
||||
|
|
|
@ -159,7 +159,7 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
framesize = i32(s)
|
||||
|
||||
self.decodermaxblock = framesize
|
||||
self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset, None)]
|
||||
self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset)]
|
||||
|
||||
self.__offset += framesize
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
"raw",
|
||||
(x, y, x1, y1),
|
||||
i32(s, i) + 28,
|
||||
(self.rawmode,),
|
||||
self.rawmode,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ class FtexImageFile(ImageFile.ImageFile):
|
|||
self._mode = "RGBA"
|
||||
self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
|
||||
elif format == Format.UNCOMPRESSED:
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, "RGB")]
|
||||
else:
|
||||
msg = f"Invalid texture compression format: {repr(format)}"
|
||||
raise ValueError(msg)
|
||||
|
|
|
@ -76,7 +76,7 @@ class GdImageFile(ImageFile.ImageFile):
|
|||
"raw",
|
||||
(0, 0) + self.size,
|
||||
7 + true_color_offset + 4 + 256 * 4,
|
||||
("L", 0, 1),
|
||||
"L",
|
||||
)
|
||||
]
|
||||
|
||||
|
|
|
@ -695,8 +695,9 @@ def _write_multiple_frames(
|
|||
)
|
||||
background = _get_background(im_frame, color)
|
||||
background_im = Image.new("P", im_frame.size, background)
|
||||
assert im_frames[0].im.palette is not None
|
||||
background_im.putpalette(im_frames[0].im.palette)
|
||||
first_palette = im_frames[0].im.palette
|
||||
assert first_palette is not None
|
||||
background_im.putpalette(first_palette, first_palette.mode)
|
||||
bbox = _getbbox(background_im, im_frame)[1]
|
||||
elif encoderinfo.get("optimize") and im_frame.mode != "1":
|
||||
if "transparency" not in encoderinfo:
|
||||
|
|
|
@ -754,7 +754,7 @@ class Image:
|
|||
|
||||
def __setstate__(self, state: list[Any]) -> None:
|
||||
Image.__init__(self)
|
||||
info, mode, size, palette, data = state
|
||||
info, mode, size, palette, data = state[:5]
|
||||
self.info = info
|
||||
self._mode = mode
|
||||
self._size = size
|
||||
|
@ -1565,7 +1565,7 @@ class Image:
|
|||
for subifd_offset in subifd_offsets:
|
||||
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
|
||||
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
|
||||
if ifd1 and ifd1.get(513):
|
||||
if ifd1 and ifd1.get(ExifTags.Base.JpegIFOffset):
|
||||
assert exif._info is not None
|
||||
ifds.append((ifd1, exif._info.next))
|
||||
|
||||
|
@ -1577,11 +1577,11 @@ class Image:
|
|||
|
||||
fp = self.fp
|
||||
if ifd is not None:
|
||||
thumbnail_offset = ifd.get(513)
|
||||
thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset)
|
||||
if thumbnail_offset is not None:
|
||||
thumbnail_offset += getattr(self, "_exif_offset", 0)
|
||||
self.fp.seek(thumbnail_offset)
|
||||
data = self.fp.read(ifd.get(514))
|
||||
data = self.fp.read(ifd.get(ExifTags.Base.JpegIFByteCount))
|
||||
fp = io.BytesIO(data)
|
||||
|
||||
with open(fp) as im:
|
||||
|
@ -2556,7 +2556,7 @@ class Image:
|
|||
self._ensure_mutable()
|
||||
|
||||
save_all = params.pop("save_all", False)
|
||||
self.encoderinfo = params
|
||||
self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params}
|
||||
self.encoderconfig: tuple[Any, ...] = ()
|
||||
|
||||
preinit()
|
||||
|
@ -2603,6 +2603,11 @@ class Image:
|
|||
except PermissionError:
|
||||
pass
|
||||
raise
|
||||
finally:
|
||||
try:
|
||||
del self.encoderinfo
|
||||
except AttributeError:
|
||||
pass
|
||||
if open_fp:
|
||||
fp.close()
|
||||
|
||||
|
@ -3884,7 +3889,7 @@ class Exif(_ExifBase):
|
|||
gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
|
||||
print(gps_ifd)
|
||||
|
||||
Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.Makernote``,
|
||||
Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.MakerNote``,
|
||||
``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``.
|
||||
|
||||
:py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
|
||||
|
@ -4047,11 +4052,11 @@ class Exif(_ExifBase):
|
|||
ifd = self._get_ifd_dict(offset, tag)
|
||||
if ifd is not None:
|
||||
self._ifds[tag] = ifd
|
||||
elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]:
|
||||
elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.MakerNote]:
|
||||
if ExifTags.IFD.Exif not in self._ifds:
|
||||
self.get_ifd(ExifTags.IFD.Exif)
|
||||
tag_data = self._ifds[ExifTags.IFD.Exif][tag]
|
||||
if tag == ExifTags.IFD.Makernote:
|
||||
if tag == ExifTags.IFD.MakerNote:
|
||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||
|
||||
if tag_data[:8] == b"FUJIFILM":
|
||||
|
@ -4138,7 +4143,7 @@ class Exif(_ExifBase):
|
|||
ifd = {
|
||||
k: v
|
||||
for (k, v) in ifd.items()
|
||||
if k not in (ExifTags.IFD.Interop, ExifTags.IFD.Makernote)
|
||||
if k not in (ExifTags.IFD.Interop, ExifTags.IFD.MakerNote)
|
||||
}
|
||||
return ifd
|
||||
|
||||
|
|
|
@ -98,8 +98,8 @@ def _tilesort(t: _Tile) -> int:
|
|||
class _Tile(NamedTuple):
|
||||
codec_name: str
|
||||
extents: tuple[int, int, int, int] | None
|
||||
offset: int
|
||||
args: tuple[Any, ...] | str | None
|
||||
offset: int = 0
|
||||
args: tuple[Any, ...] | str | None = None
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -553,7 +553,7 @@ class Color3DLUT(MultibandFilter):
|
|||
ch_out = channels or ch_in
|
||||
size_1d, size_2d, size_3d = self.size
|
||||
|
||||
table = [0] * (size_1d * size_2d * size_3d * ch_out)
|
||||
table: list[float] = [0] * (size_1d * size_2d * size_3d * ch_out)
|
||||
idx_in = 0
|
||||
idx_out = 0
|
||||
for b in range(size_3d):
|
||||
|
|
|
@ -698,10 +698,11 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
|
|||
8: Image.Transpose.ROTATE_90,
|
||||
}.get(orientation)
|
||||
if method is not None:
|
||||
transposed_image = image.transpose(method)
|
||||
if in_place:
|
||||
image.im = transposed_image.im
|
||||
image._size = transposed_image._size
|
||||
image.im = image.im.transpose(method)
|
||||
image._size = image.im.size
|
||||
else:
|
||||
transposed_image = image.transpose(method)
|
||||
exif_image = image if in_place else transposed_image
|
||||
|
||||
exif = exif_image.getexif()
|
||||
|
|
|
@ -62,7 +62,7 @@ class ImtImageFile(ImageFile.ImageFile):
|
|||
"raw",
|
||||
(0, 0) + self.size,
|
||||
self.fp.tell() - len(buffer),
|
||||
(self.mode, 0, 1),
|
||||
self.mode,
|
||||
)
|
||||
]
|
||||
|
||||
|
|
|
@ -395,6 +395,13 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
return getattr(self, "_" + name)
|
||||
raise AttributeError(name)
|
||||
|
||||
def __getstate__(self) -> list[Any]:
|
||||
return super().__getstate__() + [self.layers, self.layer]
|
||||
|
||||
def __setstate__(self, state: list[Any]) -> None:
|
||||
super().__setstate__(state)
|
||||
self.layers, self.layer = state[5:]
|
||||
|
||||
def load_read(self, read_bytes: int) -> bytes:
|
||||
"""
|
||||
internal: read more image data
|
||||
|
@ -751,7 +758,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
extra = info.get("extra", b"")
|
||||
|
||||
MAX_BYTES_IN_MARKER = 65533
|
||||
xmp = info.get("xmp", im.info.get("xmp"))
|
||||
xmp = info.get("xmp")
|
||||
if xmp:
|
||||
overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
|
||||
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
|
||||
|
|
|
@ -70,9 +70,9 @@ class MspImageFile(ImageFile.ImageFile):
|
|||
self._size = i16(s, 4), i16(s, 6)
|
||||
|
||||
if s[:4] == b"DanM":
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, ("1", 0, 1))]
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, "1")]
|
||||
else:
|
||||
self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32, None)]
|
||||
self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32)]
|
||||
|
||||
|
||||
class MspDecoder(ImageFile.PyDecoder):
|
||||
|
@ -188,7 +188,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
fp.write(o16(h))
|
||||
|
||||
# image body
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, ("1", 0, 1))])
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, "1")])
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -47,7 +47,7 @@ class PcdImageFile(ImageFile.ImageFile):
|
|||
|
||||
self._mode = "RGB"
|
||||
self._size = 768, 512 # FIXME: not correct for rotated images!
|
||||
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048, None)]
|
||||
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
|
||||
|
||||
def load_end(self) -> None:
|
||||
if self.tile_post_rotate:
|
||||
|
|
|
@ -61,9 +61,7 @@ class PixarImageFile(ImageFile.ImageFile):
|
|||
# FIXME: to be continued...
|
||||
|
||||
# create tile descriptor (assuming "dumped")
|
||||
self.tile = [
|
||||
ImageFile._Tile("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))
|
||||
]
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1024, self.mode)]
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -32,7 +32,7 @@ class QoiImageFile(ImageFile.ImageFile):
|
|||
self._mode = "RGB" if channels == 3 else "RGBA"
|
||||
|
||||
self.fp.seek(1, os.SEEK_CUR) # colorspace
|
||||
self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell(), None)]
|
||||
self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell())]
|
||||
|
||||
|
||||
class QoiDecoder(ImageFile.PyDecoder):
|
||||
|
|
|
@ -154,9 +154,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
self.rawmode = "F;32F"
|
||||
self._mode = "F"
|
||||
|
||||
self.tile = [
|
||||
ImageFile._Tile("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))
|
||||
]
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, offset, self.rawmode)]
|
||||
self._fp = self.fp # FIXME: hack
|
||||
|
||||
@property
|
||||
|
@ -211,26 +209,27 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
|
||||
|
||||
# given a list of filenames, return a list of images
|
||||
def loadImageSeries(filelist: list[str] | None = None) -> list[SpiderImageFile] | None:
|
||||
def loadImageSeries(filelist: list[str] | None = None) -> list[Image.Image] | None:
|
||||
"""create a list of :py:class:`~PIL.Image.Image` objects for use in a montage"""
|
||||
if filelist is None or len(filelist) < 1:
|
||||
return None
|
||||
|
||||
imglist = []
|
||||
byte_imgs = []
|
||||
for img in filelist:
|
||||
if not os.path.exists(img):
|
||||
print(f"unable to find {img}")
|
||||
continue
|
||||
try:
|
||||
with Image.open(img) as im:
|
||||
im = im.convert2byte()
|
||||
assert isinstance(im, SpiderImageFile)
|
||||
byte_im = im.convert2byte()
|
||||
except Exception:
|
||||
if not isSpiderImage(img):
|
||||
print(f"{img} is not a Spider image file")
|
||||
continue
|
||||
im.info["filename"] = img
|
||||
imglist.append(im)
|
||||
return imglist
|
||||
byte_im.info["filename"] = img
|
||||
byte_imgs.append(byte_im)
|
||||
return byte_imgs
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -280,9 +279,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
fp.writelines(hdr)
|
||||
|
||||
rawmode = "F;32NF" # 32-bit native floating point
|
||||
ImageFile._save(
|
||||
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
|
||||
)
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
|
||||
|
||||
|
||||
def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
|
|
|
@ -935,8 +935,8 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
self._tagdata[tag] = data
|
||||
self.tagtype[tag] = typ
|
||||
|
||||
bytes_value = size if size > 32 else repr(data)
|
||||
msg += f" - value: <table: {bytes_value} bytes>"
|
||||
msg += " - value: "
|
||||
msg += f"<table: {size} bytes>" if size > 32 else repr(data)
|
||||
|
||||
logger.debug(msg)
|
||||
|
||||
|
@ -981,11 +981,8 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
|
||||
tagname = TiffTags.lookup(tag, self.group).name
|
||||
typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
|
||||
bytes_value = len(data) if len(data) >= 16 else str(values)
|
||||
msg = (
|
||||
f"save: {tagname} ({tag}) - type: {typname} ({typ})"
|
||||
f" - value: <table: {bytes_value} bytes>"
|
||||
)
|
||||
msg = f"save: {tagname} ({tag}) - type: {typname} ({typ}) - value: "
|
||||
msg += f"<table: {len(data)} bytes>" if len(data) >= 16 else str(values)
|
||||
logger.debug(msg)
|
||||
|
||||
# count is sum of lengths for string and arbitrary data
|
||||
|
@ -1435,8 +1432,12 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
|
||||
|
||||
# size
|
||||
xsize = self.tag_v2.get(IMAGEWIDTH)
|
||||
ysize = self.tag_v2.get(IMAGELENGTH)
|
||||
try:
|
||||
xsize = self.tag_v2[IMAGEWIDTH]
|
||||
ysize = self.tag_v2[IMAGELENGTH]
|
||||
except KeyError as e:
|
||||
msg = "Missing dimensions"
|
||||
raise TypeError(msg) from e
|
||||
if not isinstance(xsize, int) or not isinstance(ysize, int):
|
||||
msg = "Invalid dimensions"
|
||||
raise ValueError(msg)
|
||||
|
@ -1917,7 +1918,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
if not getattr(Image.core, "libtiff_support_custom_tags", False):
|
||||
continue
|
||||
|
||||
if tag in ifd.tagtype:
|
||||
if tag in TiffTags.TAGS_V2_GROUPS:
|
||||
types[tag] = TiffTags.LONG8
|
||||
elif tag in ifd.tagtype:
|
||||
types[tag] = ifd.tagtype[tag]
|
||||
elif not (isinstance(value, (int, float, str, bytes))):
|
||||
continue
|
||||
|
|
|
@ -92,6 +92,9 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
|||
|
||||
# get units per inch
|
||||
self._inch = word(s, 14)
|
||||
if self._inch == 0:
|
||||
msg = "Invalid inch"
|
||||
raise ValueError(msg)
|
||||
|
||||
# get bounding box
|
||||
x0 = short(s, 6)
|
||||
|
|
|
@ -74,9 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
|
|||
self.palette = ImagePalette.raw("RGB", PALETTE)
|
||||
|
||||
self.tile = [
|
||||
ImageFile._Tile(
|
||||
"raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1)
|
||||
)
|
||||
ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), self.mode)
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ class XbmImageFile(ImageFile.ImageFile):
|
|||
self._mode = "1"
|
||||
self._size = xsize, ysize
|
||||
|
||||
self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end(), None)]
|
||||
self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end())]
|
||||
|
||||
|
||||
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
|
@ -85,7 +85,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
|
||||
fp.write(b"static char im_bits[] = {\n")
|
||||
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size, 0, None)])
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size)])
|
||||
|
||||
fp.write(b"};\n")
|
||||
|
||||
|
|
|
@ -101,9 +101,7 @@ class XpmImageFile(ImageFile.ImageFile):
|
|||
self._mode = "P"
|
||||
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
||||
|
||||
self.tile = [
|
||||
ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))
|
||||
]
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), "P")]
|
||||
|
||||
def load_read(self, read_bytes: int) -> bytes:
|
||||
#
|
||||
|
|
|
@ -127,6 +127,7 @@ features: dict[str, tuple[str, str | bool, str | None]] = {
|
|||
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
|
||||
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
|
||||
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
|
||||
"zlib_ng": ("PIL._imaging", "HAVE_ZLIBNG", "zlib_ng_version"),
|
||||
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
|
||||
"xcb": ("PIL._imaging", "HAVE_XCB", None),
|
||||
}
|
||||
|
@ -308,7 +309,11 @@ def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None:
|
|||
# this check is also in src/_imagingcms.c:setup_module()
|
||||
version_static = tuple(int(x) for x in v.split(".")) < (2, 7)
|
||||
t = "compiled for" if version_static else "loaded"
|
||||
if name == "raqm":
|
||||
if name == "zlib":
|
||||
zlib_ng_version = version_feature("zlib_ng")
|
||||
if zlib_ng_version is not None:
|
||||
v += ", compiled for zlib-ng " + zlib_ng_version
|
||||
elif name == "raqm":
|
||||
for f in ("fribidi", "harfbuzz"):
|
||||
v2 = version_feature(f)
|
||||
if v2 is not None:
|
||||
|
|
|
@ -4397,6 +4397,20 @@ setup_module(PyObject *m) {
|
|||
}
|
||||
#endif
|
||||
|
||||
PyObject *have_zlibng;
|
||||
#ifdef ZLIBNG_VERSION
|
||||
have_zlibng = Py_True;
|
||||
{
|
||||
PyObject *v = PyUnicode_FromString(ZLIBNG_VERSION);
|
||||
PyDict_SetItemString(d, "zlib_ng_version", v ? v : Py_None);
|
||||
Py_XDECREF(v);
|
||||
}
|
||||
#else
|
||||
have_zlibng = Py_False;
|
||||
#endif
|
||||
Py_INCREF(have_zlibng);
|
||||
PyModule_AddObject(m, "HAVE_ZLIBNG", have_zlibng);
|
||||
|
||||
#ifdef HAVE_LIBTIFF
|
||||
{
|
||||
extern const char *ImagingTiffVersion(void);
|
||||
|
|
|
@ -346,10 +346,10 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
|
||||
// transform color channels only
|
||||
for (i = 0; i < im->ysize; i++) {
|
||||
// transform color channels only
|
||||
for (i = 0; i < im->ysize; i++) {
|
||||
cmsDoTransform(hTransform, im->image[i], imOut->image[i], im->xsize);
|
||||
}
|
||||
|
||||
|
@ -362,9 +362,9 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
|
|||
// enough available on all platforms, so we polyfill it here for now.
|
||||
pyCMScopyAux(hTransform, imOut, im);
|
||||
|
||||
Py_END_ALLOW_THREADS
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static cmsHTRANSFORM
|
||||
|
@ -378,17 +378,17 @@ _buildTransform(
|
|||
) {
|
||||
cmsHTRANSFORM hTransform;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
|
||||
/* create the transform */
|
||||
hTransform = cmsCreateTransform(
|
||||
hInputProfile,
|
||||
findLCMStype(sInMode),
|
||||
hOutputProfile,
|
||||
findLCMStype(sOutMode),
|
||||
iRenderingIntent,
|
||||
cmsFLAGS
|
||||
);
|
||||
/* create the transform */
|
||||
hTransform = cmsCreateTransform(
|
||||
hInputProfile,
|
||||
findLCMStype(sInMode),
|
||||
hOutputProfile,
|
||||
findLCMStype(sOutMode),
|
||||
iRenderingIntent,
|
||||
cmsFLAGS
|
||||
);
|
||||
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
|
@ -412,19 +412,19 @@ _buildProofTransform(
|
|||
) {
|
||||
cmsHTRANSFORM hTransform;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
|
||||
/* create the transform */
|
||||
hTransform = cmsCreateProofingTransform(
|
||||
hInputProfile,
|
||||
findLCMStype(sInMode),
|
||||
hOutputProfile,
|
||||
findLCMStype(sOutMode),
|
||||
hProofProfile,
|
||||
iRenderingIntent,
|
||||
iProofIntent,
|
||||
cmsFLAGS
|
||||
);
|
||||
/* create the transform */
|
||||
hTransform = cmsCreateProofingTransform(
|
||||
hInputProfile,
|
||||
findLCMStype(sInMode),
|
||||
hOutputProfile,
|
||||
findLCMStype(sOutMode),
|
||||
hProofProfile,
|
||||
iRenderingIntent,
|
||||
iProofIntent,
|
||||
cmsFLAGS
|
||||
);
|
||||
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
|
|
|
@ -690,24 +690,26 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
|
|||
SetWindowLongPtr(wnd, 0, (LONG_PTR)callback);
|
||||
SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get());
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS ShowWindow(wnd, SW_SHOWNORMAL);
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
ShowWindow(wnd, SW_SHOWNORMAL);
|
||||
SetForegroundWindow(wnd); /* to make sure it's visible */
|
||||
Py_END_ALLOW_THREADS
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
return Py_BuildValue(F_HANDLE, wnd);
|
||||
return Py_BuildValue(F_HANDLE, wnd);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
|
||||
MSG msg;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
Py_END_ALLOW_THREADS
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
|
|
@ -736,7 +736,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
}
|
||||
if (tag_type) {
|
||||
int type_int = PyLong_AsLong(tag_type);
|
||||
if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) {
|
||||
if (type_int >= TIFF_BYTE && type_int <= TIFF_LONG8) {
|
||||
type = (TIFFDataType)type_int;
|
||||
}
|
||||
}
|
||||
|
@ -929,7 +929,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
);
|
||||
} else if (type == TIFF_LONG) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value)
|
||||
&encoder->state, (ttag_t)key_int, (UINT32)PyLong_AsLong(value)
|
||||
);
|
||||
} else if (type == TIFF_SSHORT) {
|
||||
status = ImagingLibTiffSetField(
|
||||
|
@ -959,6 +959,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
|
||||
);
|
||||
} else if (type == TIFF_LONG8) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (uint64_t)PyLong_AsLongLong(value)
|
||||
);
|
||||
} else {
|
||||
TRACE(
|
||||
("Unhandled type for key %d : %s \n",
|
||||
|
|
|
@ -501,7 +501,8 @@ polygon_generic(
|
|||
// Needed to draw consistent polygons
|
||||
xx[j] = xx[j - 1];
|
||||
j++;
|
||||
} else if (current->dx != 0 && roundf(xx[j - 1]) == xx[j - 1]) {
|
||||
} else if (current->dx != 0 && j % 2 == 1 &&
|
||||
roundf(xx[j - 1]) == xx[j - 1]) {
|
||||
// Connect discontiguous corners
|
||||
for (k = 0; k < i; k++) {
|
||||
Edge *other_edge = edge_table[k];
|
||||
|
@ -510,10 +511,8 @@ polygon_generic(
|
|||
continue;
|
||||
}
|
||||
// Check if the two edges join to make a corner
|
||||
if (((ymin == current->ymin && ymin == other_edge->ymin) ||
|
||||
(ymin == current->ymax && ymin == other_edge->ymax)) &&
|
||||
xx[j - 1] == (ymin - other_edge->y0) * other_edge->dx +
|
||||
other_edge->x0) {
|
||||
if (xx[j - 1] ==
|
||||
(ymin - other_edge->y0) * other_edge->dx + other_edge->x0) {
|
||||
// Determine points from the edges on the next row
|
||||
// Or if this is the last row, check the previous row
|
||||
int offset = ymin == ymax ? -1 : 1;
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 9a9d1275f025f737cdaa3c451ba07129dd95f361
|
||||
Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155
|
|
@ -7,6 +7,7 @@ import re
|
|||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
|
||||
|
@ -113,26 +114,24 @@ V = {
|
|||
"FREETYPE": "2.13.3",
|
||||
"FRIBIDI": "1.0.16",
|
||||
"HARFBUZZ": "10.1.0",
|
||||
"JPEGTURBO": "3.0.4",
|
||||
"JPEGTURBO": "3.1.0",
|
||||
"LCMS2": "2.16",
|
||||
"LIBPNG": "1.6.44",
|
||||
"LIBWEBP": "1.4.0",
|
||||
"LIBWEBP": "1.5.0",
|
||||
"OPENJPEG": "2.5.3",
|
||||
"TIFF": "4.6.0",
|
||||
"XZ": "5.6.3",
|
||||
"ZLIB": "1.3.1",
|
||||
"ZLIBNG": "2.2.2",
|
||||
}
|
||||
V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "")
|
||||
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
||||
V["ZLIB_DOTLESS"] = V["ZLIB"].replace(".", "")
|
||||
|
||||
|
||||
# dependencies, listed in order of compilation
|
||||
DEPS: dict[str, dict[str, Any]] = {
|
||||
"libjpeg": {
|
||||
"url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/FILENAME/download",
|
||||
"url": f"https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{V['JPEGTURBO']}/libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
|
||||
"filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
|
||||
"dir": f"libjpeg-turbo-{V['JPEGTURBO']}",
|
||||
"license": ["README.ijg", "LICENSE.md"],
|
||||
"license_pattern": (
|
||||
"(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n=========="
|
||||
|
@ -155,28 +154,30 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
cmd_copy("cjpeg-static.exe", "cjpeg.exe"),
|
||||
cmd_copy("djpeg-static.exe", "djpeg.exe"),
|
||||
],
|
||||
"headers": ["j*.h"],
|
||||
"headers": ["jconfig.h", r"src\j*.h"],
|
||||
"libs": ["libjpeg.lib"],
|
||||
"bins": ["cjpeg.exe", "djpeg.exe"],
|
||||
},
|
||||
"zlib": {
|
||||
"url": "https://zlib.net/FILENAME",
|
||||
"filename": f"zlib{V['ZLIB_DOTLESS']}.zip",
|
||||
"dir": f"zlib-{V['ZLIB']}",
|
||||
"license": "README",
|
||||
"license_pattern": "Copyright notice:\n\n(.+)$",
|
||||
"url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.tar.gz",
|
||||
"filename": f"zlib-ng-{V['ZLIBNG']}.tar.gz",
|
||||
"license": "LICENSE.md",
|
||||
"patch": {
|
||||
r"CMakeLists.txt": {
|
||||
"set_target_properties(zlib PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlib)", # noqa: E501
|
||||
},
|
||||
},
|
||||
"build": [
|
||||
cmd_nmake(r"win32\Makefile.msc", "clean"),
|
||||
cmd_nmake(r"win32\Makefile.msc", "zlib.lib"),
|
||||
cmd_copy("zlib.lib", "z.lib"),
|
||||
*cmds_cmake(
|
||||
"zlib", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON"
|
||||
),
|
||||
],
|
||||
"headers": [r"z*.h"],
|
||||
"libs": [r"*.lib"],
|
||||
"libs": [r"zlib.lib"],
|
||||
},
|
||||
"xz": {
|
||||
"url": f"https://github.com/tukaani-project/xz/releases/download/v{V['XZ']}/FILENAME",
|
||||
"filename": f"xz-{V['XZ']}.tar.gz",
|
||||
"dir": f"xz-{V['XZ']}",
|
||||
"license": "COPYING",
|
||||
"build": [
|
||||
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
||||
|
@ -189,7 +190,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"libwebp": {
|
||||
"url": "http://downloads.webmproject.org/releases/webp/FILENAME",
|
||||
"filename": f"libwebp-{V['LIBWEBP']}.tar.gz",
|
||||
"dir": f"libwebp-{V['LIBWEBP']}",
|
||||
"license": "COPYING",
|
||||
"patch": {
|
||||
r"src\enc\picture_csp_enc.c": {
|
||||
|
@ -211,7 +211,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"libtiff": {
|
||||
"url": "https://download.osgeo.org/libtiff/FILENAME",
|
||||
"filename": f"tiff-{V['TIFF']}.tar.gz",
|
||||
"dir": f"tiff-{V['TIFF']}",
|
||||
"license": "LICENSE.md",
|
||||
"patch": {
|
||||
r"libtiff\tif_lzma.c": {
|
||||
|
@ -244,7 +243,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/"
|
||||
f"lpng{V['LIBPNG_DOTLESS']}.zip/download",
|
||||
"filename": f"lpng{V['LIBPNG_DOTLESS']}.zip",
|
||||
"dir": f"lpng{V['LIBPNG_DOTLESS']}",
|
||||
"license": "LICENSE",
|
||||
"build": [
|
||||
*cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"),
|
||||
|
@ -258,7 +256,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"brotli": {
|
||||
"url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz",
|
||||
"filename": f"brotli-{V['BROTLI']}.tar.gz",
|
||||
"dir": f"brotli-{V['BROTLI']}",
|
||||
"license": "LICENSE",
|
||||
"build": [
|
||||
*cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
||||
|
@ -269,7 +266,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"freetype": {
|
||||
"url": "https://download.savannah.gnu.org/releases/freetype/FILENAME",
|
||||
"filename": f"freetype-{V['FREETYPE']}.tar.gz",
|
||||
"dir": f"freetype-{V['FREETYPE']}",
|
||||
"license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
|
||||
"patch": {
|
||||
r"builds\windows\vc2010\freetype.vcxproj": {
|
||||
|
@ -304,7 +300,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"lcms2": {
|
||||
"url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/FILENAME/download",
|
||||
"filename": f"lcms2-{V['LCMS2']}.tar.gz",
|
||||
"dir": f"lcms2-{V['LCMS2']}",
|
||||
"license": "LICENSE",
|
||||
"patch": {
|
||||
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
|
||||
|
@ -330,7 +325,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"openjpeg": {
|
||||
"url": f"https://github.com/uclouvain/openjpeg/archive/v{V['OPENJPEG']}.tar.gz",
|
||||
"filename": f"openjpeg-{V['OPENJPEG']}.tar.gz",
|
||||
"dir": f"openjpeg-{V['OPENJPEG']}",
|
||||
"license": "LICENSE",
|
||||
"build": [
|
||||
*cmds_cmake(
|
||||
|
@ -345,7 +339,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
# commit: Merge branch 'master' into msvc (matches 2.17.0 tag)
|
||||
"url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
|
||||
"filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
|
||||
"dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab",
|
||||
"license": "COPYRIGHT",
|
||||
"patch": {
|
||||
"CMakeLists.txt": {
|
||||
|
@ -365,7 +358,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"harfbuzz": {
|
||||
"url": f"https://github.com/harfbuzz/harfbuzz/archive/{V['HARFBUZZ']}.zip",
|
||||
"filename": f"harfbuzz-{V['HARFBUZZ']}.zip",
|
||||
"dir": f"harfbuzz-{V['HARFBUZZ']}",
|
||||
"license": "COPYING",
|
||||
"build": [
|
||||
*cmds_cmake(
|
||||
|
@ -380,7 +372,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"fribidi": {
|
||||
"url": f"https://github.com/fribidi/fribidi/archive/v{V['FRIBIDI']}.zip",
|
||||
"filename": f"fribidi-{V['FRIBIDI']}.zip",
|
||||
"dir": f"fribidi-{V['FRIBIDI']}",
|
||||
"license": "COPYING",
|
||||
"build": [
|
||||
cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['FRIBIDI']}-COPYING"),
|
||||
|
@ -517,7 +508,10 @@ def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None:
|
|||
if sources_dir_abs != member_prefix:
|
||||
msg = "Attempted Path Traversal in Tar File"
|
||||
raise RuntimeError(msg)
|
||||
tgz.extractall(sources_dir)
|
||||
if sys.version_info >= (3, 12):
|
||||
tgz.extractall(sources_dir, filter="data")
|
||||
else:
|
||||
tgz.extractall(sources_dir)
|
||||
else:
|
||||
msg = "Unknown archive type: " + filename
|
||||
raise RuntimeError(msg)
|
||||
|
@ -760,6 +754,8 @@ def main() -> None:
|
|||
}
|
||||
|
||||
for k, v in DEPS.items():
|
||||
if "dir" not in v:
|
||||
v["dir"] = re.sub(r"\.(tar\.gz|zip)", "", v["filename"])
|
||||
prefs[f"dir_{k}"] = os.path.join(sources_dir, v["dir"])
|
||||
|
||||
print()
|
||||
|
|
Loading…
Reference in New Issue
Block a user