mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-11 17:56:18 +03:00
Merge branch 'master' into fix_jpeg_magic_number
This commit is contained in:
commit
95ace8a39c
4
.github/workflows/macos-install.sh
vendored
4
.github/workflows/macos-install.sh
vendored
|
@ -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
|
||||
|
|
5
.github/workflows/test-windows.yml
vendored
5
.github/workflows/test-windows.yml
vendored
|
@ -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
|
||||
|
||||
|
|
21
CHANGES.rst
21
CHANGES.rst
|
@ -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
BIN
Tests/images/text_mono.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
Tests/images/transparent_background_text.png
Normal file
BIN
Tests/images/transparent_background_text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -1,4 +1,5 @@
|
|||
import io
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from PIL import features
|
||||
|
@ -21,6 +22,27 @@ def test_check():
|
|||
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")
|
||||
def test_webp_transparency():
|
||||
assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha()
|
||||
|
@ -37,9 +59,22 @@ def test_webp_anim():
|
|||
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():
|
||||
for feature in features.modules:
|
||||
assert features.check_module(feature) in [True, False]
|
||||
|
||||
|
||||
def test_check_codecs():
|
||||
for feature in features.codecs:
|
||||
assert features.check_codec(feature) in [True, False]
|
||||
|
||||
|
@ -64,6 +99,8 @@ def test_unsupported_codec():
|
|||
# Act / Assert
|
||||
with pytest.raises(ValueError):
|
||||
features.check_codec(codec)
|
||||
with pytest.raises(ValueError):
|
||||
features.version_codec(codec)
|
||||
|
||||
|
||||
def test_unsupported_module():
|
||||
|
@ -72,6 +109,8 @@ def test_unsupported_module():
|
|||
# Act / Assert
|
||||
with pytest.raises(ValueError):
|
||||
features.check_module(module)
|
||||
with pytest.raises(ValueError):
|
||||
features.version_module(module)
|
||||
|
||||
|
||||
def test_pilinfo():
|
||||
|
|
|
@ -2,14 +2,14 @@ import io
|
|||
import sys
|
||||
|
||||
import pytest
|
||||
from PIL import IcnsImagePlugin, Image
|
||||
from PIL import IcnsImagePlugin, Image, features
|
||||
|
||||
from .helper import assert_image_equal, assert_image_similar
|
||||
|
||||
# sample icon file
|
||||
TEST_FILE = "Tests/images/pillow.icns"
|
||||
|
||||
ENABLE_JPEG2K = hasattr(Image.core, "jp2klib_version")
|
||||
ENABLE_JPEG2K = features.check_codec("jpg_2000")
|
||||
|
||||
|
||||
def test_sanity():
|
||||
|
|
|
@ -3,7 +3,7 @@ import re
|
|||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, UnidentifiedImageError
|
||||
from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, UnidentifiedImageError, features
|
||||
|
||||
from .helper import (
|
||||
assert_image,
|
||||
|
@ -41,7 +41,7 @@ class TestFileJpeg:
|
|||
def test_sanity(self):
|
||||
|
||||
# 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:
|
||||
im.load()
|
||||
|
@ -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")
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import re
|
|||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
from PIL import Image, ImageFile, Jpeg2KImagePlugin
|
||||
from PIL import Image, ImageFile, Jpeg2KImagePlugin, features
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -35,7 +35,7 @@ def roundtrip(im, **options):
|
|||
|
||||
def test_sanity():
|
||||
# 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:
|
||||
px = im.load()
|
||||
|
|
|
@ -3,11 +3,12 @@ import io
|
|||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from ctypes import c_float
|
||||
|
||||
import pytest
|
||||
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags
|
||||
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -47,6 +48,9 @@ class 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):
|
||||
"""Test the ordinary file path load path"""
|
||||
|
||||
|
@ -299,9 +303,6 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
)
|
||||
continue
|
||||
|
||||
if libtiff and isinstance(value, bytes):
|
||||
value = value.decode()
|
||||
|
||||
assert reloaded_value == value
|
||||
|
||||
# Test with types
|
||||
|
@ -322,6 +323,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 +460,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 +687,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:
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import zlib
|
|||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
from PIL import Image, ImageFile, PngImagePlugin
|
||||
from PIL import Image, ImageFile, PngImagePlugin, features
|
||||
|
||||
from .helper import (
|
||||
PillowLeakTestCase,
|
||||
|
@ -73,7 +73,7 @@ class TestFilePng:
|
|||
def test_sanity(self, tmp_path):
|
||||
|
||||
# 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")
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import io
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from PIL import Image, WebPImagePlugin
|
||||
from PIL import Image, WebPImagePlugin, features
|
||||
|
||||
from .helper import (
|
||||
assert_image_similar,
|
||||
|
@ -36,6 +39,7 @@ class TestFileWebp:
|
|||
def test_version(self):
|
||||
_webp.WebPDecoderVersion()
|
||||
_webp.WebPDecoderBuggyAlpha()
|
||||
assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp"))
|
||||
|
||||
def test_read_rgb(self):
|
||||
"""
|
||||
|
@ -54,15 +58,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 +69,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 +108,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 +116,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):
|
||||
"""
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -4,7 +4,7 @@ import re
|
|||
from io import BytesIO
|
||||
|
||||
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
|
||||
|
||||
|
@ -46,7 +46,7 @@ def test_sanity():
|
|||
assert list(map(type, v)) == [str, str, str, str]
|
||||
|
||||
# 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()
|
||||
i = ImageCms.profileToProfile(hopper(), SRGB, SRGB)
|
||||
|
|
|
@ -7,10 +7,11 @@ import sys
|
|||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from PIL import Image, ImageDraw, ImageFont, features
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
assert_image_equal_tofile,
|
||||
assert_image_similar,
|
||||
assert_image_similar_tofile,
|
||||
is_pypy,
|
||||
|
@ -40,7 +41,7 @@ class TestImageFont:
|
|||
|
||||
@classmethod
|
||||
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"]
|
||||
for conditions, metrics in self.METRICS.items():
|
||||
|
@ -67,7 +68,7 @@ class TestImageFont:
|
|||
)
|
||||
|
||||
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):
|
||||
ttf = self.get_font()
|
||||
|
@ -150,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)
|
||||
|
@ -607,7 +620,7 @@ class TestImageFont:
|
|||
def test_variation_get(self):
|
||||
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":
|
||||
with pytest.raises(NotImplementedError):
|
||||
font.get_variation_names()
|
||||
|
@ -679,7 +692,7 @@ class TestImageFont:
|
|||
def test_variation_set_by_name(self):
|
||||
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":
|
||||
with pytest.raises(NotImplementedError):
|
||||
font.set_variation_by_name("Bold")
|
||||
|
@ -703,7 +716,7 @@ class TestImageFont:
|
|||
def test_variation_set_by_axes(self):
|
||||
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":
|
||||
with pytest.raises(NotImplementedError):
|
||||
font.set_variation_by_axes([100])
|
||||
|
@ -724,3 +737,19 @@ class TestImageFont:
|
|||
@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")
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from PIL import Image, ImageGrab
|
||||
|
||||
from .helper import assert_image
|
||||
from .helper import assert_image, assert_image_equal_tofile
|
||||
|
||||
|
||||
class TestImageGrab:
|
||||
|
@ -71,3 +72,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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.check
|
||||
.. autofunction:: PIL.features.version
|
||||
.. autofunction:: PIL.features.get_supported
|
||||
|
||||
Modules
|
||||
|
@ -16,28 +17,31 @@ Modules
|
|||
Support for the following modules can be checked:
|
||||
|
||||
* ``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`.
|
||||
* ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`.
|
||||
* ``webp``: WebP image support.
|
||||
|
||||
.. autofunction:: PIL.features.check_module
|
||||
.. autofunction:: PIL.features.version_module
|
||||
.. autofunction:: PIL.features.get_supported_modules
|
||||
|
||||
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.
|
||||
Except for ``jpg``, the version number is checked at run-time.
|
||||
|
||||
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.
|
||||
* ``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.version_codec
|
||||
.. autofunction:: PIL.features.get_supported_codecs
|
||||
|
||||
Features
|
||||
|
@ -45,16 +49,18 @@ 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.
|
||||
Feature version numbers are available only where stated.
|
||||
|
||||
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.
|
||||
* ``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`.
|
||||
* ``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`. Run-time version number is available.
|
||||
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.
|
||||
|
||||
.. autofunction:: PIL.features.check_feature
|
||||
.. autofunction:: PIL.features.version_feature
|
||||
.. autofunction:: PIL.features.get_supported_features
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -23,10 +23,10 @@ import subprocess
|
|||
import sys
|
||||
import tempfile
|
||||
|
||||
from PIL import Image, ImageFile, PngImagePlugin
|
||||
from PIL import Image, ImageFile, PngImagePlugin, features
|
||||
from PIL._binary import i8
|
||||
|
||||
enable_jpeg2k = hasattr(Image.core, "jp2klib_version")
|
||||
enable_jpeg2k = features.check_codec("jpg_2000")
|
||||
if enable_jpeg2k:
|
||||
from PIL import Jpeg2KImagePlugin
|
||||
|
||||
|
|
|
@ -3139,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)
|
||||
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -8,11 +8,11 @@ import PIL
|
|||
from . import Image
|
||||
|
||||
modules = {
|
||||
"pil": "PIL._imaging",
|
||||
"tkinter": "PIL._tkinter_finder",
|
||||
"freetype2": "PIL._imagingft",
|
||||
"littlecms2": "PIL._imagingcms",
|
||||
"webp": "PIL._webp",
|
||||
"pil": ("PIL._imaging", "PILLOW_VERSION"),
|
||||
"tkinter": ("PIL._tkinter_finder", None),
|
||||
"freetype2": ("PIL._imagingft", "freetype2_version"),
|
||||
"littlecms2": ("PIL._imagingcms", "littlecms_version"),
|
||||
"webp": ("PIL._webp", "webpdecoder_version"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -27,7 +27,7 @@ def check_module(feature):
|
|||
if not (feature in modules):
|
||||
raise ValueError("Unknown module %s" % feature)
|
||||
|
||||
module = modules[feature]
|
||||
module, ver = modules[feature]
|
||||
|
||||
try:
|
||||
__import__(module)
|
||||
|
@ -36,6 +36,24 @@ def check_module(feature):
|
|||
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():
|
||||
"""
|
||||
: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)]
|
||||
|
||||
|
||||
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):
|
||||
|
@ -57,11 +80,32 @@ def check_codec(feature):
|
|||
if feature not in codecs:
|
||||
raise ValueError("Unknown codec %s" % feature)
|
||||
|
||||
codec = codecs[feature]
|
||||
codec, lib = codecs[feature]
|
||||
|
||||
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():
|
||||
"""
|
||||
:returns: A list of all supported codecs.
|
||||
|
@ -70,13 +114,13 @@ def get_supported_codecs():
|
|||
|
||||
|
||||
features = {
|
||||
"webp_anim": ("PIL._webp", "HAVE_WEBPANIM"),
|
||||
"webp_mux": ("PIL._webp", "HAVE_WEBPMUX"),
|
||||
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"),
|
||||
"raqm": ("PIL._imagingft", "HAVE_RAQM"),
|
||||
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"),
|
||||
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT"),
|
||||
"xcb": ("PIL._imaging", "HAVE_XCB"),
|
||||
"webp_anim": ("PIL._webp", "HAVE_WEBPANIM", None),
|
||||
"webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None),
|
||||
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None),
|
||||
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
|
||||
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
|
||||
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
|
||||
"xcb": ("PIL._imaging", "HAVE_XCB", None),
|
||||
}
|
||||
|
||||
|
||||
|
@ -91,7 +135,7 @@ def check_feature(feature):
|
|||
if feature not in features:
|
||||
raise ValueError("Unknown feature %s" % feature)
|
||||
|
||||
module, flag = features[feature]
|
||||
module, flag, ver = features[feature]
|
||||
|
||||
try:
|
||||
imported_module = __import__(module, fromlist=["PIL"])
|
||||
|
@ -100,6 +144,23 @@ def check_feature(feature):
|
|||
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():
|
||||
"""
|
||||
:returns: A list of all supported features.
|
||||
|
@ -109,9 +170,9 @@ def get_supported_features():
|
|||
|
||||
def check(feature):
|
||||
"""
|
||||
:param feature: A module, feature, or codec name.
|
||||
:param feature: A module, codec, or feature name.
|
||||
:returns:
|
||||
``True`` if the module, feature, or codec is available,
|
||||
``True`` if the module, codec, or feature is available,
|
||||
``False`` or ``None`` otherwise.
|
||||
"""
|
||||
|
||||
|
@ -125,6 +186,22 @@ def check(feature):
|
|||
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():
|
||||
"""
|
||||
: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)"),
|
||||
]:
|
||||
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:
|
||||
print("***", feature, "support not installed", file=out)
|
||||
print("-" * 68, file=out)
|
||||
|
|
|
@ -4168,12 +4168,21 @@ setup_module(PyObject* m) {
|
|||
|
||||
#ifdef LIBJPEG_TURBO_VERSION
|
||||
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
|
||||
PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_False);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBIMAGEQUANT
|
||||
PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_True);
|
||||
{
|
||||
extern const char* ImagingImageQuantVersion(void);
|
||||
PyDict_SetItemString(d, "imagequant_version", PyUnicode_FromString(ImagingImageQuantVersion()));
|
||||
}
|
||||
#else
|
||||
PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_False);
|
||||
#endif
|
||||
|
|
|
@ -1608,6 +1608,7 @@ static int
|
|||
setup_module(PyObject* m) {
|
||||
PyObject *d;
|
||||
PyObject *v;
|
||||
int vn;
|
||||
|
||||
d = PyModule_GetDict(m);
|
||||
|
||||
|
@ -1622,7 +1623,8 @@ setup_module(PyObject* 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);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -81,6 +81,7 @@ typedef struct {
|
|||
|
||||
static PyTypeObject Font_Type;
|
||||
|
||||
typedef const char* (*t_raqm_version_string) (void);
|
||||
typedef bool (*t_raqm_version_atleast)(unsigned int major,
|
||||
unsigned int minor,
|
||||
unsigned int micro);
|
||||
|
@ -112,6 +113,7 @@ typedef void (*t_raqm_destroy) (raqm_t *rq);
|
|||
typedef struct {
|
||||
void* raqm;
|
||||
int version;
|
||||
t_raqm_version_string version_string;
|
||||
t_raqm_version_atleast version_atleast;
|
||||
t_raqm_create create;
|
||||
t_raqm_set_text set_text;
|
||||
|
@ -173,6 +175,7 @@ setraqm(void)
|
|||
}
|
||||
|
||||
#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.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");
|
||||
|
@ -206,6 +209,7 @@ setraqm(void)
|
|||
return 2;
|
||||
}
|
||||
#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.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");
|
||||
|
@ -609,6 +613,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 +624,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 +647,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);
|
||||
}
|
||||
|
@ -1251,6 +1261,9 @@ setup_module(PyObject* m) {
|
|||
setraqm();
|
||||
v = PyBool_FromLong(!!p_raqm.raqm);
|
||||
PyDict_SetItemString(d, "HAVE_RAQM", v);
|
||||
if (p_raqm.version_string) {
|
||||
PyDict_SetItemString(d, "raqm_version", PyUnicode_FromString(p_raqm.version_string()));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
110
src/_webp.c
110
src/_webp.c
|
@ -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);
|
||||
|
@ -794,6 +821,16 @@ PyObject* WebPDecoderVersion_wrapper() {
|
|||
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.
|
||||
* 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) {
|
||||
PyObject* d = PyModule_GetDict(m);
|
||||
addMuxFlagToModule(m);
|
||||
addAnimFlagToModule(m);
|
||||
addTransparencyFlagToModule(m);
|
||||
|
||||
PyDict_SetItemString(d, "webpdecoder_version", PyUnicode_FromString(WebPDecoderVersion_str()));
|
||||
|
||||
#ifdef HAVE_WEBPANIM
|
||||
/* Ready object types */
|
||||
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
32
src/encode.c
32
src/encode.c
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
@ -113,4 +113,13 @@ err:
|
|||
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
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
#include "QuantTypes.h"
|
||||
|
||||
int quantize_pngquant(Pixel *,
|
||||
int,
|
||||
int,
|
||||
unsigned int,
|
||||
unsigned int,
|
||||
uint32_t,
|
||||
Pixel **,
|
||||
uint32_t *,
|
||||
|
|
|
@ -373,7 +373,7 @@ ImagingZipEncodeCleanup(ImagingCodecState state) {
|
|||
const char*
|
||||
ImagingZipVersion(void)
|
||||
{
|
||||
return ZLIB_VERSION;
|
||||
return zlibVersion();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user