mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-03-18 09:12: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)
|
||||
------------------
|
||||
|
||||
- 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
|
||||
[radarhere]
|
||||
|
||||
|
|
|
@ -208,12 +208,11 @@ class PillowLeakTestCase:
|
|||
# ru_maxrss
|
||||
# This is the maximum resident set size utilized (in bytes).
|
||||
return mem / 1024 # Kb
|
||||
else:
|
||||
# linux
|
||||
# man 2 getrusage
|
||||
# ru_maxrss (since Linux 2.6.32)
|
||||
# This is the maximum resident set size used (in kilobytes).
|
||||
return mem # Kb
|
||||
# linux
|
||||
# man 2 getrusage
|
||||
# ru_maxrss (since Linux 2.6.32)
|
||||
# This is the maximum resident set size used (in kilobytes).
|
||||
return mem # Kb
|
||||
|
||||
def _test_leak(self, core):
|
||||
start_mem = self._get_mem_usage()
|
||||
|
@ -285,7 +284,7 @@ def magick_command():
|
|||
|
||||
if imagemagick and shutil.which(imagemagick[0]):
|
||||
return imagemagick
|
||||
elif graphicsmagick and shutil.which(graphicsmagick[0]):
|
||||
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
||||
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)
|
||||
|
||||
|
||||
@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():
|
||||
for enum, prefix in {
|
||||
PngImagePlugin.Disposal: "APNG_DISPOSE_",
|
||||
|
|
|
@ -14,6 +14,9 @@ def test_load_blp1():
|
|||
with Image.open("Tests/images/blp/blp1_jpeg.blp") as im:
|
||||
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():
|
||||
with Image.open("Tests/images/blp/blp2_raw.blp") as im:
|
||||
|
|
|
@ -176,6 +176,11 @@ def test_rle8():
|
|||
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(
|
||||
"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_SNORM = "Tests/images/bc5_snorm.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_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.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"))
|
||||
|
||||
|
||||
@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():
|
||||
"""Check DX10 images can be opened"""
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import pytest
|
|||
|
||||
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
|
||||
# save as...-> hopper.fli, default options.
|
||||
|
@ -79,6 +79,12 @@ def test_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():
|
||||
with Image.open(static_test_file) as im:
|
||||
assert im.n_frames == 1
|
||||
|
|
|
@ -83,18 +83,40 @@ def test_l_mode_transparency():
|
|||
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():
|
||||
with Image.open("Tests/images/iss634.gif") as im:
|
||||
expected_rgb_always = im.convert("RGB")
|
||||
|
||||
with Image.open("Tests/images/chi.gif") as im:
|
||||
expected_zero = im.convert("RGB")
|
||||
expected_rgb_always_rgba = im.convert("RGBA")
|
||||
|
||||
im.seek(1)
|
||||
expected_one = im.convert("RGB")
|
||||
expected_different = im.convert("RGB")
|
||||
|
||||
try:
|
||||
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_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.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
||||
|
@ -105,7 +127,7 @@ def test_strategy():
|
|||
|
||||
im.seek(1)
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
out = str(tmp_path / "temp.im")
|
||||
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:
|
||||
import defusedxml.ElementTree as ElementTree
|
||||
from defusedxml import ElementTree
|
||||
except ImportError:
|
||||
ElementTree = None
|
||||
|
||||
|
|
|
@ -934,7 +934,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
im.save(out, exif=tags, compression=compression)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
for tag in tags.keys():
|
||||
for tag in tags:
|
||||
assert tag not in reloaded.getexif()
|
||||
|
||||
def test_old_style_jpeg(self):
|
||||
|
|
|
@ -20,7 +20,7 @@ from .helper import (
|
|||
)
|
||||
|
||||
try:
|
||||
import defusedxml.ElementTree as ElementTree
|
||||
from defusedxml import ElementTree
|
||||
except ImportError:
|
||||
ElementTree = None
|
||||
|
||||
|
|
|
@ -240,8 +240,8 @@ def test_header_token_too_long(tmp_path):
|
|||
def test_truncated_file(tmp_path):
|
||||
# Test EOF in header
|
||||
path = str(tmp_path / "temp.pgm")
|
||||
with open(path, "w") as f:
|
||||
f.write("P6")
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"P6")
|
||||
|
||||
with pytest.raises(ValueError) as e:
|
||||
with Image.open(path):
|
||||
|
@ -256,11 +256,11 @@ def test_truncated_file(tmp_path):
|
|||
im.load()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("maxval", (0, 65536))
|
||||
@pytest.mark.parametrize("maxval", (b"0", b"65536"))
|
||||
def test_invalid_maxval(maxval, tmp_path):
|
||||
path = str(tmp_path / "temp.ppm")
|
||||
with open(path, "w") as f:
|
||||
f.write("P6\n3 1 " + str(maxval))
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"P6\n3 1 " + maxval)
|
||||
|
||||
with pytest.raises(ValueError) as e:
|
||||
with Image.open(path):
|
||||
|
@ -283,13 +283,13 @@ def test_neg_ppm():
|
|||
def test_mimetypes(tmp_path):
|
||||
path = str(tmp_path / "temp.pgm")
|
||||
|
||||
with open(path, "w") as f:
|
||||
f.write("P4\n128 128\n255")
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"P4\n128 128\n255")
|
||||
with Image.open(path) as im:
|
||||
assert im.get_format_mimetype() == "image/x-portable-bitmap"
|
||||
|
||||
with open(path, "w") as f:
|
||||
f.write("PyCMYK\n128 128\n255")
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"PyCMYK\n128 128\n255")
|
||||
with Image.open(path) as im:
|
||||
assert im.get_format_mimetype() == "image/x-portable-anymap"
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ from .helper import (
|
|||
)
|
||||
|
||||
try:
|
||||
import defusedxml.ElementTree as ElementTree
|
||||
from defusedxml import ElementTree
|
||||
except ImportError:
|
||||
ElementTree = None
|
||||
|
||||
|
|
|
@ -55,9 +55,7 @@ def test_write_exif_metadata():
|
|||
test_buffer.seek(0)
|
||||
with Image.open(test_buffer) as webp_image:
|
||||
webp_exif = webp_image.info.get("exif", None)
|
||||
assert webp_exif
|
||||
if webp_exif:
|
||||
assert webp_exif == expected_exif, "WebP EXIF didn't match"
|
||||
assert webp_exif == expected_exif[6:], "WebP EXIF didn't match"
|
||||
|
||||
|
||||
def test_read_icc_profile():
|
||||
|
|
|
@ -129,8 +129,6 @@ class TestImage:
|
|||
im.size = (3, 4)
|
||||
|
||||
def test_invalid_image(self):
|
||||
import io
|
||||
|
||||
im = io.BytesIO(b"")
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
with Image.open(im):
|
||||
|
@ -699,15 +697,15 @@ class TestImage:
|
|||
def test_empty_exif(self):
|
||||
with Image.open("Tests/images/exif.png") as im:
|
||||
exif = im.getexif()
|
||||
assert dict(exif) != {}
|
||||
assert dict(exif)
|
||||
|
||||
# Test that exif data is cleared after another load
|
||||
exif.load(None)
|
||||
assert dict(exif) == {}
|
||||
assert not dict(exif)
|
||||
|
||||
# Test loading just the EXIF header
|
||||
exif.load(b"Exif\x00\x00")
|
||||
assert dict(exif) == {}
|
||||
assert not dict(exif)
|
||||
|
||||
@mark_if_feature_version(
|
||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||
|
|
|
@ -131,8 +131,7 @@ class TestImageGetPixel(AccessTest):
|
|||
bands = Image.getmodebands(mode)
|
||||
if bands == 1:
|
||||
return 1
|
||||
else:
|
||||
return tuple(range(1, bands + 1))
|
||||
return tuple(range(1, bands + 1))
|
||||
|
||||
def check(self, mode, c=None):
|
||||
if not c:
|
||||
|
@ -345,13 +344,14 @@ class TestCffi(AccessTest):
|
|||
|
||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||
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))
|
||||
access = PyAccess.new(im, False)
|
||||
access.putpixel((0, 0), color)
|
||||
|
||||
alpha = color[3] if len(color) == 4 and mode == "PA" else 255
|
||||
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
||||
if len(color) == 3:
|
||||
color += (255,)
|
||||
assert im.convert("RGBA").getpixel((0, 0)) == color
|
||||
|
||||
|
||||
class TestImagePutPixelError(AccessTest):
|
||||
|
@ -414,7 +414,7 @@ class TestEmbeddable:
|
|||
def test_embeddable(self):
|
||||
import ctypes
|
||||
|
||||
with open("embed_pil.c", "w") as fh:
|
||||
with open("embed_pil.c", "w", encoding="utf-8") as fh:
|
||||
fh.write(
|
||||
"""
|
||||
#include "Python.h"
|
||||
|
|
|
@ -35,10 +35,13 @@ def test_toarray():
|
|||
test_with_dtype(numpy.float64)
|
||||
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):
|
||||
numpy.array(im_truncated)
|
||||
else:
|
||||
with pytest.warns(UserWarning):
|
||||
numpy.array(im_truncated)
|
||||
|
||||
|
||||
def test_fromarray():
|
||||
|
|
|
@ -38,6 +38,12 @@ def test_sanity():
|
|||
convert(im, output_mode)
|
||||
|
||||
|
||||
def test_unsupported_conversion():
|
||||
im = hopper()
|
||||
with pytest.raises(ValueError):
|
||||
im.convert("INVALID")
|
||||
|
||||
|
||||
def test_default():
|
||||
|
||||
im = hopper("P")
|
||||
|
@ -242,6 +248,17 @@ def test_p2pa_palette():
|
|||
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():
|
||||
# Arrange
|
||||
im = hopper("CMYK")
|
||||
|
|
|
@ -935,7 +935,30 @@ def test_standard_embedded_color(layout_engine):
|
|||
d = ImageDraw.Draw(im)
|
||||
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):
|
||||
|
|
|
@ -6,10 +6,8 @@ from PIL import Image, ImageMath
|
|||
def pixel(im):
|
||||
if hasattr(im, "im"):
|
||||
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
|
||||
else:
|
||||
if isinstance(im, int):
|
||||
return int(im) # hack to deal with booleans
|
||||
print(im)
|
||||
if isinstance(im, int):
|
||||
return int(im) # hack to deal with booleans
|
||||
|
||||
|
||||
A = Image.new("L", (1, 1), 1)
|
||||
|
|
|
@ -50,6 +50,16 @@ def test_getcolor():
|
|||
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(
|
||||
"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;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.
|
||||
|
||||
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``,
|
||||
or ``RGB`` data. 16-colour images are read as ``P`` images. 4-bit run-length encoding
|
||||
is not supported. Support for reading 8-bit run-length encoding was added in Pillow
|
||||
9.1.0.
|
||||
or ``RGB`` data. 16-colour images are read as ``P`` images.
|
||||
Support for reading 8-bit run-length encoding was added in Pillow 9.1.0.
|
||||
Support for reading 4-bit run-length encoding was added in Pillow 9.3.0.
|
||||
|
||||
Opening
|
||||
~~~~~~~
|
||||
|
@ -56,7 +56,8 @@ The :py:meth:`~PIL.Image.open` method sets the following
|
|||
:py:attr:`~PIL.Image.Image.info` properties:
|
||||
|
||||
**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
|
||||
^^^
|
||||
|
|
|
@ -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`.
|
||||
|
||||
ImageShow temporary files on Unix
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
|
|
|
@ -4,25 +4,6 @@
|
|||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Additions
|
||||
=============
|
||||
|
||||
|
@ -55,15 +36,25 @@ Additional images can also be appended when saving, by combining the
|
|||
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
|
||||
=============
|
||||
|
||||
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():
|
||||
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"))
|
||||
return locals()["__version__"]
|
||||
|
||||
|
|
|
@ -373,8 +373,8 @@ class BLP1Decoder(_BLPBaseDecoder):
|
|||
data = BytesIO(data)
|
||||
image = JpegImageFile(data)
|
||||
Image._decompression_bomb_check(image.size)
|
||||
image.mode = "RGB"
|
||||
image.tile = [("jpeg", (0, 0) + self.size, 0, ("BGRX", ""))]
|
||||
r, g, b = image.convert("RGB").split()
|
||||
image = Image.merge("RGB", (b, g, r))
|
||||
self.set_as_raw(image.tobytes())
|
||||
|
||||
|
||||
|
|
|
@ -214,7 +214,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
raw_mode, self.mode = "BGRA", "RGBA"
|
||||
except AttributeError:
|
||||
pass
|
||||
elif file_info["compression"] == self.RLE8:
|
||||
elif file_info["compression"] in (self.RLE8, self.RLE4):
|
||||
decoder_name = "bmp_rle"
|
||||
else:
|
||||
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
|
||||
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 = [
|
||||
(
|
||||
decoder_name,
|
||||
(0, 0, file_info["width"], file_info["height"]),
|
||||
offset or self.fp.tell(),
|
||||
(
|
||||
raw_mode,
|
||||
((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3),
|
||||
file_info["direction"],
|
||||
),
|
||||
tuple(args),
|
||||
)
|
||||
]
|
||||
|
||||
|
@ -283,6 +285,7 @@ class BmpRleDecoder(ImageFile.PyDecoder):
|
|||
_pulls_fd = True
|
||||
|
||||
def decode(self, buffer):
|
||||
rle4 = self.args[1]
|
||||
data = bytearray()
|
||||
x = 0
|
||||
while len(data) < self.state.xsize * self.state.ysize:
|
||||
|
@ -296,7 +299,16 @@ class BmpRleDecoder(ImageFile.PyDecoder):
|
|||
if x + num_pixels > self.state.xsize:
|
||||
# Too much data for row
|
||||
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
|
||||
else:
|
||||
if byte[0] == 0:
|
||||
|
@ -317,9 +329,18 @@ class BmpRleDecoder(ImageFile.PyDecoder):
|
|||
x = len(data) % self.state.xsize
|
||||
else:
|
||||
# absolute mode
|
||||
bytes_read = self.fd.read(byte[0])
|
||||
data += bytes_read
|
||||
if len(bytes_read) < byte[0]:
|
||||
if rle4:
|
||||
# 2 pixels per byte
|
||||
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
|
||||
x += byte[0]
|
||||
|
||||
|
|
|
@ -101,6 +101,8 @@ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
|
|||
DXGI_FORMAT_BC5_TYPELESS = 82
|
||||
DXGI_FORMAT_BC5_UNORM = 83
|
||||
DXGI_FORMAT_BC5_SNORM = 84
|
||||
DXGI_FORMAT_BC6H_UF16 = 95
|
||||
DXGI_FORMAT_BC6H_SF16 = 96
|
||||
DXGI_FORMAT_BC7_TYPELESS = 97
|
||||
DXGI_FORMAT_BC7_UNORM = 98
|
||||
DXGI_FORMAT_BC7_UNORM_SRGB = 99
|
||||
|
@ -181,6 +183,14 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
self.pixel_format = "BC5S"
|
||||
n = 5
|
||||
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):
|
||||
self.pixel_format = "BC7"
|
||||
n = 7
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i16le as i16
|
||||
|
@ -80,11 +81,19 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
|
||||
if i16(s, 4) == 0xF1FA:
|
||||
# look for palette chunk
|
||||
s = self.fp.read(6)
|
||||
if i16(s, 4) == 11:
|
||||
self._palette(palette, 2)
|
||||
elif i16(s, 4) == 4:
|
||||
self._palette(palette, 0)
|
||||
number_of_subchunks = i16(s, 6)
|
||||
chunk_size = None
|
||||
for _ in range(number_of_subchunks):
|
||||
if chunk_size is not None:
|
||||
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]
|
||||
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
||||
|
|
|
@ -274,6 +274,8 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
p = self.fp.read(3 << bits)
|
||||
if self._is_palette_needed(p):
|
||||
palette = ImagePalette.raw("RGB", p)
|
||||
else:
|
||||
palette = False
|
||||
|
||||
# image data
|
||||
bits = self.fp.read(1)[0]
|
||||
|
@ -298,12 +300,14 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
if self.dispose:
|
||||
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 self._frame_palette:
|
||||
self.mode = (
|
||||
"RGB" if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS else "P"
|
||||
)
|
||||
if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
||||
self.mode = "RGBA" if frame_transparency is not None else "RGB"
|
||||
else:
|
||||
self.mode = "P"
|
||||
else:
|
||||
self.mode = "L"
|
||||
|
||||
|
@ -313,7 +317,6 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
palette = copy(self.global_palette)
|
||||
self.palette = palette
|
||||
else:
|
||||
self._frame_transparency = frame_transparency
|
||||
if self.mode == "P":
|
||||
if (
|
||||
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
||||
|
@ -386,7 +389,8 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
transparency = -1
|
||||
if frame_transparency is not None:
|
||||
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"):
|
||||
transparency = frame_transparency
|
||||
self.tile = [
|
||||
|
@ -410,9 +414,9 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
temp_mode = "P" if self._frame_palette else "L"
|
||||
self._prev_im = None
|
||||
if self.__frame == 0:
|
||||
if "transparency" in self.info:
|
||||
if self._frame_transparency is not None:
|
||||
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"):
|
||||
self._prev_im = self.im
|
||||
|
@ -429,19 +433,20 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
def load_end(self):
|
||||
if self.__frame == 0:
|
||||
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
||||
self.mode = "RGB"
|
||||
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
|
||||
if self._frame_transparency is not None:
|
||||
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
|
||||
if self.mode == "P" and self._prev_im:
|
||||
if self._frame_transparency is not None:
|
||||
self.im.putpalettealpha(self._frame_transparency, 0)
|
||||
frame_im = self.im.convert("RGBA")
|
||||
else:
|
||||
frame_im = self.im.convert("RGB")
|
||||
if not self._prev_im:
|
||||
return
|
||||
if self._frame_transparency is not None:
|
||||
self.im.putpalettealpha(self._frame_transparency, 0)
|
||||
frame_im = self.im.convert("RGBA")
|
||||
else:
|
||||
if not self._prev_im:
|
||||
return
|
||||
frame_im = self.im
|
||||
frame_im = self.im.convert("RGB")
|
||||
frame_im = self._crop(frame_im, self.dispose_extent)
|
||||
|
||||
self.im = self._prev_im
|
||||
|
|
|
@ -352,7 +352,13 @@ def _save(im, fp, filename):
|
|||
fp.write(b"Lut: 1\r\n")
|
||||
fp.write(b"\000" * (511 - fp.tell()) + b"\032")
|
||||
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))])
|
||||
|
||||
|
||||
|
|
|
@ -679,12 +679,24 @@ class Image:
|
|||
new["shape"] = shape
|
||||
new["typestr"] = typestr
|
||||
new["version"] = 3
|
||||
if self.mode == "1":
|
||||
# Binary images need to be extended from bits to bytes
|
||||
# See: https://github.com/python-pillow/Pillow/issues/350
|
||||
new["data"] = self.tobytes("raw", "L")
|
||||
else:
|
||||
new["data"] = self.tobytes()
|
||||
try:
|
||||
if self.mode == "1":
|
||||
# Binary images need to be extended from bits to bytes
|
||||
# See: https://github.com/python-pillow/Pillow/issues/350
|
||||
new["data"] = self.tobytes("raw", "L")
|
||||
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
|
||||
|
||||
def __getstate__(self):
|
||||
|
@ -868,7 +880,7 @@ class Image:
|
|||
and the palette can be represented without a palette.
|
||||
|
||||
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".
|
||||
|
||||
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`,
|
||||
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 matrix: An optional conversion matrix. If given, this
|
||||
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")
|
||||
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
|
||||
if dither is None:
|
||||
dither = Dither.FLOYDSTEINBERG
|
||||
|
@ -1036,7 +1064,10 @@ class Image:
|
|||
except ValueError:
|
||||
try:
|
||||
# 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)
|
||||
except KeyError as e:
|
||||
raise ValueError("illegal conversion") from e
|
||||
|
|
|
@ -482,8 +482,8 @@ class ImageDraw:
|
|||
# extract mask and set text alpha
|
||||
color, mask = mask, mask.getband(3)
|
||||
color.fillband(3, (ink >> 24) & 0xFF)
|
||||
coord2 = coord[0] + mask.size[0], coord[1] + mask.size[1]
|
||||
self.im.paste(color, coord + coord2, mask)
|
||||
x, y = (int(c) for c in coord)
|
||||
self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask)
|
||||
else:
|
||||
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.
|
||||
:param layout_engine: Which layout engine to use, if available:
|
||||
: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
|
||||
:py:func:`PIL.features.check_feature` with ``feature="raqm"``.
|
||||
|
|
|
@ -115,7 +115,11 @@ class ImagePalette:
|
|||
raise ValueError("palette contains raw palette data")
|
||||
if isinstance(color, tuple):
|
||||
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]
|
||||
elif self.mode == "RGBA":
|
||||
if len(color) == 3:
|
||||
|
|
|
@ -136,7 +136,7 @@ class WindowsViewer(Viewer):
|
|||
"""The default viewer on Windows is the default system application for PNG files."""
|
||||
|
||||
format = "PNG"
|
||||
options = {"compress_level": 1}
|
||||
options = {"compress_level": 1, "save_all": True}
|
||||
|
||||
def get_command(self, file, **options):
|
||||
return (
|
||||
|
@ -154,7 +154,7 @@ class MacViewer(Viewer):
|
|||
"""The default viewer on macOS using ``Preview.app``."""
|
||||
|
||||
format = "PNG"
|
||||
options = {"compress_level": 1}
|
||||
options = {"compress_level": 1, "save_all": True}
|
||||
|
||||
def get_command(self, file, **options):
|
||||
# on darwin open returns immediately resulting in the temp
|
||||
|
@ -197,7 +197,7 @@ if sys.platform == "darwin":
|
|||
|
||||
class UnixViewer(Viewer):
|
||||
format = "PNG"
|
||||
options = {"compress_level": 1}
|
||||
options = {"compress_level": 1, "save_all": True}
|
||||
|
||||
def get_command(self, file, **options):
|
||||
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
|
||||
# 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")
|
||||
self.fp.seek(0)
|
||||
|
||||
xsize = ysize = 0
|
||||
|
||||
while True:
|
||||
|
||||
s = self.fp.read(1)
|
||||
if buffer:
|
||||
s = buffer[:1]
|
||||
buffer = buffer[1:]
|
||||
else:
|
||||
s = self.fp.read(1)
|
||||
if not s:
|
||||
break
|
||||
|
||||
|
@ -55,7 +59,12 @@ class ImtImageFile(ImageFile.ImageFile):
|
|||
|
||||
# image data begins
|
||||
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
|
||||
|
@ -63,8 +72,11 @@ class ImtImageFile(ImageFile.ImageFile):
|
|||
else:
|
||||
|
||||
# read key/value pair
|
||||
# FIXME: dangerous, may read whole file
|
||||
s = s + self.fp.readline()
|
||||
if b"\n" not in buffer:
|
||||
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:
|
||||
break
|
||||
if s[0] == ord(b"*"):
|
||||
|
@ -74,13 +86,13 @@ class ImtImageFile(ImageFile.ImageFile):
|
|||
if not m:
|
||||
break
|
||||
k, v = m.group(1, 2)
|
||||
if k == "width":
|
||||
if k == b"width":
|
||||
xsize = int(v)
|
||||
self._size = xsize, ysize
|
||||
elif k == "height":
|
||||
elif k == b"height":
|
||||
ysize = int(v)
|
||||
self._size = xsize, ysize
|
||||
elif k == "pixel" and v == "n8":
|
||||
elif k == b"pixel" and v == b"n8":
|
||||
self.mode = "L"
|
||||
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ class ChunkStream:
|
|||
self.close()
|
||||
|
||||
def close(self):
|
||||
self.queue = self.crc = self.fp = None
|
||||
self.queue = self.fp = None
|
||||
|
||||
def push(self, cid, pos, length):
|
||||
|
||||
|
@ -224,7 +224,7 @@ class ChunkStream:
|
|||
) from e
|
||||
|
||||
def crc_skip(self, cid, data):
|
||||
"""Read checksum. Used if the C module is not present"""
|
||||
"""Read checksum"""
|
||||
|
||||
self.fp.read(4)
|
||||
|
||||
|
@ -1089,28 +1089,28 @@ class _fdat:
|
|||
self.seq_num += 1
|
||||
|
||||
|
||||
def _write_multiple_frames(im, fp, chunk, rawmode):
|
||||
default_image = im.encoderinfo.get("default_image", im.info.get("default_image"))
|
||||
def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images):
|
||||
duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
|
||||
loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
|
||||
disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE))
|
||||
blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE))
|
||||
|
||||
if default_image:
|
||||
chain = itertools.chain(im.encoderinfo.get("append_images", []))
|
||||
chain = itertools.chain(append_images)
|
||||
else:
|
||||
chain = itertools.chain([im], im.encoderinfo.get("append_images", []))
|
||||
chain = itertools.chain([im], append_images)
|
||||
|
||||
im_frames = []
|
||||
frame_count = 0
|
||||
for im_seq in chain:
|
||||
for im_frame in ImageSequence.Iterator(im_seq):
|
||||
im_frame = im_frame.copy()
|
||||
if im_frame.mode != im.mode:
|
||||
if im.mode == "P":
|
||||
im_frame = im_frame.convert(im.mode, palette=im.palette)
|
||||
if im_frame.mode == rawmode:
|
||||
im_frame = im_frame.copy()
|
||||
else:
|
||||
if rawmode == "P":
|
||||
im_frame = im_frame.convert(rawmode, palette=im.palette)
|
||||
else:
|
||||
im_frame = im_frame.convert(im.mode)
|
||||
im_frame = im_frame.convert(rawmode)
|
||||
encoderinfo = im.encoderinfo.copy()
|
||||
if isinstance(duration, (list, tuple)):
|
||||
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):
|
||||
# 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":
|
||||
|
||||
|
@ -1373,7 +1392,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
|||
chunk(fp, b"eXIf", exif)
|
||||
|
||||
if save_all:
|
||||
_write_multiple_frames(im, fp, chunk, rawmode)
|
||||
_write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
|
||||
else:
|
||||
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"),
|
||||
(MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
|
||||
(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"),
|
||||
(MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"),
|
||||
(II, 1, (1,), 2, (16,), ()): ("I;16", "I;16R"),
|
||||
|
|
|
@ -232,7 +232,39 @@ TAGS_V2_GROUPS = {
|
|||
41730: ("CFAPattern", UNDEFINED, 1),
|
||||
},
|
||||
# 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
|
||||
40965: {1: ("InteropIndex", ASCII, 1), 2: ("InteropVersion", UNDEFINED, 1)},
|
||||
}
|
||||
|
|
|
@ -311,9 +311,11 @@ def _save(im, fp, filename):
|
|||
lossless = im.encoderinfo.get("lossless", False)
|
||||
quality = im.encoderinfo.get("quality", 80)
|
||||
icc_profile = im.encoderinfo.get("icc_profile") or ""
|
||||
exif = im.encoderinfo.get("exif", "")
|
||||
exif = im.encoderinfo.get("exif", b"")
|
||||
if isinstance(exif, Image.Exif):
|
||||
exif = exif.tobytes()
|
||||
if exif.startswith(b"Exif\x00\x00"):
|
||||
exif = exif[6:]
|
||||
xmp = im.encoderinfo.get("xmp", "")
|
||||
method = im.encoderinfo.get("method", 4)
|
||||
|
||||
|
|
|
@ -376,11 +376,8 @@ PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
|
|||
actual = "L";
|
||||
break;
|
||||
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
|
||||
actual = "RGB";
|
||||
break;
|
||||
case 6: /* BC6: 3-channel 16-bit float */
|
||||
/* TODO: support 4-channel floating point images */
|
||||
actual = "RGBAF";
|
||||
actual = "RGB";
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_ValueError, "block compression type unknown");
|
||||
|
|
|
@ -23,10 +23,6 @@ typedef struct {
|
|||
UINT8 l;
|
||||
} lum;
|
||||
|
||||
typedef struct {
|
||||
FLOAT32 r, g, b;
|
||||
} rgb32f;
|
||||
|
||||
typedef struct {
|
||||
UINT16 c0, c1;
|
||||
UINT32 lut;
|
||||
|
@ -536,53 +532,53 @@ static const bc6_mode_info bc6_modes[] = {
|
|||
|
||||
/* Table.F, encoded as a sequence of bit indices */
|
||||
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,
|
||||
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,
|
||||
129, 130, 131, 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175},
|
||||
{117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 172, 173, 132, 16, 17,
|
||||
18, 19, 20, 21, 22, 133, 174, 116, 32, 33, 34, 35, 36, 37, 38,
|
||||
175, 177, 176, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65,
|
||||
66, 67, 68, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128,
|
||||
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, 176, 177, 132, 16, 17,
|
||||
18, 19, 20, 21, 22, 133, 178, 116, 32, 33, 34, 35, 36, 37, 38,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
172, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, 128, 129, 130, 131,
|
||||
96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175},
|
||||
176, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131,
|
||||
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,
|
||||
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,
|
||||
26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, 128, 129, 130, 131,
|
||||
96, 97, 98, 99, 172, 174, 144, 145, 146, 147, 116, 175},
|
||||
26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131,
|
||||
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,
|
||||
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,
|
||||
172, 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},
|
||||
176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 42, 128, 129, 130, 131,
|
||||
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,
|
||||
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,
|
||||
172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131,
|
||||
96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175},
|
||||
176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131,
|
||||
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,
|
||||
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,
|
||||
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},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 172, 132, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 117, 116, 32, 33, 34, 35, 36, 37, 38, 39, 165, 176,
|
||||
{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, 180,
|
||||
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,
|
||||
96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 173, 132, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 133, 116, 32, 33, 34, 35, 36, 37, 38, 39, 177, 176,
|
||||
69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131,
|
||||
96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
|
||||
{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, 181, 180,
|
||||
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,
|
||||
96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175},
|
||||
{0, 1, 2, 3, 4, 5, 164, 172, 173, 132, 16, 17, 18, 19, 20,
|
||||
21, 117, 133, 174, 116, 32, 33, 34, 35, 36, 37, 165, 175, 177, 176,
|
||||
176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131,
|
||||
96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
|
||||
{0, 1, 2, 3, 4, 5, 164, 176, 177, 132, 16, 17, 18, 19, 20,
|
||||
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,
|
||||
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},
|
||||
|
@ -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
|
||||
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 t = 64 - s;
|
||||
r = (e0[0] * t + e1[0] * s) >> 6;
|
||||
g = (e0[1] * t + e1[1] * s) >> 6;
|
||||
b = (e0[2] * t + e1[2] * s) >> 6;
|
||||
col->r = bc6_finalize(r, sign);
|
||||
col->g = bc6_finalize(g, sign);
|
||||
col->b = bc6_finalize(b, sign);
|
||||
col->r = bc6_clamp(bc6_finalize(r, sign));
|
||||
col->g = bc6_clamp(bc6_finalize(g, sign));
|
||||
col->b = bc6_clamp(bc6_finalize(b, sign));
|
||||
}
|
||||
|
||||
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, ... */
|
||||
int ueps[12];
|
||||
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 */
|
||||
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 + 2], info->bb);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (sign) {
|
||||
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);
|
||||
}
|
||||
endpoints[i + 1] = (endpoints[i + 1] + endpoints[1]) & mask;
|
||||
endpoints[i + 2] = (endpoints[i + 2] + endpoints[2]) & mask;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < numep; i++) {
|
||||
|
@ -862,8 +864,8 @@ decode_bcn(
|
|||
break;
|
||||
case 6:
|
||||
while (bytes >= 16) {
|
||||
rgb32f col[16];
|
||||
decode_bc6_block(col, ptr, (state->state >> 4) & 1);
|
||||
rgba col[16];
|
||||
decode_bc6_block(col, ptr, strcmp(pixel_format, "BC6HS") == 0 ? 1 : 0);
|
||||
put_block(im, state, (const char *)col, sizeof(col[0]), C);
|
||||
ptr += 16;
|
||||
bytes -= 16;
|
||||
|
|
|
@ -432,18 +432,18 @@ fill_mask_L(
|
|||
}
|
||||
|
||||
} 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++) {
|
||||
UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize;
|
||||
UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx;
|
||||
for (x = 0; x < xsize; x++) {
|
||||
for (i = 0; i < pixelsize; i++) {
|
||||
UINT8 channel_mask = *mask;
|
||||
if ((strcmp(imOut->mode, "RGBa") == 0 ||
|
||||
strcmp(imOut->mode, "RGBA") == 0 ||
|
||||
strcmp(imOut->mode, "La") == 0 ||
|
||||
strcmp(imOut->mode, "LA") == 0 ||
|
||||
strcmp(imOut->mode, "PA") == 0) &&
|
||||
i != 3 && channel_mask != 0) {
|
||||
if (alpha_channel && i != 3 && channel_mask != 0) {
|
||||
channel_mask =
|
||||
255 - (255 - channel_mask) * (1 - (255 - out[3]) / 255);
|
||||
}
|
||||
|
|
|
@ -132,9 +132,9 @@ deps = {
|
|||
"bins": ["cjpeg.exe", "djpeg.exe"],
|
||||
},
|
||||
"zlib": {
|
||||
"url": "https://zlib.net/zlib1212.zip",
|
||||
"filename": "zlib1212.zip",
|
||||
"dir": "zlib-1.2.12",
|
||||
"url": "https://zlib.net/zlib1213.zip",
|
||||
"filename": "zlib1213.zip",
|
||||
"dir": "zlib-1.2.13",
|
||||
"build": [
|
||||
cmd_nmake(r"win32\Makefile.msc", "clean"),
|
||||
cmd_nmake(r"win32\Makefile.msc", "zlib.lib"),
|
||||
|
@ -281,9 +281,9 @@ deps = {
|
|||
"libs": [r"imagequant.lib"],
|
||||
},
|
||||
"harfbuzz": {
|
||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/5.2.0.zip",
|
||||
"filename": "harfbuzz-5.2.0.zip",
|
||||
"dir": "harfbuzz-5.2.0",
|
||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/5.3.1.zip",
|
||||
"filename": "harfbuzz-5.3.1.zip",
|
||||
"dir": "harfbuzz-5.3.1",
|
||||
"build": [
|
||||
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
||||
cmd_nmake(target="clean"),
|
||||
|
|
Loading…
Reference in New Issue
Block a user