Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6850465
Added progress callback when save_all is used
radarhere Oct 2, 2023
3465a59
Pass back index of image in sequence, instead of filename
radarhere Oct 2, 2023
b5f3228
Restored filename in progress callback
radarhere Oct 5, 2023
fe7c9d3
Moved progress code into common private method
radarhere Oct 6, 2023
fae0653
Merge branch 'main' into progress
radarhere Dec 21, 2023
19c2721
Merge branch 'main' into progress
radarhere Dec 22, 2023
4a0a011
Merge branch 'main' into progress
radarhere Dec 27, 2023
69680db
Merge branch 'main' into progress
radarhere Jan 21, 2024
344c300
Merge branch 'main' into progress
radarhere Jan 31, 2024
3c80ec0
Merge branch 'main' into progress
radarhere Feb 11, 2024
b7a5b59
Updated tests for os.path.realpath
radarhere Feb 12, 2024
14560e1
Merge branch 'main' into progress
radarhere Mar 11, 2024
d52b3a2
Merge branch 'main' into progress
radarhere Apr 1, 2024
682a9ae
Merge branch 'main' into progress
radarhere Jun 12, 2024
2f27173
Merge branch 'main' into progress
radarhere Jun 26, 2024
26e2a9f
Merge branch 'main' into progress
radarhere Jul 3, 2024
8b4b7ce
Merge branch 'main' into progress
radarhere Jul 16, 2024
ebbdc6e
Merge branch 'main' into progress
radarhere Sep 26, 2024
fc5c096
Merge branch 'main' into progress
radarhere Oct 12, 2024
62786fd
Merge branch 'main' into progress
radarhere Dec 28, 2024
af7954b
Merge branch 'main' into progress
radarhere Mar 5, 2025
ac009d0
Merge branch 'main' into progress
radarhere Mar 18, 2025
24b9ca7
Merge branch 'main' into progress
radarhere Mar 21, 2025
09e4df1
Merge branch 'main' into progress
radarhere Apr 21, 2025
5da88ea
Added progress callback to AVIF
radarhere Apr 21, 2025
ed5e327
Merge branch 'main' into progress
radarhere Jun 28, 2025
23373c8
Merge branch 'main' into progress
radarhere Jul 7, 2025
8c3c981
Merge branch 'main' into progress
radarhere Dec 6, 2025
c3ab770
Merge branch 'main' into progress
radarhere Feb 16, 2026
55afac4
Use NamedTuple
radarhere Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions Tests/test_file_apng.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import os
from io import BytesIO
from pathlib import Path

Expand Down Expand Up @@ -727,6 +728,58 @@ def test_apng_save_blend(tmp_path: Path) -> None:
assert im.getpixel((0, 0)) == (0, 255, 0, 255)


def test_save_all_progress() -> None:
out = BytesIO()
progress = []

def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
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.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.Progress(
image_index=i,
image_filename="single_frame.png",
completed_frames=i + 1,
total_frames=7,
)
)
for i in range(5):
expected.append(
Image.Progress(
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:
test_file = tmp_path / "temp.png"

Expand Down
48 changes: 48 additions & 0 deletions Tests/test_file_avif.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,54 @@ def test_file_pointer_could_be_reused(self) -> None:
with Image.open(blob) as im:
im.load()

def test_save_all_progress(self) -> None:
out = BytesIO()
progress = []

def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
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.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.Progress(
image_index=0,
image_filename="star.avifs",
completed_frames=i + 1,
total_frames=6,
)
)
expected.append(
Image.Progress(
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:
with Image.open("Tests/images/chi.gif") as im:
original_value = im.convert("RGB").getpixel((1, 1))
Expand Down
49 changes: 49 additions & 0 deletions Tests/test_file_gif.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import os
import warnings
from collections.abc import Generator
from io import BytesIO
Expand Down Expand Up @@ -318,6 +319,54 @@ def test_save_zero(size: tuple[int, int]) -> None:
im.save(b, "GIF")


def test_save_all_progress() -> None:
out = BytesIO()
progress = []

def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
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.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[Image.Progress] = [
Image.Progress(
image_index=0,
image_filename=None,
completed_frames=1,
total_frames=32,
)
]
for i in range(31):
expected.append(
Image.Progress(
image_index=1,
image_filename="chi.gif",
completed_frames=i + 2,
total_frames=32,
)
)
assert progress == expected


@pytest.mark.parametrize(
"path, mode",
(
Expand Down
44 changes: 44 additions & 0 deletions Tests/test_file_mpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,50 @@ def test_save_all() -> None:
assert "mp" not in jpg.info


def test_save_all_progress() -> None:
out = BytesIO()
progress = []

def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
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.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.Progress(
image_index=i,
image_filename=filename,
completed_frames=i * 2 + j + 1,
total_frames=4,
)
)
assert progress == expected


def test_save_xmp() -> None:
im = Image.new("RGB", (1, 1))
im2 = Image.new("RGB", (1, 1), "#f00")
Expand Down
48 changes: 45 additions & 3 deletions Tests/test_file_pdf.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from __future__ import annotations

import io
import os
import os.path
import tempfile
import time
from collections.abc import Generator
from io import BytesIO
from pathlib import Path
from typing import Any

Expand Down Expand Up @@ -179,6 +179,48 @@ def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
assert os.path.getsize(outfile) > 0


def test_save_all_progress() -> None:
out = BytesIO()
progress = []

def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
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.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.Progress(
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:
# Test saving a multiframe image without save_all
with Image.open("Tests/images/dispose_bgnd.gif") as im:
Expand Down Expand Up @@ -334,12 +376,12 @@ def test_pdf_info(tmp_path: Path) -> None:

def test_pdf_append_to_bytesio() -> None:
im = hopper("RGB")
f = io.BytesIO()
f = BytesIO()
im.save(f, format="PDF")
initial_size = len(f.getvalue())
assert initial_size > 0
im = hopper("P")
f = io.BytesIO(f.getvalue())
f = BytesIO(f.getvalue())
im.save(f, format="PDF", append=True)
assert len(f.getvalue()) > initial_size

Expand Down
51 changes: 50 additions & 1 deletion Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ def test_palette(self, mode: str, tmp_path: Path) -> None:
with Image.open(outfile) as reloaded:
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))

def test_tiff_save_all(self) -> None:
def test_save_all(self) -> None:
mp = BytesIO()
with Image.open("Tests/images/multipage.tiff") as im:
im.save(mp, format="tiff", save_all=True)
Expand Down Expand Up @@ -785,6 +785,55 @@ def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
assert isinstance(reread, TiffImagePlugin.TiffImageFile)
assert reread.n_frames == 3

def test_save_all_progress(self) -> None:
out = BytesIO()
progress = []

def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
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.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.Progress(
image_index=0,
image_filename="hopper.tif",
completed_frames=1,
total_frames=4,
)
]
for i in range(3):
expected.append(
Image.Progress(
image_index=1,
image_filename="multipage.tiff",
completed_frames=i + 2,
total_frames=4,
)
)
assert progress == expected

def test_fixoffsets(self) -> None:
b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
with TiffImagePlugin.AppendingTiffWriter(b) as a:
Expand Down
Loading
Loading