mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-13 02:36:17 +03:00
commit
4721c31b19
|
@ -51,7 +51,7 @@ build_script:
|
|||
|
||||
test_script:
|
||||
- cd c:\pillow
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml numpy olefile pyroma'
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml ipython numpy olefile pyroma'
|
||||
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
||||
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
|
||||
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
||||
|
|
|
@ -30,6 +30,7 @@ python3 -m pip install --upgrade pip
|
|||
python3 -m pip install --upgrade wheel
|
||||
python3 -m pip install coverage
|
||||
python3 -m pip install defusedxml
|
||||
python3 -m pip install ipython
|
||||
python3 -m pip install olefile
|
||||
python3 -m pip install -U pytest
|
||||
python3 -m pip install -U pytest-cov
|
||||
|
|
1
.github/workflows/macos-install.sh
vendored
1
.github/workflows/macos-install.sh
vendored
|
@ -23,6 +23,7 @@ export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
|||
|
||||
python3 -m pip install coverage
|
||||
python3 -m pip install defusedxml
|
||||
python3 -m pip install ipython
|
||||
python3 -m pip install olefile
|
||||
python3 -m pip install -U pytest
|
||||
python3 -m pip install -U pytest-cov
|
||||
|
|
1
.github/workflows/test-cygwin.yml
vendored
1
.github/workflows/test-cygwin.yml
vendored
|
@ -74,6 +74,7 @@ jobs:
|
|||
perl
|
||||
python3${{ matrix.python-minor-version }}-cython
|
||||
python3${{ matrix.python-minor-version }}-devel
|
||||
python3${{ matrix.python-minor-version }}-ipython
|
||||
python3${{ matrix.python-minor-version }}-numpy
|
||||
python3${{ matrix.python-minor-version }}-sip
|
||||
python3${{ matrix.python-minor-version }}-tkinter
|
||||
|
|
|
@ -684,6 +684,13 @@ class TestFileTiff:
|
|||
with Image.open(outfile) as reloaded:
|
||||
assert_image_equal_tofile(reloaded, infile)
|
||||
|
||||
def test_invalid_tiled_dimensions(self) -> None:
|
||||
with open("Tests/images/tiff_tiled_planar_raw.tif", "rb") as fp:
|
||||
data = fp.read()
|
||||
b = BytesIO(data[:144] + b"\x02" + data[145:])
|
||||
with pytest.raises(ValueError):
|
||||
Image.open(b)
|
||||
|
||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||
def test_palette(self, mode: str, tmp_path: Path) -> None:
|
||||
outfile = str(tmp_path / "temp.tif")
|
||||
|
|
|
@ -42,6 +42,12 @@ try:
|
|||
except ImportError:
|
||||
ElementTree = None
|
||||
|
||||
PrettyPrinter: type | None
|
||||
try:
|
||||
from IPython.lib.pretty import PrettyPrinter
|
||||
except ImportError:
|
||||
PrettyPrinter = None
|
||||
|
||||
|
||||
# Deprecation helper
|
||||
def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image:
|
||||
|
@ -91,16 +97,15 @@ class TestImage:
|
|||
# with pytest.raises(MemoryError):
|
||||
# Image.new("L", (1000000, 1000000))
|
||||
|
||||
@pytest.mark.skipif(PrettyPrinter is None, reason="IPython is not installed")
|
||||
def test_repr_pretty(self) -> None:
|
||||
class Pretty:
|
||||
def text(self, text: str) -> None:
|
||||
self.pretty_output = text
|
||||
|
||||
im = Image.new("L", (100, 100))
|
||||
|
||||
p = Pretty()
|
||||
output = io.StringIO()
|
||||
assert PrettyPrinter is not None
|
||||
p = PrettyPrinter(output)
|
||||
im._repr_pretty_(p, False)
|
||||
assert p.pretty_output == "<PIL.Image.Image image mode=L size=100x100>"
|
||||
assert output.getvalue() == "<PIL.Image.Image image mode=L size=100x100>"
|
||||
|
||||
def test_open_formats(self) -> None:
|
||||
PNGFILE = "Tests/images/hopper.png"
|
||||
|
|
|
@ -412,9 +412,8 @@ class TestPyEncoder(CodecsTest):
|
|||
with pytest.raises(NotImplementedError):
|
||||
encoder.encode_to_pyfd()
|
||||
|
||||
fh = BytesIO()
|
||||
with pytest.raises(NotImplementedError):
|
||||
encoder.encode_to_file(fh, 0)
|
||||
encoder.encode_to_file(0, 0)
|
||||
|
||||
def test_zero_height(self) -> None:
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
|
|
|
@ -5,8 +5,6 @@ import sys
|
|||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, PSDraw
|
||||
|
||||
|
||||
|
@ -49,15 +47,14 @@ def test_draw_postscript(tmp_path: Path) -> None:
|
|||
assert os.path.getsize(tempfile) > 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("buffer", (True, False))
|
||||
def test_stdout(buffer: bool) -> None:
|
||||
def test_stdout() -> None:
|
||||
# Temporarily redirect stdout
|
||||
old_stdout = sys.stdout
|
||||
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
mystdout = MyStdOut()
|
||||
|
||||
sys.stdout = mystdout
|
||||
|
||||
|
@ -67,6 +64,4 @@ def test_stdout(buffer: bool) -> None:
|
|||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if isinstance(mystdout, MyStdOut):
|
||||
mystdout = mystdout.buffer
|
||||
assert mystdout.getvalue() != b""
|
||||
assert mystdout.buffer.getvalue() != b""
|
||||
|
|
|
@ -362,6 +362,7 @@ Classes
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
.. autoclass:: PIL.Image.ImagePointHandler
|
||||
.. autoclass:: PIL.Image.ImagePointTransform
|
||||
.. autoclass:: PIL.Image.ImageTransformHandler
|
||||
|
||||
Protocols
|
||||
|
|
|
@ -319,11 +319,11 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
self.load()
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
def size(self) -> tuple[int, int]:
|
||||
return self._size
|
||||
|
||||
@size.setter
|
||||
def size(self, value):
|
||||
def size(self, value: tuple[int, int]) -> None:
|
||||
if value not in self.info["sizes"]:
|
||||
msg = "This is not one of the allowed sizes of this image"
|
||||
raise ValueError(msg)
|
||||
|
|
|
@ -221,6 +221,8 @@ if TYPE_CHECKING:
|
|||
import mmap
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
from IPython.lib.pretty import PrettyPrinter
|
||||
|
||||
from . import ImageFile, ImageFilter, ImagePalette, TiffImagePlugin
|
||||
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
|
||||
ID: list[str] = []
|
||||
|
@ -468,43 +470,53 @@ def _getencoder(
|
|||
# Simple expression analyzer
|
||||
|
||||
|
||||
class _E:
|
||||
class ImagePointTransform:
|
||||
"""
|
||||
Used with :py:meth:`~PIL.Image.Image.point` for single band images with more than
|
||||
8 bits, this represents an affine transformation, where the value is multiplied by
|
||||
``scale`` and ``offset`` is added.
|
||||
"""
|
||||
|
||||
def __init__(self, scale: float, offset: float) -> None:
|
||||
self.scale = scale
|
||||
self.offset = offset
|
||||
|
||||
def __neg__(self) -> _E:
|
||||
return _E(-self.scale, -self.offset)
|
||||
def __neg__(self) -> ImagePointTransform:
|
||||
return ImagePointTransform(-self.scale, -self.offset)
|
||||
|
||||
def __add__(self, other: _E | float) -> _E:
|
||||
if isinstance(other, _E):
|
||||
return _E(self.scale + other.scale, self.offset + other.offset)
|
||||
return _E(self.scale, self.offset + other)
|
||||
def __add__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
||||
if isinstance(other, ImagePointTransform):
|
||||
return ImagePointTransform(
|
||||
self.scale + other.scale, self.offset + other.offset
|
||||
)
|
||||
return ImagePointTransform(self.scale, self.offset + other)
|
||||
|
||||
__radd__ = __add__
|
||||
|
||||
def __sub__(self, other: _E | float) -> _E:
|
||||
def __sub__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
||||
return self + -other
|
||||
|
||||
def __rsub__(self, other: _E | float) -> _E:
|
||||
def __rsub__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
||||
return other + -self
|
||||
|
||||
def __mul__(self, other: _E | float) -> _E:
|
||||
if isinstance(other, _E):
|
||||
def __mul__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
||||
if isinstance(other, ImagePointTransform):
|
||||
return NotImplemented
|
||||
return _E(self.scale * other, self.offset * other)
|
||||
return ImagePointTransform(self.scale * other, self.offset * other)
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __truediv__(self, other: _E | float) -> _E:
|
||||
if isinstance(other, _E):
|
||||
def __truediv__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
||||
if isinstance(other, ImagePointTransform):
|
||||
return NotImplemented
|
||||
return _E(self.scale / other, self.offset / other)
|
||||
return ImagePointTransform(self.scale / other, self.offset / other)
|
||||
|
||||
|
||||
def _getscaleoffset(expr) -> tuple[float, float]:
|
||||
a = expr(_E(1, 0))
|
||||
return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
|
||||
def _getscaleoffset(
|
||||
expr: Callable[[ImagePointTransform], ImagePointTransform | float]
|
||||
) -> tuple[float, float]:
|
||||
a = expr(ImagePointTransform(1, 0))
|
||||
return (a.scale, a.offset) if isinstance(a, ImagePointTransform) else (0, a)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -677,7 +689,7 @@ class Image:
|
|||
id(self),
|
||||
)
|
||||
|
||||
def _repr_pretty_(self, p, cycle: bool) -> None:
|
||||
def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None:
|
||||
"""IPython plain text display support"""
|
||||
|
||||
# Same as __repr__ but without unpredictable id(self),
|
||||
|
@ -1880,7 +1892,13 @@ class Image:
|
|||
|
||||
def point(
|
||||
self,
|
||||
lut: Sequence[float] | NumpyArray | Callable[[int], float] | ImagePointHandler,
|
||||
lut: (
|
||||
Sequence[float]
|
||||
| NumpyArray
|
||||
| Callable[[int], float]
|
||||
| Callable[[ImagePointTransform], ImagePointTransform | float]
|
||||
| ImagePointHandler
|
||||
),
|
||||
mode: str | None = None,
|
||||
) -> Image:
|
||||
"""
|
||||
|
@ -1897,7 +1915,7 @@ class Image:
|
|||
object::
|
||||
|
||||
class Example(Image.ImagePointHandler):
|
||||
def point(self, data):
|
||||
def point(self, im: Image) -> Image:
|
||||
# Return result
|
||||
:param mode: Output mode (default is same as input). This can only be used if
|
||||
the source image has mode "L" or "P", and the output has mode "1" or the
|
||||
|
@ -1916,10 +1934,10 @@ class Image:
|
|||
# check if the function can be used with point_transform
|
||||
# UNDONE wiredfool -- I think this prevents us from ever doing
|
||||
# a gamma function point transform on > 8bit images.
|
||||
scale, offset = _getscaleoffset(lut)
|
||||
scale, offset = _getscaleoffset(lut) # type: ignore[arg-type]
|
||||
return self._new(self.im.point_transform(scale, offset))
|
||||
# for other modes, convert the function to a table
|
||||
flatLut = [lut(i) for i in range(256)] * self.im.bands
|
||||
flatLut = [lut(i) for i in range(256)] * self.im.bands # type: ignore[arg-type]
|
||||
else:
|
||||
flatLut = lut
|
||||
|
||||
|
@ -2855,11 +2873,11 @@ class Image:
|
|||
self,
|
||||
box: tuple[int, int, int, int],
|
||||
image: Image,
|
||||
method,
|
||||
data,
|
||||
method: Transform,
|
||||
data: Sequence[float],
|
||||
resample: int = Resampling.NEAREST,
|
||||
fill: bool = True,
|
||||
):
|
||||
) -> None:
|
||||
w = box[2] - box[0]
|
||||
h = box[3] - box[1]
|
||||
|
||||
|
@ -3994,15 +4012,19 @@ class Exif(_ExifBase):
|
|||
ifd[tag] = value
|
||||
return b"Exif\x00\x00" + head + ifd.tobytes(offset)
|
||||
|
||||
def get_ifd(self, tag):
|
||||
def get_ifd(self, tag: int) -> dict[int, Any]:
|
||||
if tag not in self._ifds:
|
||||
if tag == ExifTags.IFD.IFD1:
|
||||
if self._info is not None and self._info.next != 0:
|
||||
self._ifds[tag] = self._get_ifd_dict(self._info.next)
|
||||
ifd = self._get_ifd_dict(self._info.next)
|
||||
if ifd is not None:
|
||||
self._ifds[tag] = ifd
|
||||
elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]:
|
||||
offset = self._hidden_data.get(tag, self.get(tag))
|
||||
if offset is not None:
|
||||
self._ifds[tag] = self._get_ifd_dict(offset, tag)
|
||||
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]:
|
||||
if ExifTags.IFD.Exif not in self._ifds:
|
||||
self.get_ifd(ExifTags.IFD.Exif)
|
||||
|
@ -4059,7 +4081,9 @@ class Exif(_ExifBase):
|
|||
(offset,) = struct.unpack(">L", data)
|
||||
self.fp.seek(offset)
|
||||
|
||||
camerainfo = {"ModelID": self.fp.read(4)}
|
||||
camerainfo: dict[str, int | bytes] = {
|
||||
"ModelID": self.fp.read(4)
|
||||
}
|
||||
|
||||
self.fp.read(4)
|
||||
# Seconds since 2000
|
||||
|
@ -4075,16 +4099,18 @@ class Exif(_ExifBase):
|
|||
][1]
|
||||
camerainfo["Parallax"] = handler(
|
||||
ImageFileDirectory_v2(), parallax, False
|
||||
)
|
||||
)[0]
|
||||
|
||||
self.fp.read(4)
|
||||
camerainfo["Category"] = self.fp.read(2)
|
||||
|
||||
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
||||
makernote = {0x1101: camerainfo}
|
||||
self._ifds[tag] = makernote
|
||||
else:
|
||||
# Interop
|
||||
self._ifds[tag] = self._get_ifd_dict(tag_data, tag)
|
||||
ifd = self._get_ifd_dict(tag_data, tag)
|
||||
if ifd is not None:
|
||||
self._ifds[tag] = ifd
|
||||
ifd = self._ifds.get(tag, {})
|
||||
if tag == ExifTags.IFD.Exif and self._hidden_data:
|
||||
ifd = {
|
||||
|
|
|
@ -31,6 +31,7 @@ from __future__ import annotations
|
|||
import abc
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from typing import IO, Any, NamedTuple
|
||||
|
@ -555,7 +556,7 @@ def _encode_tile(
|
|||
fp: IO[bytes],
|
||||
tile: list[_Tile],
|
||||
bufsize: int,
|
||||
fh,
|
||||
fh: int | None,
|
||||
exc: BaseException | None = None,
|
||||
) -> None:
|
||||
for encoder_name, extents, offset, args in tile:
|
||||
|
@ -577,6 +578,7 @@ def _encode_tile(
|
|||
break
|
||||
else:
|
||||
# slight speedup: compress to real file object
|
||||
assert fh is not None
|
||||
errcode = encoder.encode_to_file(fh, bufsize)
|
||||
if errcode < 0:
|
||||
raise _get_oserror(errcode, encoder=True) from exc
|
||||
|
@ -801,7 +803,7 @@ class PyEncoder(PyCodec):
|
|||
self.fd.write(data)
|
||||
return bytes_consumed, errcode
|
||||
|
||||
def encode_to_file(self, fh: IO[bytes], bufsize: int) -> int:
|
||||
def encode_to_file(self, fh: int, bufsize: int) -> int:
|
||||
"""
|
||||
:param fh: File handle.
|
||||
:param bufsize: Buffer size.
|
||||
|
@ -814,5 +816,5 @@ class PyEncoder(PyCodec):
|
|||
while errcode == 0:
|
||||
status, errcode, buf = self.encode(bufsize)
|
||||
if status > 0:
|
||||
fh.write(buf[status:])
|
||||
os.write(fh, buf[status:])
|
||||
return errcode
|
||||
|
|
|
@ -214,7 +214,7 @@ def getiptcinfo(
|
|||
# as 4-byte integers, so we cannot use the get method...)
|
||||
try:
|
||||
data = im.tag_v2[TiffImagePlugin.IPTC_NAA_CHUNK]
|
||||
except (AttributeError, KeyError):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if data is None:
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import IO, TYPE_CHECKING
|
||||
|
||||
from . import EpsImagePlugin
|
||||
|
||||
|
@ -28,15 +28,12 @@ from . import EpsImagePlugin
|
|||
class PSDraw:
|
||||
"""
|
||||
Sets up printing to the given file. If ``fp`` is omitted,
|
||||
``sys.stdout.buffer`` or ``sys.stdout`` is assumed.
|
||||
``sys.stdout.buffer`` is assumed.
|
||||
"""
|
||||
|
||||
def __init__(self, fp=None):
|
||||
def __init__(self, fp: IO[bytes] | None = None) -> None:
|
||||
if not fp:
|
||||
try:
|
||||
fp = sys.stdout.buffer
|
||||
except AttributeError:
|
||||
fp = sys.stdout
|
||||
self.fp = fp
|
||||
|
||||
def begin_document(self, id: str | None = None) -> None:
|
||||
|
|
|
@ -624,12 +624,12 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
self._tagdata: dict[int, bytes] = {}
|
||||
self.tagtype = {} # added 2008-06-05 by Florian Hoech
|
||||
self._next = None
|
||||
self._offset = None
|
||||
self._offset: int | None = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(dict(self))
|
||||
|
||||
def named(self):
|
||||
def named(self) -> dict[str, Any]:
|
||||
"""
|
||||
:returns: dict of name|key: value
|
||||
|
||||
|
@ -643,7 +643,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
def __len__(self) -> int:
|
||||
return len(set(self._tagdata) | set(self._tags_v2))
|
||||
|
||||
def __getitem__(self, tag):
|
||||
def __getitem__(self, tag: int) -> Any:
|
||||
if tag not in self._tags_v2: # unpack on the fly
|
||||
data = self._tagdata[tag]
|
||||
typ = self.tagtype[tag]
|
||||
|
@ -855,7 +855,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
raise OSError(msg)
|
||||
return ret
|
||||
|
||||
def load(self, fp):
|
||||
def load(self, fp: IO[bytes]) -> None:
|
||||
self.reset()
|
||||
self._offset = fp.tell()
|
||||
|
||||
|
@ -1098,7 +1098,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
|||
for legacy_api in (False, True):
|
||||
self._setitem(tag, value, legacy_api)
|
||||
|
||||
def __getitem__(self, tag):
|
||||
def __getitem__(self, tag: int) -> Any:
|
||||
if tag not in self._tags_v1: # unpack on the fly
|
||||
data = self._tagdata[tag]
|
||||
typ = self.tagtype[tag]
|
||||
|
@ -1124,11 +1124,15 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
format_description = "Adobe TIFF"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def __init__(self, fp=None, filename=None):
|
||||
self.tag_v2 = None
|
||||
def __init__(
|
||||
self,
|
||||
fp: StrOrBytesPath | IO[bytes] | None = None,
|
||||
filename: str | bytes | None = None,
|
||||
) -> None:
|
||||
self.tag_v2: ImageFileDirectory_v2
|
||||
""" Image file directory (tag dictionary) """
|
||||
|
||||
self.tag = None
|
||||
self.tag: ImageFileDirectory_v1
|
||||
""" Legacy tag entries """
|
||||
|
||||
super().__init__(fp, filename)
|
||||
|
@ -1143,9 +1147,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
|
||||
self.tag_v2 = ImageFileDirectory_v2(ifh)
|
||||
|
||||
# legacy IFD entries will be filled in later
|
||||
self.ifd: ImageFileDirectory_v1 | None = None
|
||||
|
||||
# setup frame pointers
|
||||
self.__first = self.__next = self.tag_v2.next
|
||||
self.__frame = -1
|
||||
|
@ -1396,8 +1397,11 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
|
||||
|
||||
# size
|
||||
xsize = int(self.tag_v2.get(IMAGEWIDTH))
|
||||
ysize = int(self.tag_v2.get(IMAGELENGTH))
|
||||
xsize = self.tag_v2.get(IMAGEWIDTH)
|
||||
ysize = self.tag_v2.get(IMAGELENGTH)
|
||||
if not isinstance(xsize, int) or not isinstance(ysize, int):
|
||||
msg = "Invalid dimensions"
|
||||
raise ValueError(msg)
|
||||
self._size = xsize, ysize
|
||||
|
||||
logger.debug("- size: %s", self.size)
|
||||
|
@ -1545,8 +1549,12 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
else:
|
||||
# tiled image
|
||||
offsets = self.tag_v2[TILEOFFSETS]
|
||||
w = self.tag_v2.get(TILEWIDTH)
|
||||
tilewidth = self.tag_v2.get(TILEWIDTH)
|
||||
h = self.tag_v2.get(TILELENGTH)
|
||||
if not isinstance(tilewidth, int) or not isinstance(h, int):
|
||||
msg = "Invalid tile dimensions"
|
||||
raise ValueError(msg)
|
||||
w = tilewidth
|
||||
|
||||
for offset in offsets:
|
||||
if x + w > xsize:
|
||||
|
@ -1624,7 +1632,7 @@ SAVE_INFO = {
|
|||
}
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
def _save(im: Image.Image, fp, filename: str | bytes) -> None:
|
||||
try:
|
||||
rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
|
||||
except KeyError as e:
|
||||
|
@ -1760,6 +1768,7 @@ def _save(im, fp, filename):
|
|||
if im.mode == "1":
|
||||
inverted_im = im.copy()
|
||||
px = inverted_im.load()
|
||||
if px is not None:
|
||||
for y in range(inverted_im.height):
|
||||
for x in range(inverted_im.width):
|
||||
px[x, y] = 0 if px[x, y] == 255 else 255
|
||||
|
@ -1805,11 +1814,11 @@ def _save(im, fp, filename):
|
|||
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
|
||||
|
||||
if im.mode == "YCbCr":
|
||||
for tag, value in {
|
||||
for tag, default_value in {
|
||||
YCBCRSUBSAMPLING: (1, 1),
|
||||
REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255),
|
||||
}.items():
|
||||
ifd.setdefault(tag, value)
|
||||
ifd.setdefault(tag, default_value)
|
||||
|
||||
blocklist = [TILEWIDTH, TILELENGTH, TILEOFFSETS, TILEBYTECOUNTS]
|
||||
if libtiff:
|
||||
|
@ -1852,7 +1861,7 @@ def _save(im, fp, filename):
|
|||
]
|
||||
|
||||
# bits per sample is a single short in the tiff directory, not a list.
|
||||
atts = {BITSPERSAMPLE: bits[0]}
|
||||
atts: dict[int, Any] = {BITSPERSAMPLE: bits[0]}
|
||||
# Merge the ones that we have with (optional) more bits from
|
||||
# the original file, e.g x,y resolution so that we can
|
||||
# save(load('')) == original file.
|
||||
|
@ -1923,13 +1932,15 @@ def _save(im, fp, filename):
|
|||
offset = ifd.save(fp)
|
||||
|
||||
ImageFile._save(
|
||||
im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]
|
||||
im,
|
||||
fp,
|
||||
[ImageFile._Tile("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))],
|
||||
)
|
||||
|
||||
# -- helper for multi-page save --
|
||||
if "_debug_multipage" in encoderinfo:
|
||||
# just to access o32 and o16 (using correct byte order)
|
||||
im._debug_multipage = ifd
|
||||
setattr(im, "_debug_multipage", ifd)
|
||||
|
||||
|
||||
class AppendingTiffWriter:
|
||||
|
|
Loading…
Reference in New Issue
Block a user