mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-12 08:14:45 +03:00
Merge branch 'main' into closed
This commit is contained in:
commit
a2290358d5
12
CHANGES.rst
12
CHANGES.rst
|
@ -5,6 +5,18 @@ Changelog (Pillow)
|
|||
9.5.0 (unreleased)
|
||||
------------------
|
||||
|
||||
- Close OleFileIO instance when closing or exiting FPX or MIC #7005
|
||||
[radarhere]
|
||||
|
||||
- Added __int__ to IFDRational for Python >= 3.11 #6998
|
||||
[radarhere]
|
||||
|
||||
- Added memoryview support to Dib.frombytes() #6988
|
||||
[radarhere, nulano]
|
||||
|
||||
- Close file pointer copy in the libtiff encoder if still open #6986
|
||||
[fcarron, radarhere]
|
||||
|
||||
- Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978
|
||||
[radarhere]
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
HAS_UPLOADER = False
|
||||
|
||||
if os.environ.get("SHOW_ERRORS", None):
|
||||
if os.environ.get("SHOW_ERRORS"):
|
||||
# local img.show for errors.
|
||||
HAS_UPLOADER = True
|
||||
|
||||
|
@ -271,7 +271,7 @@ def netpbm_available():
|
|||
|
||||
def magick_command():
|
||||
if sys.platform == "win32":
|
||||
magickhome = os.environ.get("MAGICK_HOME", "")
|
||||
magickhome = os.environ.get("MAGICK_HOME")
|
||||
if magickhome:
|
||||
imagemagick = [os.path.join(magickhome, "convert.exe")]
|
||||
graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"]
|
||||
|
|
BIN
Tests/images/hopper.qoi
Normal file
BIN
Tests/images/hopper.qoi
Normal file
Binary file not shown.
BIN
Tests/images/pil123rgba.qoi
Normal file
BIN
Tests/images/pil123rgba.qoi
Normal file
Binary file not shown.
|
@ -56,6 +56,7 @@ def test_handler(tmp_path):
|
|||
|
||||
def load(self, im):
|
||||
self.loaded = True
|
||||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
def save(self, im, fp, filename):
|
||||
|
|
|
@ -60,6 +60,7 @@ def test_stub_deprecated():
|
|||
|
||||
def load(self, im):
|
||||
self.loaded = True
|
||||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
handler = Handler()
|
||||
|
|
|
@ -18,6 +18,16 @@ def test_sanity():
|
|||
assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png")
|
||||
|
||||
|
||||
def test_close():
|
||||
with Image.open("Tests/images/input_bw_one_band.fpx") as im:
|
||||
pass
|
||||
assert im.ole.fp.closed
|
||||
|
||||
im = Image.open("Tests/images/input_bw_one_band.fpx")
|
||||
im.close()
|
||||
assert im.ole.fp.closed
|
||||
|
||||
|
||||
def test_invalid_file():
|
||||
# Test an invalid OLE file
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
|
|
|
@ -56,6 +56,7 @@ def test_handler(tmp_path):
|
|||
|
||||
def load(self, im):
|
||||
self.loaded = True
|
||||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
def save(self, im, fp, filename):
|
||||
|
|
|
@ -57,6 +57,7 @@ def test_handler(tmp_path):
|
|||
|
||||
def load(self, im):
|
||||
self.loaded = True
|
||||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
def save(self, im, fp, filename):
|
||||
|
|
|
@ -984,6 +984,36 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
) as im:
|
||||
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_name, mode, size, tile",
|
||||
[
|
||||
(
|
||||
"tiff_wrong_bits_per_sample.tiff",
|
||||
"RGBA",
|
||||
(52, 53),
|
||||
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
|
||||
),
|
||||
(
|
||||
"tiff_wrong_bits_per_sample_2.tiff",
|
||||
"RGB",
|
||||
(16, 16),
|
||||
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
|
||||
),
|
||||
(
|
||||
"tiff_wrong_bits_per_sample_3.tiff",
|
||||
"RGBA",
|
||||
(512, 256),
|
||||
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
|
||||
with Image.open("Tests/images/" + file_name) as im:
|
||||
assert im.mode == mode
|
||||
assert im.size == size
|
||||
assert im.tile == tile
|
||||
im.load()
|
||||
|
||||
def test_no_rows_per_strip(self):
|
||||
# This image does not have a RowsPerStrip TIFF tag
|
||||
infile = "Tests/images/no_rows_per_strip.tif"
|
||||
|
@ -1071,3 +1101,21 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
out = str(tmp_path / "temp.tif")
|
||||
for _ in range(10000):
|
||||
im.save(out, compression="jpeg")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path, sizes",
|
||||
(
|
||||
("Tests/images/hopper.tif", ()),
|
||||
("Tests/images/child_ifd.tiff", (16, 8)),
|
||||
("Tests/images/child_ifd_jpeg.tiff", (20,)),
|
||||
),
|
||||
)
|
||||
def test_get_child_images(self, path, sizes):
|
||||
with Image.open(path) as im:
|
||||
ims = im.get_child_images()
|
||||
|
||||
assert len(ims) == len(sizes)
|
||||
for i, im in enumerate(ims):
|
||||
w = sizes[i]
|
||||
expected = Image.new("RGB", (w, w), "#f00")
|
||||
assert_image_similar(im, expected, 1)
|
||||
|
|
|
@ -51,6 +51,16 @@ def test_seek():
|
|||
assert im.tell() == 0
|
||||
|
||||
|
||||
def test_close():
|
||||
with Image.open(TEST_FILE) as im:
|
||||
pass
|
||||
assert im.ole.fp.closed
|
||||
|
||||
im = Image.open(TEST_FILE)
|
||||
im.close()
|
||||
assert im.ole.fp.closed
|
||||
|
||||
|
||||
def test_invalid_file():
|
||||
# Test an invalid OLE file
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
|
|
|
@ -8,7 +8,7 @@ import pytest
|
|||
|
||||
from PIL import Image, PdfParser, features
|
||||
|
||||
from .helper import hopper, mark_if_feature_version
|
||||
from .helper import hopper, mark_if_feature_version, skip_unless_feature
|
||||
|
||||
|
||||
def helper_save_as_pdf(tmp_path, mode, **kwargs):
|
||||
|
@ -42,6 +42,11 @@ def test_save(tmp_path, mode):
|
|||
helper_save_as_pdf(tmp_path, mode)
|
||||
|
||||
|
||||
@skip_unless_feature("jpg_2000")
|
||||
def test_save_rgba(tmp_path):
|
||||
helper_save_as_pdf(tmp_path, "RGBA")
|
||||
|
||||
|
||||
def test_monochrome(tmp_path):
|
||||
# Arrange
|
||||
mode = "1"
|
||||
|
|
28
Tests/test_file_qoi.py
Normal file
28
Tests/test_file_qoi.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import pytest
|
||||
|
||||
from PIL import Image, QoiImagePlugin
|
||||
|
||||
from .helper import assert_image_equal_tofile, assert_image_similar_tofile
|
||||
|
||||
|
||||
def test_sanity():
|
||||
with Image.open("Tests/images/hopper.qoi") as im:
|
||||
assert im.mode == "RGB"
|
||||
assert im.size == (128, 128)
|
||||
assert im.format == "QOI"
|
||||
|
||||
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||
|
||||
with Image.open("Tests/images/pil123rgba.qoi") as im:
|
||||
assert im.mode == "RGBA"
|
||||
assert im.size == (162, 150)
|
||||
assert im.format == "QOI"
|
||||
|
||||
assert_image_similar_tofile(im, "Tests/images/pil123rgba.png", 0.03)
|
||||
|
||||
|
||||
def test_invalid_file():
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
|
||||
with pytest.raises(SyntaxError):
|
||||
QoiImagePlugin.QoiImageFile(invalid_file)
|
|
@ -84,24 +84,6 @@ class TestFileTiff:
|
|||
with Image.open("Tests/images/multipage.tiff") as im:
|
||||
im.load()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path, sizes",
|
||||
(
|
||||
("Tests/images/hopper.tif", ()),
|
||||
("Tests/images/child_ifd.tiff", (16, 8)),
|
||||
("Tests/images/child_ifd_jpeg.tiff", (20,)),
|
||||
),
|
||||
)
|
||||
def test_get_child_images(self, path, sizes):
|
||||
with Image.open(path) as im:
|
||||
ims = im.get_child_images()
|
||||
|
||||
assert len(ims) == len(sizes)
|
||||
for i, im in enumerate(ims):
|
||||
w = sizes[i]
|
||||
expected = Image.new("RGB", (w, w), "#f00")
|
||||
assert_image_similar(im, expected, 1)
|
||||
|
||||
def test_mac_tiff(self):
|
||||
# Read RGBa images from macOS [@PIL136]
|
||||
|
||||
|
@ -118,36 +100,6 @@ class TestFileTiff:
|
|||
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
||||
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_name,mode,size,tile",
|
||||
[
|
||||
(
|
||||
"tiff_wrong_bits_per_sample.tiff",
|
||||
"RGBA",
|
||||
(52, 53),
|
||||
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
|
||||
),
|
||||
(
|
||||
"tiff_wrong_bits_per_sample_2.tiff",
|
||||
"RGB",
|
||||
(16, 16),
|
||||
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
|
||||
),
|
||||
(
|
||||
"tiff_wrong_bits_per_sample_3.tiff",
|
||||
"RGBA",
|
||||
(512, 256),
|
||||
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
|
||||
with Image.open("Tests/images/" + file_name) as im:
|
||||
assert im.mode == mode
|
||||
assert im.size == size
|
||||
assert im.tile == tile
|
||||
im.load()
|
||||
|
||||
def test_set_legacy_api(self):
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
with pytest.raises(Exception) as e:
|
||||
|
|
|
@ -254,17 +254,6 @@ def test_p2pa_palette():
|
|||
assert im_pa.getpalette() == im.getpalette()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
||||
def test_rgb_lab(mode):
|
||||
im = Image.new(mode, (1, 1))
|
||||
converted_im = im.convert("LAB")
|
||||
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
|
||||
|
||||
im = Image.new("LAB", (1, 1), (255, 0, 0))
|
||||
converted_im = im.convert(mode)
|
||||
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
|
||||
|
||||
|
||||
def test_matrix_illegal_conversion():
|
||||
# Arrange
|
||||
im = hopper("CMYK")
|
||||
|
|
|
@ -625,3 +625,14 @@ def test_constants_deprecation():
|
|||
for name in enum.__members__:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert getattr(ImageCms, prefix + name) == enum[name]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
||||
def test_rgb_lab(mode):
|
||||
im = Image.new(mode, (1, 1))
|
||||
converted_im = im.convert("LAB")
|
||||
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
|
||||
|
||||
im = Image.new("LAB", (1, 1), (255, 0, 0))
|
||||
converted_im = im.convert(mode)
|
||||
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
|
||||
|
|
|
@ -55,7 +55,7 @@ def test_show_without_viewers():
|
|||
viewers = ImageShow._viewers
|
||||
ImageShow._viewers = []
|
||||
|
||||
im = hopper()
|
||||
with hopper() as im:
|
||||
assert not ImageShow.show(im)
|
||||
|
||||
ImageShow._viewers = viewers
|
||||
|
|
|
@ -100,6 +100,9 @@ class TestImageWinDib:
|
|||
# Act
|
||||
# Make one the same as the using tobytes()/frombytes()
|
||||
test_buffer = dib1.tobytes()
|
||||
for datatype in ("bytes", "memoryview"):
|
||||
if datatype == "memoryview":
|
||||
test_buffer = memoryview(test_buffer)
|
||||
dib2.frombytes(test_buffer)
|
||||
|
||||
# Assert
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
# install libimagequant
|
||||
|
||||
archive=libimagequant-4.1.0
|
||||
archive=libimagequant-4.1.1
|
||||
|
||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||
|
||||
|
|
|
@ -1457,8 +1457,13 @@ PDF
|
|||
^^^
|
||||
|
||||
Pillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4
|
||||
files, using either JPEG or HEX encoding depending on the image mode (and
|
||||
whether JPEG support is available or not).
|
||||
files. Different encoding methods are used, depending on the image mode.
|
||||
|
||||
* 1 mode images are saved using TIFF encoding, or JPEG encoding if libtiff support is
|
||||
unavailable
|
||||
* L, RGB and CMYK mode images use JPEG encoding
|
||||
* P mode images use HEX encoding
|
||||
* RGBA mode images use JPEG2000 encoding
|
||||
|
||||
.. _pdf-saving:
|
||||
|
||||
|
@ -1544,6 +1549,13 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
|
|||
|
||||
.. versionadded:: 5.3.0
|
||||
|
||||
QOI
|
||||
^^^
|
||||
|
||||
.. versionadded:: 9.5.0
|
||||
|
||||
Pillow identifies and reads images in Quite OK Image format.
|
||||
|
||||
XV Thumbnails
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
* **libimagequant** provides improved color quantization
|
||||
|
||||
* Pillow has been tested with libimagequant **2.6-4.1**
|
||||
* Pillow has been tested with libimagequant **2.6-4.1.1**
|
||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||
the Pillow license, therefore we will not be distributing binaries
|
||||
with libimagequant support enabled.
|
||||
|
|
|
@ -28,6 +28,11 @@ TODO
|
|||
API Additions
|
||||
=============
|
||||
|
||||
QOI file format
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Pillow can now read images in Quite OK Image format.
|
||||
|
||||
Added ``closed`` property to images
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -61,7 +66,7 @@ TODO
|
|||
Other Changes
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
Added support for saving PDFs in RGBA mode
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
Using the JPXDecode filter, PDFs can now be saved in RGBA mode.
|
||||
|
|
8
setup.py
8
setup.py
|
@ -242,7 +242,9 @@ def _find_include_dir(self, dirname, include):
|
|||
return subdir
|
||||
|
||||
|
||||
def _cmd_exists(cmd):
|
||||
def _cmd_exists(cmd: str) -> bool:
|
||||
if "PATH" not in os.environ:
|
||||
return False
|
||||
return any(
|
||||
os.access(os.path.join(path, cmd), os.X_OK)
|
||||
for path in os.environ["PATH"].split(os.pathsep)
|
||||
|
@ -570,9 +572,7 @@ class pil_build_ext(build_ext):
|
|||
):
|
||||
for dirname in _find_library_dirs_ldconfig():
|
||||
_add_directory(library_dirs, dirname)
|
||||
if sys.platform.startswith("linux") and os.environ.get(
|
||||
"ANDROID_ROOT", None
|
||||
):
|
||||
if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"):
|
||||
# termux support for android.
|
||||
# system libraries (zlib) are installed in /system/lib
|
||||
# headers are at $PREFIX/include
|
||||
|
|
|
@ -235,6 +235,14 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
|
||||
return ImageFile.ImageFile.load(self)
|
||||
|
||||
def close(self):
|
||||
self.ole.close()
|
||||
super().close()
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.ole.close()
|
||||
super().__exit__()
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -1012,7 +1012,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
|||
if windir:
|
||||
dirs.append(os.path.join(windir, "fonts"))
|
||||
elif sys.platform in ("linux", "linux2"):
|
||||
lindirs = os.environ.get("XDG_DATA_DIRS", "")
|
||||
lindirs = os.environ.get("XDG_DATA_DIRS")
|
||||
if not lindirs:
|
||||
# According to the freedesktop spec, XDG_DATA_DIRS should
|
||||
# default to /usr/share
|
||||
|
|
|
@ -89,6 +89,14 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
def tell(self):
|
||||
return self.frame
|
||||
|
||||
def close(self):
|
||||
self.ole.close()
|
||||
super().close()
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.ole.close()
|
||||
super().__exit__()
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -173,6 +173,10 @@ def _save(im, fp, filename, save_all=False):
|
|||
filter = "DCTDecode"
|
||||
colorspace = PdfParser.PdfName("DeviceRGB")
|
||||
procset = "ImageC" # color images
|
||||
elif im.mode == "RGBA":
|
||||
filter = "JPXDecode"
|
||||
colorspace = PdfParser.PdfName("DeviceRGB")
|
||||
procset = "ImageC" # color images
|
||||
elif im.mode == "CMYK":
|
||||
filter = "DCTDecode"
|
||||
colorspace = PdfParser.PdfName("DeviceCMYK")
|
||||
|
@ -199,6 +203,8 @@ def _save(im, fp, filename, save_all=False):
|
|||
)
|
||||
elif filter == "DCTDecode":
|
||||
Image.SAVE["JPEG"](im, op, filename)
|
||||
elif filter == "JPXDecode":
|
||||
Image.SAVE["JPEG2000"](im, op, filename)
|
||||
elif filter == "FlateDecode":
|
||||
ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)])
|
||||
elif filter == "RunLengthDecode":
|
||||
|
|
105
src/PIL/QoiImagePlugin.py
Normal file
105
src/PIL/QoiImagePlugin.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
#
|
||||
# QOI support for PIL
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32be as i32
|
||||
from ._binary import o8
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == b"qoif"
|
||||
|
||||
|
||||
class QoiImageFile(ImageFile.ImageFile):
|
||||
format = "QOI"
|
||||
format_description = "Quite OK Image"
|
||||
|
||||
def _open(self):
|
||||
if not _accept(self.fp.read(4)):
|
||||
msg = "not a QOI file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
self._size = tuple(i32(self.fp.read(4)) for i in range(2))
|
||||
|
||||
channels = self.fp.read(1)[0]
|
||||
self.mode = "RGB" if channels == 3 else "RGBA"
|
||||
|
||||
self.fp.seek(1, os.SEEK_CUR) # colorspace
|
||||
self.tile = [("qoi", (0, 0) + self._size, self.fp.tell(), None)]
|
||||
|
||||
|
||||
class QoiDecoder(ImageFile.PyDecoder):
|
||||
_pulls_fd = True
|
||||
|
||||
def _add_to_previous_pixels(self, value):
|
||||
self._previous_pixel = value
|
||||
|
||||
r, g, b, a = value
|
||||
hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64
|
||||
self._previously_seen_pixels[hash_value] = value
|
||||
|
||||
def decode(self, buffer):
|
||||
self._previously_seen_pixels = {}
|
||||
self._previous_pixel = None
|
||||
self._add_to_previous_pixels(b"".join(o8(i) for i in (0, 0, 0, 255)))
|
||||
|
||||
data = bytearray()
|
||||
bands = Image.getmodebands(self.mode)
|
||||
while len(data) < self.state.xsize * self.state.ysize * bands:
|
||||
byte = self.fd.read(1)[0]
|
||||
if byte == 0b11111110: # QOI_OP_RGB
|
||||
value = self.fd.read(3) + o8(255)
|
||||
elif byte == 0b11111111: # QOI_OP_RGBA
|
||||
value = self.fd.read(4)
|
||||
else:
|
||||
op = byte >> 6
|
||||
if op == 0: # QOI_OP_INDEX
|
||||
op_index = byte & 0b00111111
|
||||
value = self._previously_seen_pixels.get(op_index, (0, 0, 0, 0))
|
||||
elif op == 1: # QOI_OP_DIFF
|
||||
value = (
|
||||
(self._previous_pixel[0] + ((byte & 0b00110000) >> 4) - 2)
|
||||
% 256,
|
||||
(self._previous_pixel[1] + ((byte & 0b00001100) >> 2) - 2)
|
||||
% 256,
|
||||
(self._previous_pixel[2] + (byte & 0b00000011) - 2) % 256,
|
||||
)
|
||||
value += (self._previous_pixel[3],)
|
||||
elif op == 2: # QOI_OP_LUMA
|
||||
second_byte = self.fd.read(1)[0]
|
||||
diff_green = (byte & 0b00111111) - 32
|
||||
diff_red = ((second_byte & 0b11110000) >> 4) - 8
|
||||
diff_blue = (second_byte & 0b00001111) - 8
|
||||
|
||||
value = tuple(
|
||||
(self._previous_pixel[i] + diff_green + diff) % 256
|
||||
for i, diff in enumerate((diff_red, 0, diff_blue))
|
||||
)
|
||||
value += (self._previous_pixel[3],)
|
||||
elif op == 3: # QOI_OP_RUN
|
||||
run_length = (byte & 0b00111111) + 1
|
||||
value = self._previous_pixel
|
||||
if bands == 3:
|
||||
value = value[:3]
|
||||
data += value * run_length
|
||||
continue
|
||||
value = b"".join(o8(i) for i in value)
|
||||
self._add_to_previous_pixels(value)
|
||||
|
||||
if bands == 3:
|
||||
value = value[:3]
|
||||
data += value
|
||||
self.set_as_raw(bytes(data))
|
||||
return -1, 0
|
||||
|
||||
|
||||
Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
|
||||
Image.register_decoder("qoi", QoiDecoder)
|
||||
Image.register_extension(QoiImageFile.format, ".qoi")
|
|
@ -425,6 +425,9 @@ class IFDRational(Rational):
|
|||
__ceil__ = _delegate("__ceil__")
|
||||
__floor__ = _delegate("__floor__")
|
||||
__round__ = _delegate("__round__")
|
||||
# Python >= 3.11
|
||||
if hasattr(Fraction, "__int__"):
|
||||
__int__ = _delegate("__int__")
|
||||
|
||||
|
||||
class ImageFileDirectory_v2(MutableMapping):
|
||||
|
|
|
@ -59,6 +59,7 @@ _plugins = [
|
|||
"PngImagePlugin",
|
||||
"PpmImagePlugin",
|
||||
"PsdImagePlugin",
|
||||
"QoiImagePlugin",
|
||||
"SgiImagePlugin",
|
||||
"SpiderImagePlugin",
|
||||
"SunImagePlugin",
|
||||
|
|
14
src/_webp.c
14
src/_webp.c
|
@ -955,6 +955,13 @@ addTransparencyFlagToModule(PyObject *m) {
|
|||
|
||||
static int
|
||||
setup_module(PyObject *m) {
|
||||
#ifdef HAVE_WEBPANIM
|
||||
/* Ready object types */
|
||||
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
|
||||
PyType_Ready(&WebPAnimEncoder_Type) < 0) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
PyObject *d = PyModule_GetDict(m);
|
||||
addMuxFlagToModule(m);
|
||||
addAnimFlagToModule(m);
|
||||
|
@ -963,13 +970,6 @@ setup_module(PyObject *m) {
|
|||
PyDict_SetItemString(
|
||||
d, "webpdecoder_version", PyUnicode_FromString(WebPDecoderVersion_str()));
|
||||
|
||||
#ifdef HAVE_WEBPANIM
|
||||
/* Ready object types */
|
||||
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
|
||||
PyType_Ready(&WebPAnimEncoder_Type) < 0) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -195,20 +195,21 @@ _releasedc(ImagingDisplayObject *display, PyObject *args) {
|
|||
|
||||
static PyObject *
|
||||
_frombytes(ImagingDisplayObject *display, PyObject *args) {
|
||||
char *ptr;
|
||||
Py_ssize_t bytes;
|
||||
Py_buffer buffer;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "y#:frombytes", &ptr, &bytes)) {
|
||||
if (!PyArg_ParseTuple(args, "y*:frombytes", &buffer)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (display->dib->ysize * display->dib->linesize != bytes) {
|
||||
if (display->dib->ysize * display->dib->linesize != buffer.len) {
|
||||
PyBuffer_Release(&buffer);
|
||||
PyErr_SetString(PyExc_ValueError, "wrong size");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(display->dib->bits, ptr, bytes);
|
||||
memcpy(display->dib->bits, buffer.buf, buffer.len);
|
||||
|
||||
PyBuffer_Release(&buffer);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
|
|
@ -487,6 +487,10 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
|
|||
goto quick_exit;
|
||||
}
|
||||
|
||||
if (strcmp(im->mode, "RGBA") == 0) {
|
||||
image->comps[3].alpha = 1;
|
||||
}
|
||||
|
||||
opj_set_error_handler(codec, j2k_error, context);
|
||||
opj_set_info_handler(codec, j2k_warn, context);
|
||||
opj_set_warning_handler(codec, j2k_warn, context);
|
||||
|
|
Loading…
Reference in New Issue
Block a user