mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-27 10:26:19 +03:00
Merge branch 'master' into eps
This commit is contained in:
commit
929c561937
33
CHANGES.rst
33
CHANGES.rst
|
@ -5,6 +5,39 @@ Changelog (Pillow)
|
||||||
8.4.0 (unreleased)
|
8.4.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Consider I;16 pixel size when drawing text #5598
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- If default conversion from P is RGB with transparency, convert to RGBA #5594
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Speed up rotating square images by 90 or 270 degrees #5646
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add support for reading DPI information from JPEG2000 images
|
||||||
|
[rogermb, radarhere]
|
||||||
|
|
||||||
|
- Catch TypeError from corrupted DPI value in EXIF #5639
|
||||||
|
[homm, radarhere]
|
||||||
|
|
||||||
|
- Do not close file pointer when saving SGI images #5645
|
||||||
|
[farizrahman4u, radarhere]
|
||||||
|
|
||||||
|
- Deprecate ImagePalette size parameter #5641
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Prefer command line tools SDK on macOS #5624
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added tags when saving YCbCr TIFF #5597
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- PSD layer count may be negative #5613
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed ImageOps expand with tuple border on P image #5615
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Ensure TIFF RowsPerStrip is multiple of 8 for JPEG compression #5588
|
- Ensure TIFF RowsPerStrip is multiple of 8 for JPEG compression #5588
|
||||||
[kmilos, radarhere]
|
[kmilos, radarhere]
|
||||||
|
|
||||||
|
|
BIN
Tests/images/broken_exif_dpi.jpg
Normal file
BIN
Tests/images/broken_exif_dpi.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
BIN
Tests/images/expected_to_read.jp2
Normal file
BIN
Tests/images/expected_to_read.jp2
Normal file
Binary file not shown.
BIN
Tests/images/invalid_header_length.jp2
Normal file
BIN
Tests/images/invalid_header_length.jp2
Normal file
Binary file not shown.
BIN
Tests/images/negative_layer_count.psd
Normal file
BIN
Tests/images/negative_layer_count.psd
Normal file
Binary file not shown.
BIN
Tests/images/not_enough_data.jp2
Normal file
BIN
Tests/images/not_enough_data.jp2
Normal file
Binary file not shown.
BIN
Tests/images/zero_dpi.jp2
Normal file
BIN
Tests/images/zero_dpi.jp2
Normal file
Binary file not shown.
|
@ -718,6 +718,15 @@ class TestFileJpeg:
|
||||||
# This should return the default, and not raise a ZeroDivisionError
|
# This should return the default, and not raise a ZeroDivisionError
|
||||||
assert im.info.get("dpi") == (72, 72)
|
assert im.info.get("dpi") == (72, 72)
|
||||||
|
|
||||||
|
def test_dpi_exif_string(self):
|
||||||
|
# Arrange
|
||||||
|
# 0x011A tag in this exif contains string '300300\x02'
|
||||||
|
with Image.open("Tests/images/broken_exif_dpi.jpg") as im:
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
# This should return the default
|
||||||
|
assert im.info.get("dpi") == (72, 72)
|
||||||
|
|
||||||
def test_no_dpi_in_exif(self):
|
def test_no_dpi_in_exif(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
# This is photoshop-200dpi.jpg with resolution removed from EXIF:
|
# This is photoshop-200dpi.jpg with resolution removed from EXIF:
|
||||||
|
|
|
@ -4,7 +4,7 @@ from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFile, Jpeg2KImagePlugin, features
|
from PIL import Image, ImageFile, Jpeg2KImagePlugin, UnidentifiedImageError, features
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -151,6 +151,28 @@ def test_reduce():
|
||||||
assert im.size == (40, 30)
|
assert im.size == (40, 30)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_dpi():
|
||||||
|
with Image.open("Tests/images/test-card-lossless.jp2") as im:
|
||||||
|
assert im.info["dpi"] == (71.9836, 71.9836)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/zero_dpi.jp2") as im:
|
||||||
|
assert "dpi" not in im.info
|
||||||
|
|
||||||
|
|
||||||
|
def test_header_errors():
|
||||||
|
for path in (
|
||||||
|
"Tests/images/invalid_header_length.jp2",
|
||||||
|
"Tests/images/not_enough_data.jp2",
|
||||||
|
):
|
||||||
|
with pytest.raises(UnidentifiedImageError):
|
||||||
|
with Image.open(path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
with Image.open("Tests/images/expected_to_read.jp2"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_layers_type(tmp_path):
|
def test_layers_type(tmp_path):
|
||||||
outfile = str(tmp_path / "temp_layers.jp2")
|
outfile = str(tmp_path / "temp_layers.jp2")
|
||||||
for quality_layers in [[100, 50, 10], (100, 50, 10), None]:
|
for quality_layers in [[100, 50, 10], (100, 50, 10), None]:
|
||||||
|
|
|
@ -670,6 +670,15 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = False
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
TiffImagePlugin.READ_LIBTIFF = False
|
TiffImagePlugin.READ_LIBTIFF = False
|
||||||
|
|
||||||
|
def test_save_ycbcr(self, tmp_path):
|
||||||
|
im = hopper("YCbCr")
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
im.save(outfile, compression="jpeg")
|
||||||
|
|
||||||
|
with Image.open(outfile) as reloaded:
|
||||||
|
assert reloaded.tag_v2[530] == (1, 1)
|
||||||
|
assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255)
|
||||||
|
|
||||||
def test_crashing_metadata(self, tmp_path):
|
def test_crashing_metadata(self, tmp_path):
|
||||||
# issue 1597
|
# issue 1597
|
||||||
with Image.open("Tests/images/rdf.tif") as im:
|
with Image.open("Tests/images/rdf.tif") as im:
|
||||||
|
|
|
@ -57,7 +57,8 @@ def test_n_frames():
|
||||||
assert im.n_frames == 1
|
assert im.n_frames == 1
|
||||||
assert not im.is_animated
|
assert not im.is_animated
|
||||||
|
|
||||||
with Image.open(test_file) as im:
|
for path in [test_file, "Tests/images/negative_layer_count.psd"]:
|
||||||
|
with Image.open(path) as im:
|
||||||
assert im.n_frames == 2
|
assert im.n_frames == 2
|
||||||
assert im.is_animated
|
assert im.is_animated
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,13 @@ def test_write(tmp_path):
|
||||||
img.save(out, format="sgi")
|
img.save(out, format="sgi")
|
||||||
assert_image_equal_tofile(img, out)
|
assert_image_equal_tofile(img, out)
|
||||||
|
|
||||||
|
out = str(tmp_path / "fp.sgi")
|
||||||
|
with open(out, "wb") as fp:
|
||||||
|
img.save(fp)
|
||||||
|
assert_image_equal_tofile(img, out)
|
||||||
|
|
||||||
|
assert not fp.closed
|
||||||
|
|
||||||
for mode in ("L", "RGB", "RGBA"):
|
for mode in ("L", "RGB", "RGBA"):
|
||||||
roundtrip(hopper(mode))
|
roundtrip(hopper(mode))
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,13 @@ class TestFileWebp:
|
||||||
hopper().save(buffer_method, format="WEBP", method=6)
|
hopper().save(buffer_method, format="WEBP", method=6)
|
||||||
assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
|
assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
|
||||||
|
|
||||||
|
def test_icc_profile(self, tmp_path):
|
||||||
|
self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None})
|
||||||
|
if _webp.HAVE_WEBPANIM:
|
||||||
|
self._roundtrip(
|
||||||
|
tmp_path, self.rgb_mode, 12.5, {"icc_profile": None, "save_all": True}
|
||||||
|
)
|
||||||
|
|
||||||
def test_write_unsupported_mode_L(self, tmp_path):
|
def test_write_unsupported_mode_L(self, tmp_path):
|
||||||
"""
|
"""
|
||||||
Saving a black-and-white file to WebP format should work, and be
|
Saving a black-and-white file to WebP format should work, and be
|
||||||
|
|
|
@ -42,10 +42,14 @@ def test_default():
|
||||||
|
|
||||||
im = hopper("P")
|
im = hopper("P")
|
||||||
assert_image(im, "P", im.size)
|
assert_image(im, "P", im.size)
|
||||||
im = im.convert()
|
converted_im = im.convert()
|
||||||
assert_image(im, "RGB", im.size)
|
assert_image(converted_im, "RGB", im.size)
|
||||||
im = im.convert()
|
converted_im = im.convert()
|
||||||
assert_image(im, "RGB", im.size)
|
assert_image(converted_im, "RGB", im.size)
|
||||||
|
|
||||||
|
im.info["transparency"] = 0
|
||||||
|
converted_im = im.convert()
|
||||||
|
assert_image(converted_im, "RGBA", im.size)
|
||||||
|
|
||||||
|
|
||||||
# ref https://github.com/python-pillow/Pillow/issues/274
|
# ref https://github.com/python-pillow/Pillow/issues/274
|
||||||
|
|
|
@ -33,6 +33,9 @@ def test_angle():
|
||||||
with Image.open("Tests/images/test-card.png") as im:
|
with Image.open("Tests/images/test-card.png") as im:
|
||||||
rotate(im, im.mode, angle)
|
rotate(im, im.mode, angle)
|
||||||
|
|
||||||
|
im = hopper()
|
||||||
|
assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
|
||||||
|
|
||||||
|
|
||||||
def test_zero():
|
def test_zero():
|
||||||
for angle in (0, 45, 90, 180, 270):
|
for angle in (0, 45, 90, 180, 270):
|
||||||
|
|
|
@ -134,6 +134,17 @@ class TestImageFont:
|
||||||
target = "Tests/images/transparent_background_text_L.png"
|
target = "Tests/images/transparent_background_text_L.png"
|
||||||
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
||||||
|
|
||||||
|
def test_I16(self):
|
||||||
|
im = Image.new(mode="I;16", 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_L.png"
|
||||||
|
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -156,21 +156,29 @@ def test_scale():
|
||||||
assert newimg.size == (25, 25)
|
assert newimg.size == (25, 25)
|
||||||
|
|
||||||
|
|
||||||
def test_expand_palette():
|
@pytest.mark.parametrize("border", (10, (1, 2, 3, 4)))
|
||||||
im = Image.open("Tests/images/p_16.tga")
|
def test_expand_palette(border):
|
||||||
im_expanded = ImageOps.expand(im, 10, (255, 0, 0))
|
with Image.open("Tests/images/p_16.tga") as im:
|
||||||
|
im_expanded = ImageOps.expand(im, border, (255, 0, 0))
|
||||||
|
|
||||||
|
if isinstance(border, int):
|
||||||
|
left = top = right = bottom = border
|
||||||
|
else:
|
||||||
|
left, top, right, bottom = border
|
||||||
px = im_expanded.convert("RGB").load()
|
px = im_expanded.convert("RGB").load()
|
||||||
for b in range(10):
|
|
||||||
for x in range(im_expanded.width):
|
for x in range(im_expanded.width):
|
||||||
|
for b in range(top):
|
||||||
assert px[x, b] == (255, 0, 0)
|
assert px[x, b] == (255, 0, 0)
|
||||||
|
for b in range(bottom):
|
||||||
assert px[x, im_expanded.height - 1 - b] == (255, 0, 0)
|
assert px[x, im_expanded.height - 1 - b] == (255, 0, 0)
|
||||||
for y in range(im_expanded.height):
|
for y in range(im_expanded.height):
|
||||||
assert px[b, x] == (255, 0, 0)
|
for b in range(left):
|
||||||
assert px[b, im_expanded.width - 1 - b] == (255, 0, 0)
|
assert px[b, y] == (255, 0, 0)
|
||||||
|
for b in range(right):
|
||||||
|
assert px[im_expanded.width - 1 - b, y] == (255, 0, 0)
|
||||||
|
|
||||||
im_cropped = im_expanded.crop(
|
im_cropped = im_expanded.crop(
|
||||||
(10, 10, im_expanded.width - 10, im_expanded.height - 10)
|
(left, top, im_expanded.width - right, im_expanded.height - bottom)
|
||||||
)
|
)
|
||||||
assert_image_equal(im_cropped, im)
|
assert_image_equal(im_cropped, im)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ def test_sanity():
|
||||||
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
||||||
assert len(palette.colors) == 256
|
assert len(palette.colors) == 256
|
||||||
|
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ImagePalette.ImagePalette("RGB", list(range(256)) * 3, 10)
|
ImagePalette.ImagePalette("RGB", list(range(256)) * 3, 10)
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,17 @@ dictionary. The :py:attr:`~PIL.JpegImagePlugin.convert_dict_qtables` method no l
|
||||||
performs any operations on the data given to it, has been deprecated and will be
|
performs any operations on the data given to it, has been deprecated and will be
|
||||||
removed in Pillow 10.0.0 (2023-01-02).
|
removed in Pillow 10.0.0 (2023-01-02).
|
||||||
|
|
||||||
|
ImagePalette size parameter
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 8.4.0
|
||||||
|
|
||||||
|
The ``size`` parameter will be removed in Pillow 10.0.0 (2023-01-02).
|
||||||
|
|
||||||
|
Before Pillow 8.3.0, ``ImagePalette`` required palette data of particular lengths by
|
||||||
|
default, and the size parameter could be used to override that. Pillow 8.3.0 removed
|
||||||
|
the default required length, also removing the need for the size parameter.
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,9 @@ Pillow supports these Python versions.
|
||||||
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
|
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||||
| Python |3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 3.5 | 3.4 | 2.7 |
|
| Python |3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 3.5 | 3.4 | 2.7 |
|
||||||
+======================+=====+=====+=====+=====+=====+=====+=====+=====+
|
+======================+=====+=====+=====+=====+=====+=====+=====+=====+
|
||||||
| Pillow >= 8.3 | Yes | Yes | Yes | Yes | Yes | | | |
|
| Pillow >= 8.4 | Yes | Yes | Yes | Yes | Yes | | | |
|
||||||
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
|
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||||
| Pillow 8.0 - 8.2 | | Yes | Yes | Yes | Yes | | | |
|
| Pillow 8.0 - 8.3 | | Yes | Yes | Yes | Yes | | | |
|
||||||
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
|
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||||
| Pillow 7.0 - 7.2 | | | Yes | Yes | Yes | Yes | | |
|
| Pillow 7.0 - 7.2 | | | Yes | Yes | Yes | Yes | | |
|
||||||
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
|
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||||
|
@ -494,11 +494,11 @@ These platforms have been reported to work at the versions mentioned.
|
||||||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||||
| | | versions | | Pillow version | | processors |
|
| | | versions | | Pillow version | | processors |
|
||||||
+==================================+===========================+==================+==============+
|
+==================================+===========================+==================+==============+
|
||||||
| macOS 11.0 Big Sur | 3.7, 3.8, 3.9 | 8.2.0 |arm |
|
| macOS 11.0 Big Sur | 3.7, 3.8, 3.9 | 8.3.1 |arm |
|
||||||
| +---------------------------+------------------+--------------+
|
| +---------------------------+------------------+--------------+
|
||||||
| | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |x86-64 |
|
| | 3.6, 3.7, 3.8, 3.9 | 8.3.1 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+---------------------------+------------------+--------------+
|
||||||
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 |
|
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.1 |x86-64 |
|
||||||
| +---------------------------+------------------+ |
|
| +---------------------------+------------------+ |
|
||||||
| | 3.5 | 7.2.0 | |
|
| | 3.5 | 7.2.0 | |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+---------------------------+------------------+--------------+
|
||||||
|
|
47
docs/releasenotes/8.4.0.rst
Normal file
47
docs/releasenotes/8.4.0.rst
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
8.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
API Changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
ImagePalette size parameter
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``size`` parameter will be removed in Pillow 10.0.0 (2023-01-02).
|
||||||
|
|
||||||
|
Before Pillow 8.3.0, ``ImagePalette`` required palette data of particular lengths by
|
||||||
|
default, and the size parameter could be used to override that. Pillow 8.3.0 removed
|
||||||
|
the default required length, also removing the need for the size parameter.
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Speed improvement when rotating square images
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Starting with Pillow 3.3.0, the speed of rotating images by 90 or 270 degrees was
|
||||||
|
improved by quickly returning :py:meth:`~PIL.Image.Image.transpose` instead, if the
|
||||||
|
rotate operation allowed for expansion and did not specify a center or post-rotate
|
||||||
|
translation.
|
||||||
|
|
||||||
|
Since the ``expand`` flag makes no difference for square images though, Pillow now
|
||||||
|
uses this faster method for square images without the ``expand`` flag as well.
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
8.4.0
|
||||||
8.3.1
|
8.3.1
|
||||||
8.3.0
|
8.3.0
|
||||||
8.2.0
|
8.2.0
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -533,6 +533,8 @@ class pil_build_ext(build_ext):
|
||||||
_add_directory(include_dirs, "/usr/X11/include")
|
_add_directory(include_dirs, "/usr/X11/include")
|
||||||
|
|
||||||
# SDK install path
|
# SDK install path
|
||||||
|
sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
|
||||||
|
if not os.path.exists(sdk_path):
|
||||||
try:
|
try:
|
||||||
sdk_path = (
|
sdk_path = (
|
||||||
subprocess.check_output(["xcrun", "--show-sdk-path"])
|
subprocess.check_output(["xcrun", "--show-sdk-path"])
|
||||||
|
|
|
@ -914,16 +914,18 @@ class Image:
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
|
has_transparency = self.info.get("transparency") is not None
|
||||||
if not mode and self.mode == "P":
|
if not mode and self.mode == "P":
|
||||||
# determine default mode
|
# determine default mode
|
||||||
if self.palette:
|
if self.palette:
|
||||||
mode = self.palette.mode
|
mode = self.palette.mode
|
||||||
else:
|
else:
|
||||||
mode = "RGB"
|
mode = "RGB"
|
||||||
|
if mode == "RGB" and has_transparency:
|
||||||
|
mode = "RGBA"
|
||||||
if not mode or (mode == self.mode and not matrix):
|
if not mode or (mode == self.mode and not matrix):
|
||||||
return self.copy()
|
return self.copy()
|
||||||
|
|
||||||
has_transparency = self.info.get("transparency") is not None
|
|
||||||
if matrix:
|
if matrix:
|
||||||
# matrix conversion
|
# matrix conversion
|
||||||
if mode not in ("L", "RGB"):
|
if mode not in ("L", "RGB"):
|
||||||
|
@ -2074,10 +2076,8 @@ class Image:
|
||||||
return self.copy()
|
return self.copy()
|
||||||
if angle == 180:
|
if angle == 180:
|
||||||
return self.transpose(ROTATE_180)
|
return self.transpose(ROTATE_180)
|
||||||
if angle == 90 and expand:
|
if angle in (90, 270) and (expand or self.width == self.height):
|
||||||
return self.transpose(ROTATE_90)
|
return self.transpose(ROTATE_90 if angle == 90 else ROTATE_270)
|
||||||
if angle == 270 and expand:
|
|
||||||
return self.transpose(ROTATE_270)
|
|
||||||
|
|
||||||
# Calculate the affine matrix. Note that this is the reverse
|
# Calculate the affine matrix. Note that this is the reverse
|
||||||
# transformation (from destination image to source) because we
|
# transformation (from destination image to source) because we
|
||||||
|
|
|
@ -21,7 +21,7 @@ import functools
|
||||||
import operator
|
import operator
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from . import Image, ImageDraw
|
from . import Image
|
||||||
|
|
||||||
#
|
#
|
||||||
# helpers
|
# helpers
|
||||||
|
@ -395,14 +395,15 @@ def expand(image, border=0, fill=0):
|
||||||
height = top + image.size[1] + bottom
|
height = top + image.size[1] + bottom
|
||||||
color = _color(fill, image.mode)
|
color = _color(fill, image.mode)
|
||||||
if image.mode == "P" and image.palette:
|
if image.mode == "P" and image.palette:
|
||||||
out = Image.new(image.mode, (width, height))
|
image.load()
|
||||||
out.putpalette(image.palette)
|
palette = image.palette.copy()
|
||||||
out.paste(image, (left, top))
|
if isinstance(color, tuple):
|
||||||
|
color = palette.getcolor(color)
|
||||||
draw = ImageDraw.Draw(out)
|
|
||||||
draw.rectangle((0, 0, width - 1, height - 1), outline=color, width=border)
|
|
||||||
else:
|
else:
|
||||||
|
palette = None
|
||||||
out = Image.new(image.mode, (width, height), color)
|
out = Image.new(image.mode, (width, height), color)
|
||||||
|
if palette:
|
||||||
|
out.putpalette(palette.palette)
|
||||||
out.paste(image, (left, top))
|
out.paste(image, (left, top))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import array
|
import array
|
||||||
|
import warnings
|
||||||
|
|
||||||
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
|
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
|
||||||
|
|
||||||
|
@ -28,12 +29,11 @@ class ImagePalette:
|
||||||
:param mode: The mode to use for the Palette. See:
|
:param mode: The mode to use for the Palette. See:
|
||||||
:ref:`concept-modes`. Defaults to "RGB"
|
:ref:`concept-modes`. Defaults to "RGB"
|
||||||
:param palette: An optional palette. If given, it must be a bytearray,
|
:param palette: An optional palette. If given, it must be a bytearray,
|
||||||
an array or a list of ints between 0-255 and of length ``size``
|
an array or a list of ints between 0-255. The list must be aligned
|
||||||
times the number of colors in ``mode``. The list must be aligned
|
|
||||||
by channel (All R values must be contiguous in the list before G
|
by channel (All R values must be contiguous in the list before G
|
||||||
and B values.) Defaults to 0 through 255 per channel.
|
and B values.) Defaults to 0 through 255 per channel.
|
||||||
:param size: An optional palette size. If given, it cannot be equal to
|
:param size: An optional palette size. If given, an error is raised
|
||||||
or greater than 256. Defaults to 0.
|
if ``palette`` is not of equal length.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, mode="RGB", palette=None, size=0):
|
def __init__(self, mode="RGB", palette=None, size=0):
|
||||||
|
@ -41,7 +41,13 @@ class ImagePalette:
|
||||||
self.rawmode = None # if set, palette contains raw data
|
self.rawmode = None # if set, palette contains raw data
|
||||||
self.palette = palette or bytearray()
|
self.palette = palette or bytearray()
|
||||||
self.dirty = None
|
self.dirty = None
|
||||||
if size != 0 and size != len(self.palette):
|
if size != 0:
|
||||||
|
warnings.warn(
|
||||||
|
"The size parameter is deprecated and will be removed in Pillow 10 "
|
||||||
|
"(2023-01-02).",
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
if size != len(self.palette):
|
||||||
raise ValueError("wrong palette size")
|
raise ValueError("wrong palette size")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#
|
#
|
||||||
# History:
|
# History:
|
||||||
# 2014-03-12 ajh Created
|
# 2014-03-12 ajh Created
|
||||||
|
# 2021-06-30 rogermb Extract dpi information from the 'resc' header box
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014 Coriolis Systems Limited
|
# Copyright (c) 2014 Coriolis Systems Limited
|
||||||
# Copyright (c) 2014 Alastair Houghton
|
# Copyright (c) 2014 Alastair Houghton
|
||||||
|
@ -19,6 +20,79 @@ import struct
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
|
||||||
|
|
||||||
|
class BoxReader:
|
||||||
|
"""
|
||||||
|
A small helper class to read fields stored in JPEG2000 header boxes
|
||||||
|
and to easily step into and read sub-boxes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, fp, length=-1):
|
||||||
|
self.fp = fp
|
||||||
|
self.has_length = length >= 0
|
||||||
|
self.length = length
|
||||||
|
self.remaining_in_box = -1
|
||||||
|
|
||||||
|
def _can_read(self, num_bytes):
|
||||||
|
if self.has_length and self.fp.tell() + num_bytes > self.length:
|
||||||
|
# Outside box: ensure we don't read past the known file length
|
||||||
|
return False
|
||||||
|
if self.remaining_in_box >= 0:
|
||||||
|
# Inside box contents: ensure read does not go past box boundaries
|
||||||
|
return num_bytes <= self.remaining_in_box
|
||||||
|
else:
|
||||||
|
return True # No length known, just read
|
||||||
|
|
||||||
|
def _read_bytes(self, num_bytes):
|
||||||
|
if not self._can_read(num_bytes):
|
||||||
|
raise SyntaxError("Not enough data in header")
|
||||||
|
|
||||||
|
data = self.fp.read(num_bytes)
|
||||||
|
if len(data) < num_bytes:
|
||||||
|
raise OSError(
|
||||||
|
f"Expected to read {num_bytes} bytes but only got {len(data)}."
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.remaining_in_box > 0:
|
||||||
|
self.remaining_in_box -= num_bytes
|
||||||
|
return data
|
||||||
|
|
||||||
|
def read_fields(self, field_format):
|
||||||
|
size = struct.calcsize(field_format)
|
||||||
|
data = self._read_bytes(size)
|
||||||
|
return struct.unpack(field_format, data)
|
||||||
|
|
||||||
|
def read_boxes(self):
|
||||||
|
size = self.remaining_in_box
|
||||||
|
data = self._read_bytes(size)
|
||||||
|
return BoxReader(io.BytesIO(data), size)
|
||||||
|
|
||||||
|
def has_next_box(self):
|
||||||
|
if self.has_length:
|
||||||
|
return self.fp.tell() + self.remaining_in_box < self.length
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def next_box_type(self):
|
||||||
|
# Skip the rest of the box if it has not been read
|
||||||
|
if self.remaining_in_box > 0:
|
||||||
|
self.fp.seek(self.remaining_in_box, os.SEEK_CUR)
|
||||||
|
self.remaining_in_box = -1
|
||||||
|
|
||||||
|
# Read the length and type of the next box
|
||||||
|
lbox, tbox = self.read_fields(">I4s")
|
||||||
|
if lbox == 1:
|
||||||
|
lbox = self.read_fields(">Q")[0]
|
||||||
|
hlen = 16
|
||||||
|
else:
|
||||||
|
hlen = 8
|
||||||
|
|
||||||
|
if lbox < hlen or not self._can_read(lbox - hlen):
|
||||||
|
raise SyntaxError("Invalid header length")
|
||||||
|
|
||||||
|
self.remaining_in_box = lbox - hlen
|
||||||
|
return tbox
|
||||||
|
|
||||||
|
|
||||||
def _parse_codestream(fp):
|
def _parse_codestream(fp):
|
||||||
"""Parse the JPEG 2000 codestream to extract the size and component
|
"""Parse the JPEG 2000 codestream to extract the size and component
|
||||||
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
||||||
|
@ -53,55 +127,45 @@ def _parse_codestream(fp):
|
||||||
return (size, mode)
|
return (size, mode)
|
||||||
|
|
||||||
|
|
||||||
|
def _res_to_dpi(num, denom, exp):
|
||||||
|
"""Convert JPEG2000's (numerator, denominator, exponent-base-10) resolution,
|
||||||
|
calculated as (num / denom) * 10^exp and stored in dots per meter,
|
||||||
|
to floating-point dots per inch."""
|
||||||
|
if denom != 0:
|
||||||
|
return (254 * num * (10 ** exp)) / (10000 * denom)
|
||||||
|
|
||||||
|
|
||||||
def _parse_jp2_header(fp):
|
def _parse_jp2_header(fp):
|
||||||
"""Parse the JP2 header box to extract size, component count and
|
"""Parse the JP2 header box to extract size, component count,
|
||||||
color space information, returning a (size, mode, mimetype) tuple."""
|
color space information, and optionally DPI information,
|
||||||
|
returning a (size, mode, mimetype, dpi) tuple."""
|
||||||
|
|
||||||
# Find the JP2 header box
|
# Find the JP2 header box
|
||||||
|
reader = BoxReader(fp)
|
||||||
header = None
|
header = None
|
||||||
mimetype = None
|
mimetype = None
|
||||||
while True:
|
while reader.has_next_box():
|
||||||
lbox, tbox = struct.unpack(">I4s", fp.read(8))
|
tbox = reader.next_box_type()
|
||||||
if lbox == 1:
|
|
||||||
lbox = struct.unpack(">Q", fp.read(8))[0]
|
|
||||||
hlen = 16
|
|
||||||
else:
|
|
||||||
hlen = 8
|
|
||||||
|
|
||||||
if lbox < hlen:
|
|
||||||
raise SyntaxError("Invalid JP2 header length")
|
|
||||||
|
|
||||||
if tbox == b"jp2h":
|
if tbox == b"jp2h":
|
||||||
header = fp.read(lbox - hlen)
|
header = reader.read_boxes()
|
||||||
break
|
break
|
||||||
elif tbox == b"ftyp":
|
elif tbox == b"ftyp":
|
||||||
if fp.read(4) == b"jpx ":
|
if reader.read_fields(">4s")[0] == b"jpx ":
|
||||||
mimetype = "image/jpx"
|
mimetype = "image/jpx"
|
||||||
fp.seek(lbox - hlen - 4, os.SEEK_CUR)
|
|
||||||
else:
|
|
||||||
fp.seek(lbox - hlen, os.SEEK_CUR)
|
|
||||||
|
|
||||||
if header is None:
|
|
||||||
raise SyntaxError("could not find JP2 header")
|
|
||||||
|
|
||||||
size = None
|
size = None
|
||||||
mode = None
|
mode = None
|
||||||
bpc = None
|
bpc = None
|
||||||
nc = None
|
nc = None
|
||||||
|
dpi = None # 2-tuple of DPI info, or None
|
||||||
|
unkc = 0 # Colorspace information unknown
|
||||||
|
|
||||||
hio = io.BytesIO(header)
|
while header.has_next_box():
|
||||||
while True:
|
tbox = header.next_box_type()
|
||||||
lbox, tbox = struct.unpack(">I4s", hio.read(8))
|
|
||||||
if lbox == 1:
|
|
||||||
lbox = struct.unpack(">Q", hio.read(8))[0]
|
|
||||||
hlen = 16
|
|
||||||
else:
|
|
||||||
hlen = 8
|
|
||||||
|
|
||||||
content = hio.read(lbox - hlen)
|
|
||||||
|
|
||||||
if tbox == b"ihdr":
|
if tbox == b"ihdr":
|
||||||
height, width, nc, bpc, c, unkc, ipr = struct.unpack(">IIHBBBB", content)
|
height, width, nc, bpc, c, unkc, ipr = header.read_fields(">IIHBBBB")
|
||||||
size = (width, height)
|
size = (width, height)
|
||||||
if unkc:
|
if unkc:
|
||||||
if nc == 1 and (bpc & 0x7F) > 8:
|
if nc == 1 and (bpc & 0x7F) > 8:
|
||||||
|
@ -114,11 +178,10 @@ def _parse_jp2_header(fp):
|
||||||
mode = "RGB"
|
mode = "RGB"
|
||||||
elif nc == 4:
|
elif nc == 4:
|
||||||
mode = "RGBA"
|
mode = "RGBA"
|
||||||
break
|
|
||||||
elif tbox == b"colr":
|
elif tbox == b"colr":
|
||||||
meth, prec, approx = struct.unpack_from(">BBB", content)
|
meth, prec, approx = header.read_fields(">BBB")
|
||||||
if meth == 1:
|
if meth == 1 and unkc == 0:
|
||||||
cs = struct.unpack_from(">I", content, 3)[0]
|
cs = header.read_fields(">I")[0]
|
||||||
if cs == 16: # sRGB
|
if cs == 16: # sRGB
|
||||||
if nc == 1 and (bpc & 0x7F) > 8:
|
if nc == 1 and (bpc & 0x7F) > 8:
|
||||||
mode = "I;16"
|
mode = "I;16"
|
||||||
|
@ -128,7 +191,6 @@ def _parse_jp2_header(fp):
|
||||||
mode = "RGB"
|
mode = "RGB"
|
||||||
elif nc == 4:
|
elif nc == 4:
|
||||||
mode = "RGBA"
|
mode = "RGBA"
|
||||||
break
|
|
||||||
elif cs == 17: # grayscale
|
elif cs == 17: # grayscale
|
||||||
if nc == 1 and (bpc & 0x7F) > 8:
|
if nc == 1 and (bpc & 0x7F) > 8:
|
||||||
mode = "I;16"
|
mode = "I;16"
|
||||||
|
@ -136,18 +198,27 @@ def _parse_jp2_header(fp):
|
||||||
mode = "L"
|
mode = "L"
|
||||||
elif nc == 2:
|
elif nc == 2:
|
||||||
mode = "LA"
|
mode = "LA"
|
||||||
break
|
|
||||||
elif cs == 18: # sYCC
|
elif cs == 18: # sYCC
|
||||||
if nc == 3:
|
if nc == 3:
|
||||||
mode = "RGB"
|
mode = "RGB"
|
||||||
elif nc == 4:
|
elif nc == 4:
|
||||||
mode = "RGBA"
|
mode = "RGBA"
|
||||||
|
elif tbox == b"res ":
|
||||||
|
res = header.read_boxes()
|
||||||
|
while res.has_next_box():
|
||||||
|
tres = res.next_box_type()
|
||||||
|
if tres == b"resc":
|
||||||
|
vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB")
|
||||||
|
hres = _res_to_dpi(hrcn, hrcd, hrce)
|
||||||
|
vres = _res_to_dpi(vrcn, vrcd, vrce)
|
||||||
|
if hres is not None and vres is not None:
|
||||||
|
dpi = (hres, vres)
|
||||||
break
|
break
|
||||||
|
|
||||||
if size is None or mode is None:
|
if size is None or mode is None:
|
||||||
raise SyntaxError("Malformed jp2 header")
|
raise SyntaxError("Malformed JP2 header")
|
||||||
|
|
||||||
return (size, mode, mimetype)
|
return (size, mode, mimetype, dpi)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -169,7 +240,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a":
|
if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a":
|
||||||
self.codec = "jp2"
|
self.codec = "jp2"
|
||||||
header = _parse_jp2_header(self.fp)
|
header = _parse_jp2_header(self.fp)
|
||||||
self._size, self.mode, self.custom_mimetype = header
|
self._size, self.mode, self.custom_mimetype, dpi = header
|
||||||
|
if dpi is not None:
|
||||||
|
self.info["dpi"] = dpi
|
||||||
else:
|
else:
|
||||||
raise SyntaxError("not a JPEG 2000 file")
|
raise SyntaxError("not a JPEG 2000 file")
|
||||||
|
|
||||||
|
|
|
@ -168,11 +168,11 @@ def APP(self, marker):
|
||||||
# 1 dpcm = 2.54 dpi
|
# 1 dpcm = 2.54 dpi
|
||||||
dpi *= 2.54
|
dpi *= 2.54
|
||||||
self.info["dpi"] = dpi, dpi
|
self.info["dpi"] = dpi, dpi
|
||||||
except (KeyError, SyntaxError, ValueError, ZeroDivisionError):
|
except (TypeError, KeyError, SyntaxError, ValueError, ZeroDivisionError):
|
||||||
# SyntaxError for invalid/unreadable EXIF
|
# SyntaxError for invalid/unreadable EXIF
|
||||||
# KeyError for dpi not included
|
# KeyError for dpi not included
|
||||||
# ZeroDivisionError for invalid dpi rational value
|
# ZeroDivisionError for invalid dpi rational value
|
||||||
# ValueError for dpi being an invalid float
|
# ValueError or TypeError for dpi being an invalid float
|
||||||
self.info["dpi"] = 72, 72
|
self.info["dpi"] = 72, 72
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i8
|
from ._binary import i8
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
|
from ._binary import si16be as si16
|
||||||
|
|
||||||
MODES = {
|
MODES = {
|
||||||
# (photoshop mode, bits) -> (pil mode, required channels)
|
# (photoshop mode, bits) -> (pil mode, required channels)
|
||||||
|
@ -179,7 +180,7 @@ def _layerinfo(fp, ct_bytes):
|
||||||
def read(size):
|
def read(size):
|
||||||
return ImageFile._safe_read(fp, size)
|
return ImageFile._safe_read(fp, size)
|
||||||
|
|
||||||
ct = i16(read(2))
|
ct = si16(read(2))
|
||||||
|
|
||||||
# sanity check
|
# sanity check
|
||||||
if ct_bytes < (abs(ct) * 20):
|
if ct_bytes < (abs(ct) * 20):
|
||||||
|
|
|
@ -193,7 +193,8 @@ def _save(im, fp, filename):
|
||||||
for channel in im.split():
|
for channel in im.split():
|
||||||
fp.write(channel.tobytes("raw", rawmode, 0, orientation))
|
fp.write(channel.tobytes("raw", rawmode, 0, orientation))
|
||||||
|
|
||||||
fp.close()
|
if hasattr(fp, "flush"):
|
||||||
|
fp.flush()
|
||||||
|
|
||||||
|
|
||||||
class SGI16Decoder(ImageFile.PyDecoder):
|
class SGI16Decoder(ImageFile.PyDecoder):
|
||||||
|
|
|
@ -93,6 +93,7 @@ SUBIFD = 330
|
||||||
EXTRASAMPLES = 338
|
EXTRASAMPLES = 338
|
||||||
SAMPLEFORMAT = 339
|
SAMPLEFORMAT = 339
|
||||||
JPEGTABLES = 347
|
JPEGTABLES = 347
|
||||||
|
YCBCRSUBSAMPLING = 530
|
||||||
REFERENCEBLACKWHITE = 532
|
REFERENCEBLACKWHITE = 532
|
||||||
COPYRIGHT = 33432
|
COPYRIGHT = 33432
|
||||||
IPTC_NAA_CHUNK = 33723 # newsphoto properties
|
IPTC_NAA_CHUNK = 33723 # newsphoto properties
|
||||||
|
@ -1596,6 +1597,13 @@ def _save(im, fp, filename):
|
||||||
# no compression by default:
|
# no compression by default:
|
||||||
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
|
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
|
||||||
|
|
||||||
|
if im.mode == "YCbCr":
|
||||||
|
for tag, value in {
|
||||||
|
YCBCRSUBSAMPLING: (1, 1),
|
||||||
|
REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255),
|
||||||
|
}.items():
|
||||||
|
ifd.setdefault(tag, value)
|
||||||
|
|
||||||
if libtiff:
|
if libtiff:
|
||||||
if "quality" in im.encoderinfo:
|
if "quality" in im.encoderinfo:
|
||||||
quality = im.encoderinfo["quality"]
|
quality = im.encoderinfo["quality"]
|
||||||
|
|
|
@ -202,7 +202,7 @@ def _save_all(im, fp, filename):
|
||||||
lossless = im.encoderinfo.get("lossless", False)
|
lossless = im.encoderinfo.get("lossless", False)
|
||||||
quality = im.encoderinfo.get("quality", 80)
|
quality = im.encoderinfo.get("quality", 80)
|
||||||
method = im.encoderinfo.get("method", 0)
|
method = im.encoderinfo.get("method", 0)
|
||||||
icc_profile = im.encoderinfo.get("icc_profile", "")
|
icc_profile = im.encoderinfo.get("icc_profile") or ""
|
||||||
exif = im.encoderinfo.get("exif", "")
|
exif = im.encoderinfo.get("exif", "")
|
||||||
if isinstance(exif, Image.Exif):
|
if isinstance(exif, Image.Exif):
|
||||||
exif = exif.tobytes()
|
exif = exif.tobytes()
|
||||||
|
@ -309,7 +309,7 @@ def _save_all(im, fp, filename):
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
lossless = im.encoderinfo.get("lossless", False)
|
lossless = im.encoderinfo.get("lossless", False)
|
||||||
quality = im.encoderinfo.get("quality", 80)
|
quality = im.encoderinfo.get("quality", 80)
|
||||||
icc_profile = im.encoderinfo.get("icc_profile", "")
|
icc_profile = im.encoderinfo.get("icc_profile") or ""
|
||||||
exif = im.encoderinfo.get("exif", "")
|
exif = im.encoderinfo.get("exif", "")
|
||||||
if isinstance(exif, Image.Exif):
|
if isinstance(exif, Image.Exif):
|
||||||
exif = exif.tobytes()
|
exif = exif.tobytes()
|
||||||
|
|
|
@ -47,6 +47,16 @@ def si16le(c, o=0):
|
||||||
return unpack_from("<h", c, o)[0]
|
return unpack_from("<h", c, o)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def si16be(c, o=0):
|
||||||
|
"""
|
||||||
|
Converts a 2-bytes (16 bits) string to a signed integer, big endian.
|
||||||
|
|
||||||
|
:param c: string containing bytes to convert
|
||||||
|
:param o: offset of bytes to convert in string
|
||||||
|
"""
|
||||||
|
return unpack_from(">h", c, o)[0]
|
||||||
|
|
||||||
|
|
||||||
def i32le(c, o=0):
|
def i32le(c, o=0):
|
||||||
"""
|
"""
|
||||||
Converts a 4-bytes (32 bits) string to an unsigned integer.
|
Converts a 4-bytes (32 bits) string to an unsigned integer.
|
||||||
|
|
|
@ -808,6 +808,12 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
av + stride * 2);
|
av + stride * 2);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
|
} else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) {
|
||||||
|
status = ImagingLibTiffSetField(
|
||||||
|
&encoder->state,
|
||||||
|
(ttag_t)key_int,
|
||||||
|
(UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)),
|
||||||
|
(UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1)));
|
||||||
} else if (type == TIFF_SHORT) {
|
} else if (type == TIFF_SHORT) {
|
||||||
UINT16 *av;
|
UINT16 *av;
|
||||||
/* malloc check ok, calloc checks for overflow */
|
/* malloc check ok, calloc checks for overflow */
|
||||||
|
|
|
@ -417,9 +417,16 @@ fill_mask_L(
|
||||||
if (imOut->image8) {
|
if (imOut->image8) {
|
||||||
for (y = 0; y < ysize; y++) {
|
for (y = 0; y < ysize; y++) {
|
||||||
UINT8 *out = imOut->image8[y + dy] + dx;
|
UINT8 *out = imOut->image8[y + dy] + dx;
|
||||||
|
if (strncmp(imOut->mode, "I;16", 4) == 0) {
|
||||||
|
out += dx;
|
||||||
|
}
|
||||||
UINT8 *mask = imMask->image8[y + sy] + sx;
|
UINT8 *mask = imMask->image8[y + sy] + sx;
|
||||||
for (x = 0; x < xsize; x++) {
|
for (x = 0; x < xsize; x++) {
|
||||||
*out = BLEND(*mask, *out, ink[0], tmp1);
|
*out = BLEND(*mask, *out, ink[0], tmp1);
|
||||||
|
if (strncmp(imOut->mode, "I;16", 4) == 0) {
|
||||||
|
out++;
|
||||||
|
*out = BLEND(*mask, *out, ink[0], tmp1);
|
||||||
|
}
|
||||||
out++, mask++;
|
out++, mask++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user