Merge branch 'master' into fix_jpeg_magic_number

This commit is contained in:
Andrew Murray 2020-06-22 18:42:58 +10:00 committed by GitHub
commit 95ace8a39c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 622 additions and 223 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

View File

@ -1,4 +1,5 @@
import io import io
import re
import pytest import pytest
from PIL import features from PIL import features
@ -21,6 +22,27 @@ def test_check():
assert features.check_feature(feature) == features.check(feature) assert features.check_feature(feature) == features.check(feature)
def test_version():
# Check the correctness of the convenience function
# and the format of version numbers
def test(name, function):
version = features.version(name)
if not features.check(name):
assert version is None
else:
assert function(name) == version
if name != "PIL":
assert version is None or re.search(r"\d+(\.\d+)*$", version)
for module in features.modules:
test(module, features.version_module)
for codec in features.codecs:
test(codec, features.version_codec)
for feature in features.features:
test(feature, features.version_feature)
@skip_unless_feature("webp") @skip_unless_feature("webp")
def test_webp_transparency(): def test_webp_transparency():
assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha() assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha()
@ -37,9 +59,22 @@ def test_webp_anim():
assert features.check("webp_anim") == _webp.HAVE_WEBPANIM assert features.check("webp_anim") == _webp.HAVE_WEBPANIM
@skip_unless_feature("libjpeg_turbo")
def test_libjpeg_turbo_version():
assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo"))
@skip_unless_feature("libimagequant")
def test_libimagequant_version():
assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant"))
def test_check_modules(): def test_check_modules():
for feature in features.modules: for feature in features.modules:
assert features.check_module(feature) in [True, False] assert features.check_module(feature) in [True, False]
def test_check_codecs():
for feature in features.codecs: for feature in features.codecs:
assert features.check_codec(feature) in [True, False] assert features.check_codec(feature) in [True, False]
@ -64,6 +99,8 @@ def test_unsupported_codec():
# Act / Assert # Act / Assert
with pytest.raises(ValueError): with pytest.raises(ValueError):
features.check_codec(codec) features.check_codec(codec)
with pytest.raises(ValueError):
features.version_codec(codec)
def test_unsupported_module(): def test_unsupported_module():
@ -72,6 +109,8 @@ def test_unsupported_module():
# Act / Assert # Act / Assert
with pytest.raises(ValueError): with pytest.raises(ValueError):
features.check_module(module) features.check_module(module)
with pytest.raises(ValueError):
features.version_module(module)
def test_pilinfo(): def test_pilinfo():

View File

@ -2,14 +2,14 @@ import io
import sys import sys
import pytest import pytest
from PIL import IcnsImagePlugin, Image from PIL import IcnsImagePlugin, Image, features
from .helper import assert_image_equal, assert_image_similar from .helper import assert_image_equal, assert_image_similar
# sample icon file # sample icon file
TEST_FILE = "Tests/images/pillow.icns" TEST_FILE = "Tests/images/pillow.icns"
ENABLE_JPEG2K = hasattr(Image.core, "jp2klib_version") ENABLE_JPEG2K = features.check_codec("jpg_2000")
def test_sanity(): def test_sanity():

View File

@ -3,7 +3,7 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, UnidentifiedImageError from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, UnidentifiedImageError, features
from .helper import ( from .helper import (
assert_image, assert_image,
@ -41,7 +41,7 @@ class TestFileJpeg:
def test_sanity(self): def test_sanity(self):
# internal version number # internal version number
assert re.search(r"\d+\.\d+$", Image.core.jpeglib_version) assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
im.load() im.load()
@ -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

@ -2,7 +2,7 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageFile, Jpeg2KImagePlugin from PIL import Image, ImageFile, Jpeg2KImagePlugin, features
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -35,7 +35,7 @@ def roundtrip(im, **options):
def test_sanity(): def test_sanity():
# Internal version number # Internal version number
assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version) assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000"))
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
px = im.load() px = im.load()

View File

@ -3,11 +3,12 @@ import io
import itertools import itertools
import logging import logging
import os import os
import re
from collections import namedtuple from collections import namedtuple
from ctypes import c_float from ctypes import c_float
import pytest import pytest
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -47,6 +48,9 @@ class LibTiffTestCase:
class TestFileLibTiff(LibTiffTestCase): class TestFileLibTiff(LibTiffTestCase):
def test_version(self):
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff"))
def test_g4_tiff(self, tmp_path): def test_g4_tiff(self, tmp_path):
"""Test the ordinary file path load path""" """Test the ordinary file path load path"""
@ -299,9 +303,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 +323,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 +460,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 +687,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

@ -3,7 +3,7 @@ import zlib
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageFile, PngImagePlugin from PIL import Image, ImageFile, PngImagePlugin, features
from .helper import ( from .helper import (
PillowLeakTestCase, PillowLeakTestCase,
@ -73,7 +73,7 @@ class TestFilePng:
def test_sanity(self, tmp_path): def test_sanity(self, tmp_path):
# internal version number # internal version number
assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", Image.core.zlib_version) assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")

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,5 +1,8 @@
import io
import re
import pytest import pytest
from PIL import Image, WebPImagePlugin from PIL import Image, WebPImagePlugin, features
from .helper import ( from .helper import (
assert_image_similar, assert_image_similar,
@ -36,6 +39,7 @@ class TestFileWebp:
def test_version(self): def test_version(self):
_webp.WebPDecoderVersion() _webp.WebPDecoderVersion()
_webp.WebPDecoderBuggyAlpha() _webp.WebPDecoderBuggyAlpha()
assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp"))
def test_read_rgb(self): def test_read_rgb(self):
""" """
@ -54,15 +58,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 +69,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 +108,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 +116,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

@ -4,7 +4,7 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageMode from PIL import Image, ImageMode, features
from .helper import assert_image, assert_image_equal, assert_image_similar, hopper from .helper import assert_image, assert_image_equal, assert_image_similar, hopper
@ -46,7 +46,7 @@ def test_sanity():
assert list(map(type, v)) == [str, str, str, str] assert list(map(type, v)) == [str, str, str, str]
# internal version number # internal version number
assert re.search(r"\d+\.\d+$", ImageCms.core.littlecms_version) assert re.search(r"\d+\.\d+$", features.version_module("littlecms2"))
skip_missing() skip_missing()
i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) i = ImageCms.profileToProfile(hopper(), SRGB, SRGB)

View File

@ -7,10 +7,11 @@ import sys
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont, features
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_pypy, is_pypy,
@ -40,7 +41,7 @@ class TestImageFont:
@classmethod @classmethod
def setup_class(self): def setup_class(self):
freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) freetype = distutils.version.StrictVersion(features.version_module("freetype2"))
self.metrics = self.METRICS["Default"] self.metrics = self.METRICS["Default"]
for conditions, metrics in self.METRICS.items(): for conditions, metrics in self.METRICS.items():
@ -67,7 +68,7 @@ class TestImageFont:
) )
def test_sanity(self): def test_sanity(self):
assert re.search(r"\d+\.\d+\.\d+$", ImageFont.core.freetype2_version) assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2"))
def test_font_properties(self): def test_font_properties(self):
ttf = self.get_font() ttf = self.get_font()
@ -150,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)
@ -607,7 +620,7 @@ class TestImageFont:
def test_variation_get(self): def test_variation_get(self):
font = self.get_font() font = self.get_font()
freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) freetype = distutils.version.StrictVersion(features.version_module("freetype2"))
if freetype < "2.9.1": if freetype < "2.9.1":
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
font.get_variation_names() font.get_variation_names()
@ -679,7 +692,7 @@ class TestImageFont:
def test_variation_set_by_name(self): def test_variation_set_by_name(self):
font = self.get_font() font = self.get_font()
freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) freetype = distutils.version.StrictVersion(features.version_module("freetype2"))
if freetype < "2.9.1": if freetype < "2.9.1":
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
font.set_variation_by_name("Bold") font.set_variation_by_name("Bold")
@ -703,7 +716,7 @@ class TestImageFont:
def test_variation_set_by_axes(self): def test_variation_set_by_axes(self):
font = self.get_font() font = self.get_font()
freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) freetype = distutils.version.StrictVersion(features.version_module("freetype2"))
if freetype < "2.9.1": if freetype < "2.9.1":
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
font.set_variation_by_axes([100]) font.set_variation_by_axes([100])
@ -724,3 +737,19 @@ class TestImageFont:
@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,11 @@
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 from .helper import assert_image, assert_image_equal_tofile
class TestImageGrab: class TestImageGrab:
@ -71,3 +72,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

@ -8,6 +8,7 @@ The :py:mod:`PIL.features` module can be used to detect which Pillow features ar
.. autofunction:: PIL.features.pilinfo .. autofunction:: PIL.features.pilinfo
.. autofunction:: PIL.features.check .. autofunction:: PIL.features.check
.. autofunction:: PIL.features.version
.. autofunction:: PIL.features.get_supported .. autofunction:: PIL.features.get_supported
Modules Modules
@ -16,28 +17,31 @@ Modules
Support for the following modules can be checked: Support for the following modules can be checked:
* ``pil``: The Pillow core module, required for all functionality. * ``pil``: The Pillow core module, required for all functionality.
* ``tkinter``: Tkinter support. * ``tkinter``: Tkinter support. Version number not available.
* ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`. * ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`.
* ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`. * ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`.
* ``webp``: WebP image support. * ``webp``: WebP image support.
.. autofunction:: PIL.features.check_module .. autofunction:: PIL.features.check_module
.. autofunction:: PIL.features.version_module
.. autofunction:: PIL.features.get_supported_modules .. autofunction:: PIL.features.get_supported_modules
Codecs Codecs
------ ------
These are only checked during Pillow compilation. Support for these is only checked during Pillow compilation.
If the required library was uninstalled from the system, the ``pil`` core module may fail to load instead. If the required library was uninstalled from the system, the ``pil`` core module may fail to load instead.
Except for ``jpg``, the version number is checked at run-time.
Support for the following codecs can be checked: Support for the following codecs can be checked:
* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats. * ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats. Only compile time version number is available.
* ``jpg_2000``: (compile time) OpenJPEG support, required for JPEG 2000 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. * ``zlib``: (compile time) Zlib support, required for zlib compressed formats, such as PNG.
* ``libtiff``: (compile time) LibTIFF support, required for TIFF based image formats. * ``libtiff``: (compile time) LibTIFF support, required for TIFF based image formats.
.. autofunction:: PIL.features.check_codec .. autofunction:: PIL.features.check_codec
.. autofunction:: PIL.features.version_codec
.. autofunction:: PIL.features.get_supported_codecs .. autofunction:: PIL.features.get_supported_codecs
Features Features
@ -45,16 +49,18 @@ Features
Some of these are only checked during Pillow compilation. 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. If the required library was uninstalled from the system, the relevant module may fail to load instead.
Feature version numbers are available only where stated.
Support for the following features can be checked: Support for the following features can be checked:
* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. * ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available.
* ``transp_webp``: Support for transparency in WebP images. * ``transp_webp``: Support for transparency in WebP images.
* ``webp_mux``: (compile time) Support for EXIF data in WebP images. * ``webp_mux``: (compile time) Support for EXIF data in WebP images.
* ``webp_anim``: (compile time) Support for animated 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`. * ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer.
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. * ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available.
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library. * ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.
.. autofunction:: PIL.features.check_feature .. autofunction:: PIL.features.check_feature
.. autofunction:: PIL.features.version_feature
.. autofunction:: PIL.features.get_supported_features .. autofunction:: PIL.features.get_supported_features

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

@ -23,10 +23,10 @@ import subprocess
import sys import sys
import tempfile import tempfile
from PIL import Image, ImageFile, PngImagePlugin from PIL import Image, ImageFile, PngImagePlugin, features
from PIL._binary import i8 from PIL._binary import i8
enable_jpeg2k = hasattr(Image.core, "jp2klib_version") enable_jpeg2k = features.check_codec("jpg_2000")
if enable_jpeg2k: if enable_jpeg2k:
from PIL import Jpeg2KImagePlugin from PIL import Jpeg2KImagePlugin

View File

@ -3139,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(

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

@ -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

@ -8,11 +8,11 @@ import PIL
from . import Image from . import Image
modules = { modules = {
"pil": "PIL._imaging", "pil": ("PIL._imaging", "PILLOW_VERSION"),
"tkinter": "PIL._tkinter_finder", "tkinter": ("PIL._tkinter_finder", None),
"freetype2": "PIL._imagingft", "freetype2": ("PIL._imagingft", "freetype2_version"),
"littlecms2": "PIL._imagingcms", "littlecms2": ("PIL._imagingcms", "littlecms_version"),
"webp": "PIL._webp", "webp": ("PIL._webp", "webpdecoder_version"),
} }
@ -27,7 +27,7 @@ def check_module(feature):
if not (feature in modules): if not (feature in modules):
raise ValueError("Unknown module %s" % feature) raise ValueError("Unknown module %s" % feature)
module = modules[feature] module, ver = modules[feature]
try: try:
__import__(module) __import__(module)
@ -36,6 +36,24 @@ def check_module(feature):
return False return False
def version_module(feature):
"""
:param feature: The module to check for.
:returns:
The loaded version number as a string, or ``None`` if unknown or not available.
:raises ValueError: If the module is not defined in this version of Pillow.
"""
if not check_module(feature):
return None
module, ver = modules[feature]
if ver is None:
return None
return getattr(__import__(module, fromlist=[ver]), ver)
def get_supported_modules(): def get_supported_modules():
""" """
:returns: A list of all supported modules. :returns: A list of all supported modules.
@ -43,7 +61,12 @@ def get_supported_modules():
return [f for f in modules if check_module(f)] return [f for f in modules if check_module(f)]
codecs = {"jpg": "jpeg", "jpg_2000": "jpeg2k", "zlib": "zip", "libtiff": "libtiff"} codecs = {
"jpg": ("jpeg", "jpeglib"),
"jpg_2000": ("jpeg2k", "jp2klib"),
"zlib": ("zip", "zlib"),
"libtiff": ("libtiff", "libtiff"),
}
def check_codec(feature): def check_codec(feature):
@ -57,11 +80,32 @@ def check_codec(feature):
if feature not in codecs: if feature not in codecs:
raise ValueError("Unknown codec %s" % feature) raise ValueError("Unknown codec %s" % feature)
codec = codecs[feature] codec, lib = codecs[feature]
return codec + "_encoder" in dir(Image.core) return codec + "_encoder" in dir(Image.core)
def version_codec(feature):
"""
:param feature: The codec to check for.
:returns:
The version number as a string, or ``None`` if not available.
Checked at compile time for ``jpg``, run-time otherwise.
:raises ValueError: If the codec is not defined in this version of Pillow.
"""
if not check_codec(feature):
return None
codec, lib = codecs[feature]
version = getattr(Image.core, lib + "_version")
if feature == "libtiff":
return version.split("\n")[0].split("Version ")[1]
return version
def get_supported_codecs(): def get_supported_codecs():
""" """
:returns: A list of all supported codecs. :returns: A list of all supported codecs.
@ -70,13 +114,13 @@ def get_supported_codecs():
features = { features = {
"webp_anim": ("PIL._webp", "HAVE_WEBPANIM"), "webp_anim": ("PIL._webp", "HAVE_WEBPANIM", None),
"webp_mux": ("PIL._webp", "HAVE_WEBPMUX"), "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None),
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"), "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None),
"raqm": ("PIL._imagingft", "HAVE_RAQM"), "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"), "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT"), "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
"xcb": ("PIL._imaging", "HAVE_XCB"), "xcb": ("PIL._imaging", "HAVE_XCB", None),
} }
@ -91,7 +135,7 @@ def check_feature(feature):
if feature not in features: if feature not in features:
raise ValueError("Unknown feature %s" % feature) raise ValueError("Unknown feature %s" % feature)
module, flag = features[feature] module, flag, ver = features[feature]
try: try:
imported_module = __import__(module, fromlist=["PIL"]) imported_module = __import__(module, fromlist=["PIL"])
@ -100,6 +144,23 @@ def check_feature(feature):
return None return None
def version_feature(feature):
"""
:param feature: The feature to check for.
:returns: The version number as a string, or ``None`` if not available.
:raises ValueError: If the feature is not defined in this version of Pillow.
"""
if not check_feature(feature):
return None
module, flag, ver = features[feature]
if ver is None:
return None
return getattr(__import__(module, fromlist=[ver]), ver)
def get_supported_features(): def get_supported_features():
""" """
:returns: A list of all supported features. :returns: A list of all supported features.
@ -109,9 +170,9 @@ def get_supported_features():
def check(feature): def check(feature):
""" """
:param feature: A module, feature, or codec name. :param feature: A module, codec, or feature name.
:returns: :returns:
``True`` if the module, feature, or codec is available, ``True`` if the module, codec, or feature is available,
``False`` or ``None`` otherwise. ``False`` or ``None`` otherwise.
""" """
@ -125,6 +186,22 @@ def check(feature):
return False return False
def version(feature):
"""
:param feature:
The module, codec, or feature to check for.
:returns:
The version number as a string, or ``None`` if unknown or not available.
"""
if feature in modules:
return version_module(feature)
if feature in codecs:
return version_codec(feature)
if feature in features:
return version_feature(feature)
return None
def get_supported(): def get_supported():
""" """
:returns: A list of all supported modules, features, and codecs. :returns: A list of all supported modules, features, and codecs.
@ -187,7 +264,15 @@ def pilinfo(out=None, supported_formats=True):
("xcb", "XCB (X protocol)"), ("xcb", "XCB (X protocol)"),
]: ]:
if check(name): if check(name):
print("---", feature, "support ok", file=out) if name == "jpg" and check_feature("libjpeg_turbo"):
v = "libjpeg-turbo " + version_feature("libjpeg_turbo")
else:
v = version(name)
if v is not None:
t = "compiled for" if name in ("pil", "jpg") else "loaded"
print("---", feature, "support ok,", t, v, file=out)
else:
print("---", feature, "support ok", file=out)
else: else:
print("***", feature, "support not installed", file=out) print("***", feature, "support not installed", file=out)
print("-" * 68, file=out) print("-" * 68, file=out)

View File

@ -4168,12 +4168,21 @@ setup_module(PyObject* m) {
#ifdef LIBJPEG_TURBO_VERSION #ifdef LIBJPEG_TURBO_VERSION
PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_True); PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_True);
#define tostr1(a) #a
#define tostr(a) tostr1(a)
PyDict_SetItemString(d, "libjpeg_turbo_version", PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION)));
#undef tostr
#undef tostr1
#else #else
PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_False); PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_False);
#endif #endif
#ifdef HAVE_LIBIMAGEQUANT #ifdef HAVE_LIBIMAGEQUANT
PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_True); PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_True);
{
extern const char* ImagingImageQuantVersion(void);
PyDict_SetItemString(d, "imagequant_version", PyUnicode_FromString(ImagingImageQuantVersion()));
}
#else #else
PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_False); PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_False);
#endif #endif

View File

@ -1608,6 +1608,7 @@ static int
setup_module(PyObject* m) { setup_module(PyObject* m) {
PyObject *d; PyObject *d;
PyObject *v; PyObject *v;
int vn;
d = PyModule_GetDict(m); d = PyModule_GetDict(m);
@ -1622,7 +1623,8 @@ setup_module(PyObject* m) {
d = PyModule_GetDict(m); d = PyModule_GetDict(m);
v = PyUnicode_FromFormat("%d.%d", LCMS_VERSION / 100, LCMS_VERSION % 100); vn = cmsGetEncodedCMMversion();
v = PyUnicode_FromFormat("%d.%d", vn / 100, vn % 100);
PyDict_SetItemString(d, "littlecms_version", v); PyDict_SetItemString(d, "littlecms_version", v);
return 0; return 0;

View File

@ -81,6 +81,7 @@ typedef struct {
static PyTypeObject Font_Type; static PyTypeObject Font_Type;
typedef const char* (*t_raqm_version_string) (void);
typedef bool (*t_raqm_version_atleast)(unsigned int major, typedef bool (*t_raqm_version_atleast)(unsigned int major,
unsigned int minor, unsigned int minor,
unsigned int micro); unsigned int micro);
@ -112,6 +113,7 @@ typedef void (*t_raqm_destroy) (raqm_t *rq);
typedef struct { typedef struct {
void* raqm; void* raqm;
int version; int version;
t_raqm_version_string version_string;
t_raqm_version_atleast version_atleast; t_raqm_version_atleast version_atleast;
t_raqm_create create; t_raqm_create create;
t_raqm_set_text set_text; t_raqm_set_text set_text;
@ -173,6 +175,7 @@ setraqm(void)
} }
#ifndef _WIN32 #ifndef _WIN32
p_raqm.version_string = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_string");
p_raqm.version_atleast = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast"); p_raqm.version_atleast = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast");
p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create"); p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create");
p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text");
@ -206,6 +209,7 @@ setraqm(void)
return 2; return 2;
} }
#else #else
p_raqm.version_string = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_string");
p_raqm.version_atleast = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast"); p_raqm.version_atleast = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast");
p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create"); p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create");
p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text");
@ -609,6 +613,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 +624,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 +647,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);
} }
@ -1251,6 +1261,9 @@ setup_module(PyObject* m) {
setraqm(); setraqm();
v = PyBool_FromLong(!!p_raqm.raqm); v = PyBool_FromLong(!!p_raqm.raqm);
PyDict_SetItemString(d, "HAVE_RAQM", v); PyDict_SetItemString(d, "HAVE_RAQM", v);
if (p_raqm.version_string) {
PyDict_SetItemString(d, "raqm_version", PyUnicode_FromString(p_raqm.version_string()));
}
return 0; return 0;
} }

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);
@ -794,6 +821,16 @@ PyObject* WebPDecoderVersion_wrapper() {
return Py_BuildValue("i", WebPGetDecoderVersion()); return Py_BuildValue("i", WebPGetDecoderVersion());
} }
// Version as string
const char*
WebPDecoderVersion_str(void)
{
static char version[20];
int version_number = WebPGetDecoderVersion();
sprintf(version, "%d.%d.%d", version_number >> 16, (version_number >> 8) % 0x100, version_number % 0x100);
return version;
}
/* /*
* The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well. * The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well.
* Files that are valid with 0.3 are reported as being invalid. * Files that are valid with 0.3 are reported as being invalid.
@ -845,10 +882,13 @@ void addTransparencyFlagToModule(PyObject* m) {
} }
static int setup_module(PyObject* m) { static int setup_module(PyObject* m) {
PyObject* d = PyModule_GetDict(m);
addMuxFlagToModule(m); addMuxFlagToModule(m);
addAnimFlagToModule(m); addAnimFlagToModule(m);
addTransparencyFlagToModule(m); addTransparencyFlagToModule(m);
PyDict_SetItemString(d, "webpdecoder_version", PyUnicode_FromString(WebPDecoderVersion_str()));
#ifdef HAVE_WEBPANIM #ifdef HAVE_WEBPANIM
/* Ready object types */ /* Ready object types */
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 || if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||

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,
@ -113,4 +113,13 @@ err:
return result; return result;
} }
const char*
ImagingImageQuantVersion(void)
{
static char version[20];
int number = liq_version();
sprintf(version, "%d.%d.%d", number / 10000, (number / 100) % 100, number % 100);
return version;
}
#endif #endif

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

@ -373,7 +373,7 @@ ImagingZipEncodeCleanup(ImagingCodecState state) {
const char* const char*
ImagingZipVersion(void) ImagingZipVersion(void)
{ {
return ZLIB_VERSION; return zlibVersion();
} }
#endif #endif

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