diff --git a/Tests/images/vtf_a8.png b/Tests/images/vtf_a8.png index 2411de7e2..1e95446f1 100644 Binary files a/Tests/images/vtf_a8.png and b/Tests/images/vtf_a8.png differ diff --git a/Tests/images/vtf_a8.vtf b/Tests/images/vtf_a8.vtf index e2bf3501f..4705e9e09 100644 Binary files a/Tests/images/vtf_a8.vtf and b/Tests/images/vtf_a8.vtf differ diff --git a/Tests/images/vtf_bgr888.vtf b/Tests/images/vtf_bgr888.vtf index 2b7640ef4..081220d80 100644 Binary files a/Tests/images/vtf_bgr888.vtf and b/Tests/images/vtf_bgr888.vtf differ diff --git a/Tests/images/vtf_dxt1.vtf b/Tests/images/vtf_dxt1.vtf index 6efc2d659..f74c3756b 100644 Binary files a/Tests/images/vtf_dxt1.vtf and b/Tests/images/vtf_dxt1.vtf differ diff --git a/Tests/images/vtf_dxt1A.vtf b/Tests/images/vtf_dxt1A.vtf index 5de222109..2ea82c302 100644 Binary files a/Tests/images/vtf_dxt1A.vtf and b/Tests/images/vtf_dxt1A.vtf differ diff --git a/Tests/images/vtf_i8.vtf b/Tests/images/vtf_i8.vtf index f5e949285..d3e5e94f5 100644 Binary files a/Tests/images/vtf_i8.vtf and b/Tests/images/vtf_i8.vtf differ diff --git a/Tests/images/vtf_ia88.vtf b/Tests/images/vtf_ia88.vtf index c691ca126..30df1e057 100644 Binary files a/Tests/images/vtf_ia88.vtf and b/Tests/images/vtf_ia88.vtf differ diff --git a/Tests/images/vtf_rgb888.vtf b/Tests/images/vtf_rgb888.vtf index 55f70d4f7..b8eb744db 100644 Binary files a/Tests/images/vtf_rgb888.vtf and b/Tests/images/vtf_rgb888.vtf differ diff --git a/Tests/images/vtf_rgba8888.vtf b/Tests/images/vtf_rgba8888.vtf index 9553f80a5..6d8248bc6 100644 Binary files a/Tests/images/vtf_rgba8888.vtf and b/Tests/images/vtf_rgba8888.vtf differ diff --git a/Tests/images/vtf_uv88.vtf b/Tests/images/vtf_uv88.vtf index 043437caa..8acc013e0 100644 Binary files a/Tests/images/vtf_uv88.vtf and b/Tests/images/vtf_uv88.vtf differ diff --git a/Tests/test_file_vtf.py b/Tests/test_file_vtf.py index 021765d16..b0e7321fe 100644 --- a/Tests/test_file_vtf.py +++ b/Tests/test_file_vtf.py @@ -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) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index ba7e57260..79f82d92e 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -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 = "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); }