Merge branch 'main' into use-ptr

This commit is contained in:
Andrew Murray 2024-09-07 14:24:18 +10:00
commit 8dcf229ea1
65 changed files with 477 additions and 191 deletions

View File

@ -6,6 +6,7 @@ numpy
packaging packaging
pytest pytest
sphinx sphinx
types-atheris
types-defusedxml types-defusedxml
types-olefile types-olefile
types-setuptools types-setuptools

View File

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.0 rev: v0.6.3
hooks: hooks:
- id: ruff - id: ruff
args: [--exit-non-zero-on-fix] args: [--exit-non-zero-on-fix]
@ -50,7 +50,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.29.1 rev: 0.29.2
hooks: hooks:
- id: check-github-workflows - id: check-github-workflows
- id: check-readthedocs - id: check-readthedocs

View File

@ -5,6 +5,33 @@ Changelog (Pillow)
11.0.0 (unreleased) 11.0.0 (unreleased)
------------------- -------------------
- Return early from BoxBlur if either width or height is zero #8347
[radarhere]
- Check text is either string or bytes #8308
[radarhere]
- Added writing XMP bytes to JPEG #8286
[radarhere]
- Support JPEG2000 RGBA palettes #8256
[radarhere]
- Expand C image to match GIF frame image size #8237
[radarhere]
- Allow saving I;16 images as PPM #8231
[radarhere]
- When IFD is missing, connect get_ifd() dictionary to Exif #8230
[radarhere]
- Skip truncated ICO mask if LOAD_TRUNCATED_IMAGES is enabled #8180
[radarhere]
- Treat unknown JPEG2000 colorspace as unspecified #8343
[radarhere]
- Updated error message when saving WebP with invalid width or height #8322 - Updated error message when saving WebP with invalid width or height #8322
[radarhere, hugovk] [radarhere, hugovk]

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

View File

@ -16,8 +16,9 @@
import atheris import atheris
from atheris.import_hook import instrument_imports
with atheris.instrument_imports(): with instrument_imports():
import sys import sys
import fuzzers import fuzzers

View File

@ -14,8 +14,9 @@
import atheris import atheris
from atheris.import_hook import instrument_imports
with atheris.instrument_imports(): with instrument_imports():
import sys import sys
import fuzzers import fuzzers

View File

@ -1,5 +1,5 @@
{ {
<py3_8_encode_current_locale> <py3_10_encode_current_locale>
Memcheck:Cond Memcheck:Cond
... ...
fun:encode_current_locale fun:encode_current_locale

View File

@ -71,6 +71,11 @@ def test_color_modes() -> None:
box_blur(sample.convert("YCbCr")) box_blur(sample.convert("YCbCr"))
@pytest.mark.parametrize("size", ((0, 1), (1, 0)))
def test_zero_dimension(size: tuple[int, int]) -> None:
assert box_blur(Image.new("L", size)).size == size
def test_radius_0() -> None: def test_radius_0() -> None:
assert_blur( assert_blur(
sample, sample,

View File

@ -1378,16 +1378,39 @@ def test_lzw_bits() -> None:
im.load() im.load()
def test_extents() -> None: @pytest.mark.parametrize(
with Image.open("Tests/images/test_extents.gif") as im: "test_file, loading_strategy",
assert im.size == (100, 100) (
("test_extents.gif", GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST),
(
"test_extents.gif",
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY,
),
(
"test_extents_transparency.gif",
GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST,
),
),
)
def test_extents(
test_file: str, loading_strategy: GifImagePlugin.LoadingStrategy
) -> None:
GifImagePlugin.LOADING_STRATEGY = loading_strategy
try:
with Image.open("Tests/images/" + test_file) as im:
assert im.size == (100, 100)
# Check that n_frames does not change the size # Check that n_frames does not change the size
assert im.n_frames == 2 assert im.n_frames == 2
assert im.size == (100, 100) assert im.size == (100, 100)
im.seek(1) im.seek(1)
assert im.size == (150, 150) assert im.size == (150, 150)
im.load()
assert im.im.size == (150, 150)
finally:
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
def test_missing_background() -> None: def test_missing_background() -> None:

View File

@ -6,7 +6,7 @@ from pathlib import Path
import pytest import pytest
from PIL import IcoImagePlugin, Image, ImageDraw from PIL import IcoImagePlugin, Image, ImageDraw, ImageFile
from .helper import assert_image_equal, assert_image_equal_tofile, hopper from .helper import assert_image_equal, assert_image_equal_tofile, hopper
@ -241,3 +241,29 @@ def test_draw_reloaded(tmp_path: Path) -> None:
with Image.open(outfile) as im: with Image.open(outfile) as im:
assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico") assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico")
def test_truncated_mask() -> None:
# 1 bpp
with open("Tests/images/hopper_mask.ico", "rb") as fp:
data = fp.read()
ImageFile.LOAD_TRUNCATED_IMAGES = True
data = data[:-3]
try:
with Image.open(io.BytesIO(data)) as im:
with Image.open("Tests/images/hopper_mask.png") as expected:
assert im.mode == "1"
# 32 bpp
output = io.BytesIO()
expected = hopper("RGBA")
expected.save(output, "ico", bitmap_format="bmp")
data = output.getvalue()[:-1]
with Image.open(io.BytesIO(data)) as im:
assert im.mode == "RGB"
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False

View File

@ -991,6 +991,21 @@ class TestFileJpeg:
else: else:
assert im.getxmp() == {"xmpmeta": None} assert im.getxmp() == {"xmpmeta": None}
def test_save_xmp(self, tmp_path: Path) -> None:
f = str(tmp_path / "temp.jpg")
im = hopper()
im.save(f, xmp=b"XMP test")
with Image.open(f) as reloaded:
assert reloaded.info["xmp"] == b"XMP test"
im.info["xmp"] = b"1" * 65504
im.save(f)
with Image.open(f) as reloaded:
assert reloaded.info["xmp"] == b"1" * 65504
with pytest.raises(ValueError):
im.save(f, xmp=b"1" * 65505)
@pytest.mark.timeout(timeout=1) @pytest.mark.timeout(timeout=1)
def test_eof(self) -> None: def test_eof(self) -> None:
# Even though this decoder never says that it is finished # Even though this decoder never says that it is finished

View File

@ -182,6 +182,15 @@ def test_restricted_icc_profile() -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
@pytest.mark.skipif(
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
)
def test_unknown_colorspace() -> None:
with Image.open(f"{EXTRA_DIR}/file8.jp2") as im:
im.load()
assert im.mode == "L"
def test_header_errors() -> None: def test_header_errors() -> None:
for path in ( for path in (
"Tests/images/invalid_header_length.jp2", "Tests/images/invalid_header_length.jp2",
@ -391,6 +400,13 @@ def test_pclr() -> None:
assert len(im.palette.colors) == 256 assert len(im.palette.colors) == 256
assert im.palette.colors[(255, 255, 255)] == 0 assert im.palette.colors[(255, 255, 255)] == 0
with Image.open(
f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2"
) as im:
assert im.mode == "P"
assert len(im.palette.colors) == 139
assert im.palette.colors[(0, 0, 0, 0)] == 0
def test_comment() -> None: def test_comment() -> None:
with Image.open("Tests/images/comment.jp2") as im: with Image.open("Tests/images/comment.jp2") as im:

View File

@ -95,7 +95,9 @@ def test_16bit_pgm_write(tmp_path: Path) -> None:
with Image.open("Tests/images/16_bit_binary.pgm") as im: with Image.open("Tests/images/16_bit_binary.pgm") as im:
filename = str(tmp_path / "temp.pgm") filename = str(tmp_path / "temp.pgm")
im.save(filename, "PPM") im.save(filename, "PPM")
assert_image_equal_tofile(im, filename)
im.convert("I;16").save(filename, "PPM")
assert_image_equal_tofile(im, filename) assert_image_equal_tofile(im, filename)

View File

@ -775,6 +775,14 @@ class TestImage:
exif.load(b"Exif\x00\x00") exif.load(b"Exif\x00\x00")
assert not dict(exif) assert not dict(exif)
def test_empty_get_ifd(self) -> None:
exif = Image.Exif()
ifd = exif.get_ifd(0x8769)
assert ifd == {}
ifd[36864] = b"0220"
assert exif.get_ifd(0x8769) == {36864: b"0220"}
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )

View File

@ -24,7 +24,7 @@ def test_toarray() -> None:
def test_with_dtype(dtype: npt.DTypeLike) -> None: def test_with_dtype(dtype: npt.DTypeLike) -> None:
ai = numpy.array(im, dtype=dtype) ai = numpy.array(im, dtype=dtype)
assert ai.dtype == dtype assert ai.dtype.type is dtype
# assert test("1") == ((100, 128), '|b1', 1600)) # assert test("1") == ((100, 128), '|b1', 1600))
assert test("L") == ((100, 128), "|u1", 12800) assert test("L") == ((100, 128), "|u1", 12800)

View File

@ -238,7 +238,9 @@ class MockImageFile(ImageFile.ImageFile):
self.rawmode = "RGBA" self.rawmode = "RGBA"
self._mode = "RGBA" self._mode = "RGBA"
self._size = (200, 200) self._size = (200, 200)
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)] self.tile = [
ImageFile._Tile("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)
]
class CodecsTest: class CodecsTest:
@ -268,7 +270,7 @@ class TestPyDecoder(CodecsTest):
buf = BytesIO(b"\x00" * 255) buf = BytesIO(b"\x00" * 255)
im = MockImageFile(buf) im = MockImageFile(buf)
im.tile = [("MOCK", None, 32, None)] im.tile = [ImageFile._Tile("MOCK", None, 32, None)]
im.load() im.load()
@ -281,12 +283,12 @@ class TestPyDecoder(CodecsTest):
buf = BytesIO(b"\x00" * 255) buf = BytesIO(b"\x00" * 255)
im = MockImageFile(buf) im = MockImageFile(buf)
im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)] im.tile = [ImageFile._Tile("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.load() im.load()
im.tile = [("MOCK", (xoff, yoff, xoff + xsize, -10), 32, None)] im.tile = [ImageFile._Tile("MOCK", (xoff, yoff, xoff + xsize, -10), 32, None)]
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.load() im.load()
@ -294,12 +296,20 @@ class TestPyDecoder(CodecsTest):
buf = BytesIO(b"\x00" * 255) buf = BytesIO(b"\x00" * 255)
im = MockImageFile(buf) im = MockImageFile(buf)
im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)] im.tile = [
ImageFile._Tile(
"MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None
)
]
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.load() im.load()
im.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None)] im.tile = [
ImageFile._Tile(
"MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None
)
]
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.load() im.load()
@ -336,7 +346,7 @@ class TestPyEncoder(CodecsTest):
buf = BytesIO(b"\x00" * 255) buf = BytesIO(b"\x00" * 255)
im = MockImageFile(buf) im = MockImageFile(buf)
im.tile = [("MOCK", None, 32, None)] im.tile = [ImageFile._Tile("MOCK", None, 32, None)]
fp = BytesIO() fp = BytesIO()
ImageFile._save(im, fp, [ImageFile._Tile("MOCK", None, 0, "RGB")]) ImageFile._save(im, fp, [ImageFile._Tile("MOCK", None, 0, "RGB")])

View File

@ -1113,6 +1113,9 @@ def test_bytes(font: ImageFont.FreeTypeFont) -> None:
) )
assert font.getmask2(b"test")[1] == font.getmask2("test")[1] assert font.getmask2(b"test")[1] == font.getmask2("test")[1]
with pytest.raises(TypeError):
font.getlength((0, 0)) # type: ignore[arg-type]
@pytest.mark.parametrize( @pytest.mark.parametrize(
"test_file", "test_file",

View File

@ -2,7 +2,7 @@
# install libimagequant # install libimagequant
archive_name=libimagequant archive_name=libimagequant
archive_version=4.3.1 archive_version=4.3.3
archive=$archive_name-$archive_version archive=$archive_name-$archive_version

View File

@ -246,7 +246,9 @@ class DdsImageFile(ImageFile.ImageFile):
msg = f"Unimplemented pixel format {repr(fourcc)}" msg = f"Unimplemented pixel format {repr(fourcc)}"
raise NotImplementedError(msg) raise NotImplementedError(msg)
self.tile = [(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))] self.tile = [
ImageFile._Tile(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))
]
def load_seek(self, pos: int) -> None: def load_seek(self, pos: int) -> None:
pass pass

View File

@ -64,7 +64,7 @@ Many of Pillow's features require external libraries:
* **libimagequant** provides improved color quantization * **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-4.3.1** * Pillow has been tested with libimagequant **2.6-4.3.3**
* Libimagequant is licensed GPLv3, which is more restrictive than * Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled. with libimagequant support enabled.

View File

@ -33,6 +33,10 @@ Internal Modules
Provides a convenient way to import type hints that are not available Provides a convenient way to import type hints that are not available
on some Python versions. on some Python versions.
.. py:class:: IntegralLike
Typing alias.
.. py:class:: NumpyArray .. py:class:: NumpyArray
Typing alias. Typing alias.

View File

@ -163,7 +163,3 @@ follow_imports = "silent"
warn_redundant_casts = true warn_redundant_casts = true
warn_unreachable = true warn_unreachable = true
warn_unused_ignores = true warn_unused_ignores = true
exclude = [
'^Tests/oss-fuzz/fuzz_font.py$',
'^Tests/oss-fuzz/fuzz_pillow.py$',
]

View File

@ -273,7 +273,7 @@ class BlpImageFile(ImageFile.ImageFile):
raise BLPFormatError(msg) raise BLPFormatError(msg)
self._mode = "RGBA" if self._blp_alpha_depth else "RGB" self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))] self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
class _BLPBaseDecoder(ImageFile.PyDecoder): class _BLPBaseDecoder(ImageFile.PyDecoder):
@ -372,7 +372,10 @@ class BLP1Decoder(_BLPBaseDecoder):
Image._decompression_bomb_check(image.size) Image._decompression_bomb_check(image.size)
if image.mode == "CMYK": if image.mode == "CMYK":
decoder_name, extents, offset, args = image.tile[0] decoder_name, extents, offset, args = image.tile[0]
image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))] assert isinstance(args, tuple)
image.tile = [
ImageFile._Tile(decoder_name, extents, offset, (args[0], "CMYK"))
]
r, g, b = image.convert("RGB").split() r, g, b = image.convert("RGB").split()
reversed_image = Image.merge("RGB", (b, g, r)) reversed_image = Image.merge("RGB", (b, g, r))
self.set_as_raw(reversed_image.tobytes()) self.set_as_raw(reversed_image.tobytes())

View File

@ -296,7 +296,7 @@ class BmpImageFile(ImageFile.ImageFile):
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
args.append(file_info["direction"]) args.append(file_info["direction"])
self.tile = [ self.tile = [
( ImageFile._Tile(
decoder_name, decoder_name,
(0, 0, file_info["width"], file_info["height"]), (0, 0, file_info["width"], file_info["height"]),
offset or self.fp.tell(), offset or self.fp.tell(),
@ -387,7 +387,7 @@ class BmpRleDecoder(ImageFile.PyDecoder):
if self.fd.tell() % 2 != 0: if self.fd.tell() % 2 != 0:
self.fd.seek(1, os.SEEK_CUR) self.fd.seek(1, os.SEEK_CUR)
rawmode = "L" if self.mode == "L" else "P" rawmode = "L" if self.mode == "L" else "P"
self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1])) self.set_as_raw(bytes(data), rawmode, (0, self.args[-1]))
return -1, 0 return -1, 0

View File

@ -17,7 +17,7 @@
# #
from __future__ import annotations from __future__ import annotations
from . import BmpImagePlugin, Image from . import BmpImagePlugin, Image, ImageFile
from ._binary import i16le as i16 from ._binary import i16le as i16
from ._binary import i32le as i32 from ._binary import i32le as i32
@ -64,7 +64,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
# patch up the bitmap height # patch up the bitmap height
self._size = self.size[0], self.size[1] // 2 self._size = self.size[0], self.size[1] // 2
d, e, o, a = self.tile[0] d, e, o, a = self.tile[0]
self.tile[0] = d, (0, 0) + self.size, o, a self.tile[0] = ImageFile._Tile(d, (0, 0) + self.size, o, a)
# #

View File

@ -367,7 +367,7 @@ class DdsImageFile(ImageFile.ImageFile):
mask_count = 3 mask_count = 3
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4)) masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
self.tile = [("dds_rgb", extents, 0, (bitcount, masks))] self.tile = [ImageFile._Tile("dds_rgb", extents, 0, (bitcount, masks))]
return return
elif pfflags & DDPF.LUMINANCE: elif pfflags & DDPF.LUMINANCE:
if bitcount == 8: if bitcount == 8:

View File

@ -67,7 +67,7 @@ class FitsImageFile(ImageFile.ImageFile):
raise ValueError(msg) raise ValueError(msg)
offset += self.fp.tell() - 80 offset += self.fp.tell() - 80
self.tile = [(decoder_name, (0, 0) + self.size, offset, args)] self.tile = [ImageFile._Tile(decoder_name, (0, 0) + self.size, offset, args)]
def _get_size( def _get_size(
self, headers: dict[bytes, bytes], prefix: bytes self, headers: dict[bytes, bytes], prefix: bytes

View File

@ -159,7 +159,7 @@ class FliImageFile(ImageFile.ImageFile):
framesize = i32(s) framesize = i32(s)
self.decodermaxblock = framesize self.decodermaxblock = framesize
self.tile = [("fli", (0, 0) + self.size, self.__offset, None)] self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset, None)]
self.__offset += framesize self.__offset += framesize

View File

@ -166,7 +166,7 @@ class FpxImageFile(ImageFile.ImageFile):
if compression == 0: if compression == 0:
self.tile.append( self.tile.append(
( ImageFile._Tile(
"raw", "raw",
(x, y, x1, y1), (x, y, x1, y1),
i32(s, i) + 28, i32(s, i) + 28,
@ -177,7 +177,7 @@ class FpxImageFile(ImageFile.ImageFile):
elif compression == 1: elif compression == 1:
# FIXME: the fill decoder is not implemented # FIXME: the fill decoder is not implemented
self.tile.append( self.tile.append(
( ImageFile._Tile(
"fill", "fill",
(x, y, x1, y1), (x, y, x1, y1),
i32(s, i) + 28, i32(s, i) + 28,
@ -205,7 +205,7 @@ class FpxImageFile(ImageFile.ImageFile):
jpegmode = rawmode jpegmode = rawmode
self.tile.append( self.tile.append(
( ImageFile._Tile(
"jpeg", "jpeg",
(x, y, x1, y1), (x, y, x1, y1),
i32(s, i) + 28, i32(s, i) + 28,

View File

@ -93,9 +93,9 @@ class FtexImageFile(ImageFile.ImageFile):
if format == Format.DXT1: if format == Format.DXT1:
self._mode = "RGBA" self._mode = "RGBA"
self.tile = [("bcn", (0, 0) + self.size, 0, 1)] self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
elif format == Format.UNCOMPRESSED: elif format == Format.UNCOMPRESSED:
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))] self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
else: else:
msg = f"Invalid texture compression format: {repr(format)}" msg = f"Invalid texture compression format: {repr(format)}"
raise ValueError(msg) raise ValueError(msg)

View File

@ -72,7 +72,7 @@ class GdImageFile(ImageFile.ImageFile):
) )
self.tile = [ self.tile = [
( ImageFile._Tile(
"raw", "raw",
(0, 0) + self.size, (0, 0) + self.size,
7 + true_color_offset + 4 + 256 * 4, 7 + true_color_offset + 4 + 256 * 4,

View File

@ -407,7 +407,7 @@ class GifImageFile(ImageFile.ImageFile):
elif self.mode not in ("RGB", "RGBA"): elif self.mode not in ("RGB", "RGBA"):
transparency = frame_transparency transparency = frame_transparency
self.tile = [ self.tile = [
( ImageFile._Tile(
"gif", "gif",
(x0, y0, x1, y1), (x0, y0, x1, y1),
self.__offset, self.__offset,
@ -438,6 +438,13 @@ class GifImageFile(ImageFile.ImageFile):
self.im.putpalette("RGB", *self._frame_palette.getdata()) self.im.putpalette("RGB", *self._frame_palette.getdata())
else: else:
self._im = None self._im = None
if not self._prev_im and self._im is not None and self.size != self.im.size:
expanded_im = Image.core.fill(self.im.mode, self.size)
if self._frame_palette:
expanded_im.putpalette("RGB", *self._frame_palette.getdata())
expanded_im.paste(self.im, (0, 0) + self.im.size)
self.im = expanded_im
self._mode = temp_mode self._mode = temp_mode
self._frame_palette = None self._frame_palette = None
@ -455,6 +462,17 @@ class GifImageFile(ImageFile.ImageFile):
return return
if not self._prev_im: if not self._prev_im:
return return
if self.size != self._prev_im.size:
if self._frame_transparency is not None:
expanded_im = Image.core.fill("RGBA", self.size)
else:
expanded_im = Image.core.fill("P", self.size)
expanded_im.putpalette("RGB", "RGB", self.im.getpalette())
expanded_im = expanded_im.convert("RGB")
expanded_im.paste(self._prev_im, (0, 0) + self._prev_im.size)
self._prev_im = expanded_im
assert self._prev_im is not None
if self._frame_transparency is not None: if self._frame_transparency is not None:
self.im.putpalettealpha(self._frame_transparency, 0) self.im.putpalettealpha(self._frame_transparency, 0)
frame_im = self.im.convert("RGBA") frame_im = self.im.convert("RGBA")

View File

@ -228,7 +228,7 @@ class IcoFile:
# change tile dimension to only encompass XOR image # change tile dimension to only encompass XOR image
im._size = (im.size[0], int(im.size[1] / 2)) im._size = (im.size[0], int(im.size[1] / 2))
d, e, o, a = im.tile[0] d, e, o, a = im.tile[0]
im.tile[0] = d, (0, 0) + im.size, o, a im.tile[0] = ImageFile._Tile(d, (0, 0) + im.size, o, a)
# figure out where AND mask image starts # figure out where AND mask image starts
if header.bpp == 32: if header.bpp == 32:
@ -243,13 +243,19 @@ class IcoFile:
alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4] alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
# convert to an 8bpp grayscale image # convert to an 8bpp grayscale image
mask = Image.frombuffer( try:
"L", # 8bpp mask = Image.frombuffer(
im.size, # (w, h) "L", # 8bpp
alpha_bytes, # source chars im.size, # (w, h)
"raw", # raw decoder alpha_bytes, # source chars
("L", 0, -1), # 8bpp inverted, unpadded, reversed "raw", # raw decoder
) ("L", 0, -1), # 8bpp inverted, unpadded, reversed
)
except ValueError:
if ImageFile.LOAD_TRUNCATED_IMAGES:
mask = None
else:
raise
else: else:
# get AND image from end of bitmap # get AND image from end of bitmap
w = im.size[0] w = im.size[0]
@ -267,19 +273,26 @@ class IcoFile:
mask_data = self.buf.read(total_bytes) mask_data = self.buf.read(total_bytes)
# convert raw data to image # convert raw data to image
mask = Image.frombuffer( try:
"1", # 1 bpp mask = Image.frombuffer(
im.size, # (w, h) "1", # 1 bpp
mask_data, # source chars im.size, # (w, h)
"raw", # raw decoder mask_data, # source chars
("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed "raw", # raw decoder
) ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed
)
except ValueError:
if ImageFile.LOAD_TRUNCATED_IMAGES:
mask = None
else:
raise
# now we have two images, im is XOR image and mask is AND image # now we have two images, im is XOR image and mask is AND image
# apply mask image as alpha channel # apply mask image as alpha channel
im = im.convert("RGBA") if mask:
im.putalpha(mask) im = im.convert("RGBA")
im.putalpha(mask)
return im return im

View File

@ -253,7 +253,11 @@ class ImImageFile(ImageFile.ImageFile):
# use bit decoder (if necessary) # use bit decoder (if necessary)
bits = int(self.rawmode[2:]) bits = int(self.rawmode[2:])
if bits not in [8, 16, 32]: if bits not in [8, 16, 32]:
self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))] self.tile = [
ImageFile._Tile(
"bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1)
)
]
return return
except ValueError: except ValueError:
pass pass
@ -263,13 +267,17 @@ class ImImageFile(ImageFile.ImageFile):
# ever stumbled upon such a file ;-) # ever stumbled upon such a file ;-)
size = self.size[0] * self.size[1] size = self.size[0] * self.size[1]
self.tile = [ self.tile = [
("raw", (0, 0) + self.size, offs, ("G", 0, -1)), ImageFile._Tile("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)), ImageFile._Tile("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)), ImageFile._Tile(
"raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)
),
] ]
else: else:
# LabEye/IFUNC files # LabEye/IFUNC files
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))] self.tile = [
ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))
]
@property @property
def n_frames(self) -> int: def n_frames(self) -> int:
@ -295,7 +303,9 @@ class ImImageFile(ImageFile.ImageFile):
self.fp = self._fp self.fp = self._fp
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))] self.tile = [
ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))
]
def tell(self) -> int: def tell(self) -> int:
return self.frame return self.frame

View File

@ -225,6 +225,11 @@ if TYPE_CHECKING:
from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
if sys.version_info >= (3, 13):
from types import CapsuleType
else:
CapsuleType = object
ID: list[str] = [] ID: list[str] = []
OPEN: dict[ OPEN: dict[
str, str,
@ -1598,7 +1603,7 @@ class Image:
self.fp.seek(offset) self.fp.seek(offset)
return child_images return child_images
def getim(self): def getim(self) -> CapsuleType:
""" """
Returns a capsule that points to the internal image memory. Returns a capsule that points to the internal image memory.
@ -3641,7 +3646,10 @@ def merge(mode: str, bands: Sequence[Image]) -> Image:
def register_open( def register_open(
id: str, id: str,
factory: Callable[[IO[bytes], str | bytes], ImageFile.ImageFile], factory: (
Callable[[IO[bytes], str | bytes], ImageFile.ImageFile]
| type[ImageFile.ImageFile]
),
accept: Callable[[bytes], bool | str] | None = None, accept: Callable[[bytes], bool | str] | None = None,
) -> None: ) -> None:
""" """
@ -4136,7 +4144,7 @@ class Exif(_ExifBase):
ifd = self._get_ifd_dict(tag_data, tag) ifd = self._get_ifd_dict(tag_data, tag)
if ifd is not None: if ifd is not None:
self._ifds[tag] = ifd self._ifds[tag] = ifd
ifd = self._ifds.get(tag, {}) ifd = self._ifds.setdefault(tag, {})
if tag == ExifTags.IFD.Exif and self._hidden_data: if tag == ExifTags.IFD.Exif and self._hidden_data:
ifd = { ifd = {
k: v k: v

View File

@ -34,12 +34,15 @@ import itertools
import os import os
import struct import struct
import sys import sys
from typing import IO, Any, NamedTuple from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast
from . import Image from . import Image
from ._deprecate import deprecate from ._deprecate import deprecate
from ._util import is_path from ._util import is_path
if TYPE_CHECKING:
from ._typing import StrOrBytesPath
MAXBLOCK = 65536 MAXBLOCK = 65536
SAFEBLOCK = 1024 * 1024 SAFEBLOCK = 1024 * 1024
@ -107,32 +110,34 @@ class _Tile(NamedTuple):
class ImageFile(Image.Image): class ImageFile(Image.Image):
"""Base class for image file format handlers.""" """Base class for image file format handlers."""
def __init__(self, fp=None, filename=None): def __init__(
self, fp: StrOrBytesPath | IO[bytes], filename: str | bytes | None = None
) -> None:
super().__init__() super().__init__()
self._min_frame = 0 self._min_frame = 0
self.custom_mimetype = None self.custom_mimetype: str | None = None
self.tile = None self.tile: list[_Tile] = []
""" A list of tile descriptors, or ``None`` """ """ A list of tile descriptors, or ``None`` """
self.readonly = 1 # until we know better self.readonly = 1 # until we know better
self.decoderconfig = () self.decoderconfig: tuple[Any, ...] = ()
self.decodermaxblock = MAXBLOCK self.decodermaxblock = MAXBLOCK
if is_path(fp): if is_path(fp):
# filename # filename
self.fp = open(fp, "rb") self.fp = open(fp, "rb")
self.filename = fp self.filename = os.path.realpath(os.fspath(fp))
self._exclusive_fp = True self._exclusive_fp = True
else: else:
# stream # stream
self.fp = fp self.fp = cast(IO[bytes], fp)
self.filename = filename self.filename = filename if filename is not None else ""
# can be overridden # can be overridden
self._exclusive_fp = None self._exclusive_fp = False
try: try:
try: try:
@ -155,6 +160,9 @@ class ImageFile(Image.Image):
self.fp.close() self.fp.close()
raise raise
def _open(self) -> None:
pass
def get_format_mimetype(self) -> str | None: def get_format_mimetype(self) -> str | None:
if self.custom_mimetype: if self.custom_mimetype:
return self.custom_mimetype return self.custom_mimetype
@ -178,7 +186,7 @@ class ImageFile(Image.Image):
def load(self) -> Image.core.PixelAccess | None: def load(self) -> Image.core.PixelAccess | None:
"""Load image data based on tile list""" """Load image data based on tile list"""
if self.tile is None: if not self.tile and self._im is None:
msg = "cannot load this image" msg = "cannot load this image"
raise OSError(msg) raise OSError(msg)
@ -214,6 +222,7 @@ class ImageFile(Image.Image):
args = (args, 0, 1) args = (args, 0, 1)
if ( if (
decoder_name == "raw" decoder_name == "raw"
and isinstance(args, tuple)
and len(args) >= 3 and len(args) >= 3
and args[0] == self.mode and args[0] == self.mode
and args[0] in Image._MAPMODES and args[0] in Image._MAPMODES
@ -736,19 +745,22 @@ class PyDecoder(PyCodec):
msg = "unavailable in base decoder" msg = "unavailable in base decoder"
raise NotImplementedError(msg) raise NotImplementedError(msg)
def set_as_raw(self, data: bytes, rawmode=None) -> None: def set_as_raw(
self, data: bytes, rawmode: str | None = None, extra: tuple[Any, ...] = ()
) -> None:
""" """
Convenience method to set the internal image from a stream of raw data Convenience method to set the internal image from a stream of raw data
:param data: Bytes to be set :param data: Bytes to be set
:param rawmode: The rawmode to be used for the decoder. :param rawmode: The rawmode to be used for the decoder.
If not specified, it will default to the mode of the image If not specified, it will default to the mode of the image
:param extra: Extra arguments for the decoder.
:returns: None :returns: None
""" """
if not rawmode: if not rawmode:
rawmode = self.mode rawmode = self.mode
d = Image._getdecoder(self.mode, "raw", rawmode) d = Image._getdecoder(self.mode, "raw", rawmode, extra)
assert self.im is not None assert self.im is not None
d.setimage(self.im, self.state.extents()) d.setimage(self.im, self.state.extents())
s = d.decode(data) s = d.decode(data)

View File

@ -34,7 +34,7 @@ import warnings
from enum import IntEnum from enum import IntEnum
from io import BytesIO from io import BytesIO
from types import ModuleType from types import ModuleType
from typing import IO, TYPE_CHECKING, Any, BinaryIO, TypedDict from typing import IO, TYPE_CHECKING, Any, BinaryIO, TypedDict, cast
from . import Image from . import Image
from ._typing import StrOrBytesPath from ._typing import StrOrBytesPath
@ -212,7 +212,7 @@ class FreeTypeFont:
def __init__( def __init__(
self, self,
font: StrOrBytesPath | BinaryIO | None = None, font: StrOrBytesPath | BinaryIO,
size: float = 10, size: float = 10,
index: int = 0, index: int = 0,
encoding: str = "", encoding: str = "",
@ -245,7 +245,7 @@ class FreeTypeFont:
self.layout_engine = layout_engine self.layout_engine = layout_engine
def load_from_bytes(f) -> None: def load_from_bytes(f: IO[bytes]) -> None:
self.font_bytes = f.read() self.font_bytes = f.read()
self.font = core.getfont( self.font = core.getfont(
"", size, index, encoding, self.font_bytes, layout_engine "", size, index, encoding, self.font_bytes, layout_engine
@ -267,7 +267,7 @@ class FreeTypeFont:
font, size, index, encoding, layout_engine=layout_engine font, size, index, encoding, layout_engine=layout_engine
) )
else: else:
load_from_bytes(font) load_from_bytes(cast(IO[bytes], font))
def __getstate__(self) -> list[Any]: def __getstate__(self) -> list[Any]:
return [self.path, self.size, self.index, self.encoding, self.layout_engine] return [self.path, self.size, self.index, self.encoding, self.layout_engine]
@ -781,7 +781,7 @@ def load(filename: str) -> ImageFont:
def truetype( def truetype(
font: StrOrBytesPath | BinaryIO | None = None, font: StrOrBytesPath | BinaryIO,
size: float = 10, size: float = 10,
index: int = 0, index: int = 0,
encoding: str = "", encoding: str = "",
@ -852,7 +852,7 @@ def truetype(
:exception ValueError: If the font size is not greater than zero. :exception ValueError: If the font size is not greater than zero.
""" """
def freetype(font: StrOrBytesPath | BinaryIO | None) -> FreeTypeFont: def freetype(font: StrOrBytesPath | BinaryIO) -> FreeTypeFont:
return FreeTypeFont(font, size, index, encoding, layout_engine) return FreeTypeFont(font, size, index, encoding, layout_engine)
try: try:

View File

@ -58,7 +58,7 @@ class ImtImageFile(ImageFile.ImageFile):
if s == b"\x0C": if s == b"\x0C":
# image data begins # image data begins
self.tile = [ self.tile = [
( ImageFile._Tile(
"raw", "raw",
(0, 0) + self.size, (0, 0) + self.size,
self.fp.tell() - len(buffer), self.fp.tell() - len(buffer),

View File

@ -147,7 +147,9 @@ class IptcImageFile(ImageFile.ImageFile):
# tile # tile
if tag == (8, 10): if tag == (8, 10):
self.tile = [("iptc", (0, 0) + self.size, offset, compression)] self.tile = [
ImageFile._Tile("iptc", (0, 0) + self.size, offset, compression)
]
def load(self) -> Image.core.PixelAccess | None: def load(self) -> Image.core.PixelAccess | None:
if len(self.tile) != 1 or self.tile[0][0] != "iptc": if len(self.tile) != 1 or self.tile[0][0] != "iptc":

View File

@ -18,6 +18,7 @@ from __future__ import annotations
import io import io
import os import os
import struct import struct
from collections.abc import Callable
from typing import IO, cast from typing import IO, cast
from . import Image, ImageFile, ImagePalette, _binary from . import Image, ImageFile, ImagePalette, _binary
@ -205,7 +206,7 @@ def _parse_jp2_header(
if bitdepth > max_bitdepth: if bitdepth > max_bitdepth:
max_bitdepth = bitdepth max_bitdepth = bitdepth
if max_bitdepth <= 8: if max_bitdepth <= 8:
palette = ImagePalette.ImagePalette() palette = ImagePalette.ImagePalette("RGBA" if npc == 4 else "RGB")
for i in range(ne): for i in range(ne):
color: list[int] = [] color: list[int] = []
for value in header.read_fields(">" + ("B" * npc)): for value in header.read_fields(">" + ("B" * npc)):
@ -286,7 +287,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
length = -1 length = -1
self.tile = [ self.tile = [
( ImageFile._Tile(
"jpeg2k", "jpeg2k",
(0, 0) + self.size, (0, 0) + self.size,
0, 0,
@ -316,8 +317,13 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
else: else:
self.fp.seek(length - 2, os.SEEK_CUR) self.fp.seek(length - 2, os.SEEK_CUR)
@property @property # type: ignore[override]
def reduce(self): def reduce(
self,
) -> (
Callable[[int | tuple[int, int], tuple[int, int, int, int] | None], Image.Image]
| int
):
# https://github.com/python-pillow/Pillow/issues/4343 found that the # https://github.com/python-pillow/Pillow/issues/4343 found that the
# new Image 'reduce' method was shadowed by this plugin's 'reduce' # new Image 'reduce' method was shadowed by this plugin's 'reduce'
# property. This attempts to allow for both scenarios # property. This attempts to allow for both scenarios
@ -338,8 +344,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
# Update the reduce and layers settings # Update the reduce and layers settings
t = self.tile[0] t = self.tile[0]
assert isinstance(t[3], tuple)
t3 = (t[3][0], self._reduce, self.layers, t[3][3], t[3][4]) t3 = (t[3][0], self._reduce, self.layers, t[3][3], t[3][4])
self.tile = [(t[0], (0, 0) + self.size, t[2], t3)] self.tile = [ImageFile._Tile(t[0], (0, 0) + self.size, t[2], t3)]
return ImageFile.ImageFile.load(self) return ImageFile.ImageFile.load(self)

View File

@ -372,7 +372,9 @@ class JpegImageFile(ImageFile.ImageFile):
rawmode = self.mode rawmode = self.mode
if self.mode == "CMYK": if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))] self.tile = [
ImageFile._Tile("jpeg", (0, 0) + self.size, 0, (rawmode, ""))
]
# self.__offset = self.fp.tell() # self.__offset = self.fp.tell()
break break
s = self.fp.read(1) s = self.fp.read(1)
@ -423,6 +425,7 @@ class JpegImageFile(ImageFile.ImageFile):
scale = 1 scale = 1
original_size = self.size original_size = self.size
assert isinstance(a, tuple)
if a[0] == "RGB" and mode in ["L", "YCbCr"]: if a[0] == "RGB" and mode in ["L", "YCbCr"]:
self._mode = mode self._mode = mode
a = mode, "" a = mode, ""
@ -432,6 +435,7 @@ class JpegImageFile(ImageFile.ImageFile):
for s in [8, 4, 2, 1]: for s in [8, 4, 2, 1]:
if scale >= s: if scale >= s:
break break
assert e is not None
e = ( e = (
e[0], e[0],
e[1], e[1],
@ -441,7 +445,7 @@ class JpegImageFile(ImageFile.ImageFile):
self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s) self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)
scale = s scale = s
self.tile = [(d, e, o, a)] self.tile = [ImageFile._Tile(d, e, o, a)]
self.decoderconfig = (scale, 0) self.decoderconfig = (scale, 0)
box = (0, 0, original_size[0] / scale, original_size[1] / scale) box = (0, 0, original_size[0] / scale, original_size[1] / scale)
@ -747,17 +751,27 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
extra = info.get("extra", b"") extra = info.get("extra", b"")
MAX_BYTES_IN_MARKER = 65533 MAX_BYTES_IN_MARKER = 65533
xmp = info.get("xmp", im.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
if len(xmp) > max_data_bytes_in_marker:
msg = "XMP data is too long"
raise ValueError(msg)
size = o16(2 + overhead_len + len(xmp))
extra += b"\xFF\xE1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
icc_profile = info.get("icc_profile") icc_profile = info.get("icc_profile")
if icc_profile: if icc_profile:
ICC_OVERHEAD_LEN = 14 overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers))
MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
markers = [] markers = []
while icc_profile: while icc_profile:
markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER]) markers.append(icc_profile[:max_data_bytes_in_marker])
icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:] icc_profile = icc_profile[max_data_bytes_in_marker:]
i = 1 i = 1
for marker in markers: for marker in markers:
size = o16(2 + ICC_OVERHEAD_LEN + len(marker)) size = o16(2 + overhead_len + len(marker))
extra += ( extra += (
b"\xFF\xE2" b"\xFF\xE2"
+ size + size
@ -844,7 +858,7 @@ def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
## ##
# Factory for making JPEG and MPO instances # Factory for making JPEG and MPO instances
def jpeg_factory( def jpeg_factory(
fp: IO[bytes] | None = None, filename: str | bytes | None = None fp: IO[bytes], filename: str | bytes | None = None
) -> JpegImageFile | MpoImageFile: ) -> JpegImageFile | MpoImageFile:
im = JpegImageFile(fp, filename) im = JpegImageFile(fp, filename)
try: try:

View File

@ -67,7 +67,9 @@ class McIdasImageFile(ImageFile.ImageFile):
offset = w[34] + w[15] offset = w[34] + w[15]
stride = w[15] + w[10] * w[11] * w[14] stride = w[15] + w[10] * w[11] * w[14]
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))] self.tile = [
ImageFile._Tile("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))
]
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -26,6 +26,7 @@ from typing import IO, Any, cast
from . import ( from . import (
Image, Image,
ImageFile,
ImageSequence, ImageSequence,
JpegImagePlugin, JpegImagePlugin,
TiffImagePlugin, TiffImagePlugin,
@ -145,7 +146,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
if self.info.get("exif") != original_exif: if self.info.get("exif") != original_exif:
self._reload_exif() self._reload_exif()
self.tile = [("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])] self.tile = [
ImageFile._Tile("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])
]
self.__frame = frame self.__frame = frame
def tell(self) -> int: def tell(self) -> int:

View File

@ -70,9 +70,9 @@ class MspImageFile(ImageFile.ImageFile):
self._size = i16(s, 4), i16(s, 6) self._size = i16(s, 4), i16(s, 6)
if s[:4] == b"DanM": if s[:4] == b"DanM":
self.tile = [("raw", (0, 0) + self.size, 32, ("1", 0, 1))] self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, ("1", 0, 1))]
else: else:
self.tile = [("MSP", (0, 0) + self.size, 32, None)] self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32, None)]
class MspDecoder(ImageFile.PyDecoder): class MspDecoder(ImageFile.PyDecoder):
@ -152,7 +152,7 @@ class MspDecoder(ImageFile.PyDecoder):
msg = f"Corrupted MSP file in row {x}" msg = f"Corrupted MSP file in row {x}"
raise OSError(msg) from e raise OSError(msg) from e
self.set_as_raw(img.getvalue(), ("1", 0, 1)) self.set_as_raw(img.getvalue(), "1")
return -1, 0 return -1, 0

View File

@ -47,7 +47,7 @@ class PcdImageFile(ImageFile.ImageFile):
self._mode = "RGB" self._mode = "RGB"
self._size = 768, 512 # FIXME: not correct for rotated images! self._size = 768, 512 # FIXME: not correct for rotated images!
self.tile = [("pcd", (0, 0) + self.size, 96 * 2048, None)] self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048, None)]
def load_end(self) -> None: def load_end(self) -> None:
if self.tile_post_rotate: if self.tile_post_rotate:

View File

@ -128,7 +128,9 @@ class PcxImageFile(ImageFile.ImageFile):
bbox = (0, 0) + self.size bbox = (0, 0) + self.size
logger.debug("size: %sx%s", *self.size) logger.debug("size: %sx%s", *self.size)
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))] self.tile = [
ImageFile._Tile("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))
]
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -61,7 +61,9 @@ class PixarImageFile(ImageFile.ImageFile):
# FIXME: to be continued... # FIXME: to be continued...
# create tile descriptor (assuming "dumped") # create tile descriptor (assuming "dumped")
self.tile = [("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))] self.tile = [
ImageFile._Tile("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))
]
# #

View File

@ -258,7 +258,9 @@ class iTXt(str):
tkey: str | bytes | None tkey: str | bytes | None
@staticmethod @staticmethod
def __new__(cls, text, lang=None, tkey=None): def __new__(
cls, text: str, lang: str | None = None, tkey: str | None = None
) -> iTXt:
""" """
:param cls: the class to use when creating the instance :param cls: the class to use when creating the instance
:param text: value for this key :param text: value for this key
@ -368,21 +370,27 @@ class PngInfo:
# PNG image stream (IHDR/IEND) # PNG image stream (IHDR/IEND)
class _RewindState(NamedTuple):
info: dict[str | tuple[int, int], Any]
tile: list[ImageFile._Tile]
seq_num: int | None
class PngStream(ChunkStream): class PngStream(ChunkStream):
def __init__(self, fp): def __init__(self, fp: IO[bytes]) -> None:
super().__init__(fp) super().__init__(fp)
# local copies of Image attributes # local copies of Image attributes
self.im_info = {} self.im_info: dict[str | tuple[int, int], Any] = {}
self.im_text = {} self.im_text: dict[str, str | iTXt] = {}
self.im_size = (0, 0) self.im_size = (0, 0)
self.im_mode = None self.im_mode = ""
self.im_tile = None self.im_tile: list[ImageFile._Tile] = []
self.im_palette = None self.im_palette: tuple[str, bytes] | None = None
self.im_custom_mimetype = None self.im_custom_mimetype: str | None = None
self.im_n_frames = None self.im_n_frames: int | None = None
self._seq_num = None self._seq_num: int | None = None
self.rewind_state = None self.rewind_state = _RewindState({}, [], None)
self.text_memory = 0 self.text_memory = 0
@ -396,16 +404,16 @@ class PngStream(ChunkStream):
raise ValueError(msg) raise ValueError(msg)
def save_rewind(self) -> None: def save_rewind(self) -> None:
self.rewind_state = { self.rewind_state = _RewindState(
"info": self.im_info.copy(), self.im_info.copy(),
"tile": self.im_tile, self.im_tile,
"seq_num": self._seq_num, self._seq_num,
} )
def rewind(self) -> None: def rewind(self) -> None:
self.im_info = self.rewind_state["info"].copy() self.im_info = self.rewind_state.info.copy()
self.im_tile = self.rewind_state["tile"] self.im_tile = self.rewind_state.tile
self._seq_num = self.rewind_state["seq_num"] self._seq_num = self.rewind_state.seq_num
def chunk_iCCP(self, pos: int, length: int) -> bytes: def chunk_iCCP(self, pos: int, length: int) -> bytes:
# ICC profile # ICC profile
@ -459,11 +467,11 @@ class PngStream(ChunkStream):
def chunk_IDAT(self, pos: int, length: int) -> NoReturn: def chunk_IDAT(self, pos: int, length: int) -> NoReturn:
# image data # image data
if "bbox" in self.im_info: if "bbox" in self.im_info:
tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)] tile = [ImageFile._Tile("zip", self.im_info["bbox"], pos, self.im_rawmode)]
else: else:
if self.im_n_frames is not None: if self.im_n_frames is not None:
self.im_info["default_image"] = True self.im_info["default_image"] = True
tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)] tile = [ImageFile._Tile("zip", (0, 0) + self.im_size, pos, self.im_rawmode)]
self.im_tile = tile self.im_tile = tile
self.im_idat = length self.im_idat = length
msg = "image data found" msg = "image data found"

View File

@ -151,7 +151,9 @@ class PpmImageFile(ImageFile.ImageFile):
decoder_name = "ppm" decoder_name = "ppm"
args = rawmode if decoder_name == "raw" else (rawmode, maxval) args = rawmode if decoder_name == "raw" else (rawmode, maxval)
self.tile = [(decoder_name, (0, 0) + self.size, self.fp.tell(), args)] self.tile = [
ImageFile._Tile(decoder_name, (0, 0) + self.size, self.fp.tell(), args)
]
# #
@ -333,7 +335,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
rawmode, head = "1;I", b"P4" rawmode, head = "1;I", b"P4"
elif im.mode == "L": elif im.mode == "L":
rawmode, head = "L", b"P5" rawmode, head = "L", b"P5"
elif im.mode == "I": elif im.mode in ("I", "I;16"):
rawmode, head = "I;16B", b"P5" rawmode, head = "I;16B", b"P5"
elif im.mode in ("RGB", "RGBA"): elif im.mode in ("RGB", "RGBA"):
rawmode, head = "RGB", b"P6" rawmode, head = "RGB", b"P6"

View File

@ -277,8 +277,8 @@ def _layerinfo(
def _maketile( def _maketile(
file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
) -> list[ImageFile._Tile] | None: ) -> list[ImageFile._Tile]:
tiles = None tiles = []
read = file.read read = file.read
compression = i16(read(2)) compression = i16(read(2))
@ -291,7 +291,6 @@ def _maketile(
if compression == 0: if compression == 0:
# #
# raw compression # raw compression
tiles = []
for channel in range(channels): for channel in range(channels):
layer = mode[channel] layer = mode[channel]
if mode == "CMYK": if mode == "CMYK":
@ -303,7 +302,6 @@ def _maketile(
# #
# packbits compression # packbits compression
i = 0 i = 0
tiles = []
bytecount = read(channels * ysize * 2) bytecount = read(channels * ysize * 2)
offset = file.tell() offset = file.tell()
for channel in range(channels): for channel in range(channels):

View File

@ -32,7 +32,7 @@ class QoiImageFile(ImageFile.ImageFile):
self._mode = "RGB" if channels == 3 else "RGBA" self._mode = "RGB" if channels == 3 else "RGBA"
self.fp.seek(1, os.SEEK_CUR) # colorspace self.fp.seek(1, os.SEEK_CUR) # colorspace
self.tile = [("qoi", (0, 0) + self._size, self.fp.tell(), None)] self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell(), None)]
class QoiDecoder(ImageFile.PyDecoder): class QoiDecoder(ImageFile.PyDecoder):

View File

@ -109,19 +109,28 @@ class SgiImageFile(ImageFile.ImageFile):
pagesize = xsize * ysize * bpc pagesize = xsize * ysize * bpc
if bpc == 2: if bpc == 2:
self.tile = [ self.tile = [
("SGI16", (0, 0) + self.size, headlen, (self.mode, 0, orientation)) ImageFile._Tile(
"SGI16",
(0, 0) + self.size,
headlen,
(self.mode, 0, orientation),
)
] ]
else: else:
self.tile = [] self.tile = []
offset = headlen offset = headlen
for layer in self.mode: for layer in self.mode:
self.tile.append( self.tile.append(
("raw", (0, 0) + self.size, offset, (layer, 0, orientation)) ImageFile._Tile(
"raw", (0, 0) + self.size, offset, (layer, 0, orientation)
)
) )
offset += pagesize offset += pagesize
elif compression == 1: elif compression == 1:
self.tile = [ self.tile = [
("sgi_rle", (0, 0) + self.size, headlen, (rawmode, orientation, bpc)) ImageFile._Tile(
"sgi_rle", (0, 0) + self.size, headlen, (rawmode, orientation, bpc)
)
] ]

View File

@ -154,7 +154,9 @@ class SpiderImageFile(ImageFile.ImageFile):
self.rawmode = "F;32F" self.rawmode = "F;32F"
self._mode = "F" self._mode = "F"
self.tile = [("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))] self.tile = [
ImageFile._Tile("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))
]
self._fp = self.fp # FIXME: hack self._fp = self.fp # FIXME: hack
@property @property

View File

@ -124,9 +124,13 @@ class SunImageFile(ImageFile.ImageFile):
# (https://www.fileformat.info/format/sunraster/egff.htm) # (https://www.fileformat.info/format/sunraster/egff.htm)
if file_type in (0, 1, 3, 4, 5): if file_type in (0, 1, 3, 4, 5):
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride))] self.tile = [
ImageFile._Tile("raw", (0, 0) + self.size, offset, (rawmode, stride))
]
elif file_type == 2: elif file_type == 2:
self.tile = [("sun_rle", (0, 0) + self.size, offset, rawmode)] self.tile = [
ImageFile._Tile("sun_rle", (0, 0) + self.size, offset, rawmode)
]
else: else:
msg = "Unsupported Sun Raster file type" msg = "Unsupported Sun Raster file type"
raise SyntaxError(msg) raise SyntaxError(msg)

View File

@ -137,7 +137,7 @@ class TgaImageFile(ImageFile.ImageFile):
if imagetype & 8: if imagetype & 8:
# compressed # compressed
self.tile = [ self.tile = [
( ImageFile._Tile(
"tga_rle", "tga_rle",
(0, 0) + self.size, (0, 0) + self.size,
self.fp.tell(), self.fp.tell(),
@ -146,7 +146,7 @@ class TgaImageFile(ImageFile.ImageFile):
] ]
else: else:
self.tile = [ self.tile = [
( ImageFile._Tile(
"raw", "raw",
(0, 0) + self.size, (0, 0) + self.size,
self.fp.tell(), self.fp.tell(),

View File

@ -61,6 +61,9 @@ from ._typing import StrOrBytesPath
from ._util import is_path from ._util import is_path
from .TiffTags import TYPES from .TiffTags import TYPES
if TYPE_CHECKING:
from ._typing import IntegralLike
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Set these to true to force use of libtiff for reading or writing. # Set these to true to force use of libtiff for reading or writing.
@ -291,22 +294,24 @@ def _accept(prefix: bytes) -> bool:
def _limit_rational( def _limit_rational(
val: float | Fraction | IFDRational, max_val: int val: float | Fraction | IFDRational, max_val: int
) -> tuple[float, float]: ) -> tuple[IntegralLike, IntegralLike]:
inv = abs(float(val)) > 1 inv = abs(float(val)) > 1
n_d = IFDRational(1 / val if inv else val).limit_rational(max_val) n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
return n_d[::-1] if inv else n_d return n_d[::-1] if inv else n_d
def _limit_signed_rational(val, max_val, min_val): def _limit_signed_rational(
val: IFDRational, max_val: int, min_val: int
) -> tuple[IntegralLike, IntegralLike]:
frac = Fraction(val) frac = Fraction(val)
n_d = frac.numerator, frac.denominator n_d: tuple[IntegralLike, IntegralLike] = frac.numerator, frac.denominator
if min(n_d) < min_val: if min(float(i) for i in n_d) < min_val:
n_d = _limit_rational(val, abs(min_val)) n_d = _limit_rational(val, abs(min_val))
if max(n_d) > max_val: n_d_float = tuple(float(i) for i in n_d)
val = Fraction(*n_d) if max(n_d_float) > max_val:
n_d = _limit_rational(val, max_val) n_d = _limit_rational(n_d_float[0] / n_d_float[1], max_val)
return n_d return n_d
@ -318,8 +323,10 @@ _load_dispatch = {}
_write_dispatch = {} _write_dispatch = {}
def _delegate(op: str): def _delegate(op: str) -> Any:
def delegate(self, *args): def delegate(
self: IFDRational, *args: tuple[float, ...]
) -> bool | float | Fraction:
return getattr(self._val, op)(*args) return getattr(self._val, op)(*args)
return delegate return delegate
@ -358,7 +365,10 @@ class IFDRational(Rational):
self._numerator = value.numerator self._numerator = value.numerator
self._denominator = value.denominator self._denominator = value.denominator
else: else:
self._numerator = value if TYPE_CHECKING:
self._numerator = cast(IntegralLike, value)
else:
self._numerator = value
self._denominator = denominator self._denominator = denominator
if denominator == 0: if denominator == 0:
@ -371,14 +381,14 @@ class IFDRational(Rational):
self._val = Fraction(value / denominator) self._val = Fraction(value / denominator)
@property @property
def numerator(self): def numerator(self) -> IntegralLike:
return self._numerator return self._numerator
@property @property
def denominator(self) -> int: def denominator(self) -> int:
return self._denominator return self._denominator
def limit_rational(self, max_denominator: int) -> tuple[float, int]: def limit_rational(self, max_denominator: int) -> tuple[IntegralLike, int]:
""" """
:param max_denominator: Integer, the maximum denominator value :param max_denominator: Integer, the maximum denominator value
@ -406,14 +416,18 @@ class IFDRational(Rational):
val = float(val) val = float(val)
return val == other return val == other
def __getstate__(self) -> list[float | Fraction]: def __getstate__(self) -> list[float | Fraction | IntegralLike]:
return [self._val, self._numerator, self._denominator] return [self._val, self._numerator, self._denominator]
def __setstate__(self, state: list[float | Fraction]) -> None: def __setstate__(self, state: list[float | Fraction | IntegralLike]) -> None:
IFDRational.__init__(self, 0) IFDRational.__init__(self, 0)
_val, _numerator, _denominator = state _val, _numerator, _denominator = state
assert isinstance(_val, (float, Fraction))
self._val = _val self._val = _val
self._numerator = _numerator if TYPE_CHECKING:
self._numerator = cast(IntegralLike, _numerator)
else:
self._numerator = _numerator
assert isinstance(_denominator, int) assert isinstance(_denominator, int)
self._denominator = _denominator self._denominator = _denominator
@ -471,8 +485,8 @@ def _register_loader(idx: int, size: int) -> Callable[[_LoaderFunc], _LoaderFunc
return decorator return decorator
def _register_writer(idx: int): def _register_writer(idx: int) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
def decorator(func): def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
_write_dispatch[idx] = func # noqa: F821 _write_dispatch[idx] = func # noqa: F821
return func return func
@ -1126,7 +1140,7 @@ class TiffImageFile(ImageFile.ImageFile):
def __init__( def __init__(
self, self,
fp: StrOrBytesPath | IO[bytes] | None = None, fp: StrOrBytesPath | IO[bytes],
filename: str | bytes | None = None, filename: str | bytes | None = None,
) -> None: ) -> None:
self.tag_v2: ImageFileDirectory_v2 self.tag_v2: ImageFileDirectory_v2
@ -1298,7 +1312,7 @@ class TiffImageFile(ImageFile.ImageFile):
# (self._compression, (extents tuple), # (self._compression, (extents tuple),
# 0, (rawmode, self._compression, fp)) # 0, (rawmode, self._compression, fp))
extents = self.tile[0][1] extents = self.tile[0][1]
args = list(self.tile[0][3]) args = self.tile[0][3]
# To be nice on memory footprint, if there's a # To be nice on memory footprint, if there's a
# file descriptor, use that instead of reading # file descriptor, use that instead of reading
@ -1316,11 +1330,12 @@ class TiffImageFile(ImageFile.ImageFile):
fp = False fp = False
if fp: if fp:
args[2] = fp assert isinstance(args, tuple)
args_list = list(args)
args_list[2] = fp
args = tuple(args_list)
decoder = Image._getdecoder( decoder = Image._getdecoder(self.mode, "libtiff", args, self.decoderconfig)
self.mode, "libtiff", tuple(args), self.decoderconfig
)
try: try:
decoder.setimage(self.im, extents) decoder.setimage(self.im, extents)
except ValueError as e: except ValueError as e:
@ -1538,7 +1553,7 @@ class TiffImageFile(ImageFile.ImageFile):
# Offset in the tile tuple is 0, we go from 0,0 to # Offset in the tile tuple is 0, we go from 0,0 to
# w,h, and we only do this once -- eds # w,h, and we only do this once -- eds
a = (rawmode, self._compression, False, self.tag_v2.offset) a = (rawmode, self._compression, False, self.tag_v2.offset)
self.tile.append(("libtiff", (0, 0, xsize, ysize), 0, a)) self.tile.append(ImageFile._Tile("libtiff", (0, 0, xsize, ysize), 0, a))
elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2: elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
# striped image # striped image
@ -1571,7 +1586,7 @@ class TiffImageFile(ImageFile.ImageFile):
args = (tile_rawmode, int(stride), 1) args = (tile_rawmode, int(stride), 1)
self.tile.append( self.tile.append(
( ImageFile._Tile(
self._compression, self._compression,
(x, y, min(x + w, xsize), min(y + h, ysize)), (x, y, min(x + w, xsize), min(y + h, ysize)),
offset, offset,

View File

@ -142,7 +142,7 @@ class WebPImageFile(ImageFile.ImageFile):
if self.fp and self._exclusive_fp: if self.fp and self._exclusive_fp:
self.fp.close() self.fp.close()
self.fp = BytesIO(data) self.fp = BytesIO(data)
self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)] self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, self.rawmode)]
return super().load() return super().load()

View File

@ -73,7 +73,11 @@ class XVThumbImageFile(ImageFile.ImageFile):
self.palette = ImagePalette.raw("RGB", PALETTE) self.palette = ImagePalette.raw("RGB", PALETTE)
self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1))] self.tile = [
ImageFile._Tile(
"raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1)
)
]
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -67,7 +67,7 @@ class XbmImageFile(ImageFile.ImageFile):
self._mode = "1" self._mode = "1"
self._size = xsize, ysize self._size = xsize, ysize
self.tile = [("xbm", (0, 0) + self.size, m.end(), None)] self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end(), None)]
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:

View File

@ -101,7 +101,9 @@ class XpmImageFile(ImageFile.ImageFile):
self._mode = "P" self._mode = "P"
self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.palette = ImagePalette.raw("RGB", b"".join(palette))
self.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", 0, 1))
]
def load_read(self, read_bytes: int) -> bytes: def load_read(self, read_bytes: int) -> bytes:
# #

View File

@ -6,6 +6,8 @@ from collections.abc import Sequence
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, Union from typing import TYPE_CHECKING, Any, Protocol, TypeVar, Union
if TYPE_CHECKING: if TYPE_CHECKING:
from numbers import _IntegralLike as IntegralLike
try: try:
import numpy.typing as npt import numpy.typing as npt
@ -38,4 +40,4 @@ class SupportsRead(Protocol[_T_co]):
StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"] StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]
__all__ = ["TypeGuard", "StrOrBytesPath", "SupportsRead"] __all__ = ["IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]

View File

@ -272,7 +272,7 @@ text_layout_raqm(
} }
set_text = raqm_set_text(rq, text, size); set_text = raqm_set_text(rq, text, size);
PyMem_Free(text); PyMem_Free(text);
} else { } else if (PyBytes_Check(string)) {
char *buffer; char *buffer;
PyBytes_AsStringAndSize(string, &buffer, &size); PyBytes_AsStringAndSize(string, &buffer, &size);
if (!buffer || !size) { if (!buffer || !size) {
@ -281,6 +281,9 @@ text_layout_raqm(
goto failed; goto failed;
} }
set_text = raqm_set_text_utf8(rq, buffer, size); set_text = raqm_set_text_utf8(rq, buffer, size);
} else {
PyErr_SetString(PyExc_TypeError, "expected string or bytes");
goto failed;
} }
if (!set_text) { if (!set_text) {
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed"); PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
@ -425,8 +428,11 @@ text_layout_fallback(
if (PyUnicode_Check(string)) { if (PyUnicode_Check(string)) {
count = PyUnicode_GET_LENGTH(string); count = PyUnicode_GET_LENGTH(string);
} else { } else if (PyBytes_Check(string)) {
PyBytes_AsStringAndSize(string, &buffer, &count); PyBytes_AsStringAndSize(string, &buffer, &count);
} else {
PyErr_SetString(PyExc_TypeError, "expected string or bytes");
return 0;
} }
if (count == 0) { if (count == 0) {
return 0; return 0;

View File

@ -238,6 +238,9 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
int i; int i;
Imaging imTransposed; Imaging imTransposed;
if (imOut->xsize == 0 || imOut->ysize == 0) {
return imOut;
}
if (n < 1) { if (n < 1) {
return ImagingError_ValueError("number of passes must be greater than zero"); return ImagingError_ValueError("number of passes must be greater than zero");
} }

View File

@ -698,8 +698,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
} }
/* Check that this image is something we can handle */ /* Check that this image is something we can handle */
if (image->numcomps < 1 || image->numcomps > 4 || if (image->numcomps < 1 || image->numcomps > 4) {
image->color_space == OPJ_CLRSPC_UNKNOWN) {
state->errcode = IMAGING_CODEC_BROKEN; state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED; state->state = J2K_STATE_FAILED;
goto quick_exit; goto quick_exit;
@ -744,7 +743,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
/* Find the correct unpacker */ /* Find the correct unpacker */
color_space = image->color_space; color_space = image->color_space;
if (color_space == OPJ_CLRSPC_UNSPECIFIED) { if (color_space == OPJ_CLRSPC_UNKNOWN || color_space == OPJ_CLRSPC_UNSPECIFIED) {
switch (image->numcomps) { switch (image->numcomps) {
case 1: case 1:
case 2: case 2:

View File

@ -110,9 +110,9 @@ ARCHITECTURES = {
V = { V = {
"BROTLI": "1.1.0", "BROTLI": "1.1.0",
"FREETYPE": "2.13.2", "FREETYPE": "2.13.3",
"FRIBIDI": "1.0.15", "FRIBIDI": "1.0.15",
"HARFBUZZ": "8.5.0", "HARFBUZZ": "9.0.0",
"JPEGTURBO": "3.0.3", "JPEGTURBO": "3.0.3",
"LCMS2": "2.16", "LCMS2": "2.16",
"LIBPNG": "1.6.43", "LIBPNG": "1.6.43",
@ -292,12 +292,8 @@ DEPS: dict[str, dict[str, Any]] = {
}, },
"build": [ "build": [
cmd_rmdir("objs"), cmd_rmdir("objs"),
cmd_msbuild( cmd_msbuild("MSBuild.sln", "Release Static", "Clean"),
r"builds\windows\vc2010\freetype.sln", "Release Static", "Clean" cmd_msbuild("MSBuild.sln", "Release Static", "Build"),
),
cmd_msbuild(
r"builds\windows\vc2010\freetype.sln", "Release Static", "Build"
),
cmd_xcopy("include", "{inc_dir}"), cmd_xcopy("include", "{inc_dir}"),
], ],
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"], "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],