mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-10 16:22:22 +03:00
Merge 23373c8a7e
into 329d6a6a62
This commit is contained in:
commit
73e9a30bcb
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -708,6 +710,56 @@ def test_apng_save_blend(tmp_path: Path) -> None:
|
||||||
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_all_progress() -> None:
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
def callback(state) -> None:
|
||||||
|
if state["image_filename"]:
|
||||||
|
state["image_filename"] = os.path.basename(state["image_filename"])
|
||||||
|
progress.append(state)
|
||||||
|
|
||||||
|
Image.new("RGB", (1, 1)).save(out, "PNG", save_all=True, progress=callback)
|
||||||
|
assert progress == [
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 1,
|
||||||
|
"total_frames": 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
with Image.open("Tests/images/apng/single_frame.png") as im:
|
||||||
|
with Image.open("Tests/images/apng/delay.png") as im2:
|
||||||
|
im.save(
|
||||||
|
out, "PNG", save_all=True, append_images=[im, im2], progress=callback
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
for i in range(2):
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": i,
|
||||||
|
"image_filename": "single_frame.png",
|
||||||
|
"completed_frames": i + 1,
|
||||||
|
"total_frames": 7,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for i in range(5):
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": 2,
|
||||||
|
"image_filename": "delay.png",
|
||||||
|
"completed_frames": i + 3,
|
||||||
|
"total_frames": 7,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert progress == expected
|
||||||
|
|
||||||
|
|
||||||
def test_apng_save_size(tmp_path: Path) -> None:
|
def test_apng_save_size(tmp_path: Path) -> None:
|
||||||
test_file = tmp_path / "temp.png"
|
test_file = tmp_path / "temp.png"
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,52 @@ class TestFileAvif:
|
||||||
with Image.open(blob) as im:
|
with Image.open(blob) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
def test_save_all_progress(self) -> None:
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
def callback(state) -> None:
|
||||||
|
if state["image_filename"]:
|
||||||
|
state["image_filename"] = os.path.basename(state["image_filename"])
|
||||||
|
progress.append(state)
|
||||||
|
|
||||||
|
Image.new("RGB", (1, 1)).save(out, "AVIF", save_all=True, progress=callback)
|
||||||
|
assert progress == [
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 1,
|
||||||
|
"total_frames": 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
with Image.open("Tests/images/avif/star.avifs") as im:
|
||||||
|
im2 = Image.new(im.mode, im.size)
|
||||||
|
im.save(out, "AVIF", save_all=True, append_images=[im2], progress=callback)
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
for i in range(5):
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": "star.avifs",
|
||||||
|
"completed_frames": i + 1,
|
||||||
|
"total_frames": 6,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": 1,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 6,
|
||||||
|
"total_frames": 6,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert progress == expected
|
||||||
|
|
||||||
def test_background_from_gif(self, tmp_path: Path) -> None:
|
def test_background_from_gif(self, tmp_path: Path) -> None:
|
||||||
with Image.open("Tests/images/chi.gif") as im:
|
with Image.open("Tests/images/chi.gif") as im:
|
||||||
original_value = im.convert("RGB").getpixel((1, 1))
|
original_value = im.convert("RGB").getpixel((1, 1))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
@ -309,6 +310,52 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None:
|
||||||
assert reloaded.getpixel((0, 0)) == 255
|
assert reloaded.getpixel((0, 0)) == 255
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_all_progress() -> None:
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
def callback(state) -> None:
|
||||||
|
if state["image_filename"]:
|
||||||
|
state["image_filename"] = os.path.basename(state["image_filename"])
|
||||||
|
progress.append(state)
|
||||||
|
|
||||||
|
Image.new("RGB", (1, 1)).save(out, "GIF", save_all=True, progress=callback)
|
||||||
|
assert progress == [
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 1,
|
||||||
|
"total_frames": 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
with Image.open("Tests/images/chi.gif") as im2:
|
||||||
|
im = Image.new("RGB", im2.size)
|
||||||
|
im.save(out, "GIF", save_all=True, append_images=[im2], progress=callback)
|
||||||
|
|
||||||
|
expected: list[dict[str, int | str | None]] = [
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 1,
|
||||||
|
"total_frames": 32,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
for i in range(31):
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": 1,
|
||||||
|
"image_filename": "chi.gif",
|
||||||
|
"completed_frames": i + 2,
|
||||||
|
"total_frames": 32,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert progress == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path, mode",
|
"path, mode",
|
||||||
(
|
(
|
||||||
|
|
|
@ -314,6 +314,48 @@ def test_save_all() -> None:
|
||||||
assert "mp" not in jpg.info
|
assert "mp" not in jpg.info
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_all_progress() -> None:
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
def callback(state) -> None:
|
||||||
|
if state["image_filename"]:
|
||||||
|
state["image_filename"] = (
|
||||||
|
state["image_filename"].replace("\\", "/").split("Tests/images/")[-1]
|
||||||
|
)
|
||||||
|
progress.append(state)
|
||||||
|
|
||||||
|
Image.new("RGB", (1, 1)).save(out, "MPO", save_all=True, progress=callback)
|
||||||
|
assert progress == [
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 1,
|
||||||
|
"total_frames": 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
with Image.open("Tests/images/sugarshack.mpo") as im:
|
||||||
|
with Image.open("Tests/images/frozenpond.mpo") as im2:
|
||||||
|
im.save(out, "MPO", save_all=True, append_images=[im2], progress=callback)
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
for i, filename in enumerate(["sugarshack.mpo", "frozenpond.mpo"]):
|
||||||
|
for j in range(2):
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": i,
|
||||||
|
"image_filename": filename,
|
||||||
|
"completed_frames": i * 2 + j + 1,
|
||||||
|
"total_frames": 4,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert progress == expected
|
||||||
|
|
||||||
|
|
||||||
def test_save_xmp() -> None:
|
def test_save_xmp() -> None:
|
||||||
im = Image.new("RGB", (1, 1))
|
im = Image.new("RGB", (1, 1))
|
||||||
im2 = Image.new("RGB", (1, 1), "#f00")
|
im2 = Image.new("RGB", (1, 1), "#f00")
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
@ -179,6 +179,46 @@ def test_save_all(tmp_path: Path) -> None:
|
||||||
assert os.path.getsize(outfile) > 0
|
assert os.path.getsize(outfile) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_all_progress() -> None:
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
def callback(state) -> None:
|
||||||
|
if state["image_filename"]:
|
||||||
|
state["image_filename"] = os.path.basename(state["image_filename"])
|
||||||
|
progress.append(state)
|
||||||
|
|
||||||
|
Image.new("RGB", (1, 1)).save(out, "PDF", save_all=True, progress=callback)
|
||||||
|
assert progress == [
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 1,
|
||||||
|
"total_frames": 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
with Image.open("Tests/images/sugarshack.mpo") as im:
|
||||||
|
with Image.open("Tests/images/frozenpond.mpo") as im2:
|
||||||
|
im.save(out, "PDF", save_all=True, append_images=[im2], progress=callback)
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
for i, filename in enumerate(["sugarshack.mpo", "frozenpond.mpo"]):
|
||||||
|
for j in range(2):
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": i,
|
||||||
|
"image_filename": filename,
|
||||||
|
"completed_frames": i * 2 + j + 1,
|
||||||
|
"total_frames": 4,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert progress == expected
|
||||||
|
|
||||||
|
|
||||||
def test_multiframe_normal_save(tmp_path: Path) -> None:
|
def test_multiframe_normal_save(tmp_path: Path) -> None:
|
||||||
# Test saving a multiframe image without save_all
|
# Test saving a multiframe image without save_all
|
||||||
with Image.open("Tests/images/dispose_bgnd.gif") as im:
|
with Image.open("Tests/images/dispose_bgnd.gif") as im:
|
||||||
|
@ -334,12 +374,12 @@ def test_pdf_info(tmp_path: Path) -> None:
|
||||||
|
|
||||||
def test_pdf_append_to_bytesio() -> None:
|
def test_pdf_append_to_bytesio() -> None:
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
f = io.BytesIO()
|
f = BytesIO()
|
||||||
im.save(f, format="PDF")
|
im.save(f, format="PDF")
|
||||||
initial_size = len(f.getvalue())
|
initial_size = len(f.getvalue())
|
||||||
assert initial_size > 0
|
assert initial_size > 0
|
||||||
im = hopper("P")
|
im = hopper("P")
|
||||||
f = io.BytesIO(f.getvalue())
|
f = BytesIO(f.getvalue())
|
||||||
im.save(f, format="PDF", append=True)
|
im.save(f, format="PDF", append=True)
|
||||||
assert len(f.getvalue()) > initial_size
|
assert len(f.getvalue()) > initial_size
|
||||||
|
|
||||||
|
|
|
@ -752,7 +752,7 @@ class TestFileTiff:
|
||||||
with Image.open(outfile) as reloaded:
|
with Image.open(outfile) as reloaded:
|
||||||
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
||||||
|
|
||||||
def test_tiff_save_all(self) -> None:
|
def test_save_all(self) -> None:
|
||||||
mp = BytesIO()
|
mp = BytesIO()
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
im.save(mp, format="tiff", save_all=True)
|
im.save(mp, format="tiff", save_all=True)
|
||||||
|
@ -785,6 +785,53 @@ class TestFileTiff:
|
||||||
assert isinstance(reread, TiffImagePlugin.TiffImageFile)
|
assert isinstance(reread, TiffImagePlugin.TiffImageFile)
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
|
def test_save_all_progress(self) -> None:
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
def callback(state) -> None:
|
||||||
|
if state["image_filename"]:
|
||||||
|
state["image_filename"] = os.path.basename(state["image_filename"])
|
||||||
|
progress.append(state)
|
||||||
|
|
||||||
|
Image.new("RGB", (1, 1)).save(out, "TIFF", save_all=True, progress=callback)
|
||||||
|
assert progress == [
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 1,
|
||||||
|
"total_frames": 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
with Image.open("Tests/images/hopper.tif") as im:
|
||||||
|
with Image.open("Tests/images/multipage.tiff") as im2:
|
||||||
|
im.save(
|
||||||
|
out, "TIFF", save_all=True, append_images=[im2], progress=callback
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": "hopper.tif",
|
||||||
|
"completed_frames": 1,
|
||||||
|
"total_frames": 4,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
for i in range(3):
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": 1,
|
||||||
|
"image_filename": "multipage.tiff",
|
||||||
|
"completed_frames": i + 2,
|
||||||
|
"total_frames": 4,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert progress == expected
|
||||||
|
|
||||||
def test_fixoffsets(self) -> None:
|
def test_fixoffsets(self) -> None:
|
||||||
b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
|
b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
|
||||||
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
@ -105,10 +106,10 @@ class TestFileWebp:
|
||||||
def test_write_method(self, tmp_path: Path) -> None:
|
def test_write_method(self, tmp_path: Path) -> None:
|
||||||
self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6})
|
self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6})
|
||||||
|
|
||||||
buffer_no_args = io.BytesIO()
|
buffer_no_args = BytesIO()
|
||||||
hopper().save(buffer_no_args, format="WEBP")
|
hopper().save(buffer_no_args, format="WEBP")
|
||||||
|
|
||||||
buffer_method = io.BytesIO()
|
buffer_method = BytesIO()
|
||||||
hopper().save(buffer_method, format="WEBP", method=6)
|
hopper().save(buffer_method, format="WEBP", method=6)
|
||||||
assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
|
assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
|
||||||
|
|
||||||
|
@ -129,6 +130,52 @@ class TestFileWebp:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
_webp.WebPEncode(im.getim(), False, 0, 0, "", 4, 0, b"", "")
|
_webp.WebPEncode(im.getim(), False, 0, 0, "", 4, 0, b"", "")
|
||||||
|
|
||||||
|
def test_save_all_progress(self) -> None:
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
def callback(state) -> None:
|
||||||
|
if state["image_filename"]:
|
||||||
|
state["image_filename"] = os.path.basename(state["image_filename"])
|
||||||
|
progress.append(state)
|
||||||
|
|
||||||
|
Image.new("RGB", (1, 1)).save(out, "WEBP", save_all=True, progress=callback)
|
||||||
|
assert progress == [
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 1,
|
||||||
|
"total_frames": 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
out = BytesIO()
|
||||||
|
progress = []
|
||||||
|
|
||||||
|
with Image.open("Tests/images/iss634.webp") as im:
|
||||||
|
im2 = Image.new("RGB", im.size)
|
||||||
|
im.save(out, "WEBP", save_all=True, append_images=[im2], progress=callback)
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
for i in range(42):
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": 0,
|
||||||
|
"image_filename": "iss634.webp",
|
||||||
|
"completed_frames": i + 1,
|
||||||
|
"total_frames": 43,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expected.append(
|
||||||
|
{
|
||||||
|
"image_index": 1,
|
||||||
|
"image_filename": None,
|
||||||
|
"completed_frames": 43,
|
||||||
|
"total_frames": 43,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert progress == expected
|
||||||
|
|
||||||
def test_icc_profile(self, tmp_path: Path) -> None:
|
def test_icc_profile(self, tmp_path: Path) -> None:
|
||||||
self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None})
|
self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None})
|
||||||
self._roundtrip(
|
self._roundtrip(
|
||||||
|
|
|
@ -235,8 +235,9 @@ def _save(
|
||||||
frame_duration = 0
|
frame_duration = 0
|
||||||
cur_idx = im.tell()
|
cur_idx = im.tell()
|
||||||
is_single_frame = total == 1
|
is_single_frame = total == 1
|
||||||
|
progress = info.get("progress")
|
||||||
try:
|
try:
|
||||||
for ims in [im] + append_images:
|
for i, ims in enumerate([im] + append_images):
|
||||||
# Get number of frames in this image
|
# Get number of frames in this image
|
||||||
nfr = getattr(ims, "n_frames", 1)
|
nfr = getattr(ims, "n_frames", 1)
|
||||||
|
|
||||||
|
@ -267,6 +268,7 @@ def _save(
|
||||||
|
|
||||||
# Update frame index
|
# Update frame index
|
||||||
frame_idx += 1
|
frame_idx += 1
|
||||||
|
im._save_all_progress(progress, ims, i, frame_idx, total)
|
||||||
|
|
||||||
if not save_all:
|
if not save_all:
|
||||||
break
|
break
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
#
|
#
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import itertools
|
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -662,12 +661,17 @@ def _write_multiple_frames(
|
||||||
duration = im.encoderinfo.get("duration")
|
duration = im.encoderinfo.get("duration")
|
||||||
disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
|
disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
|
||||||
|
|
||||||
|
im_sequences = [im, *im.encoderinfo.get("append_images", [])]
|
||||||
|
progress = im.encoderinfo.get("progress")
|
||||||
|
if progress:
|
||||||
|
total = sum(getattr(seq, "n_frames", 1) for seq in im_sequences)
|
||||||
|
|
||||||
im_frames: list[_Frame] = []
|
im_frames: list[_Frame] = []
|
||||||
previous_im: Image.Image | None = None
|
previous_im: Image.Image | None = None
|
||||||
frame_count = 0
|
frame_count = 0
|
||||||
background_im = None
|
background_im = None
|
||||||
for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
|
for i, seq in enumerate(im_sequences):
|
||||||
for im_frame in ImageSequence.Iterator(imSequence):
|
for im_frame in ImageSequence.Iterator(seq):
|
||||||
# a copy is required here since seek can still mutate the image
|
# a copy is required here since seek can still mutate the image
|
||||||
im_frame = _normalize_mode(im_frame.copy())
|
im_frame = _normalize_mode(im_frame.copy())
|
||||||
if frame_count == 0:
|
if frame_count == 0:
|
||||||
|
@ -697,6 +701,8 @@ def _write_multiple_frames(
|
||||||
# This frame is identical to the previous frame
|
# This frame is identical to the previous frame
|
||||||
if encoderinfo.get("duration"):
|
if encoderinfo.get("duration"):
|
||||||
im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"]
|
im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"]
|
||||||
|
if progress:
|
||||||
|
im._save_all_progress(progress, seq, i, frame_count, total)
|
||||||
continue
|
continue
|
||||||
if im_frames[-1].encoderinfo.get("disposal") == 2:
|
if im_frames[-1].encoderinfo.get("disposal") == 2:
|
||||||
# To appear correctly in viewers using a convention,
|
# To appear correctly in viewers using a convention,
|
||||||
|
@ -760,6 +766,8 @@ def _write_multiple_frames(
|
||||||
bbox = None
|
bbox = None
|
||||||
previous_im = im_frame
|
previous_im = im_frame
|
||||||
im_frames.append(_Frame(diff_frame or im_frame, bbox, encoderinfo))
|
im_frames.append(_Frame(diff_frame or im_frame, bbox, encoderinfo))
|
||||||
|
if progress:
|
||||||
|
im._save_all_progress(progress, seq, i, frame_count, total)
|
||||||
|
|
||||||
if len(im_frames) == 1:
|
if len(im_frames) == 1:
|
||||||
if "duration" in im.encoderinfo:
|
if "duration" in im.encoderinfo:
|
||||||
|
|
|
@ -2586,6 +2586,26 @@ class Image:
|
||||||
self.encoderinfo = {**im._default_encoderinfo, **encoderinfo}
|
self.encoderinfo = {**im._default_encoderinfo, **encoderinfo}
|
||||||
return encoderinfo
|
return encoderinfo
|
||||||
|
|
||||||
|
def _save_all_progress(
|
||||||
|
self,
|
||||||
|
progress,
|
||||||
|
im: Image | None = None,
|
||||||
|
im_index: int = 0,
|
||||||
|
completed: int = 1,
|
||||||
|
total: int = 1,
|
||||||
|
) -> None:
|
||||||
|
if not progress:
|
||||||
|
return
|
||||||
|
|
||||||
|
progress(
|
||||||
|
{
|
||||||
|
"image_index": im_index,
|
||||||
|
"image_filename": getattr(im or self, "filename", None),
|
||||||
|
"completed_frames": completed,
|
||||||
|
"total_frames": total,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def seek(self, frame: int) -> None:
|
def seek(self, frame: int) -> None:
|
||||||
"""
|
"""
|
||||||
Seeks to the given frame in this sequence file. If you seek
|
Seeks to the given frame in this sequence file. If you seek
|
||||||
|
|
|
@ -40,16 +40,20 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
append_images = im.encoderinfo.get("append_images", [])
|
append_images = im.encoderinfo.get("append_images", [])
|
||||||
|
progress = im.encoderinfo.get("progress")
|
||||||
if not append_images and not getattr(im, "is_animated", False):
|
if not append_images and not getattr(im, "is_animated", False):
|
||||||
_save(im, fp, filename)
|
_save(im, fp, filename)
|
||||||
|
im._save_all_progress(progress)
|
||||||
return
|
return
|
||||||
|
|
||||||
mpf_offset = 28
|
mpf_offset = 28
|
||||||
offsets: list[int] = []
|
offsets: list[int] = []
|
||||||
im_sequences = [im, *append_images]
|
im_sequences = [im, *append_images]
|
||||||
total = sum(getattr(seq, "n_frames", 1) for seq in im_sequences)
|
total = sum(getattr(seq, "n_frames", 1) for seq in im_sequences)
|
||||||
for im_sequence in im_sequences:
|
if progress:
|
||||||
for im_frame in ImageSequence.Iterator(im_sequence):
|
completed = 0
|
||||||
|
for i, seq in enumerate(im_sequences):
|
||||||
|
for im_frame in ImageSequence.Iterator(seq):
|
||||||
if not offsets:
|
if not offsets:
|
||||||
# APP2 marker
|
# APP2 marker
|
||||||
ifd_length = 66 + 16 * total
|
ifd_length = 66 + 16 * total
|
||||||
|
@ -73,6 +77,9 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
im_frame.save(fp, "JPEG")
|
im_frame.save(fp, "JPEG")
|
||||||
im_frame.encoderinfo = encoderinfo
|
im_frame.encoderinfo = encoderinfo
|
||||||
offsets.append(fp.tell() - offsets[-1])
|
offsets.append(fp.tell() - offsets[-1])
|
||||||
|
if progress:
|
||||||
|
completed += 1
|
||||||
|
im._save_all_progress(progress, seq, i, completed, total)
|
||||||
|
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
ifd[0xB000] = b"0100"
|
ifd[0xB000] = b"0100"
|
||||||
|
|
|
@ -254,7 +254,8 @@ def _save(
|
||||||
existing_pdf.write_catalog()
|
existing_pdf.write_catalog()
|
||||||
|
|
||||||
page_number = 0
|
page_number = 0
|
||||||
for im_sequence in ims:
|
progress = im.encoderinfo.get("progress")
|
||||||
|
for i, im_sequence in enumerate(ims):
|
||||||
im_pages: ImageSequence.Iterator | list[Image.Image] = (
|
im_pages: ImageSequence.Iterator | list[Image.Image] = (
|
||||||
ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
|
ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
|
||||||
)
|
)
|
||||||
|
@ -290,6 +291,9 @@ def _save(
|
||||||
existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)
|
existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)
|
||||||
|
|
||||||
page_number += 1
|
page_number += 1
|
||||||
|
im._save_all_progress(
|
||||||
|
progress, im_sequence, i, page_number, number_of_pages
|
||||||
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
# trailer
|
# trailer
|
||||||
|
|
|
@ -1169,16 +1169,19 @@ def _write_multiple_frames(
|
||||||
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))
|
||||||
|
progress = im.encoderinfo.get("progress")
|
||||||
|
|
||||||
if default_image:
|
im_sequences = []
|
||||||
chain = itertools.chain(append_images)
|
if not default_image:
|
||||||
else:
|
im_sequences.append(im)
|
||||||
chain = itertools.chain([im], append_images)
|
im_sequences += append_images
|
||||||
|
if progress:
|
||||||
|
total = sum(getattr(seq, "n_frames", 1) for seq in im_sequences)
|
||||||
|
|
||||||
im_frames: list[_Frame] = []
|
im_frames: list[_Frame] = []
|
||||||
frame_count = 0
|
frame_count = 0
|
||||||
for im_seq in chain:
|
for i, seq in enumerate(im_sequences):
|
||||||
for im_frame in ImageSequence.Iterator(im_seq):
|
for im_frame in ImageSequence.Iterator(seq):
|
||||||
if im_frame.mode == mode:
|
if im_frame.mode == mode:
|
||||||
im_frame = im_frame.copy()
|
im_frame = im_frame.copy()
|
||||||
else:
|
else:
|
||||||
|
@ -1225,10 +1228,14 @@ def _write_multiple_frames(
|
||||||
and "duration" in encoderinfo
|
and "duration" in encoderinfo
|
||||||
):
|
):
|
||||||
previous.encoderinfo["duration"] += encoderinfo["duration"]
|
previous.encoderinfo["duration"] += encoderinfo["duration"]
|
||||||
|
if progress:
|
||||||
|
im._save_all_progress(progress, seq, i, frame_count, total)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
bbox = None
|
bbox = None
|
||||||
im_frames.append(_Frame(im_frame, bbox, encoderinfo))
|
im_frames.append(_Frame(im_frame, bbox, encoderinfo))
|
||||||
|
if progress:
|
||||||
|
im._save_all_progress(progress, seq, i, frame_count, total)
|
||||||
|
|
||||||
if len(im_frames) == 1 and not default_image:
|
if len(im_frames) == 1 and not default_image:
|
||||||
return im_frames[0].im
|
return im_frames[0].im
|
||||||
|
|
|
@ -2296,25 +2296,36 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
|
|
||||||
|
|
||||||
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
progress = im.encoderinfo.get("progress")
|
||||||
append_images = list(im.encoderinfo.get("append_images", []))
|
append_images = list(im.encoderinfo.get("append_images", []))
|
||||||
if not hasattr(im, "n_frames") and not append_images:
|
if not hasattr(im, "n_frames") and not append_images:
|
||||||
return _save(im, fp, filename)
|
_save(im, fp, filename)
|
||||||
|
im._save_all_progress(progress)
|
||||||
|
return
|
||||||
|
|
||||||
cur_idx = im.tell()
|
cur_idx = im.tell()
|
||||||
|
im_sequences = [im] + append_images
|
||||||
|
if progress:
|
||||||
|
completed = 0
|
||||||
|
total = sum(getattr(seq, "n_frames", 1) for seq in im_sequences)
|
||||||
try:
|
try:
|
||||||
with AppendingTiffWriter(fp) as tf:
|
with AppendingTiffWriter(fp) as tf:
|
||||||
for ims in [im] + append_images:
|
for i, seq in enumerate(im_sequences):
|
||||||
encoderinfo = ims._attach_default_encoderinfo(im)
|
encoderinfo = seq._attach_default_encoderinfo(im)
|
||||||
if not hasattr(ims, "encoderconfig"):
|
if not hasattr(seq, "encoderconfig"):
|
||||||
ims.encoderconfig = ()
|
seq.encoderconfig = ()
|
||||||
nfr = getattr(ims, "n_frames", 1)
|
nfr = getattr(seq, "n_frames", 1)
|
||||||
|
|
||||||
for idx in range(nfr):
|
for idx in range(nfr):
|
||||||
ims.seek(idx)
|
seq.seek(idx)
|
||||||
ims.load()
|
seq.load()
|
||||||
_save(ims, tf, filename)
|
_save(seq, tf, filename)
|
||||||
|
if progress:
|
||||||
|
completed += 1
|
||||||
|
im._save_all_progress(progress, seq, i, completed, total)
|
||||||
|
|
||||||
tf.newFrame()
|
tf.newFrame()
|
||||||
ims.encoderinfo = encoderinfo
|
seq.encoderinfo = encoderinfo
|
||||||
finally:
|
finally:
|
||||||
im.seek(cur_idx)
|
im.seek(cur_idx)
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,7 @@ def _convert_frame(im: Image.Image) -> Image.Image:
|
||||||
|
|
||||||
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
encoderinfo = im.encoderinfo.copy()
|
encoderinfo = im.encoderinfo.copy()
|
||||||
|
progress = encoderinfo.get("progress")
|
||||||
append_images = list(encoderinfo.get("append_images", []))
|
append_images = list(encoderinfo.get("append_images", []))
|
||||||
|
|
||||||
# If total frame count is 1, then save using the legacy API, which
|
# If total frame count is 1, then save using the legacy API, which
|
||||||
|
@ -165,6 +166,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
total += getattr(ims, "n_frames", 1)
|
total += getattr(ims, "n_frames", 1)
|
||||||
if total == 1:
|
if total == 1:
|
||||||
_save(im, fp, filename)
|
_save(im, fp, filename)
|
||||||
|
im._save_all_progress(progress)
|
||||||
return
|
return
|
||||||
|
|
||||||
background: int | tuple[int, ...] = (0, 0, 0, 0)
|
background: int | tuple[int, ...] = (0, 0, 0, 0)
|
||||||
|
@ -237,7 +239,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
timestamp = 0
|
timestamp = 0
|
||||||
cur_idx = im.tell()
|
cur_idx = im.tell()
|
||||||
try:
|
try:
|
||||||
for ims in [im] + append_images:
|
for i, ims in enumerate([im] + append_images):
|
||||||
# Get number of frames in this image
|
# Get number of frames in this image
|
||||||
nfr = getattr(ims, "n_frames", 1)
|
nfr = getattr(ims, "n_frames", 1)
|
||||||
|
|
||||||
|
@ -262,6 +264,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
else:
|
else:
|
||||||
timestamp += duration
|
timestamp += duration
|
||||||
frame_idx += 1
|
frame_idx += 1
|
||||||
|
im._save_all_progress(progress, ims, i, frame_idx, total)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
im.seek(cur_idx)
|
im.seek(cur_idx)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user