Merge branch 'master' into skips

This commit is contained in:
nulano 2020-06-21 18:05:27 +01:00 committed by GitHub
commit 12ddff729b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 572 additions and 225 deletions

View File

@ -2,7 +2,7 @@
set -e set -e
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype openblas
PYTHONOPTIMIZE=0 pip install cffi PYTHONOPTIMIZE=0 pip install cffi
pip install coverage pip install coverage
@ -11,6 +11,8 @@ pip install -U pytest
pip install -U pytest-cov pip install -U pytest-cov
pip install pyroma pip install pyroma
pip install test-image-results pip install test-image-results
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
pip install numpy pip install numpy
# extra test images # extra test images

View File

@ -52,6 +52,11 @@ jobs:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }} architecture: ${{ matrix.architecture }}
- name: Set up TCL
if: "contains(matrix.python-version, 'pypy')"
run: Write-Host "::set-env name=TCL_LIBRARY::$env:pythonLocation\tcl\tcl8.5"
shell: pwsh
- name: Print build system information - name: Print build system information
run: python .github/workflows/system-info.py run: python .github/workflows/system-info.py

View File

@ -5,6 +5,24 @@ Changelog (Pillow)
7.2.0 (unreleased) 7.2.0 (unreleased)
------------------ ------------------
- Change STRIPBYTECOUNTS to LONG if necessary when saving #4626
[radarhere, hugovk]
- Write JFIF header when saving JPEG #4639
[radarhere]
- Replaced tiff_jpeg with jpeg compression when saving TIFF images #4627
[radarhere]
- Writing TIFF tags: improved BYTE, added UNDEFINED #4605
[radarhere]
- Consider transparency when pasting text on an RGBA image #4566
[radarhere]
- Added method argument to single frame WebP saving #4547
[radarhere]
- Use ImageFileDirectory_v2 in Image.Exif #4637 - Use ImageFileDirectory_v2 in Image.Exif #4637
[radarhere] [radarhere]
@ -44,6 +62,9 @@ Changelog (Pillow)
- Fix pickling WebP #4561 - Fix pickling WebP #4561
[hugovk, radarhere] [hugovk, radarhere]
- Replace IOError and WindowsError aliases with OSError #4536
[hugovk, radarhere]
7.1.2 (2020-04-25) 7.1.2 (2020-04-25)
------------------ ------------------

BIN
Tests/images/text_mono.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -90,9 +90,12 @@ class TestFileJpeg:
] ]
assert k > 0.9 assert k > 0.9
def test_dpi(self): @pytest.mark.parametrize(
"test_image_path", [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"],
)
def test_dpi(self, test_image_path):
def test(xdpi, ydpi=None): def test(xdpi, ydpi=None):
with Image.open(TEST_FILE) as im: with Image.open(test_image_path) as im:
im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))
return im.info.get("dpi") return im.info.get("dpi")

View File

@ -299,9 +299,6 @@ class TestFileLibTiff(LibTiffTestCase):
) )
continue continue
if libtiff and isinstance(value, bytes):
value = value.decode()
assert reloaded_value == value assert reloaded_value == value
# Test with types # Test with types
@ -322,6 +319,17 @@ class TestFileLibTiff(LibTiffTestCase):
) )
TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.WRITE_LIBTIFF = False
def test_xmlpacket_tag(self, tmp_path):
TiffImagePlugin.WRITE_LIBTIFF = True
out = str(tmp_path / "temp.tif")
hopper().save(out, tiffinfo={700: b"xmlpacket tag"})
TiffImagePlugin.WRITE_LIBTIFF = False
with Image.open(out) as reloaded:
if 700 in reloaded.tag_v2:
assert reloaded.tag_v2[700] == b"xmlpacket tag"
def test_int_dpi(self, tmp_path): def test_int_dpi(self, tmp_path):
# issue #1765 # issue #1765
im = hopper("RGB") im = hopper("RGB")
@ -448,6 +456,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert size_compressed > size_jpeg assert size_compressed > size_jpeg
assert size_jpeg > size_jpeg_30 assert size_jpeg > size_jpeg_30
def test_tiff_jpeg_compression(self, tmp_path):
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
im.save(out, compression="tiff_jpeg")
with Image.open(out) as reloaded:
assert reloaded.info["compression"] == "jpeg"
def test_quality(self, tmp_path): def test_quality(self, tmp_path):
im = hopper("RGB") im = hopper("RGB")
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -667,6 +683,26 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False
assert icc == icc_libtiff assert icc == icc_libtiff
def test_write_icc(self, tmp_path):
def check_write(libtiff):
TiffImagePlugin.WRITE_LIBTIFF = libtiff
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
icc_profile = img.info["icc_profile"]
out = str(tmp_path / "temp.tif")
img.save(out, icc_profile=icc_profile)
with Image.open(out) as reloaded:
assert icc_profile == reloaded.info["icc_profile"]
libtiffs = []
if Image.core.libtiff_support_custom_tags:
libtiffs.append(True)
libtiffs.append(False)
for libtiff in libtiffs:
check_write(libtiff)
def test_multipage_compression(self): def test_multipage_compression(self):
with Image.open("Tests/images/compression.tif") as im: with Image.open("Tests/images/compression.tif") as im:

View File

@ -156,6 +156,23 @@ def test_write_metadata(tmp_path):
assert value == reloaded[tag], "%s didn't roundtrip" % tag assert value == reloaded[tag], "%s didn't roundtrip" % tag
def test_change_stripbytecounts_tag_type(tmp_path):
out = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper.tif") as im:
info = im.tag_v2
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
im = im.resize((500, 500))
# STRIPBYTECOUNTS can be a SHORT or a LONG
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
im.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG
def test_no_duplicate_50741_tag(): def test_no_duplicate_50741_tag():
assert TAG_IDS["MakerNoteSafety"] == 50741 assert TAG_IDS["MakerNoteSafety"] == 50741
assert TAG_IDS["BestQualityScale"] == 50780 assert TAG_IDS["BestQualityScale"] == 50780
@ -319,13 +336,13 @@ def test_empty_values():
def test_PhotoshopInfo(tmp_path): def test_PhotoshopInfo(tmp_path):
with Image.open("Tests/images/issue_2278.tif") as im: with Image.open("Tests/images/issue_2278.tif") as im:
assert len(im.tag_v2[34377]) == 1 assert len(im.tag_v2[34377]) == 70
assert isinstance(im.tag_v2[34377][0], bytes) assert isinstance(im.tag_v2[34377], bytes)
out = str(tmp_path / "temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out) im.save(out)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert len(reloaded.tag_v2[34377]) == 1 assert len(reloaded.tag_v2[34377]) == 70
assert isinstance(reloaded.tag_v2[34377][0], bytes) assert isinstance(reloaded.tag_v2[34377], bytes)
def test_too_many_entries(): def test_too_many_entries():

View File

@ -1,3 +1,5 @@
import io
import pytest import pytest
from PIL import Image, WebPImagePlugin from PIL import Image, WebPImagePlugin
@ -54,15 +56,10 @@ class TestFileWebp:
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0) assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
def test_write_rgb(self, tmp_path): def _roundtrip(self, tmp_path, mode, epsilon, args={}):
"""
Can we write a RGB mode file to webp without error.
Does it have the bits we expect?
"""
temp_file = str(tmp_path / "temp.webp") temp_file = str(tmp_path / "temp.webp")
hopper(self.rgb_mode).save(temp_file) hopper(mode).save(temp_file, **args)
with Image.open(temp_file) as image: with Image.open(temp_file) as image:
assert image.mode == self.rgb_mode assert image.mode == self.rgb_mode
assert image.size == (128, 128) assert image.size == (128, 128)
@ -70,18 +67,38 @@ class TestFileWebp:
image.load() image.load()
image.getdata() image.getdata()
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm if mode == self.rgb_mode:
assert_image_similar_tofile( # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
image, "Tests/images/hopper_webp_write.ppm", 12.0 assert_image_similar_tofile(
) image, "Tests/images/hopper_webp_write.ppm", 12.0
)
# This test asserts that the images are similar. If the average pixel # This test asserts that the images are similar. If the average pixel
# difference between the two images is less than the epsilon value, # difference between the two images is less than the epsilon value,
# then we're going to accept that it's a reasonable lossy version of # then we're going to accept that it's a reasonable lossy version of
# the image. The old lena images for WebP are showing ~16 on # the image.
# Ubuntu, the jpegs are showing ~18. target = hopper(mode)
target = hopper(self.rgb_mode) if mode != self.rgb_mode:
assert_image_similar(image, target, 12.0) target = target.convert(self.rgb_mode)
assert_image_similar(image, target, epsilon)
def test_write_rgb(self, tmp_path):
"""
Can we write a RGB mode file to webp without error?
Does it have the bits we expect?
"""
self._roundtrip(tmp_path, self.rgb_mode, 12.5)
def test_write_method(self, tmp_path):
self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6})
buffer_no_args = io.BytesIO()
hopper().save(buffer_no_args, format="WEBP")
buffer_method = io.BytesIO()
hopper().save(buffer_method, format="WEBP", method=6)
assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
def test_write_unsupported_mode_L(self, tmp_path): def test_write_unsupported_mode_L(self, tmp_path):
""" """
@ -89,18 +106,7 @@ class TestFileWebp:
similar to the original file. similar to the original file.
""" """
temp_file = str(tmp_path / "temp.webp") self._roundtrip(tmp_path, "L", 10.0)
hopper("L").save(temp_file)
with Image.open(temp_file) as image:
assert image.mode == self.rgb_mode
assert image.size == (128, 128)
assert image.format == "WEBP"
image.load()
image.getdata()
target = hopper("L").convert(self.rgb_mode)
assert_image_similar(image, target, 10.0)
def test_write_unsupported_mode_P(self, tmp_path): def test_write_unsupported_mode_P(self, tmp_path):
""" """
@ -108,18 +114,7 @@ class TestFileWebp:
similar to the original file. similar to the original file.
""" """
temp_file = str(tmp_path / "temp.webp") self._roundtrip(tmp_path, "P", 50.0)
hopper("P").save(temp_file)
with Image.open(temp_file) as image:
assert image.mode == self.rgb_mode
assert image.size == (128, 128)
assert image.format == "WEBP"
image.load()
image.getdata()
target = hopper("P").convert(self.rgb_mode)
assert_image_similar(image, target, 50.0)
def test_WebPEncode_with_invalid_args(self): def test_WebPEncode_with_invalid_args(self):
""" """

View File

@ -236,7 +236,7 @@ class TestImagingPaste:
[ [
(127, 191, 254, 191), (127, 191, 254, 191),
(111, 207, 206, 110), (111, 207, 206, 110),
(127, 254, 127, 0), (255, 255, 255, 0) if mode == "RGBA" else (127, 254, 127, 0),
(207, 207, 239, 239), (207, 207, 239, 239),
(191, 191, 190, 191), (191, 191, 190, 191),
(207, 206, 111, 112), (207, 206, 111, 112),

View File

@ -11,9 +11,9 @@ from PIL import Image, ImageDraw, ImageFont
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
assert_image_equal_tofile,
assert_image_similar, assert_image_similar,
assert_image_similar_tofile, assert_image_similar_tofile,
is_mingw,
is_pypy, is_pypy,
is_win32, is_win32,
skip_unless_feature, skip_unless_feature,
@ -151,6 +151,18 @@ class TestImageFont:
assert_image_equal(img_path, img_filelike) assert_image_equal(img_path, img_filelike)
def test_transparent_background(self):
im = Image.new(mode="RGBA", size=(300, 100))
draw = ImageDraw.Draw(im)
ttf = self.get_font()
txt = "Hello World!"
draw.text((10, 10), txt, font=ttf)
target = "Tests/images/transparent_background_text.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 4.09)
def test_textsize_equal(self): def test_textsize_equal(self):
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -661,7 +673,22 @@ class TestImageFont:
{"name": b"Size", "minimum": 0, "maximum": 300, "default": 0} {"name": b"Size", "minimum": 0, "maximum": 300, "default": 0}
] ]
@pytest.mark.skipif(is_mingw(), reason="epsilon too high for meaningful test") def _check_text(self, font, path, epsilon):
im = Image.new("RGB", (100, 75), "white")
d = ImageDraw.Draw(im)
d.text((10, 10), "Text", font=font, fill="black")
try:
with Image.open(path) as expected:
assert_image_similar(im, expected, epsilon)
except AssertionError:
if "_adobe" in path:
path = path.replace("_adobe", "_adobe_older_harfbuzz")
with Image.open(path) as expected:
assert_image_similar(im, expected, epsilon)
else:
raise
def test_variation_set_by_name(self): def test_variation_set_by_name(self):
font = self.get_font() font = self.get_font()
@ -674,27 +701,18 @@ class TestImageFont:
with pytest.raises(OSError): with pytest.raises(OSError):
font.set_variation_by_name("Bold") font.set_variation_by_name("Bold")
def _check_text(font, path, epsilon):
im = Image.new("RGB", (100, 75), "white")
d = ImageDraw.Draw(im)
d.text((10, 10), "Text", font=font, fill="black")
with Image.open(path) as expected:
assert_image_similar(im, expected, epsilon)
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
_check_text(font, "Tests/images/variation_adobe.png", 11) self._check_text(font, "Tests/images/variation_adobe.png", 11)
for name in ["Bold", b"Bold"]: for name in ["Bold", b"Bold"]:
font.set_variation_by_name(name) font.set_variation_by_name(name)
_check_text(font, "Tests/images/variation_adobe_name.png", 11) self._check_text(font, "Tests/images/variation_adobe_name.png", 11)
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
_check_text(font, "Tests/images/variation_tiny.png", 40) self._check_text(font, "Tests/images/variation_tiny.png", 40)
for name in ["200", b"200"]: for name in ["200", b"200"]:
font.set_variation_by_name(name) font.set_variation_by_name(name)
_check_text(font, "Tests/images/variation_tiny_name.png", 40) self._check_text(font, "Tests/images/variation_tiny_name.png", 40)
@pytest.mark.skipif(is_mingw(), reason="epsilon too high for meaningful test")
def test_variation_set_by_axes(self): def test_variation_set_by_axes(self):
font = self.get_font() font = self.get_font()
@ -707,23 +725,31 @@ class TestImageFont:
with pytest.raises(OSError): with pytest.raises(OSError):
font.set_variation_by_axes([500, 50]) font.set_variation_by_axes([500, 50])
def _check_text(font, path, epsilon):
im = Image.new("RGB", (100, 75), "white")
d = ImageDraw.Draw(im)
d.text((10, 10), "Text", font=font, fill="black")
with Image.open(path) as expected:
assert_image_similar(im, expected, epsilon)
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
font.set_variation_by_axes([500, 50]) font.set_variation_by_axes([500, 50])
_check_text(font, "Tests/images/variation_adobe_axes.png", 5.1) self._check_text(font, "Tests/images/variation_adobe_axes.png", 5.1)
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
font.set_variation_by_axes([100]) font.set_variation_by_axes([100])
_check_text(font, "Tests/images/variation_tiny_axes.png", 32.5) self._check_text(font, "Tests/images/variation_tiny_axes.png", 32.5)
@skip_unless_feature("raqm") @skip_unless_feature("raqm")
class TestImageFont_RaqmLayout(TestImageFont): class TestImageFont_RaqmLayout(TestImageFont):
LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM
def test_render_mono_size():
# issue 4177
if distutils.version.StrictVersion(ImageFont.core.freetype2_version) < "2.4":
pytest.skip("Different metrics")
im = Image.new("P", (100, 30), "white")
draw = ImageDraw.Draw(im)
ttf = ImageFont.truetype(
"Tests/fonts/DejaVuSans.ttf", 18, layout_engine=ImageFont.LAYOUT_BASIC
)
draw.text((10, 10), "r" * 10, "black", ttf)
assert_image_equal_tofile(im, "Tests/images/text_mono.gif")

View File

@ -1,10 +1,10 @@
import os
import subprocess import subprocess
import sys import sys
import pytest import pytest
from PIL import Image, ImageGrab from PIL import Image, ImageGrab
from .helper import assert_image, assert_image_equal_tofile, skip_unless_feature
from .helper import assert_image, skip_unless_feature
class TestImageGrab: class TestImageGrab:
@ -71,3 +71,27 @@ $bmp = New-Object Drawing.Bitmap 200, 200
im = ImageGrab.grabclipboard() im = ImageGrab.grabclipboard()
assert_image(im, im.mode, im.size) assert_image(im, im.mode, im.size)
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
def test_grabclipboard_file(self):
p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE)
p.stdin.write(rb'Set-Clipboard -Path "Tests\images\hopper.gif"')
p.communicate()
im = ImageGrab.grabclipboard()
assert len(im) == 1
assert os.path.samefile(im[0], "Tests/images/hopper.gif")
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
def test_grabclipboard_png(self):
p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE)
p.stdin.write(
rb"""$bytes = [System.IO.File]::ReadAllBytes("Tests\images\hopper.png")
$ms = new-object System.IO.MemoryStream(, $bytes)
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[Windows.Forms.Clipboard]::SetData("PNG", $ms)"""
)
p.communicate()
im = ImageGrab.grabclipboard()
assert_image_equal_tofile(im, "Tests/images/hopper.png")

View File

@ -17,19 +17,21 @@ def test_register():
ImageShow._viewers.pop() ImageShow._viewers.pop()
def test_viewer_show(): @pytest.mark.parametrize(
"order", [-1, 0],
)
def test_viewer_show(order):
class TestViewer(ImageShow.Viewer): class TestViewer(ImageShow.Viewer):
methodCalled = False
def show_image(self, image, **options): def show_image(self, image, **options):
self.methodCalled = True self.methodCalled = True
return True return True
viewer = TestViewer() viewer = TestViewer()
ImageShow.register(viewer, -1) ImageShow.register(viewer, order)
for mode in ("1", "I;16", "LA", "RGB", "RGBA"): for mode in ("1", "I;16", "LA", "RGB", "RGBA"):
with hopper() as im: viewer.methodCalled = False
with hopper(mode) as im:
assert ImageShow.show(im) assert ImageShow.show(im)
assert viewer.methodCalled assert viewer.methodCalled

View File

@ -18,7 +18,7 @@ ImageFile.raise_ioerror
.. deprecated:: 7.2.0 .. deprecated:: 7.2.0
``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` ``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror``
is now deprecated and will be removed in a future released. Use is now deprecated and will be removed in a future release. Use
``ImageFile.raise_oserror`` instead. ``ImageFile.raise_oserror`` instead.
PILLOW_VERSION constant PILLOW_VERSION constant

View File

@ -156,7 +156,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management * **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.7-2.9**. above uses liblcms2. Tested with **1.19** and **2.7-2.11**.
* **libwebp** provides the WebP format. * **libwebp** provides the WebP format.

View File

@ -9,7 +9,7 @@ or the clipboard to a PIL image memory.
.. versionadded:: 1.1.3 .. versionadded:: 1.1.3
.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None) .. py:function:: grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None)
Take a snapshot of the screen. The pixels inside the bounding box are Take a snapshot of the screen. The pixels inside the bounding box are
returned as an "RGBA" on macOS, or an "RGB" image otherwise. returned as an "RGBA" on macOS, or an "RGB" image otherwise.
@ -26,12 +26,15 @@ or the clipboard to a PIL image memory.
.. versionadded:: 6.2.0 .. versionadded:: 6.2.0
:param xdisplay: X11 Display address. Pass ``None`` to grab the default system screen. Pass ``""`` to grab the default X11 screen on Windows or macOS. :param xdisplay:
X11 Display address. Pass ``None`` to grab the default system screen. Pass ``""`` to grab the default X11 screen on Windows or macOS.
You can check X11 support using :py:func:`PIL.features.check_feature` with ``feature="xcb"``.
.. versionadded:: 7.1.0 .. versionadded:: 7.1.0
:return: An image :return: An image
.. py:function:: PIL.ImageGrab.grabclipboard() .. py:function:: grabclipboard()
Take a snapshot of the clipboard image, if any. Only macOS and Windows are currently supported. Take a snapshot of the clipboard image, if any. Only macOS and Windows are currently supported.

View File

@ -0,0 +1,60 @@
.. py:module:: PIL.features
.. py:currentmodule:: PIL.features
:py:mod:`features` Module
==========================
The :py:mod:`PIL.features` module can be used to detect which Pillow features are available on your system.
.. autofunction:: PIL.features.pilinfo
.. autofunction:: PIL.features.check
.. autofunction:: PIL.features.get_supported
Modules
-------
Support for the following modules can be checked:
* ``pil``: The Pillow core module, required for all functionality.
* ``tkinter``: Tkinter support.
* ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`.
* ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`.
* ``webp``: WebP image support.
.. autofunction:: PIL.features.check_module
.. autofunction:: PIL.features.get_supported_modules
Codecs
------
These are only checked during Pillow compilation.
If the required library was uninstalled from the system, the ``pil`` core module may fail to load instead.
Support for the following codecs can be checked:
* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats.
* ``jpg_2000``: (compile time) OpenJPEG support, required for JPEG 2000 image formats.
* ``zlib``: (compile time) Zlib support, required for zlib compressed formats, such as PNG.
* ``libtiff``: (compile time) LibTIFF support, required for TIFF based image formats.
.. autofunction:: PIL.features.check_codec
.. autofunction:: PIL.features.get_supported_codecs
Features
--------
Some of these are only checked during Pillow compilation.
If the required library was uninstalled from the system, the relevant module may fail to load instead.
Support for the following features can be checked:
* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg.
* ``transp_webp``: Support for transparency in WebP images.
* ``webp_mux``: (compile time) Support for EXIF data in WebP images.
* ``webp_anim``: (compile time) Support for animated WebP images.
* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`.
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`.
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.
.. autofunction:: PIL.features.check_feature
.. autofunction:: PIL.features.get_supported_features

View File

@ -30,6 +30,7 @@ Reference
PSDraw PSDraw
PixelAccess PixelAccess
PyAccess PyAccess
features
../PIL ../PIL
plugins plugins
internal_design internal_design

View File

@ -27,3 +27,19 @@ Moved from the legacy :py:class:`PIL.TiffImagePlugin.ImageFileDirectory_v1` to
:py:class:`PIL.Image.Exif`. This means that Exif RATIONALs and SIGNED_RATIONALs :py:class:`PIL.Image.Exif`. This means that Exif RATIONALs and SIGNED_RATIONALs
are now read as :py:class:`PIL.TiffImagePlugin.IFDRational`, instead of as a are now read as :py:class:`PIL.TiffImagePlugin.IFDRational`, instead of as a
tuple with a numerator and a denominator. tuple with a numerator and a denominator.
TIFF BYTE tags format
^^^^^^^^^^^^^^^^^^^^^
TIFF BYTE tags were previously read as a tuple containing a bytestring. They
are now read as just a single bytestring.
Deprecations
^^^^^^^^^^^^
ImageFile.raise_ioerror
~~~~~~~~~~~~~~~~~~~~~~~
``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror``
is now deprecated and will be removed in a future release. Use
``ImageFile.raise_oserror`` instead.

View File

@ -1051,10 +1051,12 @@ class Image:
of colors. of colors.
:param colors: The desired number of colors, <= 256 :param colors: The desired number of colors, <= 256
:param method: 0 = median cut :param method: ``Image.MEDIANCUT=0`` (median cut),
1 = maximum coverage ``Image.MAXCOVERAGE=1`` (maximum coverage),
2 = fast octree ``Image.FASTOCTREE=2`` (fast octree),
3 = libimagequant ``Image.LIBIMAGEQUANT=3`` (libimagequant; check support using
:py:func:`PIL.features.check_feature`
with ``feature="libimagequant"``).
:param kmeans: Integer :param kmeans: Integer
:param palette: Quantize to the palette of given :param palette: Quantize to the palette of given
:py:class:`PIL.Image.Image`. :py:class:`PIL.Image.Image`.
@ -3137,11 +3139,10 @@ def register_encoder(name, encoder):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Simple display support. User code may override this. # Simple display support.
def _show(image, **options): def _show(image, **options):
# override me, as necessary
_showxv(image, **options) _showxv(image, **options)

View File

@ -259,7 +259,7 @@ class FreeTypeFont:
:return: (width, height) :return: (width, height)
""" """
size, offset = self.font.getsize(text, direction, features, language) size, offset = self.font.getsize(text, False, direction, features, language)
return ( return (
size[0] + stroke_width * 2 + offset[0], size[0] + stroke_width * 2 + offset[0],
size[1] + stroke_width * 2 + offset[1], size[1] + stroke_width * 2 + offset[1],
@ -468,7 +468,9 @@ class FreeTypeFont:
:py:mod:`PIL.Image.core` interface module, and the text offset, the :py:mod:`PIL.Image.core` interface module, and the text offset, the
gap between the starting coordinate and the first marking gap between the starting coordinate and the first marking
""" """
size, offset = self.font.getsize(text, direction, features, language) size, offset = self.font.getsize(
text, mode == "1", direction, features, language
)
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2 size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
im = fill("L", size, 0) im = fill("L", size, 0)
self.font.render( self.font.render(
@ -637,6 +639,11 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
encoding of any text provided in subsequent operations. encoding of any text provided in subsequent operations.
:param layout_engine: Which layout engine to use, if available: :param layout_engine: Which layout engine to use, if available:
`ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`. `ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`.
You can check support for Raqm layout using
:py:func:`PIL.features.check_feature` with ``feature="raqm"``.
.. versionadded:: 4.2.0
:return: A font object. :return: A font object.
:exception OSError: If the file could not be read. :exception OSError: If the file could not be read.
""" """

View File

@ -93,12 +93,28 @@ def grabclipboard():
os.unlink(filepath) os.unlink(filepath)
return im return im
elif sys.platform == "win32": elif sys.platform == "win32":
data = Image.core.grabclipboard_win32() fmt, data = Image.core.grabclipboard_win32()
if fmt == "file": # CF_HDROP
import struct
o = struct.unpack_from("I", data)[0]
if data[16] != 0:
files = data[o:].decode("utf-16le").split("\0")
else:
files = data[o:].decode("mbcs").split("\0")
return files[: files.index("")]
if isinstance(data, bytes): if isinstance(data, bytes):
from . import BmpImagePlugin
import io import io
return BmpImagePlugin.DibImageFile(io.BytesIO(data)) data = io.BytesIO(data)
return data if fmt == "png":
from . import PngImagePlugin
return PngImagePlugin.PngImageFile(data)
elif fmt == "DIB":
from . import BmpImagePlugin
return BmpImagePlugin.DibImageFile(data)
return None
else: else:
raise NotImplementedError("ImageGrab.grabclipboard() is macOS and Windows only") raise NotImplementedError("ImageGrab.grabclipboard() is macOS and Windows only")

View File

@ -31,7 +31,7 @@ def register(viewer, order=1):
pass # raised if viewer wasn't a class pass # raised if viewer wasn't a class
if order > 0: if order > 0:
_viewers.append(viewer) _viewers.append(viewer)
elif order < 0: else:
_viewers.insert(0, viewer) _viewers.insert(0, viewer)

View File

@ -251,8 +251,8 @@ class PdfDict(collections.UserDict):
def __getattr__(self, key): def __getattr__(self, key):
try: try:
value = self[key.encode("us-ascii")] value = self[key.encode("us-ascii")]
except KeyError: except KeyError as e:
raise AttributeError(key) raise AttributeError(key) from e
if isinstance(value, bytes): if isinstance(value, bytes):
value = decode_text(value) value = decode_text(value)
if key.endswith("Date"): if key.endswith("Date"):
@ -811,11 +811,11 @@ class PdfParser:
if m: if m:
try: try:
stream_len = int(result[b"Length"]) stream_len = int(result[b"Length"])
except (TypeError, KeyError, ValueError): except (TypeError, KeyError, ValueError) as e:
raise PdfFormatError( raise PdfFormatError(
"bad or missing Length in stream dict (%r)" "bad or missing Length in stream dict (%r)"
% result.get(b"Length", None) % result.get(b"Length", None)
) ) from e
stream_data = data[m.end() : m.end() + stream_len] stream_data = data[m.end() : m.end() + stream_len]
m = cls.re_stream_end.match(data, m.end() + stream_len) m = cls.re_stream_end.match(data, m.end() + stream_len)
check_format_condition(m, "stream end not found") check_format_condition(m, "stream end not found")

View File

@ -23,23 +23,29 @@
import logging import logging
import sys import sys
from cffi import FFI try:
from cffi import FFI
defs = """
struct Pixel_RGBA {
unsigned char r,g,b,a;
};
struct Pixel_I16 {
unsigned char l,r;
};
"""
ffi = FFI()
ffi.cdef(defs)
except ImportError as ex:
# Allow error import for doc purposes, but error out when accessing
# anything in core.
from ._util import deferred_error
FFI = ffi = deferred_error(ex)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
defs = """
struct Pixel_RGBA {
unsigned char r,g,b,a;
};
struct Pixel_I16 {
unsigned char l,r;
};
"""
ffi = FFI()
ffi.cdef(defs)
class PyAccess: class PyAccess:
def __init__(self, img, readonly=False): def __init__(self, img, readonly=False):
vals = dict(img.im.unsafe_ptrs) vals = dict(img.im.unsafe_ptrs)

View File

@ -553,9 +553,10 @@ class ImageFileDirectory_v2(MutableMapping):
) )
elif all(isinstance(v, float) for v in values): elif all(isinstance(v, float) for v in values):
self.tagtype[tag] = TiffTags.DOUBLE self.tagtype[tag] = TiffTags.DOUBLE
else: elif all(isinstance(v, str) for v in values):
if all(isinstance(v, str) for v in values): self.tagtype[tag] = TiffTags.ASCII
self.tagtype[tag] = TiffTags.ASCII elif all(isinstance(v, bytes) for v in values):
self.tagtype[tag] = TiffTags.BYTE
if self.tagtype[tag] == TiffTags.UNDEFINED: if self.tagtype[tag] == TiffTags.UNDEFINED:
values = [ values = [
@ -573,8 +574,10 @@ class ImageFileDirectory_v2(MutableMapping):
# Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.
# No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
# Don't mess with the legacy api, since it's frozen. # Don't mess with the legacy api, since it's frozen.
if (info.length == 1) or ( if (
info.length is None and len(values) == 1 and not legacy_api (info.length == 1)
or self.tagtype[tag] == TiffTags.BYTE
or (info.length is None and len(values) == 1 and not legacy_api)
): ):
# Don't mess with the legacy api, since it's frozen. # Don't mess with the legacy api, since it's frozen.
if legacy_api and self.tagtype[tag] in [ if legacy_api and self.tagtype[tag] in [
@ -1405,6 +1408,9 @@ def _save(im, fp, filename):
compression = im.encoderinfo.get("compression", im.info.get("compression")) compression = im.encoderinfo.get("compression", im.info.get("compression"))
if compression is None: if compression is None:
compression = "raw" compression = "raw"
elif compression == "tiff_jpeg":
# OJPEG is obsolete, so use new-style JPEG compression instead
compression = "jpeg"
libtiff = WRITE_LIBTIFF or compression != "raw" libtiff = WRITE_LIBTIFF or compression != "raw"
@ -1485,7 +1491,10 @@ def _save(im, fp, filename):
# data orientation # data orientation
stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
ifd[ROWSPERSTRIP] = im.size[1] ifd[ROWSPERSTRIP] = im.size[1]
ifd[STRIPBYTECOUNTS] = stride * im.size[1] strip_byte_counts = stride * im.size[1]
if strip_byte_counts >= 2 ** 16:
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
ifd[STRIPBYTECOUNTS] = strip_byte_counts
ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
# no compression by default: # no compression by default:
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
@ -1546,16 +1555,17 @@ def _save(im, fp, filename):
# Custom items are supported for int, float, unicode, string and byte # Custom items are supported for int, float, unicode, string and byte
# values. Other types and tuples require a tagtype. # values. Other types and tuples require a tagtype.
if tag not in TiffTags.LIBTIFF_CORE: if tag not in TiffTags.LIBTIFF_CORE:
if ( if not Image.core.libtiff_support_custom_tags:
TiffTags.lookup(tag).type == TiffTags.UNDEFINED
or not Image.core.libtiff_support_custom_tags
):
continue continue
if tag in ifd.tagtype: if tag in ifd.tagtype:
types[tag] = ifd.tagtype[tag] types[tag] = ifd.tagtype[tag]
elif not (isinstance(value, (int, float, str, bytes))): elif not (isinstance(value, (int, float, str, bytes))):
continue continue
else:
type = TiffTags.lookup(tag).type
if type:
types[tag] = type
if tag not in atts and tag not in blocklist: if tag not in atts and tag not in blocklist:
if isinstance(value, str): if isinstance(value, str):
atts[tag] = value.encode("ascii", "replace") + b"\0" atts[tag] = value.encode("ascii", "replace") + b"\0"

View File

@ -314,6 +314,7 @@ def _save(im, fp, filename):
if isinstance(exif, Image.Exif): if isinstance(exif, Image.Exif):
exif = exif.tobytes() exif = exif.tobytes()
xmp = im.encoderinfo.get("xmp", "") xmp = im.encoderinfo.get("xmp", "")
method = im.encoderinfo.get("method", 0)
if im.mode not in _VALID_WEBP_LEGACY_MODES: if im.mode not in _VALID_WEBP_LEGACY_MODES:
alpha = ( alpha = (
@ -331,6 +332,7 @@ def _save(im, fp, filename):
float(quality), float(quality),
im.mode, im.mode,
icc_profile, icc_profile,
method,
exif, exif,
xmp, xmp,
) )

View File

@ -17,6 +17,13 @@ modules = {
def check_module(feature): def check_module(feature):
"""
Checks if a module is available.
:param feature: The module to check for.
:returns: ``True`` if available, ``False`` otherwise.
:raises ValueError: If the module is not defined in this version of Pillow.
"""
if not (feature in modules): if not (feature in modules):
raise ValueError("Unknown module %s" % feature) raise ValueError("Unknown module %s" % feature)
@ -30,6 +37,9 @@ def check_module(feature):
def get_supported_modules(): def get_supported_modules():
"""
:returns: A list of all supported modules.
"""
return [f for f in modules if check_module(f)] return [f for f in modules if check_module(f)]
@ -37,6 +47,13 @@ codecs = {"jpg": "jpeg", "jpg_2000": "jpeg2k", "zlib": "zip", "libtiff": "libtif
def check_codec(feature): def check_codec(feature):
"""
Checks if a codec is available.
:param feature: The codec to check for.
:returns: ``True`` if available, ``False`` otherwise.
:raises ValueError: If the codec is not defined in this version of Pillow.
"""
if feature not in codecs: if feature not in codecs:
raise ValueError("Unknown codec %s" % feature) raise ValueError("Unknown codec %s" % feature)
@ -46,6 +63,9 @@ def check_codec(feature):
def get_supported_codecs(): def get_supported_codecs():
"""
:returns: A list of all supported codecs.
"""
return [f for f in codecs if check_codec(f)] return [f for f in codecs if check_codec(f)]
@ -61,6 +81,13 @@ features = {
def check_feature(feature): def check_feature(feature):
"""
Checks if a feature is available.
:param feature: The feature to check for.
:returns: ``True`` if available, ``False`` if unavailable, ``None`` if unknown.
:raises ValueError: If the feature is not defined in this version of Pillow.
"""
if feature not in features: if feature not in features:
raise ValueError("Unknown feature %s" % feature) raise ValueError("Unknown feature %s" % feature)
@ -74,10 +101,20 @@ def check_feature(feature):
def get_supported_features(): def get_supported_features():
"""
:returns: A list of all supported features.
"""
return [f for f in features if check_feature(f)] return [f for f in features if check_feature(f)]
def check(feature): def check(feature):
"""
:param feature: A module, feature, or codec name.
:returns:
``True`` if the module, feature, or codec is available,
``False`` or ``None`` otherwise.
"""
if feature in modules: if feature in modules:
return check_module(feature) return check_module(feature)
if feature in codecs: if feature in codecs:
@ -89,6 +126,10 @@ def check(feature):
def get_supported(): def get_supported():
"""
:returns: A list of all supported modules, features, and codecs.
"""
ret = get_supported_modules() ret = get_supported_modules()
ret.extend(get_supported_features()) ret.extend(get_supported_features())
ret.extend(get_supported_codecs()) ret.extend(get_supported_codecs())
@ -96,6 +137,16 @@ def get_supported():
def pilinfo(out=None, supported_formats=True): def pilinfo(out=None, supported_formats=True):
"""
Prints information about this installation of Pillow.
This function can be called with ``python -m PIL``.
:param out:
The output stream to print to. Defaults to ``sys.stdout`` if ``None``.
:param supported_formats:
If ``True``, a list of all supported image file formats will be printed.
"""
if out is None: if out is None:
out = sys.stdout out = sys.stdout

View File

@ -609,6 +609,8 @@ font_getsize(FontObject* self, PyObject* args)
FT_Face face; FT_Face face;
int xoffset, yoffset; int xoffset, yoffset;
int horizontal_dir; int horizontal_dir;
int mask = 0;
int load_flags;
const char *dir = NULL; const char *dir = NULL;
const char *lang = NULL; const char *lang = NULL;
size_t i, count; size_t i, count;
@ -618,11 +620,11 @@ font_getsize(FontObject* self, PyObject* args)
/* calculate size and bearing for a given string */ /* calculate size and bearing for a given string */
PyObject* string; PyObject* string;
if (!PyArg_ParseTuple(args, "O|zOz:getsize", &string, &dir, &features, &lang)) { if (!PyArg_ParseTuple(args, "O|izOz:getsize", &string, &mask, &dir, &features, &lang)) {
return NULL; return NULL;
} }
count = text_layout(string, self, dir, features, lang, &glyph_info, 0); count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
return NULL; return NULL;
} }
@ -641,7 +643,11 @@ font_getsize(FontObject* self, PyObject* args)
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 /* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960
* Yifu Yu<root@jackyyf.com>, 2014-10-15 * Yifu Yu<root@jackyyf.com>, 2014-10-15
*/ */
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP); load_flags = FT_LOAD_NO_BITMAP;
if (mask) {
load_flags |= FT_LOAD_TARGET_MONO;
}
error = FT_Load_Glyph(face, index, load_flags);
if (error) { if (error) {
return geterror(error); return geterror(error);
} }

View File

@ -545,6 +545,7 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
int height; int height;
int lossless; int lossless;
float quality_factor; float quality_factor;
int method;
uint8_t* rgb; uint8_t* rgb;
uint8_t* icc_bytes; uint8_t* icc_bytes;
uint8_t* exif_bytes; uint8_t* exif_bytes;
@ -556,49 +557,75 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
Py_ssize_t exif_size; Py_ssize_t exif_size;
Py_ssize_t xmp_size; Py_ssize_t xmp_size;
size_t ret_size; size_t ret_size;
int rgba_mode;
int channels;
int ok;
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
WebPConfig config;
WebPMemoryWriter writer;
WebPPicture pic;
if (!PyArg_ParseTuple(args, "y#iiifss#s#s#", if (!PyArg_ParseTuple(args, "y#iiifss#is#s#",
(char**)&rgb, &size, &width, &height, &lossless, &quality_factor, &mode, (char**)&rgb, &size, &width, &height, &lossless, &quality_factor, &mode,
&icc_bytes, &icc_size, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) { &icc_bytes, &icc_size, &method, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) {
return NULL; return NULL;
} }
if (strcmp(mode, "RGBA")==0){
if (size < width * height * 4){ rgba_mode = strcmp(mode, "RGBA") == 0;
Py_RETURN_NONE; if (!rgba_mode && strcmp(mode, "RGB") != 0) {
}
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
if (lossless) {
ImagingSectionEnter(&cookie);
ret_size = WebPEncodeLosslessRGBA(rgb, width, height, 4 * width, &output);
ImagingSectionLeave(&cookie);
} else
#endif
{
ImagingSectionEnter(&cookie);
ret_size = WebPEncodeRGBA(rgb, width, height, 4 * width, quality_factor, &output);
ImagingSectionLeave(&cookie);
}
} else if (strcmp(mode, "RGB")==0){
if (size < width * height * 3){
Py_RETURN_NONE;
}
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
if (lossless) {
ImagingSectionEnter(&cookie);
ret_size = WebPEncodeLosslessRGB(rgb, width, height, 3 * width, &output);
ImagingSectionLeave(&cookie);
} else
#endif
{
ImagingSectionEnter(&cookie);
ret_size = WebPEncodeRGB(rgb, width, height, 3 * width, quality_factor, &output);
ImagingSectionLeave(&cookie);
}
} else {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
channels = rgba_mode ? 4 : 3;
if (size < width * height * channels) {
Py_RETURN_NONE;
}
// Setup config for this frame
if (!WebPConfigInit(&config)) {
PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!");
return NULL;
}
config.lossless = lossless;
config.quality = quality_factor;
config.method = method;
// Validate the config
if (!WebPValidateConfig(&config)) {
PyErr_SetString(PyExc_ValueError, "invalid configuration");
return NULL;
}
if (!WebPPictureInit(&pic)) {
PyErr_SetString(PyExc_ValueError, "could not initialise picture");
return NULL;
}
pic.width = width;
pic.height = height;
pic.use_argb = 1; // Don't convert RGB pixels to YUV
if (rgba_mode) {
WebPPictureImportRGBA(&pic, rgb, channels * width);
} else {
WebPPictureImportRGB(&pic, rgb, channels * width);
}
WebPMemoryWriterInit(&writer);
pic.writer = WebPMemoryWrite;
pic.custom_ptr = &writer;
ImagingSectionEnter(&cookie);
ok = WebPEncode(&config, &pic);
ImagingSectionLeave(&cookie);
WebPPictureFree(&pic);
if (!ok) {
PyErr_SetString(PyExc_ValueError, "encoding error");
return NULL;
}
output = writer.mem;
ret_size = writer.size;
#ifndef HAVE_WEBPMUX #ifndef HAVE_WEBPMUX
if (ret_size > 0) { if (ret_size > 0) {
PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size); PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size);

View File

@ -502,33 +502,45 @@ PyObject*
PyImaging_GrabClipboardWin32(PyObject* self, PyObject* args) PyImaging_GrabClipboardWin32(PyObject* self, PyObject* args)
{ {
int clip; int clip;
HANDLE handle; HANDLE handle = NULL;
int size; int size;
void* data; void* data;
PyObject* result; PyObject* result;
UINT format;
UINT formats[] = { CF_DIB, CF_DIBV5, CF_HDROP, RegisterClipboardFormatA("PNG"), 0 };
LPCSTR format_names[] = { "DIB", "DIB", "file", "png", NULL };
clip = OpenClipboard(NULL); if (!OpenClipboard(NULL)) {
/* FIXME: check error status */ PyErr_SetString(PyExc_OSError, "failed to open clipboard");
return NULL;
handle = GetClipboardData(CF_DIB); }
if (!handle) {
/* FIXME: add CF_HDROP support to allow cut-and-paste from // find best format as set by clipboard owner
the explorer */ format = 0;
CloseClipboard(); while (!handle && (format = EnumClipboardFormats(format))) {
Py_INCREF(Py_None); for (UINT i = 0; formats[i] != 0; i++) {
return Py_None; if (format == formats[i]) {
handle = GetClipboardData(format);
format = i;
break;
}
}
}
if (!handle) {
CloseClipboard();
return Py_BuildValue("zO", NULL, Py_None);
} }
size = GlobalSize(handle);
data = GlobalLock(handle); data = GlobalLock(handle);
size = GlobalSize(handle);
result = PyBytes_FromStringAndSize(data, size); result = PyBytes_FromStringAndSize(data, size);
GlobalUnlock(handle); GlobalUnlock(handle);
CloseClipboard(); CloseClipboard();
return result; return Py_BuildValue("zN", format_names[format], result);
} }
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */

View File

@ -761,12 +761,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
} }
} }
if (PyBytes_Check(value) &&
(type == TIFF_BYTE || type == TIFF_UNDEFINED)) {
// For backwards compatibility
type = TIFF_ASCII;
}
if (PyTuple_Check(value)) { if (PyTuple_Check(value)) {
Py_ssize_t len; Py_ssize_t len;
len = PyTuple_Size(value); len = PyTuple_Size(value);
@ -790,28 +784,24 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
if (!is_core_tag) { if (!is_core_tag) {
// Register field for non core tags. // Register field for non core tags.
if (type == TIFF_BYTE) {
is_var_length = 1;
}
if (ImagingLibTiffMergeFieldInfo(&encoder->state, type, key_int, is_var_length)) { if (ImagingLibTiffMergeFieldInfo(&encoder->state, type, key_int, is_var_length)) {
continue; continue;
} }
} }
if (is_var_length) { if (type == TIFF_BYTE || type == TIFF_UNDEFINED) {
status = ImagingLibTiffSetField(&encoder->state,
(ttag_t) key_int,
PyBytes_Size(value), PyBytes_AsString(value));
} else if (is_var_length) {
Py_ssize_t len,i; Py_ssize_t len,i;
TRACE(("Setting from Tuple: %d \n", key_int)); TRACE(("Setting from Tuple: %d \n", key_int));
len = PyTuple_Size(value); len = PyTuple_Size(value);
if (type == TIFF_BYTE) { if (type == TIFF_SHORT) {
UINT8 *av;
/* malloc check ok, calloc checks for overflow */
av = calloc(len, sizeof(UINT8));
if (av) {
for (i=0;i<len;i++) {
av[i] = (UINT8)PyLong_AsLong(PyTuple_GetItem(value,i));
}
status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, len, av);
free(av);
}
} else if (type == TIFF_SHORT) {
UINT16 *av; UINT16 *av;
/* malloc check ok, calloc checks for overflow */ /* malloc check ok, calloc checks for overflow */
av = calloc(len, sizeof(UINT16)); av = calloc(len, sizeof(UINT16));
@ -914,10 +904,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
status = ImagingLibTiffSetField(&encoder->state, status = ImagingLibTiffSetField(&encoder->state,
(ttag_t) key_int, (ttag_t) key_int,
(FLOAT64)PyFloat_AsDouble(value)); (FLOAT64)PyFloat_AsDouble(value));
} else if (type == TIFF_BYTE) {
status = ImagingLibTiffSetField(&encoder->state,
(ttag_t) key_int,
(UINT8)PyLong_AsLong(value));
} else if (type == TIFF_SBYTE) { } else if (type == TIFF_SBYTE) {
status = ImagingLibTiffSetField(&encoder->state, status = ImagingLibTiffSetField(&encoder->state,
(ttag_t) key_int, (ttag_t) key_int,

View File

@ -110,7 +110,7 @@ typedef struct {
int extra_offset; int extra_offset;
int rawExifLen; /* EXIF data length */ size_t rawExifLen; /* EXIF data length */
char* rawExif; /* EXIF buffer pointer */ char* rawExif; /* EXIF buffer pointer */
} JPEGENCODERSTATE; } JPEGENCODERSTATE;

View File

@ -50,7 +50,7 @@ static OPJ_SIZE_T
j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
{ {
ImagingCodecState state = (ImagingCodecState)p_user_data; ImagingCodecState state = (ImagingCodecState)p_user_data;
int result; unsigned int result;
result = _imaging_write_pyFd(state->fd, p_buffer, p_nb_bytes); result = _imaging_write_pyFd(state->fd, p_buffer, p_nb_bytes);
@ -399,8 +399,8 @@ j2k_encode_entry(Imaging im, ImagingCodecState state)
Py_ssize_t n; Py_ssize_t n;
float *pq; float *pq;
if (len) { if (len > 0) {
if (len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) { if ((unsigned)len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) {
len = sizeof(params.tcp_rates)/sizeof(params.tcp_rates[0]); len = sizeof(params.tcp_rates)/sizeof(params.tcp_rates[0]);
} }

View File

@ -222,6 +222,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
context->cinfo.smoothing_factor = context->smooth; context->cinfo.smoothing_factor = context->smooth;
context->cinfo.optimize_coding = (boolean) context->optimize; context->cinfo.optimize_coding = (boolean) context->optimize;
if (context->xdpi > 0 && context->ydpi > 0) { if (context->xdpi > 0 && context->ydpi > 0) {
context->cinfo.write_JFIF_header = TRUE;
context->cinfo.density_unit = 1; /* dots per inch */ context->cinfo.density_unit = 1; /* dots per inch */
context->cinfo.X_density = context->xdpi; context->cinfo.X_density = context->xdpi;
context->cinfo.Y_density = context->ydpi; context->cinfo.Y_density = context->ydpi;

View File

@ -391,9 +391,19 @@ fill_mask_L(Imaging imOut, const UINT8* ink, Imaging imMask,
UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; UINT8* mask = (UINT8*) imMask->image[y+sy]+sx;
for (x = 0; x < xsize; x++) { for (x = 0; x < xsize; x++) {
for (i = 0; i < pixelsize; i++) { for (i = 0; i < pixelsize; i++) {
*out = BLEND(*mask, *out, ink[i], tmp1); UINT8 channel_mask = *mask;
out++; if ((
strcmp(imOut->mode, "RGBa") == 0 ||
strcmp(imOut->mode, "RGBA") == 0 ||
strcmp(imOut->mode, "La") == 0 ||
strcmp(imOut->mode, "LA") == 0 ||
strcmp(imOut->mode, "PA") == 0
) && i != 3) {
channel_mask = 255 - (255 - channel_mask) * (1 - (255 - out[3]) / 255);
}
out[i] = BLEND(channel_mask, out[i], ink[i], tmp1);
} }
out += pixelsize;
mask++; mask++;
} }
} }

View File

@ -28,6 +28,7 @@
#include <string.h> #include <string.h>
#include <limits.h> #include <limits.h>
#include "ImagingUtils.h"
#include "QuantOctree.h" #include "QuantOctree.h"
typedef struct _ColorBucket{ typedef struct _ColorBucket{
@ -152,10 +153,10 @@ static void
avg_color_from_color_bucket(const ColorBucket bucket, Pixel *dst) { avg_color_from_color_bucket(const ColorBucket bucket, Pixel *dst) {
float count = bucket->count; float count = bucket->count;
if (count != 0) { if (count != 0) {
dst->c.r = (int)(bucket->r / count); dst->c.r = CLIP8((int)(bucket->r / count));
dst->c.g = (int)(bucket->g / count); dst->c.g = CLIP8((int)(bucket->g / count));
dst->c.b = (int)(bucket->b / count); dst->c.b = CLIP8((int)(bucket->b / count));
dst->c.a = (int)(bucket->a / count); dst->c.a = CLIP8((int)(bucket->a / count));
} else { } else {
dst->c.r = 0; dst->c.r = 0;
dst->c.g = 0; dst->c.g = 0;

View File

@ -20,8 +20,8 @@
int int
quantize_pngquant( quantize_pngquant(
Pixel *pixelData, Pixel *pixelData,
int width, unsigned int width,
int height, unsigned int height,
uint32_t quantPixels, uint32_t quantPixels,
Pixel **palette, Pixel **palette,
uint32_t *paletteLength, uint32_t *paletteLength,

View File

@ -4,8 +4,8 @@
#include "QuantTypes.h" #include "QuantTypes.h"
int quantize_pngquant(Pixel *, int quantize_pngquant(Pixel *,
int, unsigned int,
int, unsigned int,
uint32_t, uint32_t,
Pixel **, Pixel **,
uint32_t *, uint32_t *,

View File

@ -105,9 +105,9 @@ header = [
# dependencies, listed in order of compilation # dependencies, listed in order of compilation
deps = { deps = {
"libjpeg": { "libjpeg": {
"url": SF_MIRROR + "/project/libjpeg-turbo/2.0.3/libjpeg-turbo-2.0.3.tar.gz", "url": SF_MIRROR + "/project/libjpeg-turbo/2.0.4/libjpeg-turbo-2.0.4.tar.gz",
"filename": "libjpeg-turbo-2.0.3.tar.gz", "filename": "libjpeg-turbo-2.0.4.tar.gz",
"dir": "libjpeg-turbo-2.0.3", "dir": "libjpeg-turbo-2.0.4",
"build": [ "build": [
cmd_cmake( cmd_cmake(
[ [
@ -195,9 +195,9 @@ deps = {
# "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"],
}, },
"lcms2": { "lcms2": {
"url": SF_MIRROR + "/project/lcms/lcms/2.10/lcms2-2.10.tar.gz", "url": SF_MIRROR + "/project/lcms/lcms/2.11/lcms2-2.11.tar.gz",
"filename": "lcms2-2.10.tar.gz", "filename": "lcms2-2.11.tar.gz",
"dir": "lcms2-2.10", "dir": "lcms2-2.11",
"patch": { "patch": {
r"Projects\VC2017\lcms2_static\lcms2_static.vcxproj": { r"Projects\VC2017\lcms2_static\lcms2_static.vcxproj": {
# default is /MD for x86 and /MT for x64, we need /MD always # default is /MD for x86 and /MT for x64, we need /MD always
@ -251,9 +251,9 @@ deps = {
"libs": [r"*.lib"], "libs": [r"*.lib"],
}, },
"harfbuzz": { "harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/2.6.4.zip", "url": "https://github.com/harfbuzz/harfbuzz/archive/2.6.7.zip",
"filename": "harfbuzz-2.6.4.zip", "filename": "harfbuzz-2.6.7.zip",
"dir": "harfbuzz-2.6.4", "dir": "harfbuzz-2.6.7",
"build": [ "build": [
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
cmd_nmake(target="clean"), cmd_nmake(target="clean"),