mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-05-29 18:23:24 +03:00
Add switch to DXT1 encoder to select block mode (With alpha/Without alpha)
This commit is contained in:
parent
a634a44a9c
commit
c3457f10eb
Binary file not shown.
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -63,7 +63,7 @@ def test_get_mipmap_count(size: Tuple[int, int], expected_count: int):
|
|||
],
|
||||
)
|
||||
def test_get_texture_size(
|
||||
pixel_format: VtfPF, size: Tuple[int, int], expected_size: int
|
||||
pixel_format: VtfPF, size: Tuple[int, int], expected_size: int
|
||||
):
|
||||
assert _get_texture_size(pixel_format, *size) == expected_size
|
||||
|
||||
|
@ -72,17 +72,18 @@ def test_get_texture_size(
|
|||
("etalon_path", "file_path", "expected_mode", "epsilon"),
|
||||
[
|
||||
("Tests/images/vtf_i8.png", "Tests/images/vtf_i8.vtf", "L", 0.0),
|
||||
("Tests/images/vtf_a8.png", "Tests/images/vtf_a8.vtf", "L", 0.0),
|
||||
("Tests/images/vtf_a8.png", "Tests/images/vtf_a8.vtf", "RGBA", 0.0),
|
||||
("Tests/images/vtf_ia88.png", "Tests/images/vtf_ia88.vtf", "LA", 0.0),
|
||||
("Tests/images/vtf_uv88.png", "Tests/images/vtf_uv88.vtf", "RGB", 0.0),
|
||||
("Tests/images/vtf_rgb888.png", "Tests/images/vtf_rgb888.vtf", "RGB", 0.0),
|
||||
("Tests/images/vtf_bgr888.png", "Tests/images/vtf_bgr888.vtf", "RGB", 0.0),
|
||||
("Tests/images/vtf_dxt1.png", "Tests/images/vtf_dxt1.vtf", "RGBA", 3.0),
|
||||
("Tests/images/vtf_dxt1A.png", "Tests/images/vtf_dxt1A.vtf", "RGBA", 8.0),
|
||||
("Tests/images/vtf_rgba8888.png", "Tests/images/vtf_rgba8888.vtf", "RGBA", 0),
|
||||
],
|
||||
)
|
||||
def test_vtf_loading(
|
||||
etalon_path: str, file_path: str, expected_mode: str, epsilon: float
|
||||
def test_vtf_read(
|
||||
etalon_path: str, file_path: str, expected_mode: str, epsilon: float
|
||||
):
|
||||
e = Image.open(etalon_path)
|
||||
f = Image.open(file_path)
|
||||
|
@ -92,3 +93,32 @@ def test_vtf_loading(
|
|||
assert_image_equal(e, f)
|
||||
else:
|
||||
assert_image_similar(e, f, epsilon)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pixel_format", "file_path", "expected_mode", "epsilon"),
|
||||
[
|
||||
(VtfPF.I8, "Tests/images/vtf_i8.png", "L", 0.0),
|
||||
(VtfPF.A8, "Tests/images/vtf_a8.png", "RGBA", 0.0),
|
||||
(VtfPF.IA88, "Tests/images/vtf_ia88.png", "LA", 0.0),
|
||||
(VtfPF.UV88, "Tests/images/vtf_uv88.png", "RGB", 0.0),
|
||||
(VtfPF.RGB888, "Tests/images/vtf_rgb888.png", "RGB", 0.0),
|
||||
(VtfPF.BGR888, "Tests/images/vtf_bgr888.png", "RGB", 0.0),
|
||||
(VtfPF.DXT1, "Tests/images/vtf_dxt1.png", "RGBA", 3.0),
|
||||
(VtfPF.DXT1_ONEBITALPHA, "Tests/images/vtf_dxt1A.png", "RGBA", 8.0),
|
||||
(VtfPF.RGBA8888, "Tests/images/vtf_rgba8888.png", "RGBA", 0),
|
||||
],
|
||||
)
|
||||
def test_vtf_save(pixel_format: VtfPF, file_path: str,
|
||||
expected_mode: str, epsilon: float, tmp_path):
|
||||
f: Image.Image = Image.open(file_path)
|
||||
out = (tmp_path / "tmp.vtf").as_posix()
|
||||
f.save(out, pixel_format=pixel_format)
|
||||
if pixel_format == VtfPF.DXT1:
|
||||
f = f.convert("RGBA")
|
||||
e = Image.open(out)
|
||||
assert e.mode == expected_mode
|
||||
if epsilon == 0:
|
||||
assert_image_equal(e, f)
|
||||
else:
|
||||
assert_image_similar(e, f, epsilon)
|
||||
|
|
|
@ -76,29 +76,29 @@ class VtfPF(IntEnum):
|
|||
ABGR8888 = 1
|
||||
RGB888 = 2
|
||||
BGR888 = 3
|
||||
RGB565 = 4
|
||||
# RGB565 = 4
|
||||
I8 = 5
|
||||
IA88 = 6
|
||||
P8 = 7
|
||||
# P8 = 7
|
||||
A8 = 8
|
||||
RGB888_BLUESCREEN = 9
|
||||
BGR888_BLUESCREEN = 10
|
||||
# RGB888_BLUESCREEN = 9
|
||||
# BGR888_BLUESCREEN = 10
|
||||
ARGB8888 = 11
|
||||
BGRA8888 = 12
|
||||
DXT1 = 13
|
||||
DXT3 = 14
|
||||
DXT5 = 15
|
||||
BGRX8888 = 16
|
||||
BGR565 = 17
|
||||
BGRX5551 = 18
|
||||
BGRA4444 = 19
|
||||
# BGR565 = 17
|
||||
# BGRX5551 = 18
|
||||
# BGRA4444 = 19
|
||||
DXT1_ONEBITALPHA = 20
|
||||
BGRA5551 = 21
|
||||
# BGRA5551 = 21
|
||||
UV88 = 22
|
||||
UVWQ8888 = 23
|
||||
RGBA16161616F = 24
|
||||
RGBA16161616 = 25
|
||||
UVLX8888 = 26
|
||||
# UVWQ8888 = 23
|
||||
# RGBA16161616F = 24
|
||||
# RGBA16161616 = 25
|
||||
# UVLX8888 = 26
|
||||
|
||||
|
||||
VTFHeader = NamedTuple(
|
||||
|
@ -125,26 +125,8 @@ VTFHeader = NamedTuple(
|
|||
("resource_count", int),
|
||||
],
|
||||
)
|
||||
RGB_FORMATS = (
|
||||
VtfPF.DXT1,
|
||||
VtfPF.RGB888,
|
||||
VtfPF.BGR888,
|
||||
VtfPF.UV88,
|
||||
)
|
||||
RGBA_FORMATS = (
|
||||
VtfPF.DXT1_ONEBITALPHA,
|
||||
VtfPF.DXT3,
|
||||
VtfPF.DXT5,
|
||||
VtfPF.RGBA8888,
|
||||
)
|
||||
L_FORMATS = (
|
||||
VtfPF.A8,
|
||||
VtfPF.I8,
|
||||
)
|
||||
LA_FORMATS = (VtfPF.IA88,)
|
||||
|
||||
BLOCK_COMPRESSED = (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5)
|
||||
SUPPORTED_FORMATS = RGBA_FORMATS + RGB_FORMATS + LA_FORMATS + L_FORMATS
|
||||
HEADER_V70 = "<I2HI2H4x3f4xfIbI2b"
|
||||
HEADER_V72 = "<I2HI2H4x3f4xfIbI2bH"
|
||||
HEADER_V73 = "<I2HI2H4x3f4xfIbI2bH3xI8x"
|
||||
|
@ -153,22 +135,13 @@ HEADER_V73 = "<I2HI2H4x3f4xfIbI2bH3xI8x"
|
|||
def _get_texture_size(pixel_format: VtfPF, width, height):
|
||||
if pixel_format in (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA):
|
||||
return width * height // 2
|
||||
elif (
|
||||
pixel_format
|
||||
in (
|
||||
VtfPF.DXT3,
|
||||
VtfPF.DXT5,
|
||||
)
|
||||
+ L_FORMATS
|
||||
):
|
||||
elif pixel_format in (VtfPF.DXT3, VtfPF.DXT5):
|
||||
return width * height
|
||||
elif pixel_format == VtfPF.UV88:
|
||||
elif pixel_format in (VtfPF.A8, VtfPF.I8,):
|
||||
return width * height
|
||||
elif pixel_format in (VtfPF.UV88, VtfPF.IA88):
|
||||
return width * height * 2
|
||||
elif pixel_format in LA_FORMATS:
|
||||
return width * height * 2
|
||||
elif pixel_format == VtfPF.RGB888:
|
||||
return width * height * 3
|
||||
elif pixel_format == VtfPF.BGR888:
|
||||
elif pixel_format in (VtfPF.RGB888, VtfPF.BGR888):
|
||||
return width * height * 3
|
||||
elif pixel_format == VtfPF.RGBA8888:
|
||||
return width * height * 4
|
||||
|
@ -190,36 +163,28 @@ def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF):
|
|||
if pixel_format == VtfPF.DXT1:
|
||||
encoder = "bcn"
|
||||
encoder_args = (1, "DXT1")
|
||||
im = im.convert("RGB")
|
||||
im = im.convert("RGBA")
|
||||
elif pixel_format == VtfPF.DXT1_ONEBITALPHA:
|
||||
encoder = "bcn"
|
||||
encoder_args = (1, "DXT1A")
|
||||
im = im.convert("RGBA")
|
||||
elif pixel_format == VtfPF.DXT3:
|
||||
encoder = "bcn"
|
||||
encoder_args = (3, "DXT3")
|
||||
im = im.convert("RGBA")
|
||||
elif pixel_format == VtfPF.DXT5:
|
||||
encoder = "bcn"
|
||||
encoder_args = (5, "DXT5")
|
||||
im = im.convert("RGBA")
|
||||
elif pixel_format == VtfPF.RGB888:
|
||||
encoder = "raw"
|
||||
encoder_args = ("RGB", 0, 0)
|
||||
im = im.convert("RGB")
|
||||
elif pixel_format == VtfPF.BGR888:
|
||||
encoder = "raw"
|
||||
encoder_args = ("BGR", 0, 0)
|
||||
im = im.convert("RGB")
|
||||
elif pixel_format == VtfPF.RGBA8888:
|
||||
encoder = "raw"
|
||||
encoder_args = ("RGBA", 0, 0)
|
||||
im = im.convert("RGBA")
|
||||
elif pixel_format == VtfPF.A8:
|
||||
encoder = "raw"
|
||||
encoder_args = ("L", 0, 0)
|
||||
*_, a = im.split()
|
||||
im = Image.merge("L", (a,))
|
||||
encoder_args = ("A", 0, 0)
|
||||
elif pixel_format == VtfPF.I8:
|
||||
encoder = "raw"
|
||||
encoder_args = ("L", 0, 0)
|
||||
|
@ -240,7 +205,7 @@ def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF):
|
|||
|
||||
def _closest_power(x):
|
||||
possible_results = round(log(x, 2)), ceil(log(x, 2))
|
||||
return 2 ** min(possible_results, key=lambda z: abs(x - 2**z))
|
||||
return 2 ** min(possible_results, key=lambda z: abs(x - 2 ** z))
|
||||
|
||||
|
||||
class VtfImageFile(ImageFile.ImageFile):
|
||||
|
@ -281,15 +246,14 @@ class VtfImageFile(ImageFile.ImageFile):
|
|||
# flags = CompiledVtfFlags(header.flags)
|
||||
pixel_format = VtfPF(header.pixel_format)
|
||||
low_format = VtfPF(header.low_pixel_format)
|
||||
if pixel_format == VtfPF.DXT1: # Special case for DXT1
|
||||
if pixel_format in (VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT1, VtfPF.DXT3, VtfPF.DXT5,
|
||||
VtfPF.RGBA8888, VtfPF.BGRA8888,VtfPF.A8):
|
||||
self.mode = "RGBA"
|
||||
elif pixel_format in RGB_FORMATS:
|
||||
elif pixel_format in (VtfPF.RGB888, VtfPF.BGR888, VtfPF.UV88):
|
||||
self.mode = "RGB"
|
||||
elif pixel_format in RGBA_FORMATS:
|
||||
self.mode = "RGBA"
|
||||
elif pixel_format in L_FORMATS:
|
||||
elif pixel_format == VtfPF.I8:
|
||||
self.mode = "L"
|
||||
elif pixel_format in LA_FORMATS:
|
||||
elif pixel_format == VtfPF.IA88:
|
||||
self.mode = "LA"
|
||||
else:
|
||||
raise VTFException(f"Unsupported VTF pixel format: {pixel_format}")
|
||||
|
@ -311,19 +275,21 @@ class VtfImageFile(ImageFile.ImageFile):
|
|||
tile = ("bcn", (0, 0) + self.size, data_start, (2, "DXT3"))
|
||||
elif pixel_format == VtfPF.DXT5:
|
||||
tile = ("bcn", (0, 0) + self.size, data_start, (3, "DXT5"))
|
||||
elif pixel_format in (VtfPF.RGBA8888,):
|
||||
elif pixel_format == VtfPF.RGBA8888:
|
||||
tile = ("raw", (0, 0) + self.size, data_start, ("RGBA", 0, 1))
|
||||
elif pixel_format in (VtfPF.RGB888,):
|
||||
elif pixel_format == VtfPF.RGB888:
|
||||
tile = ("raw", (0, 0) + self.size, data_start, ("RGB", 0, 1))
|
||||
elif pixel_format in (VtfPF.BGR888,):
|
||||
elif pixel_format == VtfPF.BGR888:
|
||||
tile = ("raw", (0, 0) + self.size, data_start, ("BGR", 0, 1))
|
||||
elif pixel_format in (VtfPF.BGRA8888,):
|
||||
elif pixel_format == VtfPF.BGRA8888:
|
||||
tile = ("raw", (0, 0) + self.size, data_start, ("BGRA", 0, 1))
|
||||
elif pixel_format in (VtfPF.UV88,):
|
||||
elif pixel_format == VtfPF.UV88:
|
||||
tile = ("raw", (0, 0) + self.size, data_start, ("RG", 0, 1))
|
||||
elif pixel_format in L_FORMATS:
|
||||
elif pixel_format == VtfPF.I8:
|
||||
tile = ("raw", (0, 0) + self.size, data_start, ("L", 0, 1))
|
||||
elif pixel_format in LA_FORMATS:
|
||||
elif pixel_format == VtfPF.A8:
|
||||
tile = ("raw", (0, 0) + self.size, data_start, ("A", 0, 1))
|
||||
elif pixel_format == VtfPF.IA88:
|
||||
tile = ("raw", (0, 0) + self.size, data_start, ("LA", 0, 1))
|
||||
else:
|
||||
raise VTFException(f"Unsupported VTF pixel format: {pixel_format}")
|
||||
|
@ -343,15 +309,12 @@ def _save(im, fp, filename):
|
|||
|
||||
if pixel_format == VtfPF.DXT1_ONEBITALPHA:
|
||||
flags |= CompiledVtfFlags.ONEBITALPHA
|
||||
elif pixel_format == VtfPF.A8:
|
||||
elif pixel_format in (VtfPF.DXT3, VtfPF.DXT5,
|
||||
VtfPF.RGBA8888, VtfPF.BGRA8888,
|
||||
VtfPF.A8, VtfPF.IA88):
|
||||
flags |= CompiledVtfFlags.EIGHTBITALPHA
|
||||
elif pixel_format in RGBA_FORMATS + LA_FORMATS:
|
||||
flags |= CompiledVtfFlags.EIGHTBITALPHA
|
||||
elif pixel_format in RGB_FORMATS + L_FORMATS:
|
||||
pass
|
||||
else:
|
||||
raise VTFException("Unhandled case")
|
||||
|
||||
pass
|
||||
im = im.resize((_closest_power(im.width), _closest_power(im.height)))
|
||||
width, height = im.size
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ get_closest_color_index(const UINT16 *colors, UINT16 color) {
|
|||
if (error == 0) {
|
||||
return color_id;
|
||||
}
|
||||
if (error < color_error) {
|
||||
if (error <= color_error) {
|
||||
color_error = error;
|
||||
lowest_id = color_id;
|
||||
}
|
||||
|
@ -122,10 +122,10 @@ get_closest_color_index(const UINT16 *colors, UINT16 color) {
|
|||
int
|
||||
encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||
UINT8 *dst = buf;
|
||||
UINT8 no_alpha = 0;
|
||||
UINT8 alpha = 0;
|
||||
INT32 block_index;
|
||||
if (strcmp(((BCNSTATE *)state->context)->pixel_format, "DXT1A") != 0) {
|
||||
no_alpha = 1;
|
||||
if (strcmp(((BCNSTATE *)state->context)->pixel_format, "DXT1A") == 0) {
|
||||
alpha = 1;
|
||||
}
|
||||
INT32 block_count = (im->xsize * im->ysize) / 16;
|
||||
if (block_count * sizeof(bc1_color) > bytes) {
|
||||
|
@ -143,6 +143,7 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
|||
UINT16 unique_colors[16];
|
||||
UINT8 color_frequency[16];
|
||||
UINT8 opaque[16];
|
||||
UINT8 local_alpha = 0;
|
||||
memset(all_colors, 0, sizeof(all_colors));
|
||||
memset(unique_colors, 0, sizeof(unique_colors));
|
||||
memset(color_frequency, 0, sizeof(color_frequency));
|
||||
|
@ -157,7 +158,8 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
|||
UINT8 b = im->image[y][x * im->pixelsize + 0];
|
||||
UINT8 a = im->image[y][x * im->pixelsize + 3];
|
||||
UINT16 color = PACK_SHORT_565(r, g, b);
|
||||
opaque[bx + by * 4] = a >= 127;
|
||||
opaque[bx + by * 4] = a >= 128;
|
||||
local_alpha |= a <= 128;
|
||||
all_colors[bx + by * 4] = color;
|
||||
|
||||
UINT8 new_color = 1;
|
||||
|
@ -179,17 +181,23 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
|||
|
||||
UINT16 c0 = 0, c1 = 0;
|
||||
pick_2_major_colors(unique_colors, color_frequency, unique_count, &c0, &c1);
|
||||
if (c0 < c1 && no_alpha) {
|
||||
SWAP(UINT16, c0, c1);
|
||||
if (alpha && local_alpha) {
|
||||
if (c0 > c1) {
|
||||
SWAP(UINT16, c0, c1);
|
||||
}
|
||||
} else {
|
||||
if (c0 < c1) {
|
||||
SWAP(UINT16, c0, c1);
|
||||
}
|
||||
}
|
||||
|
||||
UINT16 palette[4] = {c0, c1, 0, 0};
|
||||
if (no_alpha) {
|
||||
palette[2] = rgb565_lerp(c0, c1, 2, 1);
|
||||
palette[3] = rgb565_lerp(c0, c1, 1, 2);
|
||||
} else {
|
||||
if (alpha && local_alpha) {
|
||||
palette[2] = rgb565_lerp(c0, c1, 1, 1);
|
||||
palette[3] = 0;
|
||||
} else {
|
||||
palette[2] = rgb565_lerp(c0, c1, 2, 1);
|
||||
palette[3] = rgb565_lerp(c0, c1, 1, 2);
|
||||
}
|
||||
bc1_color block = {0};
|
||||
block.c0 = c0;
|
||||
|
@ -197,10 +205,10 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
|||
UINT32 color_id;
|
||||
for (color_id = 0; color_id < 16; ++color_id) {
|
||||
UINT8 bc_color_id;
|
||||
if (opaque[color_id] || no_alpha) {
|
||||
bc_color_id = get_closest_color_index(palette, all_colors[color_id]);
|
||||
} else {
|
||||
if ((alpha && local_alpha) && !opaque[color_id]) {
|
||||
bc_color_id = 3;
|
||||
} else {
|
||||
bc_color_id = get_closest_color_index(palette, all_colors[color_id]);
|
||||
}
|
||||
SET_BITS(block.lut, color_id * 2, 2, bc_color_id);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user