mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-03-22 11:04:13 +03:00
Merge branch 'main' into main
This commit is contained in:
commit
05be47783e
54
CHANGES.rst
54
CHANGES.rst
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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
BIN
Tests/images/bc6h.dds
Normal file
Binary file not shown.
BIN
Tests/images/bc6h.png
Normal file
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
BIN
Tests/images/bc6h_sf.dds
Normal file
Binary file not shown.
BIN
Tests/images/bc6h_sf.png
Normal file
BIN
Tests/images/bc6h_sf.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
Tests/images/blp/blp1_jpeg2.blp
Normal file
BIN
Tests/images/blp/blp1_jpeg2.blp
Normal file
Binary file not shown.
BIN
Tests/images/bw_gradient.imt
Normal file
BIN
Tests/images/bw_gradient.imt
Normal file
Binary file not shown.
BIN
Tests/images/hopper_palette_chunk_second.fli
Normal file
BIN
Tests/images/hopper_palette_chunk_second.fli
Normal file
Binary file not shown.
BIN
Tests/images/no_palette_after_rgb.gif
Normal file
BIN
Tests/images/no_palette_after_rgb.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 101 B |
BIN
Tests/images/palette_not_needed_for_second_frame.gif
Normal file
BIN
Tests/images/palette_not_needed_for_second_frame.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
Tests/images/text_float_coord.png
Normal file
BIN
Tests/images/text_float_coord.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
BIN
Tests/images/text_float_coord_1_alt.png
Normal file
BIN
Tests/images/text_float_coord_1_alt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 807 B |
Binary file not shown.
|
@ -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_",
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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",
|
||||||
(
|
(
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
19
Tests/test_file_imt.py
Normal 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)
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
[
|
[
|
||||||
|
|
|
@ -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 doesn’t 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 doesn’t 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`
|
||||||
|
|
|
@ -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
|
||||||
^^^
|
^^^
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -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__"]
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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"``.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)])
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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)},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user