Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
df3c025
Initial VTF read support
REDxEYE Aug 7, 2022
9223c06
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 7, 2022
59c2d19
Adjusted formatting regions
REDxEYE Aug 7, 2022
719a3ba
Initial implementation of Bcn encoder
REDxEYE Aug 10, 2022
b883b64
Update copyright
REDxEYE Aug 10, 2022
0d9ac2a
Update copyright
REDxEYE Aug 10, 2022
43592dd
Add Bcn BC1 encoder
REDxEYE Aug 12, 2022
8e11a61
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 12, 2022
1c3a1df
Add back accidentally removed static from decode_565
REDxEYE Aug 12, 2022
1ce95d1
Fix variable type
REDxEYE Aug 12, 2022
affaa62
Made loops compatible with old C standart
REDxEYE Aug 12, 2022
a9364f3
Fix flake errors
REDxEYE Aug 12, 2022
2cc5893
Restored black formatting
radarhere Aug 12, 2022
a05be82
Minor changes
radarhere Aug 12, 2022
c6c0749
Remove pointer casts as they may lead to UB and refactor code to not …
REDxEYE Aug 12, 2022
7285b74
Add support for other common pixel formats
REDxEYE Aug 13, 2022
afc0e8e
Fix mipmap calculation
REDxEYE Aug 13, 2022
59a4375
Refactor vtf saving
REDxEYE Aug 13, 2022
b7a5894
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 13, 2022
ed70519
Add better check for DXT1 with alpha
REDxEYE Aug 13, 2022
3e78437
Add support for few more formats
REDxEYE Aug 13, 2022
f368f30
Add basic read tests for VTF
REDxEYE Aug 13, 2022
53e1831
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 13, 2022
6b781a6
Add raw encoders/decoders for 2 channel RG format
REDxEYE Aug 14, 2022
9ff852f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 14, 2022
a634a44
Add missing images
REDxEYE Aug 14, 2022
c3457f1
Add switch to DXT1 encoder to select block mode (With alpha/Without a…
REDxEYE Aug 14, 2022
906890b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 14, 2022
a2729c0
Merge branch 'main' into vtf-support
radarhere Dec 24, 2023
d5e330e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 24, 2023
f73325f
mode is read-only
radarhere Dec 24, 2023
0ed7d5c
Assign exception string to variable first
radarhere Dec 24, 2023
1b567c5
Merge branch 'main' into vtf-support
radarhere May 22, 2024
44d70b8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 22, 2024
72c3420
Merge branch 'main' into vtf-support
radarhere Jul 19, 2024
f41910c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 19, 2024
753964c
Fixed type hints
radarhere Apr 26, 2025
1c7b3e4
Merge branch 'main' into vtf-support
radarhere May 3, 2025
e29c0a5
Removed support for saving DXT1_ONEBITALPHA
radarhere May 3, 2025
64f992e
Corrected comment
radarhere May 4, 2025
2ec01c9
Added type hints
radarhere May 4, 2025
2f16077
Use ImageFile._Tile
radarhere May 4, 2025
f594d20
Removed unneeded converts
radarhere May 4, 2025
1eb30cf
Update src/PIL/VtfImagePlugin.py
REDxEYE May 4, 2025
487c7b2
Restored use of bytes
radarhere May 4, 2025
b40aa68
Default to pixel format specific to mode
radarhere May 6, 2025
2703130
Do not use buffer
radarhere May 6, 2025
8cb320a
Comment unused enums
radarhere May 5, 2025
766f60e
Removed HEADER_V72 and HEADER_V73
radarhere May 6, 2025
99f8d8f
Convert to RGBA for low-res DXT1
radarhere May 6, 2025
320dff8
Thumbnail does not scale up
radarhere May 6, 2025
3f09f9b
DXT1 supports 8-bit alpha
radarhere May 6, 2025
076b93f
Only use alpha flag for bcn if alpha exists
radarhere May 6, 2025
e152b2b
Removed test parameter
radarhere May 6, 2025
2533e0f
Updated test image to avoid converting
radarhere May 6, 2025
207843f
Update src/libImaging/Unpack.c
REDxEYE May 31, 2025
956fe16
Merge branch 'main' into vtf-support
radarhere Oct 21, 2025
d7ec9b3
Added new rawmode
radarhere Oct 22, 2025
6d15142
Assert fp is not None
radarhere Dec 29, 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/vtf_a8.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/vtf_a8.vtf
Binary file not shown.
Binary file added Tests/images/vtf_bgr888.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/vtf_bgr888.vtf
Binary file not shown.
Binary file added Tests/images/vtf_dxt1.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/vtf_dxt1.vtf
Binary file not shown.
Binary file added Tests/images/vtf_dxt1A.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/vtf_dxt1A.vtf
Binary file not shown.
Binary file added Tests/images/vtf_i8.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/vtf_i8.vtf
Binary file not shown.
Binary file added Tests/images/vtf_ia88.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/vtf_ia88.vtf
Binary file not shown.
Binary file added Tests/images/vtf_rgb888.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/vtf_rgb888.vtf
Binary file not shown.
Binary file added Tests/images/vtf_rgba8888.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/vtf_rgba8888.vtf
Binary file not shown.
Binary file added Tests/images/vtf_uv88.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/vtf_uv88.vtf
Binary file not shown.
167 changes: 167 additions & 0 deletions Tests/test_file_vtf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
from __future__ import annotations

import struct
from io import BytesIO
from pathlib import Path

import pytest

from PIL import Image
from PIL.VtfImagePlugin import (
VTFException,
VtfImageFile,
VtfPF,
_closest_power,
_get_mipmap_count,
_get_texture_size,
)

from .helper import assert_image_equal, assert_image_similar


@pytest.mark.parametrize(
"size, expected_size",
[
(8, 8),
(7, 8),
(9, 8),
(192, 256),
(1, 1),
(2000, 2048),
],
)
def test_closest_power(size: int, expected_size: int) -> None:
assert _closest_power(size) == expected_size


@pytest.mark.parametrize(
"size, expected_count",
[
((1, 1), 1),
((2, 2), 2),
((4, 4), 3),
((8, 8), 4),
((128, 128), 8),
((256, 256), 9),
((512, 512), 10),
((1024, 1024), 11),
((1024, 1), 11),
],
)
def test_get_mipmap_count(size: tuple[int, int], expected_count: int) -> None:
assert _get_mipmap_count(size) == expected_count


@pytest.mark.parametrize(
"pixel_format, size, expected_size",
[
(VtfPF.DXT1, (16, 16), (16 * 16) // 2),
(VtfPF.DXT1_ONEBITALPHA, (16, 16), (16 * 16) // 2),
(VtfPF.DXT3, (16, 16), 16 * 16),
(VtfPF.DXT5, (16, 16), 16 * 16),
(VtfPF.BGR888, (16, 16), 16 * 16 * 3),
(VtfPF.RGB888, (16, 16), 16 * 16 * 3),
(VtfPF.RGBA8888, (16, 16), 16 * 16 * 4),
(VtfPF.UV88, (16, 16), 16 * 16 * 2),
(VtfPF.A8, (16, 16), 16 * 16),
(VtfPF.I8, (16, 16), 16 * 16),
(VtfPF.IA88, (16, 16), 16 * 16 * 2),
],
)
def test_get_texture_size(
pixel_format: VtfPF, size: tuple[int, int], expected_size: int
) -> None:
assert _get_texture_size(pixel_format, size) == expected_size


@pytest.mark.parametrize(
"file_path, expected_mode, epsilon",
[
("Tests/images/vtf_i8.vtf", "L", 0),
("Tests/images/vtf_a8.vtf", "RGBA", 0),
("Tests/images/vtf_ia88.vtf", "LA", 0),
("Tests/images/vtf_uv88.vtf", "RGB", 0),
("Tests/images/vtf_rgb888.vtf", "RGB", 0),
("Tests/images/vtf_bgr888.vtf", "RGB", 0),
("Tests/images/vtf_dxt1.vtf", "RGBA", 3),
("Tests/images/vtf_dxt1A.vtf", "RGBA", 8),
("Tests/images/vtf_rgba8888.vtf", "RGBA", 0),
],
)
def test_vtf_read(file_path: str, expected_mode: str, epsilon: int) -> None:
with Image.open(file_path) as im:
assert im.mode == expected_mode
with Image.open(file_path.replace(".vtf", ".png")) as expected:
if epsilon:
assert_image_similar(im, expected, epsilon)
else:
assert_image_equal(im, expected)


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

with pytest.raises(SyntaxError, match="not a VTF file"):
VtfImageFile(invalid_file)


def test_vtf_read_unsupported_version() -> None:
b = BytesIO(b"VTF\x00" + struct.pack("<2I", 7, 5))
with pytest.raises(VTFException, match=r"Unsupported VTF version: \(7, 5\)"):
with Image.open(b):
pass


def test_vtf_read_unsupported_pixel_format() -> None:
b = BytesIO(b"VTF\x00" + struct.pack("<2I40xI7x", 7, 2, 1))
with pytest.raises(VTFException, match="Unsupported VTF pixel format: 1"):
with Image.open(b):
pass


@pytest.mark.parametrize(
"pixel_format, file_path, expected_mode, epsilon",
[
(VtfPF.I8, "Tests/images/vtf_i8.png", "L", 0),
(VtfPF.A8, "Tests/images/vtf_a8.png", "RGBA", 0),
(VtfPF.IA88, "Tests/images/vtf_ia88.png", "LA", 0),
(VtfPF.UV88, "Tests/images/vtf_uv88.png", "RGB", 0),
(VtfPF.RGB888, "Tests/images/vtf_rgb888.png", "RGB", 0),
(VtfPF.BGR888, "Tests/images/vtf_bgr888.png", "RGB", 0),
(VtfPF.DXT1, "Tests/images/vtf_dxt1.png", "RGBA", 3),
(VtfPF.RGBA8888, "Tests/images/vtf_rgba8888.png", "RGBA", 0),
],
)
@pytest.mark.parametrize("version", ((7, 1), (7, 2), (7, 3)))
def test_vtf_save(
pixel_format: VtfPF,
file_path: str,
expected_mode: str,
epsilon: int,
version: tuple[int, int],
tmp_path: Path,
) -> None:
im: Image.Image
with Image.open(file_path) as im:
out = tmp_path / "tmp.vtf"
im.save(out, pixel_format=pixel_format, version=version)
with Image.open(out) as reloaded:
assert reloaded.mode == expected_mode
if epsilon:
assert_image_similar(im, reloaded, epsilon)
else:
assert_image_equal(im, reloaded)


def test_vtf_save_unsupported_mode(tmp_path: Path) -> None:
out = tmp_path / "temp.vtf"
im = Image.new("HSV", (1, 1))
with pytest.raises(OSError, match="cannot write mode HSV as VTF"):
im.save(out)


def test_vtf_save_unsupported_version(tmp_path: Path) -> None:
out = tmp_path / "temp.vtf"
im = Image.new("L", (1, 1))
with pytest.raises(VTFException, match=r"Unsupported VTF version: \(7, 5\)"):
im.save(out, version=(7, 5))
Loading
Loading