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
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
pip install coverage
@ -11,6 +11,8 @@ pip install -U pytest
pip install -U pytest-cov
pip install pyroma
pip install test-image-results
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
pip install numpy
# extra test images

View File

@ -52,6 +52,11 @@ jobs:
python-version: ${{ matrix.python-version }}
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
run: python .github/workflows/system-info.py

View File

@ -5,6 +5,24 @@ Changelog (Pillow)
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
[radarhere]
@ -44,6 +62,9 @@ Changelog (Pillow)
- Fix pickling WebP #4561
[hugovk, radarhere]
- Replace IOError and WindowsError aliases with OSError #4536
[hugovk, radarhere]
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
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):
with Image.open(TEST_FILE) as im:
with Image.open(test_image_path) as im:
im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))
return im.info.get("dpi")

View File

@ -299,9 +299,6 @@ class TestFileLibTiff(LibTiffTestCase):
)
continue
if libtiff and isinstance(value, bytes):
value = value.decode()
assert reloaded_value == value
# Test with types
@ -322,6 +319,17 @@ class TestFileLibTiff(LibTiffTestCase):
)
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):
# issue #1765
im = hopper("RGB")
@ -448,6 +456,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert size_compressed > size_jpeg
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):
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
@ -667,6 +683,26 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False
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):
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
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():
assert TAG_IDS["MakerNoteSafety"] == 50741
assert TAG_IDS["BestQualityScale"] == 50780
@ -319,13 +336,13 @@ def test_empty_values():
def test_PhotoshopInfo(tmp_path):
with Image.open("Tests/images/issue_2278.tif") as im:
assert len(im.tag_v2[34377]) == 1
assert isinstance(im.tag_v2[34377][0], bytes)
assert len(im.tag_v2[34377]) == 70
assert isinstance(im.tag_v2[34377], bytes)
out = str(tmp_path / "temp.tiff")
im.save(out)
with Image.open(out) as reloaded:
assert len(reloaded.tag_v2[34377]) == 1
assert isinstance(reloaded.tag_v2[34377][0], bytes)
assert len(reloaded.tag_v2[34377]) == 70
assert isinstance(reloaded.tag_v2[34377], bytes)
def test_too_many_entries():

View File

@ -1,3 +1,5 @@
import io
import pytest
from PIL import Image, WebPImagePlugin
@ -54,15 +56,10 @@ class TestFileWebp:
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
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?
"""
def _roundtrip(self, tmp_path, mode, epsilon, args={}):
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:
assert image.mode == self.rgb_mode
assert image.size == (128, 128)
@ -70,18 +67,38 @@ class TestFileWebp:
image.load()
image.getdata()
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
assert_image_similar_tofile(
image, "Tests/images/hopper_webp_write.ppm", 12.0
)
if mode == self.rgb_mode:
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
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
# 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
# the image. The old lena images for WebP are showing ~16 on
# Ubuntu, the jpegs are showing ~18.
target = hopper(self.rgb_mode)
assert_image_similar(image, target, 12.0)
# the image.
target = hopper(mode)
if mode != self.rgb_mode:
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):
"""
@ -89,18 +106,7 @@ class TestFileWebp:
similar to the original file.
"""
temp_file = str(tmp_path / "temp.webp")
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)
self._roundtrip(tmp_path, "L", 10.0)
def test_write_unsupported_mode_P(self, tmp_path):
"""
@ -108,18 +114,7 @@ class TestFileWebp:
similar to the original file.
"""
temp_file = str(tmp_path / "temp.webp")
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)
self._roundtrip(tmp_path, "P", 50.0)
def test_WebPEncode_with_invalid_args(self):
"""

View File

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

View File

@ -11,9 +11,9 @@ from PIL import Image, ImageDraw, ImageFont
from .helper import (
assert_image_equal,
assert_image_equal_tofile,
assert_image_similar,
assert_image_similar_tofile,
is_mingw,
is_pypy,
is_win32,
skip_unless_feature,
@ -151,6 +151,18 @@ class TestImageFont:
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):
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
@ -661,7 +673,22 @@ class TestImageFont:
{"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):
font = self.get_font()
@ -674,27 +701,18 @@ class TestImageFont:
with pytest.raises(OSError):
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)
_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"]:
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)
_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"]:
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):
font = self.get_font()
@ -707,23 +725,31 @@ class TestImageFont:
with pytest.raises(OSError):
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.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.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")
class TestImageFont_RaqmLayout(TestImageFont):
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 sys
import pytest
from PIL import Image, ImageGrab
from .helper import assert_image, skip_unless_feature
from .helper import assert_image, assert_image_equal_tofile, skip_unless_feature
class TestImageGrab:
@ -71,3 +71,27 @@ $bmp = New-Object Drawing.Bitmap 200, 200
im = ImageGrab.grabclipboard()
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()
def test_viewer_show():
@pytest.mark.parametrize(
"order", [-1, 0],
)
def test_viewer_show(order):
class TestViewer(ImageShow.Viewer):
methodCalled = False
def show_image(self, image, **options):
self.methodCalled = True
return True
viewer = TestViewer()
ImageShow.register(viewer, -1)
ImageShow.register(viewer, order)
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 viewer.methodCalled

View File

@ -18,7 +18,7 @@ ImageFile.raise_ioerror
.. deprecated:: 7.2.0
``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.
PILLOW_VERSION constant

View File

@ -156,7 +156,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management
* 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.

View File

@ -9,7 +9,7 @@ or the clipboard to a PIL image memory.
.. 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
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
: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
: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.

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
PixelAccess
PyAccess
features
../PIL
plugins
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
are now read as :py:class:`PIL.TiffImagePlugin.IFDRational`, instead of as a
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.
:param colors: The desired number of colors, <= 256
:param method: 0 = median cut
1 = maximum coverage
2 = fast octree
3 = libimagequant
:param method: ``Image.MEDIANCUT=0`` (median cut),
``Image.MAXCOVERAGE=1`` (maximum coverage),
``Image.FASTOCTREE=2`` (fast octree),
``Image.LIBIMAGEQUANT=3`` (libimagequant; check support using
:py:func:`PIL.features.check_feature`
with ``feature="libimagequant"``).
:param kmeans: Integer
:param palette: Quantize to the palette of given
: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):
# override me, as necessary
_showxv(image, **options)

View File

@ -259,7 +259,7 @@ class FreeTypeFont:
:return: (width, height)
"""
size, offset = self.font.getsize(text, direction, features, language)
size, offset = self.font.getsize(text, False, direction, features, language)
return (
size[0] + stroke_width * 2 + offset[0],
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
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
im = fill("L", size, 0)
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.
:param layout_engine: Which layout engine to use, if available:
`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.
:exception OSError: If the file could not be read.
"""

View File

@ -93,12 +93,28 @@ def grabclipboard():
os.unlink(filepath)
return im
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):
from . import BmpImagePlugin
import io
return BmpImagePlugin.DibImageFile(io.BytesIO(data))
return data
data = io.BytesIO(data)
if fmt == "png":
from . import PngImagePlugin
return PngImagePlugin.PngImageFile(data)
elif fmt == "DIB":
from . import BmpImagePlugin
return BmpImagePlugin.DibImageFile(data)
return None
else:
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
if order > 0:
_viewers.append(viewer)
elif order < 0:
else:
_viewers.insert(0, viewer)

View File

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

View File

@ -23,23 +23,29 @@
import logging
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__)
defs = """
struct Pixel_RGBA {
unsigned char r,g,b,a;
};
struct Pixel_I16 {
unsigned char l,r;
};
"""
ffi = FFI()
ffi.cdef(defs)
class PyAccess:
def __init__(self, img, readonly=False):
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):
self.tagtype[tag] = TiffTags.DOUBLE
else:
if all(isinstance(v, str) for v in values):
self.tagtype[tag] = TiffTags.ASCII
elif all(isinstance(v, str) for v in values):
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:
values = [
@ -573,8 +574,10 @@ class ImageFileDirectory_v2(MutableMapping):
# Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.
# No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
# Don't mess with the legacy api, since it's frozen.
if (info.length == 1) or (
info.length is None and len(values) == 1 and not legacy_api
if (
(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.
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"))
if compression is None:
compression = "raw"
elif compression == "tiff_jpeg":
# OJPEG is obsolete, so use new-style JPEG compression instead
compression = "jpeg"
libtiff = WRITE_LIBTIFF or compression != "raw"
@ -1485,7 +1491,10 @@ def _save(im, fp, filename):
# data orientation
stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
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
# no compression by default:
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
# values. Other types and tuples require a tagtype.
if tag not in TiffTags.LIBTIFF_CORE:
if (
TiffTags.lookup(tag).type == TiffTags.UNDEFINED
or not Image.core.libtiff_support_custom_tags
):
if not Image.core.libtiff_support_custom_tags:
continue
if tag in ifd.tagtype:
types[tag] = ifd.tagtype[tag]
elif not (isinstance(value, (int, float, str, bytes))):
continue
else:
type = TiffTags.lookup(tag).type
if type:
types[tag] = type
if tag not in atts and tag not in blocklist:
if isinstance(value, str):
atts[tag] = value.encode("ascii", "replace") + b"\0"

View File

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

View File

@ -17,6 +17,13 @@ modules = {
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):
raise ValueError("Unknown module %s" % feature)
@ -30,6 +37,9 @@ def check_module(feature):
def get_supported_modules():
"""
:returns: A list of all supported modules.
"""
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):
"""
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:
raise ValueError("Unknown codec %s" % feature)
@ -46,6 +63,9 @@ def check_codec(feature):
def get_supported_codecs():
"""
:returns: A list of all supported codecs.
"""
return [f for f in codecs if check_codec(f)]
@ -61,6 +81,13 @@ features = {
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:
raise ValueError("Unknown feature %s" % feature)
@ -74,10 +101,20 @@ def check_feature(feature):
def get_supported_features():
"""
:returns: A list of all supported features.
"""
return [f for f in features if check_feature(f)]
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:
return check_module(feature)
if feature in codecs:
@ -89,6 +126,10 @@ def check(feature):
def get_supported():
"""
:returns: A list of all supported modules, features, and codecs.
"""
ret = get_supported_modules()
ret.extend(get_supported_features())
ret.extend(get_supported_codecs())
@ -96,6 +137,16 @@ def get_supported():
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:
out = sys.stdout

View File

@ -609,6 +609,8 @@ font_getsize(FontObject* self, PyObject* args)
FT_Face face;
int xoffset, yoffset;
int horizontal_dir;
int mask = 0;
int load_flags;
const char *dir = NULL;
const char *lang = NULL;
size_t i, count;
@ -618,11 +620,11 @@ font_getsize(FontObject* self, PyObject* args)
/* calculate size and bearing for a given 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;
}
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()) {
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
* 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) {
return geterror(error);
}

View File

@ -545,6 +545,7 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
int height;
int lossless;
float quality_factor;
int method;
uint8_t* rgb;
uint8_t* icc_bytes;
uint8_t* exif_bytes;
@ -556,49 +557,75 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
Py_ssize_t exif_size;
Py_ssize_t xmp_size;
size_t ret_size;
int rgba_mode;
int channels;
int ok;
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,
&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;
}
if (strcmp(mode, "RGBA")==0){
if (size < width * height * 4){
Py_RETURN_NONE;
}
#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 {
rgba_mode = strcmp(mode, "RGBA") == 0;
if (!rgba_mode && strcmp(mode, "RGB") != 0) {
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
if (ret_size > 0) {
PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size);

View File

@ -502,33 +502,45 @@ PyObject*
PyImaging_GrabClipboardWin32(PyObject* self, PyObject* args)
{
int clip;
HANDLE handle;
HANDLE handle = NULL;
int size;
void* data;
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);
/* FIXME: check error status */
handle = GetClipboardData(CF_DIB);
if (!handle) {
/* FIXME: add CF_HDROP support to allow cut-and-paste from
the explorer */
CloseClipboard();
Py_INCREF(Py_None);
return Py_None;
if (!OpenClipboard(NULL)) {
PyErr_SetString(PyExc_OSError, "failed to open clipboard");
return NULL;
}
// find best format as set by clipboard owner
format = 0;
while (!handle && (format = EnumClipboardFormats(format))) {
for (UINT i = 0; formats[i] != 0; i++) {
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);
size = GlobalSize(handle);
result = PyBytes_FromStringAndSize(data, size);
GlobalUnlock(handle);
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)) {
Py_ssize_t len;
len = PyTuple_Size(value);
@ -790,28 +784,24 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
if (!is_core_tag) {
// Register field for non core tags.
if (type == TIFF_BYTE) {
is_var_length = 1;
}
if (ImagingLibTiffMergeFieldInfo(&encoder->state, type, key_int, is_var_length)) {
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;
TRACE(("Setting from Tuple: %d \n", key_int));
len = PyTuple_Size(value);
if (type == TIFF_BYTE) {
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) {
if (type == TIFF_SHORT) {
UINT16 *av;
/* malloc check ok, calloc checks for overflow */
av = calloc(len, sizeof(UINT16));
@ -914,10 +904,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
status = ImagingLibTiffSetField(&encoder->state,
(ttag_t) key_int,
(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) {
status = ImagingLibTiffSetField(&encoder->state,
(ttag_t) key_int,

View File

@ -110,7 +110,7 @@ typedef struct {
int extra_offset;
int rawExifLen; /* EXIF data length */
size_t rawExifLen; /* EXIF data length */
char* rawExif; /* EXIF buffer pointer */
} 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)
{
ImagingCodecState state = (ImagingCodecState)p_user_data;
int result;
unsigned int result;
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;
float *pq;
if (len) {
if (len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) {
if (len > 0) {
if ((unsigned)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.optimize_coding = (boolean) context->optimize;
if (context->xdpi > 0 && context->ydpi > 0) {
context->cinfo.write_JFIF_header = TRUE;
context->cinfo.density_unit = 1; /* dots per inch */
context->cinfo.X_density = context->xdpi;
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;
for (x = 0; x < xsize; x++) {
for (i = 0; i < pixelsize; i++) {
*out = BLEND(*mask, *out, ink[i], tmp1);
out++;
UINT8 channel_mask = *mask;
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++;
}
}

View File

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

View File

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

View File

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

View File

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