Merge branch 'main' into main

This commit is contained in:
Andrew Murray 2022-10-24 18:31:11 +11:00 committed by GitHub
commit 05be47783e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 561 additions and 217 deletions

View File

@ -5,6 +5,60 @@ Changelog (Pillow)
9.3.0 (unreleased) 9.3.0 (unreleased)
------------------ ------------------
- Added support for reading BMP images with RLE4 compression #6674
[npjg, radarhere]
- Decode JPEG compressed BLP1 data in original mode #6678
[radarhere]
- Added GPS TIFF tag info #6661
[radarhere]
- Added conversion between RGB/RGBA/RGBX and LAB #6647
[radarhere]
- Do not attempt normalization if mode is already normal #6644
[radarhere]
- Fixed seeking to an L frame in a GIF #6576
[radarhere]
- Consider all frames when selecting mode for PNG save_all #6610
[radarhere]
- Don't reassign crc on ChunkStream close #6627
[wiredfool, radarhere]
- Raise a warning if NumPy failed to raise an error during conversion #6594
[radarhere]
- Show all frames in ImageShow #6611
[radarhere]
- Allow FLI palette chunk to not be first #6626
[radarhere]
- If first GIF frame has transparency for RGB_ALWAYS loading strategy, use RGBA mode #6592
[radarhere]
- Round box position to integer when pasting embedded color #6517
[radarhere, nulano]
- Removed EXIF prefix when saving WebP #6582
[radarhere]
- Pad IM palette to 768 bytes when saving #6579
[radarhere]
- Added DDS BC6H reading #6449
[ShadelessFox, REDxEYE, radarhere]
- Added support for opening WhiteIsZero 16-bit integer TIFF images #6642
[JayWiz, radarhere]
- Raise an error when allocating translucent color to RGB palette #6654
[jsbueno, radarhere]
- Added reading of TIFF child images #6569 - Added reading of TIFF child images #6569
[radarhere] [radarhere]

View File

@ -208,12 +208,11 @@ class PillowLeakTestCase:
# ru_maxrss # ru_maxrss
# This is the maximum resident set size utilized (in bytes). # This is the maximum resident set size utilized (in bytes).
return mem / 1024 # Kb return mem / 1024 # Kb
else: # linux
# linux # man 2 getrusage
# man 2 getrusage # ru_maxrss (since Linux 2.6.32)
# ru_maxrss (since Linux 2.6.32) # This is the maximum resident set size used (in kilobytes).
# This is the maximum resident set size used (in kilobytes). return mem # Kb
return mem # Kb
def _test_leak(self, core): def _test_leak(self, core):
start_mem = self._get_mem_usage() start_mem = self._get_mem_usage()
@ -285,7 +284,7 @@ def magick_command():
if imagemagick and shutil.which(imagemagick[0]): if imagemagick and shutil.which(imagemagick[0]):
return imagemagick return imagemagick
elif graphicsmagick and shutil.which(graphicsmagick[0]): if graphicsmagick and shutil.which(graphicsmagick[0]):
return graphicsmagick return graphicsmagick

BIN
Tests/images/bc6h.dds Normal file

Binary file not shown.

BIN
Tests/images/bc6h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
Tests/images/bc6h_sf.dds Normal file

Binary file not shown.

BIN
Tests/images/bc6h_sf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

View File

@ -647,6 +647,16 @@ def test_seek_after_close():
im.seek(0) im.seek(0)
@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P"))
def test_different_modes_in_later_frames(mode, tmp_path):
test_file = str(tmp_path / "temp.png")
im = Image.new("L", (1, 1))
im.save(test_file, save_all=True, append_images=[Image.new(mode, (1, 1))])
with Image.open(test_file) as reloaded:
assert reloaded.mode == mode
def test_constants_deprecation(): def test_constants_deprecation():
for enum, prefix in { for enum, prefix in {
PngImagePlugin.Disposal: "APNG_DISPOSE_", PngImagePlugin.Disposal: "APNG_DISPOSE_",

View File

@ -14,6 +14,9 @@ def test_load_blp1():
with Image.open("Tests/images/blp/blp1_jpeg.blp") as im: with Image.open("Tests/images/blp/blp1_jpeg.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png") assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
im.load()
def test_load_blp2_raw(): def test_load_blp2_raw():
with Image.open("Tests/images/blp/blp2_raw.blp") as im: with Image.open("Tests/images/blp/blp2_raw.blp") as im:

View File

@ -176,6 +176,11 @@ def test_rle8():
im.load() im.load()
def test_rle4():
with Image.open("Tests/images/bmp/g/pal4rle.bmp") as im:
assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"file_name,length", "file_name,length",
( (

View File

@ -16,6 +16,8 @@ TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
TEST_FILE_BC5S = "Tests/images/bc5s.dds" TEST_FILE_BC5S = "Tests/images/bc5s.dds"
TEST_FILE_BC6H = "Tests/images/bc6h.dds"
TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds"
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds" TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds" TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds"
TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds" TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds"
@ -114,6 +116,20 @@ def test_dx10_bc5(image_path, expected_path):
assert_image_equal_tofile(im, expected_path.replace(".dds", ".png")) assert_image_equal_tofile(im, expected_path.replace(".dds", ".png"))
@pytest.mark.parametrize("image_path", (TEST_FILE_BC6H, TEST_FILE_BC6HS))
def test_dx10_bc6h(image_path):
"""Check DX10 BC6H/BC6HS images can be opened"""
with Image.open(image_path) as im:
im.load()
assert im.format == "DDS"
assert im.mode == "RGB"
assert im.size == (128, 128)
assert_image_equal_tofile(im, image_path.replace(".dds", ".png"))
def test_dx10_bc7(): def test_dx10_bc7():
"""Check DX10 images can be opened""" """Check DX10 images can be opened"""

View File

@ -4,7 +4,7 @@ import pytest
from PIL import FliImagePlugin, Image from PIL import FliImagePlugin, Image
from .helper import assert_image_equal_tofile, is_pypy from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
# created as an export of a palette image from Gimp2.6 # created as an export of a palette image from Gimp2.6
# save as...-> hopper.fli, default options. # save as...-> hopper.fli, default options.
@ -79,6 +79,12 @@ def test_invalid_file():
FliImagePlugin.FliImageFile(invalid_file) FliImagePlugin.FliImageFile(invalid_file)
def test_palette_chunk_second():
with Image.open("Tests/images/hopper_palette_chunk_second.fli") as im:
with Image.open(static_test_file) as expected:
assert_image_equal(im.convert("RGB"), expected.convert("RGB"))
def test_n_frames(): def test_n_frames():
with Image.open(static_test_file) as im: with Image.open(static_test_file) as im:
assert im.n_frames == 1 assert im.n_frames == 1

View File

@ -83,18 +83,40 @@ def test_l_mode_transparency():
assert im.load()[0, 0] == 128 assert im.load()[0, 0] == 128
def test_l_mode_after_rgb():
with Image.open("Tests/images/no_palette_after_rgb.gif") as im:
im.seek(1)
assert im.mode == "RGB"
im.seek(2)
assert im.mode == "RGB"
def test_palette_not_needed_for_second_frame():
with Image.open("Tests/images/palette_not_needed_for_second_frame.gif") as im:
im.seek(1)
assert_image_similar(im, hopper("L").convert("RGB"), 8)
def test_strategy(): def test_strategy():
with Image.open("Tests/images/iss634.gif") as im:
expected_rgb_always = im.convert("RGB")
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
expected_zero = im.convert("RGB") expected_rgb_always_rgba = im.convert("RGBA")
im.seek(1) im.seek(1)
expected_one = im.convert("RGB") expected_different = im.convert("RGB")
try: try:
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/iss634.gif") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
assert_image_equal(im, expected_zero) assert_image_equal(im, expected_rgb_always)
with Image.open("Tests/images/chi.gif") as im:
assert im.mode == "RGBA"
assert_image_equal(im, expected_rgb_always_rgba)
GifImagePlugin.LOADING_STRATEGY = ( GifImagePlugin.LOADING_STRATEGY = (
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
@ -105,7 +127,7 @@ def test_strategy():
im.seek(1) im.seek(1)
assert im.mode == "P" assert im.mode == "P"
assert_image_equal(im.convert("RGB"), expected_one) assert_image_equal(im.convert("RGB"), expected_different)
# Change to RGB mode when a frame has an individual palette # Change to RGB mode when a frame has an individual palette
with Image.open("Tests/images/iss634.gif") as im: with Image.open("Tests/images/iss634.gif") as im:

View File

@ -86,6 +86,18 @@ def test_roundtrip(mode, tmp_path):
assert_image_equal_tofile(im, out) assert_image_equal_tofile(im, out)
def test_small_palette(tmp_path):
im = Image.new("P", (1, 1))
colors = [0, 1, 2]
im.putpalette(colors)
out = str(tmp_path / "temp.im")
im.save(out)
with Image.open(out) as reloaded:
assert reloaded.getpalette() == colors + [0] * 765
def test_save_unsupported_mode(tmp_path): def test_save_unsupported_mode(tmp_path):
out = str(tmp_path / "temp.im") out = str(tmp_path / "temp.im")
im = hopper("HSV") im = hopper("HSV")

19
Tests/test_file_imt.py Normal file
View File

@ -0,0 +1,19 @@
import io
import pytest
from PIL import Image, ImtImagePlugin
from .helper import assert_image_equal_tofile
def test_sanity():
with Image.open("Tests/images/bw_gradient.imt") as im:
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
@pytest.mark.parametrize("data", (b"\n", b"\n-", b"width 1\n"))
def test_invalid_file(data):
with io.BytesIO(data) as fp:
with pytest.raises(SyntaxError):
ImtImagePlugin.ImtImageFile(fp)

View File

@ -30,7 +30,7 @@ from .helper import (
) )
try: try:
import defusedxml.ElementTree as ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
ElementTree = None ElementTree = None

View File

@ -934,7 +934,7 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, exif=tags, compression=compression) im.save(out, exif=tags, compression=compression)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
for tag in tags.keys(): for tag in tags:
assert tag not in reloaded.getexif() assert tag not in reloaded.getexif()
def test_old_style_jpeg(self): def test_old_style_jpeg(self):

View File

@ -20,7 +20,7 @@ from .helper import (
) )
try: try:
import defusedxml.ElementTree as ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
ElementTree = None ElementTree = None

View File

@ -240,8 +240,8 @@ def test_header_token_too_long(tmp_path):
def test_truncated_file(tmp_path): def test_truncated_file(tmp_path):
# Test EOF in header # Test EOF in header
path = str(tmp_path / "temp.pgm") path = str(tmp_path / "temp.pgm")
with open(path, "w") as f: with open(path, "wb") as f:
f.write("P6") f.write(b"P6")
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
with Image.open(path): with Image.open(path):
@ -256,11 +256,11 @@ def test_truncated_file(tmp_path):
im.load() im.load()
@pytest.mark.parametrize("maxval", (0, 65536)) @pytest.mark.parametrize("maxval", (b"0", b"65536"))
def test_invalid_maxval(maxval, tmp_path): def test_invalid_maxval(maxval, tmp_path):
path = str(tmp_path / "temp.ppm") path = str(tmp_path / "temp.ppm")
with open(path, "w") as f: with open(path, "wb") as f:
f.write("P6\n3 1 " + str(maxval)) f.write(b"P6\n3 1 " + maxval)
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
with Image.open(path): with Image.open(path):
@ -283,13 +283,13 @@ def test_neg_ppm():
def test_mimetypes(tmp_path): def test_mimetypes(tmp_path):
path = str(tmp_path / "temp.pgm") path = str(tmp_path / "temp.pgm")
with open(path, "w") as f: with open(path, "wb") as f:
f.write("P4\n128 128\n255") f.write(b"P4\n128 128\n255")
with Image.open(path) as im: with Image.open(path) as im:
assert im.get_format_mimetype() == "image/x-portable-bitmap" assert im.get_format_mimetype() == "image/x-portable-bitmap"
with open(path, "w") as f: with open(path, "wb") as f:
f.write("PyCMYK\n128 128\n255") f.write(b"PyCMYK\n128 128\n255")
with Image.open(path) as im: with Image.open(path) as im:
assert im.get_format_mimetype() == "image/x-portable-anymap" assert im.get_format_mimetype() == "image/x-portable-anymap"

View File

@ -18,7 +18,7 @@ from .helper import (
) )
try: try:
import defusedxml.ElementTree as ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
ElementTree = None ElementTree = None

View File

@ -55,9 +55,7 @@ def test_write_exif_metadata():
test_buffer.seek(0) test_buffer.seek(0)
with Image.open(test_buffer) as webp_image: with Image.open(test_buffer) as webp_image:
webp_exif = webp_image.info.get("exif", None) webp_exif = webp_image.info.get("exif", None)
assert webp_exif assert webp_exif == expected_exif[6:], "WebP EXIF didn't match"
if webp_exif:
assert webp_exif == expected_exif, "WebP EXIF didn't match"
def test_read_icc_profile(): def test_read_icc_profile():

View File

@ -129,8 +129,6 @@ class TestImage:
im.size = (3, 4) im.size = (3, 4)
def test_invalid_image(self): def test_invalid_image(self):
import io
im = io.BytesIO(b"") im = io.BytesIO(b"")
with pytest.raises(UnidentifiedImageError): with pytest.raises(UnidentifiedImageError):
with Image.open(im): with Image.open(im):
@ -699,15 +697,15 @@ class TestImage:
def test_empty_exif(self): def test_empty_exif(self):
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
exif = im.getexif() exif = im.getexif()
assert dict(exif) != {} assert dict(exif)
# Test that exif data is cleared after another load # Test that exif data is cleared after another load
exif.load(None) exif.load(None)
assert dict(exif) == {} assert not dict(exif)
# Test loading just the EXIF header # Test loading just the EXIF header
exif.load(b"Exif\x00\x00") exif.load(b"Exif\x00\x00")
assert dict(exif) == {} assert not dict(exif)
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"

View File

@ -131,8 +131,7 @@ class TestImageGetPixel(AccessTest):
bands = Image.getmodebands(mode) bands = Image.getmodebands(mode)
if bands == 1: if bands == 1:
return 1 return 1
else: return tuple(range(1, bands + 1))
return tuple(range(1, bands + 1))
def check(self, mode, c=None): def check(self, mode, c=None):
if not c: if not c:
@ -345,13 +344,14 @@ class TestCffi(AccessTest):
@pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("mode", ("P", "PA"))
def test_p_putpixel_rgb_rgba(self, mode): def test_p_putpixel_rgb_rgba(self, mode):
for color in [(255, 0, 0), (255, 0, 0, 127)]: for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)):
im = Image.new(mode, (1, 1)) im = Image.new(mode, (1, 1))
access = PyAccess.new(im, False) access = PyAccess.new(im, False)
access.putpixel((0, 0), color) access.putpixel((0, 0), color)
alpha = color[3] if len(color) == 4 and mode == "PA" else 255 if len(color) == 3:
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha) color += (255,)
assert im.convert("RGBA").getpixel((0, 0)) == color
class TestImagePutPixelError(AccessTest): class TestImagePutPixelError(AccessTest):
@ -414,7 +414,7 @@ class TestEmbeddable:
def test_embeddable(self): def test_embeddable(self):
import ctypes import ctypes
with open("embed_pil.c", "w") as fh: with open("embed_pil.c", "w", encoding="utf-8") as fh:
fh.write( fh.write(
""" """
#include "Python.h" #include "Python.h"

View File

@ -35,10 +35,13 @@ def test_toarray():
test_with_dtype(numpy.float64) test_with_dtype(numpy.float64)
test_with_dtype(numpy.uint8) test_with_dtype(numpy.uint8)
if parse_version(numpy.__version__) >= parse_version("1.23"): with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated: if parse_version(numpy.__version__) >= parse_version("1.23"):
with pytest.raises(OSError): with pytest.raises(OSError):
numpy.array(im_truncated) numpy.array(im_truncated)
else:
with pytest.warns(UserWarning):
numpy.array(im_truncated)
def test_fromarray(): def test_fromarray():

View File

@ -38,6 +38,12 @@ def test_sanity():
convert(im, output_mode) convert(im, output_mode)
def test_unsupported_conversion():
im = hopper()
with pytest.raises(ValueError):
im.convert("INVALID")
def test_default(): def test_default():
im = hopper("P") im = hopper("P")
@ -242,6 +248,17 @@ def test_p2pa_palette():
assert im_pa.getpalette() == im.getpalette() assert im_pa.getpalette() == im.getpalette()
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
def test_rgb_lab(mode):
im = Image.new(mode, (1, 1))
converted_im = im.convert("LAB")
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
im = Image.new("LAB", (1, 1), (255, 0, 0))
converted_im = im.convert(mode)
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
def test_matrix_illegal_conversion(): def test_matrix_illegal_conversion():
# Arrange # Arrange
im = hopper("CMYK") im = hopper("CMYK")

View File

@ -935,7 +935,30 @@ def test_standard_embedded_color(layout_engine):
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True) d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True)
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 6.2) assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
@pytest.mark.parametrize("fontmode", ("1", "L", "RGBA"))
def test_float_coord(layout_engine, fontmode):
txt = "Hello World!"
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
im = Image.new("RGB", (300, 64), "white")
d = ImageDraw.Draw(im)
if fontmode == "1":
d.fontmode = "1"
embedded_color = fontmode == "RGBA"
d.text((9.5, 9.5), txt, font=ttf, fill="#fa6", embedded_color=embedded_color)
try:
assert_image_similar_tofile(im, "Tests/images/text_float_coord.png", 3.9)
except AssertionError:
if fontmode == "1" and layout_engine == ImageFont.Layout.BASIC:
assert_image_similar_tofile(
im, "Tests/images/text_float_coord_1_alt.png", 1
)
else:
raise
def test_cbdt(layout_engine): def test_cbdt(layout_engine):

View File

@ -6,10 +6,8 @@ from PIL import Image, ImageMath
def pixel(im): def pixel(im):
if hasattr(im, "im"): if hasattr(im, "im"):
return f"{im.mode} {repr(im.getpixel((0, 0)))}" return f"{im.mode} {repr(im.getpixel((0, 0)))}"
else: if isinstance(im, int):
if isinstance(im, int): return int(im) # hack to deal with booleans
return int(im) # hack to deal with booleans
print(im)
A = Image.new("L", (1, 1), 1) A = Image.new("L", (1, 1), 1)

View File

@ -50,6 +50,16 @@ def test_getcolor():
palette.getcolor("unknown") palette.getcolor("unknown")
def test_getcolor_rgba_color_rgb_palette():
palette = ImagePalette.ImagePalette("RGB")
# Opaque RGBA colors are converted
assert palette.getcolor((0, 0, 0, 255)) == palette.getcolor((0, 0, 0))
with pytest.raises(ValueError):
palette.getcolor((0, 0, 0, 128))
@pytest.mark.parametrize( @pytest.mark.parametrize(
"index, palette", "index, palette",
[ [

View File

@ -60,7 +60,10 @@ Pillow also provides limited support for a few additional modes, including:
* ``BGR;24`` (24-bit reversed true colour) * ``BGR;24`` (24-bit reversed true colour)
* ``BGR;32`` (32-bit reversed true colour) * ``BGR;32`` (32-bit reversed true colour)
However, Pillow doesnt support user-defined modes; if you need to handle band Apart from these additional modes, Pillow doesn't yet support multichannel
images with a depth of more than 8 bits per channel.
Pillow also doesnt support user-defined modes; if you need to handle band
combinations that are not listed above, use a sequence of Image objects. combinations that are not listed above, use a sequence of Image objects.
You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode` You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode`

View File

@ -45,9 +45,9 @@ BMP
^^^ ^^^
Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``, Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``,
or ``RGB`` data. 16-colour images are read as ``P`` images. 4-bit run-length encoding or ``RGB`` data. 16-colour images are read as ``P`` images.
is not supported. Support for reading 8-bit run-length encoding was added in Pillow Support for reading 8-bit run-length encoding was added in Pillow 9.1.0.
9.1.0. Support for reading 4-bit run-length encoding was added in Pillow 9.3.0.
Opening Opening
~~~~~~~ ~~~~~~~
@ -56,7 +56,8 @@ The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**compression** **compression**
Set to ``bmp_rle`` if the file is run-length encoded. Set to 1 if the file is a 256-color run-length encoded image.
Set to 2 if the file is a 16-color run-length encoded image.
DDS DDS
^^^ ^^^

View File

@ -202,7 +202,7 @@ Pillow now builds binary wheels for musllinux, suitable for Linux distributions
(rather than the glibc library used by manylinux wheels). See :pep:`656`. (rather than the glibc library used by manylinux wheels). See :pep:`656`.
ImageShow temporary files on Unix ImageShow temporary files on Unix
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When calling :py:meth:`~PIL.Image.Image.show` or using :py:mod:`~PIL.ImageShow`, When calling :py:meth:`~PIL.Image.Image.show` or using :py:mod:`~PIL.ImageShow`,
a temporary file is created from the image. On Unix, Pillow will no longer delete these a temporary file is created from the image. On Unix, Pillow will no longer delete these

View File

@ -4,25 +4,6 @@
Backwards Incompatible Changes Backwards Incompatible Changes
============================== ==============================
TODO
^^^^
Deprecations
============
TODO
^^^^
TODO
API Changes
===========
TODO
^^^^
TODO
API Additions API Additions
============= =============
@ -55,15 +36,25 @@ Additional images can also be appended when saving, by combining the
Security Security
======== ========
TODO Decode JPEG compressed BLP1 data in original mode
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO Within the BLP image format, BLP1 data may use JPEG compression. Instead of
telling the JPEG library that this data is in BGRX mode, Pillow will now
decode the data in its natural CMYK mode, then convert it to RGB and rearrange
the channels afterwards. Trying to load the data in an incorrect mode could
result in a segmentation fault.
Other Changes Other Changes
============= =============
Added DDS ATI1 and ATI2 reading Added DDS ATI1, ATI2 and BC6H reading
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Support has been added to read the ATI1 and ATI2 formats of DDS images. Support has been added to read the ATI1, ATI2 and BC6H formats of DDS images.
Show all frames with ImageShow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When calling :py:meth:`~PIL.Image.Image.show` or using
:py:mod:`~PIL.ImageShow`, all frames will now be shown.

View File

@ -23,7 +23,7 @@ from setuptools.command.build_ext import build_ext
def get_version(): def get_version():
version_file = "src/PIL/_version.py" version_file = "src/PIL/_version.py"
with open(version_file) as f: with open(version_file, encoding="utf-8") as f:
exec(compile(f.read(), version_file, "exec")) exec(compile(f.read(), version_file, "exec"))
return locals()["__version__"] return locals()["__version__"]

View File

@ -373,8 +373,8 @@ class BLP1Decoder(_BLPBaseDecoder):
data = BytesIO(data) data = BytesIO(data)
image = JpegImageFile(data) image = JpegImageFile(data)
Image._decompression_bomb_check(image.size) Image._decompression_bomb_check(image.size)
image.mode = "RGB" r, g, b = image.convert("RGB").split()
image.tile = [("jpeg", (0, 0) + self.size, 0, ("BGRX", ""))] image = Image.merge("RGB", (b, g, r))
self.set_as_raw(image.tobytes()) self.set_as_raw(image.tobytes())

View File

@ -214,7 +214,7 @@ class BmpImageFile(ImageFile.ImageFile):
raw_mode, self.mode = "BGRA", "RGBA" raw_mode, self.mode = "BGRA", "RGBA"
except AttributeError: except AttributeError:
pass pass
elif file_info["compression"] == self.RLE8: elif file_info["compression"] in (self.RLE8, self.RLE4):
decoder_name = "bmp_rle" decoder_name = "bmp_rle"
else: else:
raise OSError(f"Unsupported BMP compression ({file_info['compression']})") raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
@ -253,16 +253,18 @@ class BmpImageFile(ImageFile.ImageFile):
# ---------------------------- Finally set the tile data for the plugin # ---------------------------- Finally set the tile data for the plugin
self.info["compression"] = file_info["compression"] self.info["compression"] = file_info["compression"]
args = [raw_mode]
if decoder_name == "bmp_rle":
args.append(file_info["compression"] == self.RLE4)
else:
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
args.append(file_info["direction"])
self.tile = [ self.tile = [
( (
decoder_name, decoder_name,
(0, 0, file_info["width"], file_info["height"]), (0, 0, file_info["width"], file_info["height"]),
offset or self.fp.tell(), offset or self.fp.tell(),
( tuple(args),
raw_mode,
((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3),
file_info["direction"],
),
) )
] ]
@ -283,6 +285,7 @@ class BmpRleDecoder(ImageFile.PyDecoder):
_pulls_fd = True _pulls_fd = True
def decode(self, buffer): def decode(self, buffer):
rle4 = self.args[1]
data = bytearray() data = bytearray()
x = 0 x = 0
while len(data) < self.state.xsize * self.state.ysize: while len(data) < self.state.xsize * self.state.ysize:
@ -296,7 +299,16 @@ class BmpRleDecoder(ImageFile.PyDecoder):
if x + num_pixels > self.state.xsize: if x + num_pixels > self.state.xsize:
# Too much data for row # Too much data for row
num_pixels = max(0, self.state.xsize - x) num_pixels = max(0, self.state.xsize - x)
data += byte * num_pixels if rle4:
first_pixel = o8(byte[0] >> 4)
second_pixel = o8(byte[0] & 0x0F)
for index in range(num_pixels):
if index % 2 == 0:
data += first_pixel
else:
data += second_pixel
else:
data += byte * num_pixels
x += num_pixels x += num_pixels
else: else:
if byte[0] == 0: if byte[0] == 0:
@ -317,9 +329,18 @@ class BmpRleDecoder(ImageFile.PyDecoder):
x = len(data) % self.state.xsize x = len(data) % self.state.xsize
else: else:
# absolute mode # absolute mode
bytes_read = self.fd.read(byte[0]) if rle4:
data += bytes_read # 2 pixels per byte
if len(bytes_read) < byte[0]: byte_count = byte[0] // 2
bytes_read = self.fd.read(byte_count)
for byte_read in bytes_read:
data += o8(byte_read >> 4)
data += o8(byte_read & 0x0F)
else:
byte_count = byte[0]
bytes_read = self.fd.read(byte_count)
data += bytes_read
if len(bytes_read) < byte_count:
break break
x += byte[0] x += byte[0]

View File

@ -101,6 +101,8 @@ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
DXGI_FORMAT_BC5_TYPELESS = 82 DXGI_FORMAT_BC5_TYPELESS = 82
DXGI_FORMAT_BC5_UNORM = 83 DXGI_FORMAT_BC5_UNORM = 83
DXGI_FORMAT_BC5_SNORM = 84 DXGI_FORMAT_BC5_SNORM = 84
DXGI_FORMAT_BC6H_UF16 = 95
DXGI_FORMAT_BC6H_SF16 = 96
DXGI_FORMAT_BC7_TYPELESS = 97 DXGI_FORMAT_BC7_TYPELESS = 97
DXGI_FORMAT_BC7_UNORM = 98 DXGI_FORMAT_BC7_UNORM = 98
DXGI_FORMAT_BC7_UNORM_SRGB = 99 DXGI_FORMAT_BC7_UNORM_SRGB = 99
@ -181,6 +183,14 @@ class DdsImageFile(ImageFile.ImageFile):
self.pixel_format = "BC5S" self.pixel_format = "BC5S"
n = 5 n = 5
self.mode = "RGB" self.mode = "RGB"
elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
self.pixel_format = "BC6H"
n = 6
self.mode = "RGB"
elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
self.pixel_format = "BC6HS"
n = 6
self.mode = "RGB"
elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM): elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
self.pixel_format = "BC7" self.pixel_format = "BC7"
n = 7 n = 7

View File

@ -15,6 +15,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import os
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16 from ._binary import i16le as i16
@ -80,11 +81,19 @@ class FliImageFile(ImageFile.ImageFile):
if i16(s, 4) == 0xF1FA: if i16(s, 4) == 0xF1FA:
# look for palette chunk # look for palette chunk
s = self.fp.read(6) number_of_subchunks = i16(s, 6)
if i16(s, 4) == 11: chunk_size = None
self._palette(palette, 2) for _ in range(number_of_subchunks):
elif i16(s, 4) == 4: if chunk_size is not None:
self._palette(palette, 0) self.fp.seek(chunk_size - 6, os.SEEK_CUR)
s = self.fp.read(6)
chunk_type = i16(s, 4)
if chunk_type in (4, 11):
self._palette(palette, 2 if chunk_type == 11 else 0)
break
chunk_size = i32(s)
if not chunk_size:
break
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette] palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.palette = ImagePalette.raw("RGB", b"".join(palette))

View File

@ -274,6 +274,8 @@ class GifImageFile(ImageFile.ImageFile):
p = self.fp.read(3 << bits) p = self.fp.read(3 << bits)
if self._is_palette_needed(p): if self._is_palette_needed(p):
palette = ImagePalette.raw("RGB", p) palette = ImagePalette.raw("RGB", p)
else:
palette = False
# image data # image data
bits = self.fp.read(1)[0] bits = self.fp.read(1)[0]
@ -298,12 +300,14 @@ class GifImageFile(ImageFile.ImageFile):
if self.dispose: if self.dispose:
self.im.paste(self.dispose, self.dispose_extent) self.im.paste(self.dispose, self.dispose_extent)
self._frame_palette = palette or self.global_palette self._frame_palette = palette if palette is not None else self.global_palette
self._frame_transparency = frame_transparency
if frame == 0: if frame == 0:
if self._frame_palette: if self._frame_palette:
self.mode = ( if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
"RGB" if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS else "P" self.mode = "RGBA" if frame_transparency is not None else "RGB"
) else:
self.mode = "P"
else: else:
self.mode = "L" self.mode = "L"
@ -313,7 +317,6 @@ class GifImageFile(ImageFile.ImageFile):
palette = copy(self.global_palette) palette = copy(self.global_palette)
self.palette = palette self.palette = palette
else: else:
self._frame_transparency = frame_transparency
if self.mode == "P": if self.mode == "P":
if ( if (
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
@ -386,7 +389,8 @@ class GifImageFile(ImageFile.ImageFile):
transparency = -1 transparency = -1
if frame_transparency is not None: if frame_transparency is not None:
if frame == 0: if frame == 0:
self.info["transparency"] = frame_transparency if LOADING_STRATEGY != LoadingStrategy.RGB_ALWAYS:
self.info["transparency"] = frame_transparency
elif self.mode not in ("RGB", "RGBA"): elif self.mode not in ("RGB", "RGBA"):
transparency = frame_transparency transparency = frame_transparency
self.tile = [ self.tile = [
@ -410,9 +414,9 @@ class GifImageFile(ImageFile.ImageFile):
temp_mode = "P" if self._frame_palette else "L" temp_mode = "P" if self._frame_palette else "L"
self._prev_im = None self._prev_im = None
if self.__frame == 0: if self.__frame == 0:
if "transparency" in self.info: if self._frame_transparency is not None:
self.im = Image.core.fill( self.im = Image.core.fill(
temp_mode, self.size, self.info["transparency"] temp_mode, self.size, self._frame_transparency
) )
elif self.mode in ("RGB", "RGBA"): elif self.mode in ("RGB", "RGBA"):
self._prev_im = self.im self._prev_im = self.im
@ -429,19 +433,20 @@ class GifImageFile(ImageFile.ImageFile):
def load_end(self): def load_end(self):
if self.__frame == 0: if self.__frame == 0:
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
self.mode = "RGB" if self._frame_transparency is not None:
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) self.im.putpalettealpha(self._frame_transparency, 0)
self.mode = "RGBA"
else:
self.mode = "RGB"
self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
return return
if self.mode == "P" and self._prev_im: if not self._prev_im:
if self._frame_transparency is not None: return
self.im.putpalettealpha(self._frame_transparency, 0) if self._frame_transparency is not None:
frame_im = self.im.convert("RGBA") self.im.putpalettealpha(self._frame_transparency, 0)
else: frame_im = self.im.convert("RGBA")
frame_im = self.im.convert("RGB")
else: else:
if not self._prev_im: frame_im = self.im.convert("RGB")
return
frame_im = self.im
frame_im = self._crop(frame_im, self.dispose_extent) frame_im = self._crop(frame_im, self.dispose_extent)
self.im = self._prev_im self.im = self._prev_im

View File

@ -352,7 +352,13 @@ def _save(im, fp, filename):
fp.write(b"Lut: 1\r\n") fp.write(b"Lut: 1\r\n")
fp.write(b"\000" * (511 - fp.tell()) + b"\032") fp.write(b"\000" * (511 - fp.tell()) + b"\032")
if im.mode in ["P", "PA"]: if im.mode in ["P", "PA"]:
fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes im_palette = im.im.getpalette("RGB", "RGB;L")
colors = len(im_palette) // 3
palette = b""
for i in range(3):
palette += im_palette[colors * i : colors * (i + 1)]
palette += b"\x00" * (256 - colors)
fp.write(palette) # 768 bytes
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))]) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))])

View File

@ -679,12 +679,24 @@ class Image:
new["shape"] = shape new["shape"] = shape
new["typestr"] = typestr new["typestr"] = typestr
new["version"] = 3 new["version"] = 3
if self.mode == "1": try:
# Binary images need to be extended from bits to bytes if self.mode == "1":
# See: https://github.com/python-pillow/Pillow/issues/350 # Binary images need to be extended from bits to bytes
new["data"] = self.tobytes("raw", "L") # See: https://github.com/python-pillow/Pillow/issues/350
else: new["data"] = self.tobytes("raw", "L")
new["data"] = self.tobytes() else:
new["data"] = self.tobytes()
except Exception as e:
if not isinstance(e, (MemoryError, RecursionError)):
try:
import numpy
from packaging.version import parse as parse_version
except ImportError:
pass
else:
if parse_version(numpy.__version__) < parse_version("1.23"):
warnings.warn(e)
raise
return new return new
def __getstate__(self): def __getstate__(self):
@ -868,7 +880,7 @@ class Image:
and the palette can be represented without a palette. and the palette can be represented without a palette.
The current version supports all possible conversions between The current version supports all possible conversions between
"L", "RGB" and "CMYK." The ``matrix`` argument only supports "L" "L", "RGB" and "CMYK". The ``matrix`` argument only supports "L"
and "RGB". and "RGB".
When translating a color image to greyscale (mode "L"), When translating a color image to greyscale (mode "L"),
@ -887,6 +899,9 @@ class Image:
this passes the operation to :py:meth:`~PIL.Image.Image.quantize`, this passes the operation to :py:meth:`~PIL.Image.Image.quantize`,
and ``dither`` and ``palette`` are ignored. and ``dither`` and ``palette`` are ignored.
When converting from "PA", if an "RGBA" palette is present, the alpha
channel from the image will be used instead of the values from the palette.
:param mode: The requested mode. See: :ref:`concept-modes`. :param mode: The requested mode. See: :ref:`concept-modes`.
:param matrix: An optional conversion matrix. If given, this :param matrix: An optional conversion matrix. If given, this
should be 4- or 12-tuple containing floating point values. should be 4- or 12-tuple containing floating point values.
@ -1027,6 +1042,19 @@ class Image:
warnings.warn("Couldn't allocate palette entry for transparency") warnings.warn("Couldn't allocate palette entry for transparency")
return new return new
if "LAB" in (self.mode, mode):
other_mode = mode if self.mode == "LAB" else self.mode
if other_mode in ("RGB", "RGBA", "RGBX"):
from . import ImageCms
srgb = ImageCms.createProfile("sRGB")
lab = ImageCms.createProfile("LAB")
profiles = [lab, srgb] if self.mode == "LAB" else [srgb, lab]
transform = ImageCms.buildTransform(
profiles[0], profiles[1], self.mode, mode
)
return transform.apply(self)
# colorspace conversion # colorspace conversion
if dither is None: if dither is None:
dither = Dither.FLOYDSTEINBERG dither = Dither.FLOYDSTEINBERG
@ -1036,7 +1064,10 @@ class Image:
except ValueError: except ValueError:
try: try:
# normalize source image and try again # normalize source image and try again
im = self.im.convert(getmodebase(self.mode)) modebase = getmodebase(self.mode)
if modebase == self.mode:
raise
im = self.im.convert(modebase)
im = im.convert(mode, dither) im = im.convert(mode, dither)
except KeyError as e: except KeyError as e:
raise ValueError("illegal conversion") from e raise ValueError("illegal conversion") from e

View File

@ -482,8 +482,8 @@ class ImageDraw:
# extract mask and set text alpha # extract mask and set text alpha
color, mask = mask, mask.getband(3) color, mask = mask, mask.getband(3)
color.fillband(3, (ink >> 24) & 0xFF) color.fillband(3, (ink >> 24) & 0xFF)
coord2 = coord[0] + mask.size[0], coord[1] + mask.size[1] x, y = (int(c) for c in coord)
self.im.paste(color, coord + coord2, mask) self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask)
else: else:
self.draw.draw_bitmap(coord, mask, ink) self.draw.draw_bitmap(coord, mask, ink)

View File

@ -955,6 +955,11 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
encoding of any text provided in subsequent operations. encoding of any text provided in subsequent operations.
:param layout_engine: Which layout engine to use, if available: :param layout_engine: Which layout engine to use, if available:
:data:`.ImageFont.Layout.BASIC` or :data:`.ImageFont.Layout.RAQM`. :data:`.ImageFont.Layout.BASIC` or :data:`.ImageFont.Layout.RAQM`.
If it is available, Raqm layout will be used by default.
Otherwise, basic layout will be used.
Raqm layout is recommended for all non-English text. If Raqm layout
is not required, basic layout will have better performance.
You can check support for Raqm layout using You can check support for Raqm layout using
:py:func:`PIL.features.check_feature` with ``feature="raqm"``. :py:func:`PIL.features.check_feature` with ``feature="raqm"``.

View File

@ -115,7 +115,11 @@ class ImagePalette:
raise ValueError("palette contains raw palette data") raise ValueError("palette contains raw palette data")
if isinstance(color, tuple): if isinstance(color, tuple):
if self.mode == "RGB": if self.mode == "RGB":
if len(color) == 4 and color[3] == 255: if len(color) == 4:
if color[3] != 255:
raise ValueError(
"cannot add non-opaque RGBA color to RGB palette"
)
color = color[:3] color = color[:3]
elif self.mode == "RGBA": elif self.mode == "RGBA":
if len(color) == 3: if len(color) == 3:

View File

@ -136,7 +136,7 @@ class WindowsViewer(Viewer):
"""The default viewer on Windows is the default system application for PNG files.""" """The default viewer on Windows is the default system application for PNG files."""
format = "PNG" format = "PNG"
options = {"compress_level": 1} options = {"compress_level": 1, "save_all": True}
def get_command(self, file, **options): def get_command(self, file, **options):
return ( return (
@ -154,7 +154,7 @@ class MacViewer(Viewer):
"""The default viewer on macOS using ``Preview.app``.""" """The default viewer on macOS using ``Preview.app``."""
format = "PNG" format = "PNG"
options = {"compress_level": 1} options = {"compress_level": 1, "save_all": True}
def get_command(self, file, **options): def get_command(self, file, **options):
# on darwin open returns immediately resulting in the temp # on darwin open returns immediately resulting in the temp
@ -197,7 +197,7 @@ if sys.platform == "darwin":
class UnixViewer(Viewer): class UnixViewer(Viewer):
format = "PNG" format = "PNG"
options = {"compress_level": 1} options = {"compress_level": 1, "save_all": True}
def get_command(self, file, **options): def get_command(self, file, **options):
command = self.get_command_ex(file, **options)[0] command = self.get_command_ex(file, **options)[0]

View File

@ -39,15 +39,19 @@ class ImtImageFile(ImageFile.ImageFile):
# Quick rejection: if there's not a LF among the first # Quick rejection: if there's not a LF among the first
# 100 bytes, this is (probably) not a text header. # 100 bytes, this is (probably) not a text header.
if b"\n" not in self.fp.read(100): buffer = self.fp.read(100)
if b"\n" not in buffer:
raise SyntaxError("not an IM file") raise SyntaxError("not an IM file")
self.fp.seek(0)
xsize = ysize = 0 xsize = ysize = 0
while True: while True:
s = self.fp.read(1) if buffer:
s = buffer[:1]
buffer = buffer[1:]
else:
s = self.fp.read(1)
if not s: if not s:
break break
@ -55,7 +59,12 @@ class ImtImageFile(ImageFile.ImageFile):
# image data begins # image data begins
self.tile = [ self.tile = [
("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1)) (
"raw",
(0, 0) + self.size,
self.fp.tell() - len(buffer),
(self.mode, 0, 1),
)
] ]
break break
@ -63,8 +72,11 @@ class ImtImageFile(ImageFile.ImageFile):
else: else:
# read key/value pair # read key/value pair
# FIXME: dangerous, may read whole file if b"\n" not in buffer:
s = s + self.fp.readline() buffer += self.fp.read(100)
lines = buffer.split(b"\n")
s += lines.pop(0)
buffer = b"\n".join(lines)
if len(s) == 1 or len(s) > 100: if len(s) == 1 or len(s) > 100:
break break
if s[0] == ord(b"*"): if s[0] == ord(b"*"):
@ -74,13 +86,13 @@ class ImtImageFile(ImageFile.ImageFile):
if not m: if not m:
break break
k, v = m.group(1, 2) k, v = m.group(1, 2)
if k == "width": if k == b"width":
xsize = int(v) xsize = int(v)
self._size = xsize, ysize self._size = xsize, ysize
elif k == "height": elif k == b"height":
ysize = int(v) ysize = int(v)
self._size = xsize, ysize self._size = xsize, ysize
elif k == "pixel" and v == "n8": elif k == b"pixel" and v == b"n8":
self.mode = "L" self.mode = "L"

View File

@ -189,7 +189,7 @@ class ChunkStream:
self.close() self.close()
def close(self): def close(self):
self.queue = self.crc = self.fp = None self.queue = self.fp = None
def push(self, cid, pos, length): def push(self, cid, pos, length):
@ -224,7 +224,7 @@ class ChunkStream:
) from e ) from e
def crc_skip(self, cid, data): def crc_skip(self, cid, data):
"""Read checksum. Used if the C module is not present""" """Read checksum"""
self.fp.read(4) self.fp.read(4)
@ -1089,28 +1089,28 @@ class _fdat:
self.seq_num += 1 self.seq_num += 1
def _write_multiple_frames(im, fp, chunk, rawmode): def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images):
default_image = im.encoderinfo.get("default_image", im.info.get("default_image"))
duration = im.encoderinfo.get("duration", im.info.get("duration", 0)) duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE))
blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE)) blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE))
if default_image: if default_image:
chain = itertools.chain(im.encoderinfo.get("append_images", [])) chain = itertools.chain(append_images)
else: else:
chain = itertools.chain([im], im.encoderinfo.get("append_images", [])) chain = itertools.chain([im], append_images)
im_frames = [] im_frames = []
frame_count = 0 frame_count = 0
for im_seq in chain: for im_seq in chain:
for im_frame in ImageSequence.Iterator(im_seq): for im_frame in ImageSequence.Iterator(im_seq):
im_frame = im_frame.copy() if im_frame.mode == rawmode:
if im_frame.mode != im.mode: im_frame = im_frame.copy()
if im.mode == "P": else:
im_frame = im_frame.convert(im.mode, palette=im.palette) if rawmode == "P":
im_frame = im_frame.convert(rawmode, palette=im.palette)
else: else:
im_frame = im_frame.convert(im.mode) im_frame = im_frame.convert(rawmode)
encoderinfo = im.encoderinfo.copy() encoderinfo = im.encoderinfo.copy()
if isinstance(duration, (list, tuple)): if isinstance(duration, (list, tuple)):
encoderinfo["duration"] = duration[frame_count] encoderinfo["duration"] = duration[frame_count]
@ -1221,7 +1221,26 @@ def _save_all(im, fp, filename):
def _save(im, fp, filename, chunk=putchunk, save_all=False): def _save(im, fp, filename, chunk=putchunk, save_all=False):
# save an image to disk (called by the save method) # save an image to disk (called by the save method)
mode = im.mode if save_all:
default_image = im.encoderinfo.get(
"default_image", im.info.get("default_image")
)
modes = set()
append_images = im.encoderinfo.get("append_images", [])
if default_image:
chain = itertools.chain(append_images)
else:
chain = itertools.chain([im], append_images)
for im_seq in chain:
for im_frame in ImageSequence.Iterator(im_seq):
modes.add(im_frame.mode)
for mode in ("RGBA", "RGB", "P"):
if mode in modes:
break
else:
mode = modes.pop()
else:
mode = im.mode
if mode == "P": if mode == "P":
@ -1373,7 +1392,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
chunk(fp, b"eXIf", exif) chunk(fp, b"eXIf", exif)
if save_all: if save_all:
_write_multiple_frames(im, fp, chunk, rawmode) _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
else: else:
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])

View File

@ -173,6 +173,7 @@ OPEN_INFO = {
(II, 1, (1,), 2, (8,), ()): ("L", "L;R"), (II, 1, (1,), 2, (8,), ()): ("L", "L;R"),
(MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
(II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"),
(II, 0, (1,), 1, (16,), ()): ("I;16", "I;16"),
(II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"), (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"),
(MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"), (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"),
(II, 1, (1,), 2, (16,), ()): ("I;16", "I;16R"), (II, 1, (1,), 2, (16,), ()): ("I;16", "I;16R"),

View File

@ -232,7 +232,39 @@ TAGS_V2_GROUPS = {
41730: ("CFAPattern", UNDEFINED, 1), 41730: ("CFAPattern", UNDEFINED, 1),
}, },
# GPSInfoIFD # GPSInfoIFD
34853: {}, 34853: {
0: ("GPSVersionID", BYTE, 4),
1: ("GPSLatitudeRef", ASCII, 2),
2: ("GPSLatitude", RATIONAL, 3),
3: ("GPSLongitudeRef", ASCII, 2),
4: ("GPSLongitude", RATIONAL, 3),
5: ("GPSAltitudeRef", BYTE, 1),
6: ("GPSAltitude", RATIONAL, 1),
7: ("GPSTimeStamp", RATIONAL, 3),
8: ("GPSSatellites", ASCII, 0),
9: ("GPSStatus", ASCII, 2),
10: ("GPSMeasureMode", ASCII, 2),
11: ("GPSDOP", RATIONAL, 1),
12: ("GPSSpeedRef", ASCII, 2),
13: ("GPSSpeed", RATIONAL, 1),
14: ("GPSTrackRef", ASCII, 2),
15: ("GPSTrack", RATIONAL, 1),
16: ("GPSImgDirectionRef", ASCII, 2),
17: ("GPSImgDirection", RATIONAL, 1),
18: ("GPSMapDatum", ASCII, 0),
19: ("GPSDestLatitudeRef", ASCII, 2),
20: ("GPSDestLatitude", RATIONAL, 3),
21: ("GPSDestLongitudeRef", ASCII, 2),
22: ("GPSDestLongitude", RATIONAL, 3),
23: ("GPSDestBearingRef", ASCII, 2),
24: ("GPSDestBearing", RATIONAL, 1),
25: ("GPSDestDistanceRef", ASCII, 2),
26: ("GPSDestDistance", RATIONAL, 1),
27: ("GPSProcessingMethod", UNDEFINED, 0),
28: ("GPSAreaInformation", UNDEFINED, 0),
29: ("GPSDateStamp", ASCII, 11),
30: ("GPSDifferential", SHORT, 1),
},
# InteroperabilityIFD # InteroperabilityIFD
40965: {1: ("InteropIndex", ASCII, 1), 2: ("InteropVersion", UNDEFINED, 1)}, 40965: {1: ("InteropIndex", ASCII, 1), 2: ("InteropVersion", UNDEFINED, 1)},
} }

View File

@ -311,9 +311,11 @@ 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") or "" icc_profile = im.encoderinfo.get("icc_profile") or ""
exif = im.encoderinfo.get("exif", "") exif = im.encoderinfo.get("exif", b"")
if isinstance(exif, Image.Exif): if isinstance(exif, Image.Exif):
exif = exif.tobytes() exif = exif.tobytes()
if exif.startswith(b"Exif\x00\x00"):
exif = exif[6:]
xmp = im.encoderinfo.get("xmp", "") xmp = im.encoderinfo.get("xmp", "")
method = im.encoderinfo.get("method", 4) method = im.encoderinfo.get("method", 4)

View File

@ -376,11 +376,8 @@ PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
actual = "L"; actual = "L";
break; break;
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */ case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
actual = "RGB";
break;
case 6: /* BC6: 3-channel 16-bit float */ case 6: /* BC6: 3-channel 16-bit float */
/* TODO: support 4-channel floating point images */ actual = "RGB";
actual = "RGBAF";
break; break;
default: default:
PyErr_SetString(PyExc_ValueError, "block compression type unknown"); PyErr_SetString(PyExc_ValueError, "block compression type unknown");

View File

@ -23,10 +23,6 @@ typedef struct {
UINT8 l; UINT8 l;
} lum; } lum;
typedef struct {
FLOAT32 r, g, b;
} rgb32f;
typedef struct { typedef struct {
UINT16 c0, c1; UINT16 c0, c1;
UINT32 lut; UINT32 lut;
@ -536,53 +532,53 @@ static const bc6_mode_info bc6_modes[] = {
/* Table.F, encoded as a sequence of bit indices */ /* Table.F, encoded as a sequence of bit indices */
static const UINT8 bc6_bit_packings[][75] = { static const UINT8 bc6_bit_packings[][75] = {
{116, 132, 176, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, {116, 132, 180, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 39, 40, 41, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65,
66, 67, 68, 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 66, 67, 68, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128,
129, 130, 131, 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, 129, 130, 131, 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
{117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 172, 173, 132, 16, 17, {117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 176, 177, 132, 16, 17,
18, 19, 20, 21, 22, 133, 174, 116, 32, 33, 34, 35, 36, 37, 38, 18, 19, 20, 21, 22, 133, 178, 116, 32, 33, 34, 35, 36, 37, 38,
175, 177, 176, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 179, 181, 180, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65,
66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128,
129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
48, 49, 50, 51, 52, 10, 112, 113, 114, 115, 64, 65, 66, 67, 26, 48, 49, 50, 51, 52, 10, 112, 113, 114, 115, 64, 65, 66, 67, 26,
172, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, 128, 129, 130, 131, 176, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131,
96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
48, 49, 50, 51, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, 48, 49, 50, 51, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68,
26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, 128, 129, 130, 131, 26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131,
96, 97, 98, 99, 172, 174, 144, 145, 146, 147, 116, 175}, 96, 97, 98, 99, 176, 178, 144, 145, 146, 147, 116, 179},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
48, 49, 50, 51, 10, 132, 112, 113, 114, 115, 64, 65, 66, 67, 26, 48, 49, 50, 51, 10, 132, 112, 113, 114, 115, 64, 65, 66, 67, 26,
172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 42, 128, 129, 130, 131, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 42, 128, 129, 130, 131,
96, 97, 98, 99, 173, 174, 144, 145, 146, 147, 176, 175}, 96, 97, 98, 99, 177, 178, 144, 145, 146, 147, 180, 179},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 132, 16, 17, 18, 19, 20, {0, 1, 2, 3, 4, 5, 6, 7, 8, 132, 16, 17, 18, 19, 20,
21, 22, 23, 24, 116, 32, 33, 34, 35, 36, 37, 38, 39, 40, 176, 21, 22, 23, 24, 116, 32, 33, 34, 35, 36, 37, 38, 39, 40, 180,
48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68,
172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131,
96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
{0, 1, 2, 3, 4, 5, 6, 7, 164, 132, 16, 17, 18, 19, 20, {0, 1, 2, 3, 4, 5, 6, 7, 164, 132, 16, 17, 18, 19, 20,
21, 22, 23, 174, 116, 32, 33, 34, 35, 36, 37, 38, 39, 175, 176, 21, 22, 23, 178, 116, 32, 33, 34, 35, 36, 37, 38, 39, 179, 180,
48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68,
172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131,
96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149},
{0, 1, 2, 3, 4, 5, 6, 7, 172, 132, 16, 17, 18, 19, 20, {0, 1, 2, 3, 4, 5, 6, 7, 176, 132, 16, 17, 18, 19, 20,
21, 22, 23, 117, 116, 32, 33, 34, 35, 36, 37, 38, 39, 165, 176, 21, 22, 23, 117, 116, 32, 33, 34, 35, 36, 37, 38, 39, 165, 180,
48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68,
69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131,
96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
{0, 1, 2, 3, 4, 5, 6, 7, 173, 132, 16, 17, 18, 19, 20, {0, 1, 2, 3, 4, 5, 6, 7, 177, 132, 16, 17, 18, 19, 20,
21, 22, 23, 133, 116, 32, 33, 34, 35, 36, 37, 38, 39, 177, 176, 21, 22, 23, 133, 116, 32, 33, 34, 35, 36, 37, 38, 39, 181, 180,
48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68,
172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131,
96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
{0, 1, 2, 3, 4, 5, 164, 172, 173, 132, 16, 17, 18, 19, 20, {0, 1, 2, 3, 4, 5, 164, 176, 177, 132, 16, 17, 18, 19, 20,
21, 117, 133, 174, 116, 32, 33, 34, 35, 36, 37, 165, 175, 177, 176, 21, 117, 133, 178, 116, 32, 33, 34, 35, 36, 37, 165, 179, 181, 180,
48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68,
69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131,
96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149},
@ -681,20 +677,31 @@ bc6_finalize(int v, int sign) {
} }
} }
static UINT8
bc6_clamp(float value) {
if (value < 0.0f) {
return 0;
} else if (value > 1.0f) {
return 255;
} else {
return (UINT8) (value * 255.0f);
}
}
static void static void
bc6_lerp(rgb32f *col, int *e0, int *e1, int s, int sign) { bc6_lerp(rgba *col, int *e0, int *e1, int s, int sign) {
int r, g, b; int r, g, b;
int t = 64 - s; int t = 64 - s;
r = (e0[0] * t + e1[0] * s) >> 6; r = (e0[0] * t + e1[0] * s) >> 6;
g = (e0[1] * t + e1[1] * s) >> 6; g = (e0[1] * t + e1[1] * s) >> 6;
b = (e0[2] * t + e1[2] * s) >> 6; b = (e0[2] * t + e1[2] * s) >> 6;
col->r = bc6_finalize(r, sign); col->r = bc6_clamp(bc6_finalize(r, sign));
col->g = bc6_finalize(g, sign); col->g = bc6_clamp(bc6_finalize(g, sign));
col->b = bc6_finalize(b, sign); col->b = bc6_clamp(bc6_finalize(b, sign));
} }
static void static void
decode_bc6_block(rgb32f *col, const UINT8 *src, int sign) { decode_bc6_block(rgba *col, const UINT8 *src, int sign) {
UINT16 endpoints[12]; /* storage for r0, g0, b0, r1, ... */ UINT16 endpoints[12]; /* storage for r0, g0, b0, r1, ... */
int ueps[12]; int ueps[12];
int i, i0, ib2, di, dw, mask, numep, s; int i, i0, ib2, di, dw, mask, numep, s;
@ -744,21 +751,16 @@ decode_bc6_block(rgb32f *col, const UINT8 *src, int sign) {
} }
if (sign || info->tr) { /* sign-extend e1,2,3 if signed or deltas */ if (sign || info->tr) { /* sign-extend e1,2,3 if signed or deltas */
for (i = 3; i < numep; i += 3) { for (i = 3; i < numep; i += 3) {
bc6_sign_extend(&endpoints[i + 0], info->rb); bc6_sign_extend(&endpoints[i], info->rb);
bc6_sign_extend(&endpoints[i + 1], info->gb); bc6_sign_extend(&endpoints[i + 1], info->gb);
bc6_sign_extend(&endpoints[i + 2], info->bb); bc6_sign_extend(&endpoints[i + 2], info->bb);
} }
} }
if (info->tr) { /* apply deltas */ if (info->tr) { /* apply deltas */
for (i = 3; i < numep; i++) { for (i = 3; i < numep; i += 3) {
endpoints[i] = (endpoints[i] + endpoints[0]) & mask; endpoints[i] = (endpoints[i] + endpoints[0]) & mask;
} endpoints[i + 1] = (endpoints[i + 1] + endpoints[1]) & mask;
if (sign) { endpoints[i + 2] = (endpoints[i + 2] + endpoints[2]) & mask;
for (i = 3; i < numep; i += 3) {
bc6_sign_extend(&endpoints[i + 0], info->rb);
bc6_sign_extend(&endpoints[i + 1], info->gb);
bc6_sign_extend(&endpoints[i + 2], info->bb);
}
} }
} }
for (i = 0; i < numep; i++) { for (i = 0; i < numep; i++) {
@ -862,8 +864,8 @@ decode_bcn(
break; break;
case 6: case 6:
while (bytes >= 16) { while (bytes >= 16) {
rgb32f col[16]; rgba col[16];
decode_bc6_block(col, ptr, (state->state >> 4) & 1); decode_bc6_block(col, ptr, strcmp(pixel_format, "BC6HS") == 0 ? 1 : 0);
put_block(im, state, (const char *)col, sizeof(col[0]), C); put_block(im, state, (const char *)col, sizeof(col[0]), C);
ptr += 16; ptr += 16;
bytes -= 16; bytes -= 16;

View File

@ -432,18 +432,18 @@ fill_mask_L(
} }
} else { } else {
int alpha_channel = 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;
for (y = 0; y < ysize; y++) { for (y = 0; y < ysize; y++) {
UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize; UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize;
UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx;
for (x = 0; x < xsize; x++) { for (x = 0; x < xsize; x++) {
for (i = 0; i < pixelsize; i++) { for (i = 0; i < pixelsize; i++) {
UINT8 channel_mask = *mask; UINT8 channel_mask = *mask;
if ((strcmp(imOut->mode, "RGBa") == 0 || if (alpha_channel && i != 3 && channel_mask != 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 != 0) {
channel_mask = channel_mask =
255 - (255 - channel_mask) * (1 - (255 - out[3]) / 255); 255 - (255 - channel_mask) * (1 - (255 - out[3]) / 255);
} }

View File

@ -132,9 +132,9 @@ deps = {
"bins": ["cjpeg.exe", "djpeg.exe"], "bins": ["cjpeg.exe", "djpeg.exe"],
}, },
"zlib": { "zlib": {
"url": "https://zlib.net/zlib1212.zip", "url": "https://zlib.net/zlib1213.zip",
"filename": "zlib1212.zip", "filename": "zlib1213.zip",
"dir": "zlib-1.2.12", "dir": "zlib-1.2.13",
"build": [ "build": [
cmd_nmake(r"win32\Makefile.msc", "clean"), cmd_nmake(r"win32\Makefile.msc", "clean"),
cmd_nmake(r"win32\Makefile.msc", "zlib.lib"), cmd_nmake(r"win32\Makefile.msc", "zlib.lib"),
@ -281,9 +281,9 @@ deps = {
"libs": [r"imagequant.lib"], "libs": [r"imagequant.lib"],
}, },
"harfbuzz": { "harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/5.2.0.zip", "url": "https://github.com/harfbuzz/harfbuzz/archive/5.3.1.zip",
"filename": "harfbuzz-5.2.0.zip", "filename": "harfbuzz-5.3.1.zip",
"dir": "harfbuzz-5.2.0", "dir": "harfbuzz-5.3.1",
"build": [ "build": [
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
cmd_nmake(target="clean"), cmd_nmake(target="clean"),