mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-05-31 11:13:08 +03:00
Merge branch 'main' into image_grab_wayland_kde
This commit is contained in:
commit
ec11f7aaed
BIN
Tests/images/drawing_emf_ref_72_144.png
Normal file
BIN
Tests/images/drawing_emf_ref_72_144.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 984 B |
Binary file not shown.
Before Width: | Height: | Size: 533 B After Width: | Height: | Size: 533 B |
|
@ -224,3 +224,13 @@ def test_offset() -> None:
|
||||||
# to exclude the palette size from the pixel data offset
|
# to exclude the palette size from the pixel data offset
|
||||||
with Image.open("Tests/images/pal8_offset.bmp") as im:
|
with Image.open("Tests/images/pal8_offset.bmp") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp")
|
assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp")
|
||||||
|
|
||||||
|
|
||||||
|
def test_use_raw_alpha(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
with Image.open("Tests/images/bmp/g/rgb32.bmp") as im:
|
||||||
|
assert im.info["compression"] == BmpImagePlugin.BmpImageFile.COMPRESSIONS["RAW"]
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
|
||||||
|
monkeypatch.setattr(BmpImagePlugin, "USE_RAW_ALPHA", True)
|
||||||
|
with Image.open("Tests/images/bmp/g/rgb32.bmp") as im:
|
||||||
|
assert im.mode == "RGBA"
|
||||||
|
|
|
@ -1026,6 +1026,17 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
|
with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
||||||
|
|
||||||
|
def test_old_style_jpeg_orientation(self) -> None:
|
||||||
|
with open("Tests/images/old-style-jpeg-compression.tif", "rb") as fp:
|
||||||
|
data = fp.read()
|
||||||
|
|
||||||
|
# Set EXIF Orientation to 2
|
||||||
|
data = data[:102] + b"\x02" + data[103:]
|
||||||
|
|
||||||
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
||||||
|
|
||||||
def test_open_missing_samplesperpixel(self) -> None:
|
def test_open_missing_samplesperpixel(self) -> None:
|
||||||
with Image.open(
|
with Image.open(
|
||||||
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
|
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
|
||||||
|
|
|
@ -43,6 +43,11 @@ def roundtrip(tmp_path: Path, mode: str) -> None:
|
||||||
|
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
converted = open_with_magick(magick, tmp_path, outfile)
|
converted = open_with_magick(magick, tmp_path, outfile)
|
||||||
|
if mode == "P":
|
||||||
|
assert converted.mode == "P"
|
||||||
|
|
||||||
|
im = im.convert("RGB")
|
||||||
|
converted = converted.convert("RGB")
|
||||||
assert_image_equal(converted, im)
|
assert_image_equal(converted, im)
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +60,6 @@ def test_monochrome(tmp_path: Path) -> None:
|
||||||
roundtrip(tmp_path, mode)
|
roundtrip(tmp_path, mode)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="Palm P image is wrong")
|
|
||||||
def test_p_mode(tmp_path: Path) -> None:
|
def test_p_mode(tmp_path: Path) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
mode = "P"
|
mode = "P"
|
||||||
|
|
|
@ -8,7 +8,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFile, WmfImagePlugin
|
from PIL import Image, ImageFile, WmfImagePlugin
|
||||||
|
|
||||||
from .helper import assert_image_similar_tofile, hopper
|
from .helper import assert_image_equal_tofile, assert_image_similar_tofile, hopper
|
||||||
|
|
||||||
|
|
||||||
def test_load_raw() -> None:
|
def test_load_raw() -> None:
|
||||||
|
@ -44,6 +44,15 @@ def test_load_zero_inch() -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_render() -> None:
|
||||||
|
with open("Tests/images/drawing.emf", "rb") as fp:
|
||||||
|
data = fp.read()
|
||||||
|
b = BytesIO(data[:808] + b"\x00" + data[809:])
|
||||||
|
with Image.open(b) as im:
|
||||||
|
if hasattr(Image.core, "drawwmf"):
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/drawing.emf")
|
||||||
|
|
||||||
|
|
||||||
def test_register_handler(tmp_path: Path) -> None:
|
def test_register_handler(tmp_path: Path) -> None:
|
||||||
class TestHandler(ImageFile.StubHandler):
|
class TestHandler(ImageFile.StubHandler):
|
||||||
methodCalled = False
|
methodCalled = False
|
||||||
|
@ -88,6 +97,20 @@ def test_load_set_dpi() -> None:
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
|
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/drawing.emf") as im:
|
||||||
|
assert im.size == (1625, 1625)
|
||||||
|
|
||||||
|
if not hasattr(Image.core, "drawwmf"):
|
||||||
|
return
|
||||||
|
im.load(im.info["dpi"])
|
||||||
|
assert im.size == (1625, 1625)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/drawing.emf") as im:
|
||||||
|
im.load((72, 144))
|
||||||
|
assert im.size == (82, 164)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/drawing_emf_ref_72_144.png")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("ext", (".wmf", ".emf"))
|
@pytest.mark.parametrize("ext", (".wmf", ".emf"))
|
||||||
def test_save(ext: str, tmp_path: Path) -> None:
|
def test_save(ext: str, tmp_path: Path) -> None:
|
||||||
|
|
|
@ -1704,7 +1704,7 @@ def test_discontiguous_corners_polygon() -> None:
|
||||||
BLACK,
|
BLACK,
|
||||||
)
|
)
|
||||||
expected = os.path.join(IMAGES_PATH, "discontiguous_corners_polygon.png")
|
expected = os.path.join(IMAGES_PATH, "discontiguous_corners_polygon.png")
|
||||||
assert_image_similar_tofile(img, expected, 1)
|
assert_image_equal_tofile(img, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_polygon2() -> None:
|
def test_polygon2() -> None:
|
||||||
|
|
|
@ -131,6 +131,26 @@ class TestImageFile:
|
||||||
|
|
||||||
assert_image_equal(im1, im2)
|
assert_image_equal(im1, im2)
|
||||||
|
|
||||||
|
def test_tile_size(self) -> None:
|
||||||
|
with open("Tests/images/hopper.tif", "rb") as im_fp:
|
||||||
|
data = im_fp.read()
|
||||||
|
|
||||||
|
reads = []
|
||||||
|
|
||||||
|
class FP(BytesIO):
|
||||||
|
def read(self, size: int | None = None) -> bytes:
|
||||||
|
reads.append(size)
|
||||||
|
return super().read(size)
|
||||||
|
|
||||||
|
fp = FP(data)
|
||||||
|
with Image.open(fp) as im:
|
||||||
|
assert len(im.tile) == 7
|
||||||
|
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
# Despite multiple tiles, assert only one tile caused a read of maxblock size
|
||||||
|
assert reads.count(im.decodermaxblock) == 1
|
||||||
|
|
||||||
def test_raise_oserror(self) -> None:
|
def test_raise_oserror(self) -> None:
|
||||||
with pytest.warns(DeprecationWarning):
|
with pytest.warns(DeprecationWarning):
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
|
|
|
@ -4,21 +4,12 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
TODO
|
Undefined shift when loading compressed DDS images
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
When loading some compressed DDS formats, an integer was bitshifted by 24 places to
|
||||||
|
generate the 32 bits of the lookup table. This was undefined behaviour, and has been
|
||||||
:cve:`YYYY-XXXXX`: TODO
|
present since Pillow 3.4.0.
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Backwards Incompatible Changes
|
|
||||||
==============================
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
@ -36,10 +27,14 @@ an :py:class:`PIL.ImageFile.ImageFile` instance.
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
TODO
|
``append_images`` no longer requires ``save_all``
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
Previously, ``save_all`` was required to in order to use ``append_images``. Now,
|
||||||
|
``save_all`` will default to ``True`` if ``append_images`` is not empty and the format
|
||||||
|
supports saving multiple frames::
|
||||||
|
|
||||||
|
im.save("out.gif", append_images=ims)
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
@ -73,11 +68,3 @@ Compressed DDS images can now be saved using a ``pixel_format`` argument. DXT1,
|
||||||
DXT5, BC2, BC3 and BC5 are supported::
|
DXT5, BC2, BC3 and BC5 are supported::
|
||||||
|
|
||||||
im.save("out.dds", pixel_format="DXT1")
|
im.save("out.dds", pixel_format="DXT1")
|
||||||
|
|
||||||
Other Changes
|
|
||||||
=============
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
|
@ -48,6 +48,8 @@ BIT2MODE = {
|
||||||
32: ("RGB", "BGRX"),
|
32: ("RGB", "BGRX"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
USE_RAW_ALPHA = False
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return prefix.startswith(b"BM")
|
return prefix.startswith(b"BM")
|
||||||
|
@ -242,7 +244,9 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
msg = "Unsupported BMP bitfields layout"
|
msg = "Unsupported BMP bitfields layout"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
elif file_info["compression"] == self.COMPRESSIONS["RAW"]:
|
elif file_info["compression"] == self.COMPRESSIONS["RAW"]:
|
||||||
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
|
if file_info["bits"] == 32 and (
|
||||||
|
header == 22 or USE_RAW_ALPHA # 32-bit .cur offset
|
||||||
|
):
|
||||||
raw_mode, self._mode = "BGRA", "RGBA"
|
raw_mode, self._mode = "BGRA", "RGBA"
|
||||||
elif file_info["compression"] in (
|
elif file_info["compression"] in (
|
||||||
self.COMPRESSIONS["RLE8"],
|
self.COMPRESSIONS["RLE8"],
|
||||||
|
|
|
@ -24,6 +24,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
|
from ._util import DeferredError
|
||||||
from .PcxImagePlugin import PcxImageFile
|
from .PcxImagePlugin import PcxImageFile
|
||||||
|
|
||||||
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
||||||
|
@ -66,6 +67,8 @@ class DcxImageFile(PcxImageFile):
|
||||||
def seek(self, frame: int) -> None:
|
def seek(self, frame: int) -> None:
|
||||||
if not self._seek_check(frame):
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
self.fp = self._fp
|
self.fp = self._fp
|
||||||
self.fp.seek(self._offset[frame])
|
self.fp.seek(self._offset[frame])
|
||||||
|
|
|
@ -22,6 +22,7 @@ from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
|
from ._util import DeferredError
|
||||||
|
|
||||||
#
|
#
|
||||||
# decoder
|
# decoder
|
||||||
|
@ -134,6 +135,8 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
self._seek(f)
|
self._seek(f)
|
||||||
|
|
||||||
def _seek(self, frame: int) -> None:
|
def _seek(self, frame: int) -> None:
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
self.__frame = -1
|
self.__frame = -1
|
||||||
self._fp.seek(self.__rewind)
|
self._fp.seek(self.__rewind)
|
||||||
|
|
|
@ -45,6 +45,7 @@ from . import (
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._binary import o16le as o16
|
from ._binary import o16le as o16
|
||||||
|
from ._util import DeferredError
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import _imaging
|
from . import _imaging
|
||||||
|
@ -167,6 +168,8 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
raise EOFError(msg) from e
|
raise EOFError(msg) from e
|
||||||
|
|
||||||
def _seek(self, frame: int, update_image: bool = True) -> None:
|
def _seek(self, frame: int, update_image: bool = True) -> None:
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
# rewind
|
# rewind
|
||||||
self.__offset = 0
|
self.__offset = 0
|
||||||
|
|
|
@ -31,6 +31,7 @@ import re
|
||||||
from typing import IO, Any
|
from typing import IO, Any
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
|
from ._util import DeferredError
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Standard tags
|
# Standard tags
|
||||||
|
@ -290,6 +291,8 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
def seek(self, frame: int) -> None:
|
def seek(self, frame: int) -> None:
|
||||||
if not self._seek_check(frame):
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
|
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
|
|
||||||
|
|
|
@ -621,6 +621,8 @@ class Image:
|
||||||
more information.
|
more information.
|
||||||
"""
|
"""
|
||||||
if getattr(self, "map", None):
|
if getattr(self, "map", None):
|
||||||
|
if sys.platform == "win32" and hasattr(sys, "pypy_version_info"):
|
||||||
|
self.map.close()
|
||||||
self.map: mmap.mmap | None = None
|
self.map: mmap.mmap | None = None
|
||||||
|
|
||||||
# Instead of simply setting to None, we're setting up a
|
# Instead of simply setting to None, we're setting up a
|
||||||
|
|
|
@ -34,7 +34,6 @@ import itertools
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import sys
|
|
||||||
from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast
|
from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast
|
||||||
|
|
||||||
from . import ExifTags, Image
|
from . import ExifTags, Image
|
||||||
|
@ -167,7 +166,7 @@ class ImageFile(Image.Image):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _close_fp(self):
|
def _close_fp(self):
|
||||||
if getattr(self, "_fp", False):
|
if getattr(self, "_fp", False) and not isinstance(self._fp, DeferredError):
|
||||||
if self._fp != self.fp:
|
if self._fp != self.fp:
|
||||||
self._fp.close()
|
self._fp.close()
|
||||||
self._fp = DeferredError(ValueError("Operation on closed image"))
|
self._fp = DeferredError(ValueError("Operation on closed image"))
|
||||||
|
@ -278,8 +277,6 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
self.map: mmap.mmap | None = None
|
self.map: mmap.mmap | None = None
|
||||||
use_mmap = self.filename and len(self.tile) == 1
|
use_mmap = self.filename and len(self.tile) == 1
|
||||||
# As of pypy 2.1.0, memory mapping was failing here.
|
|
||||||
use_mmap = use_mmap and not hasattr(sys, "pypy_version_info")
|
|
||||||
|
|
||||||
readonly = 0
|
readonly = 0
|
||||||
|
|
||||||
|
@ -345,7 +342,7 @@ class ImageFile(Image.Image):
|
||||||
self.tile, lambda tile: (tile[0], tile[1], tile[3])
|
self.tile, lambda tile: (tile[0], tile[1], tile[3])
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
for decoder_name, extents, offset, args in self.tile:
|
for i, (decoder_name, extents, offset, args) in enumerate(self.tile):
|
||||||
seek(offset)
|
seek(offset)
|
||||||
decoder = Image._getdecoder(
|
decoder = Image._getdecoder(
|
||||||
self.mode, decoder_name, args, self.decoderconfig
|
self.mode, decoder_name, args, self.decoderconfig
|
||||||
|
@ -358,8 +355,13 @@ class ImageFile(Image.Image):
|
||||||
else:
|
else:
|
||||||
b = prefix
|
b = prefix
|
||||||
while True:
|
while True:
|
||||||
|
read_bytes = self.decodermaxblock
|
||||||
|
if i + 1 < len(self.tile):
|
||||||
|
next_offset = self.tile[i + 1].offset
|
||||||
|
if next_offset > offset:
|
||||||
|
read_bytes = next_offset - offset
|
||||||
try:
|
try:
|
||||||
s = read(self.decodermaxblock)
|
s = read(read_bytes)
|
||||||
except (IndexError, struct.error) as e:
|
except (IndexError, struct.error) as e:
|
||||||
# truncated png/gif
|
# truncated png/gif
|
||||||
if LOAD_TRUNCATED_IMAGES:
|
if LOAD_TRUNCATED_IMAGES:
|
||||||
|
|
|
@ -32,6 +32,7 @@ from . import (
|
||||||
TiffImagePlugin,
|
TiffImagePlugin,
|
||||||
)
|
)
|
||||||
from ._binary import o32le
|
from ._binary import o32le
|
||||||
|
from ._util import DeferredError
|
||||||
|
|
||||||
|
|
||||||
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
@ -125,11 +126,15 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
self.readonly = 1
|
self.readonly = 1
|
||||||
|
|
||||||
def load_seek(self, pos: int) -> None:
|
def load_seek(self, pos: int) -> None:
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
self._fp.seek(pos)
|
self._fp.seek(pos)
|
||||||
|
|
||||||
def seek(self, frame: int) -> None:
|
def seek(self, frame: int) -> None:
|
||||||
if not self._seek_check(frame):
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
self.fp = self._fp
|
self.fp = self._fp
|
||||||
self.offset = self.__mpoffsets[frame]
|
self.offset = self.__mpoffsets[frame]
|
||||||
|
|
||||||
|
|
|
@ -116,9 +116,6 @@ _COMPRESSION_TYPES = {"none": 0xFF, "rle": 0x01, "scanline": 0x00}
|
||||||
|
|
||||||
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
# we assume this is a color Palm image with the standard colormap,
|
|
||||||
# unless the "info" dict has a "custom-colormap" field
|
|
||||||
|
|
||||||
rawmode = "P"
|
rawmode = "P"
|
||||||
bpp = 8
|
bpp = 8
|
||||||
version = 1
|
version = 1
|
||||||
|
@ -172,12 +169,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
compression_type = _COMPRESSION_TYPES["none"]
|
compression_type = _COMPRESSION_TYPES["none"]
|
||||||
|
|
||||||
flags = 0
|
flags = 0
|
||||||
if im.mode == "P" and "custom-colormap" in im.info:
|
if im.mode == "P":
|
||||||
assert im.palette is not None
|
flags |= _FLAGS["custom-colormap"]
|
||||||
flags = flags & _FLAGS["custom-colormap"]
|
colormap = im.im.getpalette()
|
||||||
colormapsize = 4 * 256 + 2
|
colors = len(colormap) // 3
|
||||||
colormapmode = im.palette.mode
|
colormapsize = 4 * colors + 2
|
||||||
colormap = im.getdata().getpalette()
|
|
||||||
else:
|
else:
|
||||||
colormapsize = 0
|
colormapsize = 0
|
||||||
|
|
||||||
|
@ -196,22 +192,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
# now write colormap if necessary
|
# now write colormap if necessary
|
||||||
|
|
||||||
if colormapsize > 0:
|
if colormapsize:
|
||||||
fp.write(o16b(256))
|
fp.write(o16b(colors))
|
||||||
for i in range(256):
|
for i in range(colors):
|
||||||
fp.write(o8(i))
|
fp.write(o8(i))
|
||||||
if colormapmode == "RGB":
|
fp.write(colormap[3 * i : 3 * i + 3])
|
||||||
fp.write(
|
|
||||||
o8(colormap[3 * i])
|
|
||||||
+ o8(colormap[3 * i + 1])
|
|
||||||
+ o8(colormap[3 * i + 2])
|
|
||||||
)
|
|
||||||
elif colormapmode == "RGBA":
|
|
||||||
fp.write(
|
|
||||||
o8(colormap[4 * i])
|
|
||||||
+ o8(colormap[4 * i + 1])
|
|
||||||
+ o8(colormap[4 * i + 2])
|
|
||||||
)
|
|
||||||
|
|
||||||
# now convert data to raw form
|
# now convert data to raw form
|
||||||
ImageFile._save(
|
ImageFile._save(
|
||||||
|
|
|
@ -48,6 +48,7 @@ from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._binary import o16be as o16
|
from ._binary import o16be as o16
|
||||||
from ._binary import o32be as o32
|
from ._binary import o32be as o32
|
||||||
|
from ._util import DeferredError
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import _imaging
|
from . import _imaging
|
||||||
|
@ -869,6 +870,8 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def _seek(self, frame: int, rewind: bool = False) -> None:
|
def _seek(self, frame: int, rewind: bool = False) -> None:
|
||||||
assert self.png is not None
|
assert self.png is not None
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
|
|
||||||
self.dispose: _imaging.ImagingCore | None
|
self.dispose: _imaging.ImagingCore | None
|
||||||
dispose_extent = None
|
dispose_extent = None
|
||||||
|
|
|
@ -27,6 +27,7 @@ from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
from ._binary import si16be as si16
|
from ._binary import si16be as si16
|
||||||
from ._binary import si32be as si32
|
from ._binary import si32be as si32
|
||||||
|
from ._util import DeferredError
|
||||||
|
|
||||||
MODES = {
|
MODES = {
|
||||||
# (photoshop mode, bits) -> (pil mode, required channels)
|
# (photoshop mode, bits) -> (pil mode, required channels)
|
||||||
|
@ -148,6 +149,8 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
|
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
|
||||||
layers = []
|
layers = []
|
||||||
if self._layers_position is not None:
|
if self._layers_position is not None:
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
self._fp.seek(self._layers_position)
|
self._fp.seek(self._layers_position)
|
||||||
_layer_data = io.BytesIO(ImageFile._safe_read(self._fp, self._layers_size))
|
_layer_data = io.BytesIO(ImageFile._safe_read(self._fp, self._layers_size))
|
||||||
layers = _layerinfo(_layer_data, self._layers_size)
|
layers = _layerinfo(_layer_data, self._layers_size)
|
||||||
|
@ -167,6 +170,8 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
def seek(self, layer: int) -> None:
|
def seek(self, layer: int) -> None:
|
||||||
if not self._seek_check(layer):
|
if not self._seek_check(layer):
|
||||||
return
|
return
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
|
|
||||||
# seek to given layer (1..max)
|
# seek to given layer (1..max)
|
||||||
_, mode, _, tile = self.layers[layer - 1]
|
_, mode, _, tile = self.layers[layer - 1]
|
||||||
|
|
|
@ -40,6 +40,7 @@ import sys
|
||||||
from typing import IO, TYPE_CHECKING, Any, cast
|
from typing import IO, TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
from ._util import DeferredError
|
||||||
|
|
||||||
|
|
||||||
def isInt(f: Any) -> int:
|
def isInt(f: Any) -> int:
|
||||||
|
@ -178,6 +179,8 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
raise EOFError(msg)
|
raise EOFError(msg)
|
||||||
if not self._seek_check(frame):
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
||||||
self.fp = self._fp
|
self.fp = self._fp
|
||||||
self.fp.seek(self.stkoffset)
|
self.fp.seek(self.stkoffset)
|
||||||
|
|
|
@ -58,7 +58,7 @@ from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._deprecate import deprecate
|
from ._deprecate import deprecate
|
||||||
from ._typing import StrOrBytesPath
|
from ._typing import StrOrBytesPath
|
||||||
from ._util import is_path
|
from ._util import DeferredError, is_path
|
||||||
from .TiffTags import TYPES
|
from .TiffTags import TYPES
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -1222,6 +1222,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self._im = None
|
self._im = None
|
||||||
|
|
||||||
def _seek(self, frame: int) -> None:
|
def _seek(self, frame: int) -> None:
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
self.fp = self._fp
|
self.fp = self._fp
|
||||||
|
|
||||||
while len(self._frame_pos) <= frame:
|
while len(self._frame_pos) <= frame:
|
||||||
|
|
|
@ -80,8 +80,6 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
format_description = "Windows Metafile"
|
format_description = "Windows Metafile"
|
||||||
|
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
self._inch = None
|
|
||||||
|
|
||||||
# check placable header
|
# check placable header
|
||||||
s = self.fp.read(80)
|
s = self.fp.read(80)
|
||||||
|
|
||||||
|
@ -89,10 +87,11 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
# placeable windows metafile
|
# placeable windows metafile
|
||||||
|
|
||||||
# get units per inch
|
# get units per inch
|
||||||
self._inch = word(s, 14)
|
inch = word(s, 14)
|
||||||
if self._inch == 0:
|
if inch == 0:
|
||||||
msg = "Invalid inch"
|
msg = "Invalid inch"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
self._inch: tuple[float, float] = inch, inch
|
||||||
|
|
||||||
# get bounding box
|
# get bounding box
|
||||||
x0 = short(s, 6)
|
x0 = short(s, 6)
|
||||||
|
@ -103,8 +102,8 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
# normalize size to 72 dots per inch
|
# normalize size to 72 dots per inch
|
||||||
self.info["dpi"] = 72
|
self.info["dpi"] = 72
|
||||||
size = (
|
size = (
|
||||||
(x1 - x0) * self.info["dpi"] // self._inch,
|
(x1 - x0) * self.info["dpi"] // inch,
|
||||||
(y1 - y0) * self.info["dpi"] // self._inch,
|
(y1 - y0) * self.info["dpi"] // inch,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.info["wmf_bbox"] = x0, y0, x1, y1
|
self.info["wmf_bbox"] = x0, y0, x1, y1
|
||||||
|
@ -138,6 +137,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
self.info["dpi"] = xdpi
|
self.info["dpi"] = xdpi
|
||||||
else:
|
else:
|
||||||
self.info["dpi"] = xdpi, ydpi
|
self.info["dpi"] = xdpi, ydpi
|
||||||
|
self._inch = xdpi, ydpi
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = "Unsupported file format"
|
msg = "Unsupported file format"
|
||||||
|
@ -153,13 +153,17 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
def _load(self) -> ImageFile.StubHandler | None:
|
def _load(self) -> ImageFile.StubHandler | None:
|
||||||
return _handler
|
return _handler
|
||||||
|
|
||||||
def load(self, dpi: int | None = None) -> Image.core.PixelAccess | None:
|
def load(
|
||||||
if dpi is not None and self._inch is not None:
|
self, dpi: float | tuple[float, float] | None = None
|
||||||
|
) -> Image.core.PixelAccess | None:
|
||||||
|
if dpi is not None:
|
||||||
self.info["dpi"] = dpi
|
self.info["dpi"] = dpi
|
||||||
x0, y0, x1, y1 = self.info["wmf_bbox"]
|
x0, y0, x1, y1 = self.info["wmf_bbox"]
|
||||||
|
if not isinstance(dpi, tuple):
|
||||||
|
dpi = dpi, dpi
|
||||||
self._size = (
|
self._size = (
|
||||||
(x1 - x0) * self.info["dpi"] // self._inch,
|
int((x1 - x0) * dpi[0] / self._inch[0]),
|
||||||
(y1 - y0) * self.info["dpi"] // self._inch,
|
int((y1 - y0) * dpi[1] / self._inch[1]),
|
||||||
)
|
)
|
||||||
return super().load()
|
return super().load()
|
||||||
|
|
||||||
|
|
|
@ -687,6 +687,14 @@ PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
#define GET32(p, o) ((DWORD *)(p + o))[0]
|
#define GET32(p, o) ((DWORD *)(p + o))[0]
|
||||||
|
|
||||||
|
static int CALLBACK
|
||||||
|
enhMetaFileProc(
|
||||||
|
HDC hdc, HANDLETABLE *lpht, const ENHMETARECORD *lpmr, int nHandles, LPARAM data
|
||||||
|
) {
|
||||||
|
PlayEnhMetaFileRecord(hdc, lpht, lpmr, nHandles);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyImaging_DrawWmf(PyObject *self, PyObject *args) {
|
PyImaging_DrawWmf(PyObject *self, PyObject *args) {
|
||||||
HBITMAP bitmap;
|
HBITMAP bitmap;
|
||||||
|
@ -767,10 +775,7 @@ PyImaging_DrawWmf(PyObject *self, PyObject *args) {
|
||||||
/* FIXME: make background transparent? configurable? */
|
/* FIXME: make background transparent? configurable? */
|
||||||
FillRect(dc, &rect, GetStockObject(WHITE_BRUSH));
|
FillRect(dc, &rect, GetStockObject(WHITE_BRUSH));
|
||||||
|
|
||||||
if (!PlayEnhMetaFile(dc, meta, &rect)) {
|
EnumEnhMetaFile(dc, meta, enhMetaFileProc, NULL, &rect);
|
||||||
PyErr_SetString(PyExc_OSError, "cannot render metafile");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* step 4: extract bits from bitmap */
|
/* step 4: extract bits from bitmap */
|
||||||
|
|
||||||
|
|
|
@ -501,55 +501,49 @@ polygon_generic(
|
||||||
// Needed to draw consistent polygons
|
// Needed to draw consistent polygons
|
||||||
xx[j] = xx[j - 1];
|
xx[j] = xx[j - 1];
|
||||||
j++;
|
j++;
|
||||||
} else if (current->dx != 0 && j % 2 == 1 &&
|
} else if ((ymin == current->ymin || ymin == current->ymax) &&
|
||||||
roundf(xx[j - 1]) == xx[j - 1]) {
|
current->dx != 0) {
|
||||||
// Connect discontiguous corners
|
// Connect discontiguous corners
|
||||||
for (k = 0; k < i; k++) {
|
for (k = 0; k < i; k++) {
|
||||||
Edge *other_edge = edge_table[k];
|
Edge *other_edge = edge_table[k];
|
||||||
if ((current->dx > 0 && other_edge->dx <= 0) ||
|
if ((ymin != other_edge->ymin && ymin != other_edge->ymax) ||
|
||||||
(current->dx < 0 && other_edge->dx >= 0)) {
|
other_edge->dx == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Check if the two edges join to make a corner
|
// Check if the two edges join to make a corner
|
||||||
if (xx[j - 1] ==
|
if (roundf(xx[j - 1]) ==
|
||||||
(ymin - other_edge->y0) * other_edge->dx + other_edge->x0) {
|
roundf(
|
||||||
|
(ymin - other_edge->y0) * other_edge->dx +
|
||||||
|
other_edge->x0
|
||||||
|
)) {
|
||||||
// Determine points from the edges on the next row
|
// Determine points from the edges on the next row
|
||||||
// Or if this is the last row, check the previous row
|
// Or if this is the last row, check the previous row
|
||||||
int offset = ymin == ymax ? -1 : 1;
|
int offset = ymin == current->ymax ? -1 : 1;
|
||||||
adjacent_line_x =
|
adjacent_line_x =
|
||||||
(ymin + offset - current->y0) * current->dx +
|
(ymin + offset - current->y0) * current->dx +
|
||||||
current->x0;
|
current->x0;
|
||||||
adjacent_line_x_other_edge =
|
if (ymin + offset >= other_edge->ymin &&
|
||||||
(ymin + offset - other_edge->y0) * other_edge->dx +
|
ymin + offset <= other_edge->ymax) {
|
||||||
other_edge->x0;
|
adjacent_line_x_other_edge =
|
||||||
if (ymin == current->ymax) {
|
(ymin + offset - other_edge->y0) * other_edge->dx +
|
||||||
if (current->dx > 0) {
|
other_edge->x0;
|
||||||
xx[k] =
|
if (xx[j - 1] > adjacent_line_x + 1 &&
|
||||||
fmax(
|
xx[j - 1] > adjacent_line_x_other_edge + 1) {
|
||||||
|
xx[j - 1] =
|
||||||
|
roundf(fmax(
|
||||||
adjacent_line_x, adjacent_line_x_other_edge
|
adjacent_line_x, adjacent_line_x_other_edge
|
||||||
) +
|
)) +
|
||||||
1;
|
1;
|
||||||
} else {
|
} else if (xx[j - 1] < adjacent_line_x - 1 &&
|
||||||
xx[k] =
|
xx[j - 1] < adjacent_line_x_other_edge - 1) {
|
||||||
fmin(
|
xx[j - 1] =
|
||||||
|
roundf(fmin(
|
||||||
adjacent_line_x, adjacent_line_x_other_edge
|
adjacent_line_x, adjacent_line_x_other_edge
|
||||||
) -
|
)) -
|
||||||
1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (current->dx > 0) {
|
|
||||||
xx[k] = fmin(
|
|
||||||
adjacent_line_x, adjacent_line_x_other_edge
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
xx[k] =
|
|
||||||
fmax(
|
|
||||||
adjacent_line_x, adjacent_line_x_other_edge
|
|
||||||
) +
|
|
||||||
1;
|
1;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,6 +299,7 @@ _decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img.orientation = ORIENTATION_TOPLEFT;
|
||||||
img.req_orientation = ORIENTATION_TOPLEFT;
|
img.req_orientation = ORIENTATION_TOPLEFT;
|
||||||
img.col_offset = 0;
|
img.col_offset = 0;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user