Merge branch 'main' into deprecate-getsize
30
CHANGES.rst
|
@ -5,6 +5,36 @@ Changelog (Pillow)
|
||||||
9.2.0 (unreleased)
|
9.2.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Fixed null check for fribidi_version_info in FriBiDi shim #6376
|
||||||
|
[nulano]
|
||||||
|
|
||||||
|
- Added GIF decompression bomb check #6402
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Handle PCF fonts files with less than 256 characters #6386
|
||||||
|
[dawidcrivelli, radarhere]
|
||||||
|
|
||||||
|
- Improved GIF optimize condition #6378
|
||||||
|
[raygard, radarhere]
|
||||||
|
|
||||||
|
- Reverted to __array_interface__ with the release of NumPy 1.23 #6394
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Pad PCX palette to 768 bytes when saving #6391
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed bug with rounding pixels to palette colors #6377
|
||||||
|
[btrekkie, radarhere]
|
||||||
|
|
||||||
|
- Use gnome-screenshot on Linux if available #6361
|
||||||
|
[radarhere, nulano]
|
||||||
|
|
||||||
|
- Fixed loading L mode BMP RLE8 images #6384
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed incorrect operator in ImageCms error #6370
|
||||||
|
[LostBenjamin, hugovk, radarhere]
|
||||||
|
|
||||||
- Limit FPX tile size to avoid extending outside image #6368
|
- Limit FPX tile size to avoid extending outside image #6368
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
|
BIN
Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf
Normal file
BIN
Tests/images/decompression_bomb_extents.gif
Normal file
After Width: | Height: | Size: 368 B |
BIN
Tests/images/hopper_rle8_greyscale.bmp
Normal file
After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
@ -61,6 +61,11 @@ class TestDecompressionBomb:
|
||||||
with Image.open("Tests/images/decompression_bomb.gif"):
|
with Image.open("Tests/images/decompression_bomb.gif"):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_exception_gif_extents(self):
|
||||||
|
with Image.open("Tests/images/decompression_bomb_extents.gif") as im:
|
||||||
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
|
im.seek(1)
|
||||||
|
|
||||||
def test_exception_bmp(self):
|
def test_exception_bmp(self):
|
||||||
with pytest.raises(Image.DecompressionBombError):
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
with Image.open("Tests/images/bmp/b/reallybig.bmp"):
|
with Image.open("Tests/images/bmp/b/reallybig.bmp"):
|
||||||
|
|
|
@ -134,6 +134,9 @@ def test_rle8():
|
||||||
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
||||||
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/hopper_rle8_greyscale.bmp") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
|
||||||
|
|
||||||
# This test image has been manually hexedited
|
# This test image has been manually hexedited
|
||||||
# to have rows with too much data
|
# to have rows with too much data
|
||||||
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:
|
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:
|
||||||
|
|
|
@ -158,6 +158,9 @@ def test_optimize_correctness():
|
||||||
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
||||||
|
|
||||||
# These do optimize the palette
|
# These do optimize the palette
|
||||||
|
check(256, 511, 256)
|
||||||
|
check(255, 511, 255)
|
||||||
|
check(129, 511, 129)
|
||||||
check(128, 511, 128)
|
check(128, 511, 128)
|
||||||
check(64, 511, 64)
|
check(64, 511, 64)
|
||||||
check(4, 511, 4)
|
check(4, 511, 4)
|
||||||
|
@ -167,11 +170,6 @@ def test_optimize_correctness():
|
||||||
check(64, 513, 256)
|
check(64, 513, 256)
|
||||||
check(4, 513, 256)
|
check(4, 513, 256)
|
||||||
|
|
||||||
# Other limits that don't optimize the palette
|
|
||||||
check(129, 511, 256)
|
|
||||||
check(255, 511, 256)
|
|
||||||
check(256, 511, 256)
|
|
||||||
|
|
||||||
|
|
||||||
def test_optimize_full_l():
|
def test_optimize_full_l():
|
||||||
im = Image.frombytes("L", (16, 16), bytes(range(256)))
|
im = Image.frombytes("L", (16, 16), bytes(range(256)))
|
||||||
|
@ -180,6 +178,19 @@ def test_optimize_full_l():
|
||||||
assert im.mode == "L"
|
assert im.mode == "L"
|
||||||
|
|
||||||
|
|
||||||
|
def test_optimize_if_palette_can_be_reduced_by_half():
|
||||||
|
with Image.open("Tests/images/test.colors.gif") as im:
|
||||||
|
# Reduce dimensions because original is too big for _get_optimize()
|
||||||
|
im = im.resize((591, 443))
|
||||||
|
im_rgb = im.convert("RGB")
|
||||||
|
|
||||||
|
for (optimize, colors) in ((False, 256), (True, 8)):
|
||||||
|
out = BytesIO()
|
||||||
|
im_rgb.save(out, "GIF", optimize=optimize)
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert len(reloaded.palette.palette) // 3 == colors
|
||||||
|
|
||||||
|
|
||||||
def test_roundtrip(tmp_path):
|
def test_roundtrip(tmp_path):
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
@ -982,8 +993,8 @@ def test_append_images(tmp_path):
|
||||||
def test_transparent_optimize(tmp_path):
|
def test_transparent_optimize(tmp_path):
|
||||||
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
|
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
|
||||||
# transparency.
|
# transparency.
|
||||||
# Need a palette that isn't using the 0 color, and one that's > 128 items where the
|
# Need a palette that isn't using the 0 color,
|
||||||
# transparent color is actually the top palette entry to trigger the bug.
|
# where the transparent color is actually the top palette entry to trigger the bug.
|
||||||
|
|
||||||
data = bytes(range(1, 254))
|
data = bytes(range(1, 254))
|
||||||
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
||||||
|
@ -993,10 +1004,10 @@ def test_transparent_optimize(tmp_path):
|
||||||
im.putpalette(palette)
|
im.putpalette(palette)
|
||||||
|
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
im.save(out, transparency=253)
|
im.save(out, transparency=im.getpixel((252, 0)))
|
||||||
with Image.open(out) as reloaded:
|
|
||||||
|
|
||||||
assert reloaded.info["transparency"] == 253
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
|
||||||
|
|
||||||
|
|
||||||
def test_rgb_transparency(tmp_path):
|
def test_rgb_transparency(tmp_path):
|
||||||
|
|
|
@ -20,6 +20,11 @@ def test_sanity(tmp_path):
|
||||||
for mode in ("1", "L", "P", "RGB"):
|
for mode in ("1", "L", "P", "RGB"):
|
||||||
_roundtrip(tmp_path, hopper(mode))
|
_roundtrip(tmp_path, hopper(mode))
|
||||||
|
|
||||||
|
# Test a palette with less than 256 colors
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
im.putpalette((255, 0, 0))
|
||||||
|
_roundtrip(tmp_path, im)
|
||||||
|
|
||||||
# Test an unsupported mode
|
# Test an unsupported mode
|
||||||
f = str(tmp_path / "temp.pcx")
|
f = str(tmp_path / "temp.pcx")
|
||||||
im = hopper("RGBA")
|
im = hopper("RGBA")
|
||||||
|
|
|
@ -49,6 +49,14 @@ def test_sanity(request, tmp_path):
|
||||||
save_font(request, tmp_path)
|
save_font(request, tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_less_than_256_characters():
|
||||||
|
with open("Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf", "rb") as test_file:
|
||||||
|
font = PcfFontFile.PcfFontFile(test_file)
|
||||||
|
assert isinstance(font, FontFile.FontFile)
|
||||||
|
# check the number of characters in the font
|
||||||
|
assert len([_f for _f in font.glyph if _f]) == 127
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
with open("Tests/images/flower.jpg", "rb") as fp:
|
with open("Tests/images/flower.jpg", "rb") as fp:
|
||||||
with pytest.raises(SyntaxError):
|
with pytest.raises(SyntaxError):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
from packaging.version import parse as parse_version
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ def test_toarray():
|
||||||
test_with_dtype(numpy.float64)
|
test_with_dtype(numpy.float64)
|
||||||
test_with_dtype(numpy.uint8)
|
test_with_dtype(numpy.uint8)
|
||||||
|
|
||||||
|
if parse_version(numpy.__version__) >= parse_version("1.23"):
|
||||||
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
|
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
numpy.array(im_truncated)
|
numpy.array(im_truncated)
|
||||||
|
|
|
@ -9,7 +9,7 @@ def test_entropy():
|
||||||
assert round(abs(entropy("L") - 7.063008716585465), 7) == 0
|
assert round(abs(entropy("L") - 7.063008716585465), 7) == 0
|
||||||
assert round(abs(entropy("I") - 7.063008716585465), 7) == 0
|
assert round(abs(entropy("I") - 7.063008716585465), 7) == 0
|
||||||
assert round(abs(entropy("F") - 7.063008716585465), 7) == 0
|
assert round(abs(entropy("F") - 7.063008716585465), 7) == 0
|
||||||
assert round(abs(entropy("P") - 5.0530452472519745), 7) == 0
|
assert round(abs(entropy("P") - 5.082506854662517), 7) == 0
|
||||||
assert round(abs(entropy("RGB") - 8.821286587714319), 7) == 0
|
assert round(abs(entropy("RGB") - 8.821286587714319), 7) == 0
|
||||||
assert round(abs(entropy("RGBA") - 7.42724306524488), 7) == 0
|
assert round(abs(entropy("RGBA") - 7.42724306524488), 7) == 0
|
||||||
assert round(abs(entropy("CMYK") - 7.4272430652448795), 7) == 0
|
assert round(abs(entropy("CMYK") - 7.4272430652448795), 7) == 0
|
||||||
|
|
|
@ -16,7 +16,7 @@ def test_getcolors():
|
||||||
assert getcolors("L") == 255
|
assert getcolors("L") == 255
|
||||||
assert getcolors("I") == 255
|
assert getcolors("I") == 255
|
||||||
assert getcolors("F") == 255
|
assert getcolors("F") == 255
|
||||||
assert getcolors("P") == 90 # fixed palette
|
assert getcolors("P") == 96 # fixed palette
|
||||||
assert getcolors("RGB") is None
|
assert getcolors("RGB") is None
|
||||||
assert getcolors("RGBA") is None
|
assert getcolors("RGBA") is None
|
||||||
assert getcolors("CMYK") is None
|
assert getcolors("CMYK") is None
|
||||||
|
|
|
@ -10,7 +10,7 @@ def test_histogram():
|
||||||
assert histogram("L") == (256, 0, 662)
|
assert histogram("L") == (256, 0, 662)
|
||||||
assert histogram("I") == (256, 0, 662)
|
assert histogram("I") == (256, 0, 662)
|
||||||
assert histogram("F") == (256, 0, 662)
|
assert histogram("F") == (256, 0, 662)
|
||||||
assert histogram("P") == (256, 0, 1871)
|
assert histogram("P") == (256, 0, 1551)
|
||||||
assert histogram("RGB") == (768, 4, 675)
|
assert histogram("RGB") == (768, 4, 675)
|
||||||
assert histogram("RGBA") == (1024, 0, 16384)
|
assert histogram("RGBA") == (1024, 0, 16384)
|
||||||
assert histogram("CMYK") == (1024, 0, 16384)
|
assert histogram("CMYK") == (1024, 0, 16384)
|
||||||
|
|
|
@ -65,6 +65,22 @@ def test_quantize_no_dither():
|
||||||
assert converted.palette.palette == palette.palette.palette
|
assert converted.palette.palette == palette.palette.palette
|
||||||
|
|
||||||
|
|
||||||
|
def test_quantize_no_dither2():
|
||||||
|
im = Image.new("RGB", (9, 1))
|
||||||
|
im.putdata(list((p,) * 3 for p in range(0, 36, 4)))
|
||||||
|
|
||||||
|
palette = Image.new("P", (1, 1))
|
||||||
|
data = (0, 0, 0, 32, 32, 32)
|
||||||
|
palette.putpalette(data)
|
||||||
|
quantized = im.quantize(dither=Image.Dither.NONE, palette=palette)
|
||||||
|
|
||||||
|
assert tuple(quantized.palette.palette) == data
|
||||||
|
|
||||||
|
px = quantized.load()
|
||||||
|
for x in range(9):
|
||||||
|
assert px[x, 0] == (0 if x < 5 else 1)
|
||||||
|
|
||||||
|
|
||||||
def test_quantize_dither_diff():
|
def test_quantize_dither_diff():
|
||||||
image = hopper()
|
image = hopper()
|
||||||
with Image.open("Tests/images/caption_6_33_22.png") as palette:
|
with Image.open("Tests/images/caption_6_33_22.png") as palette:
|
||||||
|
|
|
@ -94,4 +94,3 @@ Indices and tables
|
||||||
|
|
||||||
* :ref:`genindex`
|
* :ref:`genindex`
|
||||||
* :ref:`modindex`
|
* :ref:`modindex`
|
||||||
* :ref:`search`
|
|
||||||
|
|
|
@ -15,7 +15,10 @@ or the clipboard to a PIL image memory.
|
||||||
returned as an "RGBA" on macOS, or an "RGB" image otherwise.
|
returned as an "RGBA" on macOS, or an "RGB" image otherwise.
|
||||||
If the bounding box is omitted, the entire screen is copied.
|
If the bounding box is omitted, the entire screen is copied.
|
||||||
|
|
||||||
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux (X11))
|
On Linux, if ``xdisplay`` is ``None`` then ``gnome-screenshot`` will be used if it
|
||||||
|
is installed. To capture the default X11 display instead, pass ``xdisplay=""``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)
|
||||||
|
|
||||||
:param bbox: What region to copy. Default is the entire screen.
|
:param bbox: What region to copy. Default is the entire screen.
|
||||||
Note that on Windows OS, the top-left point may be negative if ``all_screens=True`` is used.
|
Note that on Windows OS, the top-left point may be negative if ``all_screens=True`` is used.
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
9.2.0
|
9.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Backwards Incompatible Changes
|
|
||||||
==============================
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -46,14 +40,6 @@ Image.coerce_e
|
||||||
This undocumented method has been deprecated and will be removed in Pillow 10
|
This undocumented method has been deprecated and will be removed in Pillow 10
|
||||||
(2023-07-01).
|
(2023-07-01).
|
||||||
|
|
||||||
API Changes
|
|
||||||
===========
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -68,15 +54,14 @@ The image's palette mode will become "RGBA", and "transparency" will be removed
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
TODO
|
An additional decompression bomb check has been added for the GIF format.
|
||||||
^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
TODO
|
Using gnome-screenshot on Linux
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
In :py:meth:`~PIL.ImageGrab.grab` on Linux, if ``xdisplay`` is ``None`` then
|
||||||
|
``gnome-screenshot`` will be used to capture the display if it is installed. To capture
|
||||||
|
the default X11 display instead, pass ``xdisplay=""``.
|
||||||
|
|
|
@ -321,7 +321,8 @@ class BmpRleDecoder(ImageFile.PyDecoder):
|
||||||
# align to 16-bit word boundary
|
# align to 16-bit word boundary
|
||||||
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)
|
||||||
self.set_as_raw(bytes(data), ("P", 0, self.args[-1]))
|
rawmode = "L" if self.mode == "L" else "P"
|
||||||
|
self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1]))
|
||||||
return -1, 0
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
|
x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
|
||||||
if (x1 > self.size[0] or y1 > self.size[1]) and update_image:
|
if (x1 > self.size[0] or y1 > self.size[1]) and update_image:
|
||||||
self._size = max(x1, self.size[0]), max(y1, self.size[1])
|
self._size = max(x1, self.size[0]), max(y1, self.size[1])
|
||||||
|
Image._decompression_bomb_check(self._size)
|
||||||
frame_dispose_extent = x0, y0, x1, y1
|
frame_dispose_extent = x0, y0, x1, y1
|
||||||
flags = s[8]
|
flags = s[8]
|
||||||
|
|
||||||
|
@ -824,9 +825,18 @@ def _get_optimize(im, info):
|
||||||
if count:
|
if count:
|
||||||
used_palette_colors.append(i)
|
used_palette_colors.append(i)
|
||||||
|
|
||||||
if optimise or (
|
if optimise or max(used_palette_colors) >= len(used_palette_colors):
|
||||||
len(used_palette_colors) <= 128
|
return used_palette_colors
|
||||||
and max(used_palette_colors) > len(used_palette_colors)
|
|
||||||
|
num_palette_colors = len(im.palette.palette) // Image.getmodebands(
|
||||||
|
im.palette.mode
|
||||||
|
)
|
||||||
|
current_palette_size = 1 << (num_palette_colors - 1).bit_length()
|
||||||
|
if (
|
||||||
|
# check that the palette would become smaller when saved
|
||||||
|
len(used_palette_colors) <= current_palette_size // 2
|
||||||
|
# check that the palette is not already the smallest possible size
|
||||||
|
and current_palette_size > 2
|
||||||
):
|
):
|
||||||
return used_palette_colors
|
return used_palette_colors
|
||||||
|
|
||||||
|
|
|
@ -671,14 +671,9 @@ class Image:
|
||||||
raise ValueError("Could not save to PNG for display") from e
|
raise ValueError("Could not save to PNG for display") from e
|
||||||
return b.getvalue()
|
return b.getvalue()
|
||||||
|
|
||||||
class _ArrayData:
|
@property
|
||||||
def __init__(self, new):
|
def __array_interface__(self):
|
||||||
self.__array_interface__ = new
|
|
||||||
|
|
||||||
def __array__(self, dtype=None):
|
|
||||||
# numpy array interface support
|
# numpy array interface support
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
new = {}
|
new = {}
|
||||||
shape, typestr = _conv_type_shape(self)
|
shape, typestr = _conv_type_shape(self)
|
||||||
new["shape"] = shape
|
new["shape"] = shape
|
||||||
|
@ -690,8 +685,7 @@ class Image:
|
||||||
new["data"] = self.tobytes("raw", "L")
|
new["data"] = self.tobytes("raw", "L")
|
||||||
else:
|
else:
|
||||||
new["data"] = self.tobytes()
|
new["data"] = self.tobytes()
|
||||||
|
return new
|
||||||
return np.array(self._ArrayData(new), dtype)
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()]
|
return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()]
|
||||||
|
|
|
@ -15,15 +15,14 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
|
|
||||||
if sys.platform == "darwin":
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
|
|
||||||
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
|
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
|
||||||
if xdisplay is None:
|
if xdisplay is None:
|
||||||
|
@ -62,6 +61,18 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
|
||||||
left, top, right, bottom = bbox
|
left, top, right, bottom = bbox
|
||||||
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
||||||
return im
|
return im
|
||||||
|
elif shutil.which("gnome-screenshot"):
|
||||||
|
fh, filepath = tempfile.mkstemp(".png")
|
||||||
|
os.close(fh)
|
||||||
|
subprocess.call(["gnome-screenshot", "-f", filepath])
|
||||||
|
im = Image.open(filepath)
|
||||||
|
im.load()
|
||||||
|
os.unlink(filepath)
|
||||||
|
if bbox:
|
||||||
|
im_cropped = im.crop(bbox)
|
||||||
|
im.close()
|
||||||
|
return im_cropped
|
||||||
|
return im
|
||||||
# use xdisplay=None for default display on non-win32/macOS systems
|
# use xdisplay=None for default display on non-win32/macOS systems
|
||||||
if not Image.core.HAVE_XCB:
|
if not Image.core.HAVE_XCB:
|
||||||
raise OSError("Pillow was built without XCB support")
|
raise OSError("Pillow was built without XCB support")
|
||||||
|
|
|
@ -84,8 +84,7 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
#
|
#
|
||||||
# create glyph structure
|
# create glyph structure
|
||||||
|
|
||||||
for ch in range(256):
|
for ch, ix in enumerate(encoding):
|
||||||
ix = encoding[ch]
|
|
||||||
if ix is not None:
|
if ix is not None:
|
||||||
x, y, l, r, w, a, d, f = metrics[ix]
|
x, y, l, r, w, a, d, f = metrics[ix]
|
||||||
glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix]
|
glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix]
|
||||||
|
@ -219,10 +218,6 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
return bitmaps
|
return bitmaps
|
||||||
|
|
||||||
def _load_encoding(self):
|
def _load_encoding(self):
|
||||||
|
|
||||||
# map character code to bitmap index
|
|
||||||
encoding = [None] * 256
|
|
||||||
|
|
||||||
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
|
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
|
||||||
|
|
||||||
first_col, last_col = i16(fp.read(2)), i16(fp.read(2))
|
first_col, last_col = i16(fp.read(2)), i16(fp.read(2))
|
||||||
|
@ -232,6 +227,9 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
|
|
||||||
nencoding = (last_col - first_col + 1) * (last_row - first_row + 1)
|
nencoding = (last_col - first_col + 1) * (last_row - first_row + 1)
|
||||||
|
|
||||||
|
# map character code to bitmap index
|
||||||
|
encoding = [None] * min(256, nencoding)
|
||||||
|
|
||||||
encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)]
|
encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)]
|
||||||
|
|
||||||
for i in range(first_col, len(encoding)):
|
for i in range(first_col, len(encoding)):
|
||||||
|
|
|
@ -198,7 +198,9 @@ def _save(im, fp, filename):
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
# colour palette
|
# colour palette
|
||||||
fp.write(o8(12))
|
fp.write(o8(12))
|
||||||
fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
|
palette = im.im.getpalette("RGB", "RGB")
|
||||||
|
palette += b"\x00" * (768 - len(palette))
|
||||||
|
fp.write(palette) # 768 bytes
|
||||||
elif im.mode == "L":
|
elif im.mode == "L":
|
||||||
# greyscale palette
|
# greyscale palette
|
||||||
fp.write(o8(12))
|
fp.write(o8(12))
|
||||||
|
|
|
@ -200,15 +200,15 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) {
|
||||||
|
|
||||||
/* Find min and max distances to any point in the box */
|
/* Find min and max distances to any point in the box */
|
||||||
r = palette->palette[i * 4 + 0];
|
r = palette->palette[i * 4 + 0];
|
||||||
tmin = (r < r0) ? RDIST(r, r1) : (r > r1) ? RDIST(r, r0) : 0;
|
tmin = (r < r0) ? RDIST(r, r0) : (r > r1) ? RDIST(r, r1) : 0;
|
||||||
tmax = (r <= rc) ? RDIST(r, r1) : RDIST(r, r0);
|
tmax = (r <= rc) ? RDIST(r, r1) : RDIST(r, r0);
|
||||||
|
|
||||||
g = palette->palette[i * 4 + 1];
|
g = palette->palette[i * 4 + 1];
|
||||||
tmin += (g < g0) ? GDIST(g, g1) : (g > g1) ? GDIST(g, g0) : 0;
|
tmin += (g < g0) ? GDIST(g, g0) : (g > g1) ? GDIST(g, g1) : 0;
|
||||||
tmax += (g <= gc) ? GDIST(g, g1) : GDIST(g, g0);
|
tmax += (g <= gc) ? GDIST(g, g1) : GDIST(g, g0);
|
||||||
|
|
||||||
b = palette->palette[i * 4 + 2];
|
b = palette->palette[i * 4 + 2];
|
||||||
tmin += (b < b0) ? BDIST(b, b1) : (b > b1) ? BDIST(b, b0) : 0;
|
tmin += (b < b0) ? BDIST(b, b0) : (b > b1) ? BDIST(b, b1) : 0;
|
||||||
tmax += (b <= bc) ? BDIST(b, b1) : BDIST(b, b0);
|
tmax += (b <= bc) ? BDIST(b, b1) : BDIST(b, b0);
|
||||||
|
|
||||||
dmin[i] = tmin;
|
dmin[i] = tmin;
|
||||||
|
|
10
src/thirdparty/fribidi-shim/fribidi.c
vendored
|
@ -33,6 +33,7 @@ static void fribidi_get_bracket_types_compat(
|
||||||
|
|
||||||
int load_fribidi(void) {
|
int load_fribidi(void) {
|
||||||
int error = 0;
|
int error = 0;
|
||||||
|
const char **p_fribidi_version_info = 0;
|
||||||
|
|
||||||
p_fribidi = 0;
|
p_fribidi = 0;
|
||||||
|
|
||||||
|
@ -87,20 +88,21 @@ int load_fribidi(void) {
|
||||||
LOAD_FUNCTION(fribidi_get_par_embedding_levels);
|
LOAD_FUNCTION(fribidi_get_par_embedding_levels);
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info");
|
p_fribidi_version_info = (const char**)dlsym(p_fribidi, "fribidi_version_info");
|
||||||
if (error || (fribidi_version_info == 0)) {
|
if (error || (p_fribidi_version_info == 0) || (*p_fribidi_version_info == 0)) {
|
||||||
dlclose(p_fribidi);
|
dlclose(p_fribidi);
|
||||||
p_fribidi = 0;
|
p_fribidi = 0;
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info");
|
p_fribidi_version_info = (const char**)GetProcAddress(p_fribidi, "fribidi_version_info");
|
||||||
if (error || (fribidi_version_info == 0)) {
|
if (error || (p_fribidi_version_info == 0) || (*p_fribidi_version_info == 0)) {
|
||||||
FreeLibrary(p_fribidi);
|
FreeLibrary(p_fribidi);
|
||||||
p_fribidi = 0;
|
p_fribidi = 0;
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
fribidi_version_info = *p_fribidi_version_info;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,9 +281,9 @@ deps = {
|
||||||
"libs": [r"imagequant.lib"],
|
"libs": [r"imagequant.lib"],
|
||||||
},
|
},
|
||||||
"harfbuzz": {
|
"harfbuzz": {
|
||||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/4.3.0.zip",
|
"url": "https://github.com/harfbuzz/harfbuzz/archive/4.4.1.zip",
|
||||||
"filename": "harfbuzz-4.3.0.zip",
|
"filename": "harfbuzz-4.4.1.zip",
|
||||||
"dir": "harfbuzz-4.3.0",
|
"dir": "harfbuzz-4.4.1",
|
||||||
"build": [
|
"build": [
|
||||||
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
||||||
cmd_nmake(target="clean"),
|
cmd_nmake(target="clean"),
|
||||||
|
|