Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expanded .ani and .cur functionality #6606

Open
wants to merge 60 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
32a6c22
did not work with all .cur files
Sep 21, 2022
e28357e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 21, 2022
f4fde29
added ability to save as cursor
Sep 22, 2022
95b8061
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2022
fbcf1b6
removed unused imports
Sep 22, 2022
6a5878d
reverted to original as change no longer necessary
Sep 22, 2022
d722d0d
Merge branch 'main' of https://github.com/jlwoolf/Pillow
Sep 22, 2022
2e08e89
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2022
73e5955
hotspots now accessible in info
Sep 22, 2022
a18bb13
Merge branch 'main' of https://github.com/jlwoolf/Pillow
Sep 22, 2022
6f22968
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2022
7585c21
added exceptions to pass tests
Sep 22, 2022
569feee
Merge branch 'main' of https://github.com/jlwoolf/Pillow
Sep 22, 2022
69b4ebf
Hopefully corrected bpp issue with older .cur
Sep 22, 2022
1d846c2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2022
3eda472
added more cursors for testing
Sep 23, 2022
c74859d
fixed bug with transparency
Sep 23, 2022
0b3bc19
added another test
Sep 23, 2022
c11dea9
added .ani plugin
Sep 23, 2022
9b8c29f
Merge branch 'main' of https://github.com/jlwoolf/Pillow
Sep 23, 2022
86476a9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 23, 2022
17453f2
fixed bug in test
Sep 23, 2022
ce07c0b
Merge branch 'python-pillow:main' into main
jlwoolf Sep 27, 2022
cd4ee04
docstring for cur plugin
Sep 27, 2022
da093b9
doc string for ani plugin
Sep 27, 2022
fc8a288
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 27, 2022
084704d
linting
Sep 27, 2022
0c1ff74
Merge branch 'main' of https://github.com/jlwoolf/Pillow
Sep 27, 2022
bac4ae4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 27, 2022
01d71be
bitmaps should be transparent if 32bits
Sep 27, 2022
551e2a6
completed cursor tests
Sep 27, 2022
5eb3e53
added animated cursor tests
Sep 27, 2022
8aa0a86
Merge branch 'main' of https://github.com/jlwoolf/Pillow
Sep 27, 2022
3547591
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 27, 2022
915df6c
fixed bug regarding bitmap (added alpha flag)
Sep 27, 2022
7045898
Merge branch 'main' of https://github.com/jlwoolf/Pillow
Sep 27, 2022
d46f45a
Merge branch 'python-pillow:main' into main
jlwoolf Sep 27, 2022
e8684d7
Update src/PIL/CurImagePlugin.py
jlwoolf Oct 3, 2022
1d57114
Merge branch 'python-pillow:main' into main
jlwoolf Oct 4, 2022
792f519
Do not use PyAccess after re-assigning im
radarhere Oct 20, 2022
05be477
Merge branch 'main' into main
radarhere Oct 24, 2022
444a58d
Merge branch 'jlwoolf'
radarhere Oct 27, 2022
6ef6973
Merge branch 'python-pillow'
radarhere Dec 30, 2022
23f5550
Merge branch 'python-pillow'
radarhere Feb 25, 2023
c5e8d12
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 25, 2023
cd1fec4
Merge branch 'main' into main
radarhere Sep 2, 2023
5f6d3f1
mode is read-only
radarhere Sep 2, 2023
c52ade8
Merge branch 'main' into main
radarhere Dec 9, 2023
227c84f
Removed unnecessary list comprehension
radarhere Dec 9, 2023
bc01ee2
Merge branch 'main' into main
radarhere Dec 24, 2023
76179a2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 24, 2023
4128e8c
Merge branch 'main' into main
radarhere Jan 21, 2024
4524742
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 21, 2024
50cbabe
Merge branch 'main' into main
radarhere Feb 24, 2024
7268920
Merge branch 'main' into main
radarhere Mar 1, 2024
374f4d0
Merge branch 'main' into main
radarhere Apr 21, 2024
88750f4
Merge branch 'main' into main
radarhere May 18, 2024
7188a65
Merge branch 'main' into main
radarhere Jul 19, 2024
2ef7a51
Merge branch 'main' into main
radarhere Oct 4, 2024
b16b579
Merge branch 'main' into main
radarhere Mar 3, 2025
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
Binary file added Tests/images/ani/aero_busy.ani
Binary file not shown.
Binary file added Tests/images/ani/aero_busy_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/ani/aero_busy_8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/ani/posy_busy.ani
Binary file not shown.
Binary file added Tests/images/ani/posy_busy_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/ani/posy_busy_24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/ani/stopwtch.ani
Binary file not shown.
Binary file added Tests/images/ani/stopwtch_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/ani/stopwtch_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/cur/aero_arrow.cur
Binary file not shown.
File renamed without changes.
File renamed without changes.
Binary file added Tests/images/cur/posy_link.cur
Binary file not shown.
Binary file added Tests/images/cur/posy_link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/cur/stopwtch.cur
Binary file not shown.
Binary file added Tests/images/cur/win98_arrow.cur
Binary file not shown.
Binary file added Tests/images/cur/win98_arrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
171 changes: 171 additions & 0 deletions Tests/test_file_ani.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
from __future__ import annotations

from io import BytesIO

import pytest

from PIL import Image


def test_aero_busy():
with Image.open("Tests/images/ani/aero_busy.ani") as im:
assert im.size == (64, 64)
assert im.info["frames"] == 18

with Image.open("Tests/images/ani/aero_busy_0.png") as png:
assert png.tobytes() == im.tobytes()

im.seek(8)
with Image.open("Tests/images/ani/aero_busy_8.png") as png:
assert png.tobytes() == im.tobytes()

with pytest.raises(EOFError):
im.seek(-1)

with pytest.raises(EOFError):
im.seek(18)


def test_posy_busy():
with Image.open("Tests/images/ani/posy_busy.ani") as im:
assert im.size == (96, 96)
assert im.info["frames"] == 77

with Image.open("Tests/images/ani/posy_busy_0.png") as png:
assert png.tobytes() == im.tobytes()

im.seek(24)
with Image.open("Tests/images/ani/posy_busy_24.png") as png:
assert png.tobytes() == im.tobytes()

with pytest.raises(EOFError):
im.seek(77)


def test_stopwtch():
with Image.open("Tests/images/ani/stopwtch.ani") as im:
assert im.size == (32, 32)
assert im.info["frames"] == 8

assert im.info["seq"][0] == 0
assert im.info["seq"][2] == 0

for i, r in enumerate(im.info["rate"]):
if i == 1 or i == 2:
assert r == 16
else:
assert r == 8

with Image.open("Tests/images/ani/stopwtch_0.png") as png:
assert png.tobytes() == im.tobytes()

im.seek(5)
with Image.open("Tests/images/ani/stopwtch_5.png") as png:
assert png.tobytes() == im.tobytes()

with pytest.raises(EOFError):
im.seek(8)


def test_save():
directory_path = "Tests/images/ani/"
filenames = [
"aero_busy_0.png",
"aero_busy_8.png",
"posy_busy_0.png",
"posy_busy_24.png",
"stopwtch_0.png",
"stopwtch_5.png",
]

images = [Image.open(directory_path + filename) for filename in filenames]

with BytesIO() as output:
images[0].save(
output, append_images=[images[1]], seq=[0, 1], rate=[5, 10], format="ANI"
)

with Image.open(output, formats=["ANI"]) as im:
assert im.tobytes() == images[0].tobytes()
im.seek(1)
assert im.tobytes() == images[1].tobytes()
assert im.info["seq"] == [0, 1]
assert im.info["rate"] == [5, 10]

with BytesIO() as output:
images[2].save(
output,
append_images=[images[3]],
seq=[1, 0],
rate=[2, 2],
format="ANI",
sizes=[(96, 96)],
)

with Image.open(output, formats=["ANI"]) as im:
assert im.tobytes() == images[2].tobytes()
im.seek(1)
assert im.tobytes() == images[3].tobytes()
assert im.info["seq"] == [1, 0]
assert im.info["rate"] == [2, 2]

with BytesIO() as output:
images[4].save(
output, append_images=[images[5]], seq=[0, 1], rate=[3, 4], format="ANI"
)

with Image.open(output, formats=["ANI"]) as im:
assert im.tobytes() == images[4].tobytes()
im.seek(1)
assert im.tobytes() == images[5].tobytes()
assert im.info["seq"] == [0, 1]
assert im.info["rate"] == [3, 4]

with BytesIO() as output:
images[0].save(
output,
append_images=images[1:],
seq=[0, 2, 4, 1, 3, 5, 0, 1, 0, 1],
rate=[1, 2, 3, 1, 2, 3, 1, 2, 3, 4],
format="ANI",
sizes=[(32, 32)],
)

with Image.open(output, formats=["ANI"]) as im:
assert im.info["frames"] == 6
assert im.info["seq"] == [0, 2, 4, 1, 3, 5, 0, 1, 0, 1]
assert im.info["rate"] == [1, 2, 3, 1, 2, 3, 1, 2, 3, 4]
assert im.size == (32, 32)

im.seek(4)
assert im.tobytes() == images[4].tobytes()

with BytesIO() as output:
with pytest.raises(ValueError):
images[0].save(
output,
append_images=images[1:],
seq=[0, 1, 8, 1, 2],
rate=[1, 1, 1, 1, 1],
format="ANI",
sizes=[(32, 32)],
)

with pytest.raises(ValueError):
images[0].save(
output,
append_images=images[1:],
seq=[0, 1, 1, 1, 2],
rate=[1, 1, 1, 1],
format="ANI",
sizes=[(32, 32)],
)

with pytest.raises(ValueError):
images[0].save(
output,
append_images=images[1:],
rate=[1, 1, 1, 1],
format="ANI",
sizes=[(32, 32)],
)
117 changes: 110 additions & 7 deletions Tests/test_file_cur.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,135 @@
from __future__ import annotations

from io import BytesIO

import pytest

from PIL import CurImagePlugin, Image

TEST_FILE = "Tests/images/deerstalker.cur"


def test_sanity() -> None:
with Image.open(TEST_FILE) as im:
def test_deerstalker() -> None:
with Image.open("Tests/images/cur/deerstalker.cur") as im:
assert im.size == (32, 32)
assert im.info["hotspots"] == [(0, 0)]
assert isinstance(im, CurImagePlugin.CurImageFile)
# Check some pixel colors to ensure image is loaded properly
assert im.getpixel((10, 1)) == (0, 0, 0, 0)
assert im.getpixel((11, 1)) == (253, 254, 254, 1)
assert im.getpixel((16, 16)) == (84, 87, 86, 255)


def test_posy_link():
with Image.open("Tests/images/cur/posy_link.cur") as im:
assert im.size == (128, 128)
assert im.info["sizes"] == [(128, 128), (96, 96), (64, 64), (48, 48), (32, 32)]
assert im.info["hotspots"] == [(25, 7), (18, 5), (12, 3), (9, 2), (5, 1)]
# check some pixel colors
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((20, 20)) == (0, 0, 0, 255)
assert im.getpixel((40, 40)) == (255, 255, 255, 255)

im.size = (32, 32)
im.load()
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((10, 10)) == (191, 191, 191, 255)


def test_stopwtch():
with Image.open("Tests/images/cur/stopwtch.cur") as im:
assert im.size == (32, 32)
assert im.info["hotspots"] == [(16, 19)]

assert im.getpixel((16, 16)) == (0, 0, 255, 255)
assert im.getpixel((8, 16)) == (255, 0, 0, 255)


def test_win98_arrow():
with Image.open("Tests/images/cur/win98_arrow.cur") as im:
assert im.size == (32, 32)
assert im.info["hotspots"] == [(10, 10)]

assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((16, 16)) == (0, 0, 0, 255)
assert im.getpixel((14, 19)) == (255, 255, 255, 255)


def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
invalid_file = "Tests/images/cur/posy_link.png"

with pytest.raises(SyntaxError):
CurImagePlugin.CurImageFile(invalid_file)

no_cursors_file = "Tests/images/no_cursors.cur"
no_cursors_file = "Tests/images/cur/no_cursors.cur"

cur = CurImagePlugin.CurImageFile(TEST_FILE)
cur = CurImagePlugin.CurImageFile("Tests/images/cur/deerstalker.cur")
cur.fp.close()
with open(no_cursors_file, "rb") as cur.fp:
with pytest.raises(TypeError):
cur._open()


def test_save_win98_arrow():
with Image.open("Tests/images/cur/win98_arrow.png") as im:
# save the data
with BytesIO() as output:
im.save(
output,
format="CUR",
sizes=[(32, 32)],
hotspots=[(10, 10)],
bitmap_format="bmp",
)

with Image.open(output) as im2:
assert im.tobytes() == im2.tobytes()

with BytesIO() as output:
im.save(output, format="CUR")

# check default save params
with Image.open(output) as im2:
assert im2.size == (32, 32)
assert im2.info["sizes"] == [(32, 32), (24, 24), (16, 16)]
assert im2.info["hotspots"] == [(0, 0), (0, 0), (0, 0)]


def test_save_posy_link():
sizes = [(128, 128), (96, 96), (64, 64), (48, 48), (32, 32)]
hotspots = [(25, 7), (18, 5), (12, 3), (9, 2), (5, 1)]

with Image.open("Tests/images/cur/posy_link.png") as im:
# save the data
with BytesIO() as output:
im.save(
output,
sizes=sizes,
hotspots=hotspots,
format="CUR",
bitmap_format="bmp",
)

# make sure saved output is readable
# and sizes/hotspots are correct
with Image.open(output, formats=["CUR"]) as im2:
assert (128, 128) == im2.size
assert sizes == im2.info["sizes"]

with BytesIO() as output:
im.save(output, sizes=sizes[3:], hotspots=hotspots[3:], format="CUR")

# make sure saved output is readable
# and sizes/hotspots are correct
with Image.open(output, formats=["CUR"]) as im2:
assert (48, 48) == im2.size
assert sizes[3:] == im2.info["sizes"]

# make sure error is thrown when size and hotspot len's
# don't match
with pytest.raises(ValueError):
im.save(
output,
sizes=sizes[2:],
hotspots=hotspots[3:],
format="CUR",
bitmap_format="bmp",
)
Loading
Loading